Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .history/src/models/User_20241230142456.ts

This file was deleted.

2 changes: 0 additions & 2 deletions .history/src/models/User_20241230142459.ts

This file was deleted.

12 changes: 0 additions & 12 deletions .history/src/models/User_20241230142508.ts

This file was deleted.

15 changes: 0 additions & 15 deletions .history/src/models/User_20241230142511.ts

This file was deleted.

19 changes: 0 additions & 19 deletions .history/src/models/User_20241230142512.ts

This file was deleted.

19 changes: 0 additions & 19 deletions .history/src/models/User_20241230145653.ts

This file was deleted.

19 changes: 0 additions & 19 deletions .history/src/models/User_20241230145700.ts

This file was deleted.

18 changes: 0 additions & 18 deletions .history/src/models/User_20241230145704.ts

This file was deleted.

7 changes: 6 additions & 1 deletion src/caching/DenoKVCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ export class DenoKVCache implements CacheAdapter {

async set<T>(key: string, value: T, ttl?: number): Promise<void> {
if (!this.kv) throw new Error("Cache not connected");
const options = ttl ? { expireIn: ttl } : undefined;

// Use the provided TTL, fall back to the default TTL if available
const expireIn = ttl !== undefined ? ttl :
this.defaultTtl !== undefined ? this.defaultTtl * 1000 : undefined;

const options = expireIn !== undefined ? { expireIn } : undefined;
await this.kv.set([key], value, options);
}

Expand Down
22 changes: 15 additions & 7 deletions src/decorators/Audited.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,20 @@ export function Audited(options: {
// Override save method to create audit records
const originalSave = constructor.prototype.save;
constructor.prototype.save = async function (adapter: any): Promise<void> {
if (!this._originalValues) {
this._originalValues = {};
// Create a public getter for accessing protected properties
const getOriginalValues = () => (this as any)._originalValues;
const setOriginalValues = (values: Record<string, any>) =>
(this as any)._originalValues = values;
const checkIsNew = () =>
(this as any).id === undefined || (this as any).id === null ||
(this as any).id === 0;

if (!getOriginalValues()) {
setOriginalValues({});
}

// Track if it's an update or insert
const isNew = this.isNew();
const isNew = checkIsNew();
const action = isNew ? "INSERT" : "UPDATE";

// Create an audit record with changes
Expand All @@ -71,11 +79,11 @@ export function Audited(options: {

// If field changed, record the change
if (
this._originalValues[field] !== undefined &&
this._originalValues[field] !== (this as any)[field]
getOriginalValues()[field] !== undefined &&
getOriginalValues()[field] !== (this as any)[field]
) {
changes[field] = {
old: this._originalValues[field],
old: getOriginalValues()[field],
new: (this as any)[field],
};
}
Expand Down Expand Up @@ -137,7 +145,7 @@ export function Audited(options: {
}

// Reset original values after save
this._originalValues = {};
setOriginalValues({});
};

// Override delete to audit deletions
Expand Down
46 changes: 37 additions & 9 deletions src/decorators/ManyToOne.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,48 @@ interface ManyToOneOptions {
inverse: (object: any) => any;
}

export function ManyToOne(p0: () => typeof User, p1: string, options: ManyToOneOptions) {
return function (target: any, propertyKey: string) {
// Fix the signature to accept options object instead of separate parameters
export function ManyToOne(options: ManyToOneOptions): PropertyDecorator;
export function ManyToOne(
targetClass: () => any,
propertyName: string,
options: ManyToOneOptions,
): PropertyDecorator;
export function ManyToOne(
optionsOrTargetClass: ManyToOneOptions | (() => any),
propertyName?: string,
options?: ManyToOneOptions,
): PropertyDecorator {
return function (target: any, propertyKey: string | symbol) {
if (!getMetadata("relations", target.constructor)) {
defineMetadata("relations", [], target.constructor);
}

const relations = getMetadata("relations", target.constructor) as any[];
const metadata = {
type: "ManyToOne",
targetName: options.target(),
inverse: options.inverse,
propertyKey,
};
let metadata;

// Handle both forms of invocation
if (typeof optionsOrTargetClass === "function" && propertyName && options) {
// Old style: ManyToOne(targetClass, propertyName, options)
metadata = {
type: "ManyToOne",
targetName: options.target(),
inverse: options.inverse,
propertyKey,
};
} else {
// New style: ManyToOne(options)
const opts = optionsOrTargetClass as ManyToOneOptions;
metadata = {
type: "ManyToOne",
targetName: opts.target(),
inverse: opts.inverse,
propertyKey,
};
}

relations.push(metadata);
defineMetadata("relations", relations, target.constructor);
ModelRegistry.registerRelation(target.constructor, metadata);
ModelRegistry.registerRelation(target.constructor as unknown as Constructor, metadata);
};
}
66 changes: 49 additions & 17 deletions src/decorators/OneToMany.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
import "reflect-metadata";
import { defineMetadata, getMetadata } from "../deps.ts";
import { ModelRegistry } from "../models/ModelRegistry.ts";

type Constructor<T = any> = { new (...args: any[]): T };
type Constructor<T = unknown> = { new (...args: unknown[]): T };

interface OneToManyOptions {
target: () => string;
inverse: (object: any) => any;
inverse: (object: unknown) => unknown;
}

export function OneToMany(p0: () => typeof Post, p1: string, options: OneToManyOptions) {
return function (target: any, propertyKey: string) {
if (!Reflect.hasMetadata("relations", target.constructor)) {
Reflect.defineMetadata("relations", [], target.constructor);
// Change the signature to accept just options object
export function OneToMany(options: OneToManyOptions): PropertyDecorator;
export function OneToMany(
targetClass: () => unknown,
propertyName: string,
options: OneToManyOptions,
): PropertyDecorator;
export function OneToMany(
optionsOrTargetClass: OneToManyOptions|(() => unknown),
propertyName?: string,
options?: OneToManyOptions,
): PropertyDecorator {
return function(target: object, _propertyKey: string|symbol) {
if(!getMetadata("relations", target.constructor)) {
defineMetadata("relations", [], target.constructor);
}
const relations = Reflect.getMetadata(

const relations = getMetadata(
"relations",
target.constructor,
) as any[];
const metadata = {
type: "OneToMany",
targetName: options.target(),
inverse: options.inverse,
propertyKey,
) as Array<unknown>;

let metadata: {
type: string;
targetName: string;
inverse: (object: unknown) => unknown;
propertyKey: string | symbol;
};

if(typeof optionsOrTargetClass === "function" && propertyName && options) {
// Old style: OneToMany(targetClass, propertyName, options)
metadata = {
type: "OneToMany",
targetName: options.target(),
inverse: options.inverse,
propertyKey: _propertyKey,
};
} else {
// New style: OneToMany(options)
const opts = optionsOrTargetClass as OneToManyOptions;
metadata = {
type: "OneToMany",
targetName: opts.target(),
inverse: opts.inverse,
propertyKey: _propertyKey,
};
}

relations.push(metadata);
Reflect.defineMetadata("relations", relations, target.constructor);
ModelRegistry.registerRelation(target.constructor, metadata);
defineMetadata("relations", relations, target.constructor);
ModelRegistry.registerRelation(target.constructor as Constructor<unknown>, metadata);
};
}
}
4 changes: 3 additions & 1 deletion src/decorators/Versioned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export function Versioned(options: {
const originalSave = constructor.prototype.save;
constructor.prototype.save = async function (adapter: any): Promise<void> {
// Only create a version for updates, not inserts
if (!this.isNew()) {
const checkIsNew = () => (this as any).id === undefined || (this as any).id === null || (this as any).id === 0;

if (!checkIsNew()) {
// Create a snapshot of the current state before changes
const currentVersion = {
entity_id: this.id,
Expand Down
9 changes: 7 additions & 2 deletions src/graphql/GraphQLSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,21 @@ export class GraphQLSchemaGenerator {
targetModel as ModelConstructor,
);

// Convert symbol propertyKey to string if needed
const fieldName = typeof relation.propertyKey === 'symbol'
? relation.propertyKey.toString().replace(/Symbol\(|\)/g, '')
: relation.propertyKey;

switch (relation.type) {
case "OneToMany":
fields[relation.propertyKey] = {
fields[fieldName] = {
type: new graphql.GraphQLList(targetType),
args: {},
};
break;
case "ManyToOne":
case "OneToOne":
fields[relation.propertyKey] = {
fields[fieldName] = {
type: targetType,
args: {},
};
Expand Down
2 changes: 1 addition & 1 deletion src/models/ModelRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface RelationMetadata {
targetName: string;
inverse: (object: unknown) => unknown;
joinTable?: string;
propertyKey: string;
propertyKey: string | symbol;
}

interface ModelMetadata {
Expand Down
2 changes: 1 addition & 1 deletion src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class User extends BaseModel implements IUser {

@OneToMany({
target: () => "Post",
inverse: (post: Post) => post.user,
inverse: (object: unknown) => (object as Post).user,
})
posts!: Post[];

Expand Down
Loading
Loading