diff --git a/src/class-model.ts b/src/class-model.ts index 275dce0..177c7a5 100644 --- a/src/class-model.ts +++ b/src/class-model.ts @@ -5,12 +5,11 @@ import type { IModelType as MSTIModelType, ModelActions } from "mobx-state-tree" import { types as mstTypes } from "mobx-state-tree"; import { RegistrationError } from "./errors"; import { buildFastInstantiator } from "./fast-instantiator"; +import { FastGetBuilder } from "./fast-getter"; import { defaultThrowAction, mstPropsFromQuickProps, propsFromModelPropsDeclaration } from "./model"; import { $context, $identifier, - $memoizedKeys, - $memos, $originalDescriptor, $parent, $quickType, @@ -40,7 +39,7 @@ type ActionMetadata = { }; /** @internal */ -type ViewMetadata = { +export type ViewMetadata = { type: "view"; property: string; }; @@ -53,7 +52,8 @@ export type VolatileMetadata = { }; type VolatileInitializer = (instance: T) => Record; -type PropertyMetadata = ActionMetadata | ViewMetadata | VolatileMetadata; +/** @internal */ +export type PropertyMetadata = ActionMetadata | ViewMetadata | VolatileMetadata; const metadataPrefix = "mqt:properties"; const viewKeyPrefix = `${metadataPrefix}:view`; @@ -90,10 +90,6 @@ class BaseClassModel { readonly [$parent]?: IStateTreeNode | null; /** @hidden */ [$identifier]?: any; - /** @hidden */ - [$memos]!: Record | null; - /** @hidden */ - [$memoizedKeys]!: Record | null; } /** @@ -158,6 +154,8 @@ export function register { + if (metadata.type !== "view") return false; + const property = metadata.property; + const descriptor = getPropertyDescriptor(klass.prototype, property); + if (!descriptor) { + throw new RegistrationError(`Property ${property} not found on ${klass} prototype, can't register view for class model`); + } + return descriptor.get !== undefined; + }) + .map((metadata) => metadata.property); + } + + buildGetter(property: string, descriptor: PropertyDescriptor) { + const builder = eval(` + ( + function build({ $readOnly, getValue }) { + return function get${property}(model, imports) { + if (!this[$readOnly]) return getValue.call(this); + const value = getValue.call(this); + Object.defineProperty(this, "${property}", { value, writable: false }) + return value; + } + } + ) + //# sourceURL=mqt-eval/dynamic/${this.klass.name}-${property}-get.js + `); + + return builder({ $readOnly, getValue: descriptor.get }); + } +} diff --git a/src/fast-instantiator.ts b/src/fast-instantiator.ts index 8c4f09a..99f97f5 100644 --- a/src/fast-instantiator.ts +++ b/src/fast-instantiator.ts @@ -1,17 +1,21 @@ import { ArrayType, QuickArray } from "./array"; +import type { FastGetBuilder } from "./fast-getter"; import { FrozenType } from "./frozen"; import { MapType, QuickMap } from "./map"; import { OptionalType } from "./optional"; import { ReferenceType, SafeReferenceType } from "./reference"; import { DateType, IntegerType, LiteralType, SimpleType } from "./simple"; -import { $context, $identifier, $memoizedKeys, $memos, $parent, $readOnly, $type } from "./symbols"; +import { $context, $identifier, $parent, $readOnly, $type } from "./symbols"; import type { IAnyType, IClassModelType, ValidOptionalValue } from "./types"; /** * Compiles a fast function for taking snapshots and turning them into instances of a class model. **/ -export const buildFastInstantiator = , any, any>>(model: T): T => { - return new InstantiatorBuilder(model).build(); +export const buildFastInstantiator = , any, any>>( + model: T, + fastGetters: FastGetBuilder, +): T => { + return new InstantiatorBuilder(model, fastGetters).build(); }; type DirectlyAssignableType = SimpleType | IntegerType | LiteralType | DateType; @@ -28,7 +32,10 @@ const isDirectlyAssignableType = (type: IAnyType): type is DirectlyAssignableTyp class InstantiatorBuilder, any, any>> { aliases = new Map(); - constructor(readonly model: T) {} + constructor( + readonly model: T, + readonly getters: FastGetBuilder, + ) {} build(): T { const segments: string[] = []; @@ -81,9 +88,6 @@ class InstantiatorBuilder, an const defineClassStatement = ` return class ${className} extends model { - [$memos] = null; - [$memoizedKeys] = null; - static createReadOnly = (snapshot, env) => { const context = { referenceCache: new Map(), @@ -137,7 +141,7 @@ class InstantiatorBuilder, an `; const aliasFuncBody = ` - const { QuickMap, QuickArray, $identifier, $context, $parent, $memos, $memoizedKeys, $readOnly, $type } = imports; + const { QuickMap, QuickArray, $identifier, $context, $parent, $readOnly, $type } = imports; ${Array.from(this.aliases.entries()) .map(([expression, alias]) => `const ${alias} = ${expression};`) @@ -166,8 +170,6 @@ class InstantiatorBuilder, an $identifier, $context, $parent, - $memos, - $memoizedKeys, $readOnly, $type, QuickMap, diff --git a/src/symbols.ts b/src/symbols.ts index 8d7437c..113fbb8 100644 --- a/src/symbols.ts +++ b/src/symbols.ts @@ -36,15 +36,3 @@ export const $registered = Symbol.for("MQT_registered"); * @hidden **/ export const $volatileDefiner = Symbol.for("MQT_volatileDefiner"); - -/** - * The values of memoized properties on an MQT instance - * @hidden - **/ -export const $memos = Symbol.for("mqt:class-model-memos"); - -/** - * The list of properties which have been memoized - * @hidden - **/ -export const $memoizedKeys = Symbol.for("mqt:class-model-memoized-keys");