Last active
June 6, 2024 16:26
-
-
Save adrien2p/d34166bdfde8c8869cad2b46d1f07f0b to your computer and use it in GitHub Desktop.
quick and dirty DML poc to show the feasibility. The end code will not look this at all
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Ref<T> = T | |
type Factory<TModel> = () => Ref<TModel> | |
type PropertyType = 'string' | 'number' | 'boolean' | 'manyToOne' | 'oneToMany' | |
type PropertyMetadata<Type = PropertyType> = { | |
type: Type | |
default?: any | |
optional?: boolean | |
linkable?: string | |
foreignKey?: string | |
reverse?: string | |
reference?: { | |
resolver: Factory<any> | |
target: Model | |
relation: 'manyToOne' | 'oneToMany' | |
} | |
} | |
type ExtendedModelWithRelation<TModel extends Model, TProp extends string, TRelation extends Model, TDirection extends PropertyType> = TModel & { | |
columns: TRelation['columns'] & { | |
[K in TProp]: PropertyDefinition<TDirection> | |
} | |
} | |
class PropertyDefinition<TType extends PropertyType = any> { | |
metadata: PropertyMetadata<TType> = {} as PropertyMetadata<TType> | |
constructor(config: { type: TType, default?: any, foreignKey?: string, reverse?: string}) { | |
this.metadata = {...this.metadata, ...config } | |
} | |
static string() { | |
return new PropertyDefinition({type: 'string'}) | |
} | |
static number() { | |
return new PropertyDefinition({type: 'number'}) | |
} | |
static boolean() { | |
return new PropertyDefinition({type: 'boolean'}) | |
} | |
static manyToOne<TRelation extends Model>(reference: Factory<TRelation>, reverse?: keyof TRelation['columns']) { | |
const propertyDefinition = new PropertyDefinition({type: 'manyToOne', reverse: reverse as string }) | |
return propertyDefinition.setManyToOne(reference) | |
} | |
static oneToMany<TRelation extends Model>(reference: Factory<TRelation>, foreignKey: string) { | |
const propertyDefinition = new PropertyDefinition({type: 'oneToMany', foreignKey}) | |
return propertyDefinition.setOneToMany(reference) | |
} | |
default(value: TType extends 'string' ? string : TType extends 'number' ? number : boolean): PropertyDefinition<TType> { | |
this.metadata = {...this.metadata, default: value} | |
return this | |
} | |
optional() { | |
this.metadata = {...this.metadata, optional: true} | |
return this | |
} | |
linkable(as: string) { | |
this.metadata = {...this.metadata, linkable: as} | |
return this | |
} | |
setManyToOne(resolver: Factory<any>) { | |
this.metadata = { | |
...this.metadata, | |
reference: { | |
resolver: resolver, | |
relation: 'manyToOne' | |
} as PropertyMetadata['reference'] | |
} | |
return this | |
} | |
setOneToMany(resolver: Factory<any>) { | |
this.metadata = { | |
...this.metadata, | |
reference: { | |
resolver: resolver, | |
relation: 'oneToMany' | |
} as PropertyMetadata['reference'] | |
} | |
return this | |
} | |
toJSON() { | |
if (this.metadata.reference) { | |
this.metadata.reference.target = this.metadata.reference.resolver() | |
} | |
return this | |
} | |
} | |
class Model<TModelPropertyDefinitions = any> { | |
name: string | |
columns = {} as { [K in keyof TModelPropertyDefinitions]: PropertyDefinition } | |
constructor(name: string, columnDefinitons: { [K in keyof TModelPropertyDefinitions]: PropertyDefinition }) { | |
this.columns = columnDefinitons | |
this.name = name | |
} | |
static define<TPropertyDefinitions extends { | |
[key: string]: PropertyDefinition | |
}>(name: string, config: TPropertyDefinitions): Model<TPropertyDefinitions> { | |
return new Model<TPropertyDefinitions>(name, config) | |
} | |
static string() { | |
return PropertyDefinition.string() | |
} | |
static number() { | |
return PropertyDefinition.number() | |
} | |
static boolean() { | |
return PropertyDefinition.boolean() | |
} | |
oneToMany<TProp extends string>(prop: TProp, resolver: Function, foreignKey: string): ExtendedModelWithRelation<this, TProp, Model, 'oneToMany'> { | |
this.columns = { | |
...this.columns, | |
[prop]: PropertyDefinition.oneToMany(resolver as any, foreignKey) | |
} | |
return this as unknown as ExtendedModelWithRelation<this, TProp, any, 'oneToMany'> | |
} | |
manyToOne<TProp extends string>(prop: TProp, resolver: Function, reverse: string): ExtendedModelWithRelation<this, TProp, Model, 'manyToOne'> { | |
this.columns = { | |
...this.columns, | |
[prop]: { | |
...PropertyDefinition.manyToOne(resolver as any, reverse) | |
} | |
} | |
return this as unknown as ExtendedModelWithRelation<this, TProp, any, 'manyToOne'> | |
} | |
} | |
const product = Model.define('product', { | |
id: Model.string().optional().linkable('product_id'), | |
name: Model.string().default('N/A'), | |
is_enabled: Model.boolean().default(true), | |
count: Model.number().default(0), | |
}) | |
.manyToOne('variants', () => variant, 'product_id') | |
const variant = Model.define('variant', { | |
id: Model.string().optional().linkable('variant_id'), | |
name: Model.string().default('N/A'), | |
product_id: Model.string(), | |
}) | |
.oneToMany('product', () => product, 'product_id') | |
console.log(JSON.stringify(product, null, 4)) | |
/* | |
{ | |
"columns": { | |
"id": { | |
"metadata": { | |
"type": "string", | |
"optional": true, | |
"linkable": "product_id" | |
} | |
}, | |
"name": { | |
"metadata": { | |
"type": "string", | |
"default": "N/A" | |
} | |
}, | |
"is_enabled": { | |
"metadata": { | |
"type": "boolean", | |
"default": true | |
} | |
}, | |
"count": { | |
"metadata": { | |
"type": "number", | |
"default": 0 | |
} | |
}, | |
"variants": { | |
"metadata": { | |
"type": "manyToOne", | |
"reverse": "product_id", | |
"reference": { | |
"relation": "manyToOne" | |
} | |
} | |
} | |
}, | |
"name": "product" | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment