import { BaseField, ReferenceField } from './fields'; import { FieldsSym } from './fields/base'; import { BaseRelationField } from './fields/base_relation'; import { hook } from './mobx_internal'; const SKIP = {}; export interface ModelMeta { [key: string]: any; } export interface InstanceMeta { collectionsAttached: boolean; [key: string]: any; } export type AnyObject = { [key: string]: any } export type AnyModel = Model; export type ModelDataType = T extends Model ? U & { id: number } : never; type ClassOf = Function & { prototype: T } export const ModelMetaSym = Symbol('model_meta'); export class Model { static model_name: string; constructor(data?: T, meta: Partial = {}) { for (let [fld, fd] of Object.entries(this._model._fields)) { fd.initializeInstance(this); } for (let [key, cd] of this.collectionDescs()) { if (meta[key]) this[key] = meta[key]; } data ??= {} as any; this['_initial_data'] = data; this._savedState = data; hook.run(Model, 'construct', [this]); } readonly _meta: InstanceMeta = {} as any; // static new(data: ModelDataType) { // const inst = new (this as any)(data); // return inst; // } //#region Collection Management private _parentCollection; get parentCollection() { return this._parentCollection } private *fieldDescriptions>(types: ClassOf | ClassOf[]): Generator<[string, T]> { const typeArray = Array.isArray(types) ? types : [types]; for (let [key, fd] of Object.entries(this._model._fields)) { for (let typ of typeArray) { if (fd instanceof typ) yield [key, fd as any]; } } } private *collectionDescs(): IterableIterator<[string, BaseRelationField]> { for (let [k, fd] of this.fieldDescriptions(BaseRelationField)) { yield [k, fd] } } attachToCollections(force = false) { for (let [key, cd] of this.collectionDescs()) { if (force) { cd.attach(this); } else { cd.possiblyAttach(this); } } } detachFromCollections() { for (let [key, cd] of this.collectionDescs()) { cd.detach(this); } } allowCollectionAttachment(collection?: BaseRelationField) { return true; } protected attachmentChanged(collName: string, collDesc: ReferenceField, oldCollection) { } //#endregion //#region State/Serialization Helpers protected _savedState: any; get savedState(): Partial { return this._savedState } serialize() { const obj = {} for (let fd of Object.values(this._model._fields)) { fd.serialize(this, obj, { field: fd, model: this, networkObject: obj, SKIP, }) } return obj; } deserialize(data: T) { if (!data) return; for (let fd of Object.values(this._model._fields)) { fd.deserialize(this, data, { field: fd, model: this, networkObject: data as any, SKIP, }) } } /** * Generates a snapshot of this object. * Default implementation is an alias to serialize() */ protected generateSnapshot() { return this.serialize(); } /** * Restores this object to a previous state. * Default implementation is an alias to deserialize() */ protected restoreSnapshot(initialState: any) { this.deserialize(initialState); } protected async withTransaction(func: () => any) { const snap = this.generateSnapshot(); try { return await func(); } catch (e) { this.restoreSnapshot(snap); throw e; } } //#endregion get _model() { return this.constructor as typeof Model } static get _fields() { return this[Symbol.metadata]?.[FieldsSym] ?? {} } static get _meta() { if (!Object.hasOwn.call(this[Symbol.metadata], ModelMetaSym)) { const meta = {}; Object.setPrototypeOf(meta, this[Symbol.metadata]?.[ModelMetaSym] ?? {} as any); this[Symbol.metadata][ModelMetaSym] = meta; } return this[Symbol.metadata]?.[ModelMetaSym] } // @InheritedDict static _meta: ModelMeta; // @InheritedDict static _fields: { [name: string]: AnyField }; // static addField(k: string, field: AnyField) { this._fields[k] = field } static getField(name: string) { return this._fields[name] } }