Skip to content

Instantly share code, notes, and snippets.

@adrien2p
Last active June 6, 2024 16:26
Show Gist options
  • Save adrien2p/d34166bdfde8c8869cad2b46d1f07f0b to your computer and use it in GitHub Desktop.
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
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