mobx-keystone 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/mobx-keystone.esm.js +6702 -5797
  3. package/dist/mobx-keystone.esm.mjs +6702 -5797
  4. package/dist/mobx-keystone.umd.js +6718 -5798
  5. package/dist/types/action/runUnprotected.d.ts +8 -4
  6. package/dist/types/model/Model.d.ts +5 -0
  7. package/dist/types/modelShared/prop.d.ts +8 -4
  8. package/dist/types/snapshot/modelInitialData.d.ts +1 -0
  9. package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
  10. package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
  11. package/package.json +11 -10
  12. package/src/action/middleware.ts +5 -18
  13. package/src/action/mutationBatch.ts +72 -0
  14. package/src/action/runUnprotected.ts +21 -8
  15. package/src/action/wrapInAction.ts +45 -15
  16. package/src/dataModel/BaseDataModel.ts +4 -8
  17. package/src/deepChange/onDeepChange.ts +246 -16
  18. package/src/model/BaseModel.ts +4 -12
  19. package/src/model/Model.ts +5 -0
  20. package/src/model/newModel.ts +68 -36
  21. package/src/modelShared/prop.ts +105 -63
  22. package/src/modelShared/sharedInternalModel.ts +91 -60
  23. package/src/parent/core.ts +16 -10
  24. package/src/parent/coreObjectChildren.ts +219 -21
  25. package/src/parent/path.ts +6 -5
  26. package/src/parent/setParent.ts +5 -5
  27. package/src/patch/emitPatch.ts +102 -20
  28. package/src/snapshot/fromArraySnapshot.ts +11 -2
  29. package/src/snapshot/fromModelSnapshot.ts +10 -3
  30. package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
  31. package/src/snapshot/fromSnapshot.ts +31 -9
  32. package/src/snapshot/getSnapshot.ts +8 -3
  33. package/src/snapshot/internal.ts +243 -82
  34. package/src/snapshot/modelInitialData.ts +41 -0
  35. package/src/snapshot/reconcileArraySnapshot.ts +9 -3
  36. package/src/snapshot/reconcileModelSnapshot.ts +97 -17
  37. package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
  38. package/src/tweaker/core.ts +30 -5
  39. package/src/tweaker/treeNodeMetadata.ts +38 -0
  40. package/src/tweaker/tweak.ts +15 -4
  41. package/src/tweaker/tweakArray.ts +159 -121
  42. package/src/tweaker/tweakFrozen.ts +2 -2
  43. package/src/tweaker/tweakModel.ts +2 -2
  44. package/src/tweaker/tweakPlainObject.ts +114 -122
  45. package/src/tweaker/typeChecking.ts +10 -1
  46. package/src/types/TypeChecker.ts +261 -15
  47. package/src/types/arrayBased/typesArray.ts +38 -40
  48. package/src/types/arrayBased/typesTuple.ts +51 -34
  49. package/src/types/createCachedTypeCheck.ts +181 -0
  50. package/src/types/objectBased/typesArraySet.ts +42 -32
  51. package/src/types/objectBased/typesDataModelData.ts +18 -11
  52. package/src/types/objectBased/typesModel.ts +1 -1
  53. package/src/types/objectBased/typesObject.ts +57 -28
  54. package/src/types/objectBased/typesObjectMap.ts +51 -40
  55. package/src/types/objectBased/typesRecord.ts +59 -39
  56. package/src/types/objectBased/typesRef.ts +1 -1
  57. package/src/types/primitiveBased/typesPrimitive.ts +9 -9
  58. package/src/types/tProp.ts +80 -44
  59. package/src/types/utility/typesOr.ts +35 -24
  60. package/src/types/utility/typesRefinement.ts +21 -8
  61. package/src/types/utility/typesSkipCheck.ts +21 -8
  62. package/src/types/utility/typesTag.ts +21 -9
  63. package/src/types/utility/typesUnchecked.ts +2 -3
  64. package/src/utils/ModelPool.ts +34 -19
  65. package/src/utils/errorDiagnostics.ts +60 -18
  66. package/src/utils/index.ts +9 -7
  67. package/src/types/createPerEntryCachedCheck.ts +0 -166
  68. /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Runs a block in unprocted mode, as if it were run inside a model action.
3
- * Consider using a proper model action instead since these kind of actions are not recorded.
2
+ * Runs a block in unprotected mode, as if it were run inside a model action.
3
+ * Structural mutations share one synchronous mutation batch, but the block is
4
+ * not recorded as an action. Consider using a proper model action when action
5
+ * recording is needed.
4
6
  *
5
7
  * @template T Return type.
6
8
  * @param name Mobx action name.
@@ -9,8 +11,10 @@
9
11
  */
10
12
  export declare function runUnprotected<T>(name: string, fn: () => T): T;
11
13
  /**
12
- * Runs a block in unprocted mode, as if it were run inside a model action.
13
- * Consider using a proper model action instead since these kind of actions are not recorded.
14
+ * Runs a block in unprotected mode, as if it were run inside a model action.
15
+ * Structural mutations share one synchronous mutation batch, but the block is
16
+ * not recorded as an action. Consider using a proper model action when action
17
+ * recording is needed.
14
18
  *
15
19
  * @template T Return type.
16
20
  * @param fn Action block.
@@ -76,6 +76,9 @@ export interface ModelOptions<TProps extends ModelProps, FS, TS> {
76
76
  /**
77
77
  * Optional transformation that will be run when converting from a snapshot to the data part of the model.
78
78
  * Useful for example to do versioning and keep the data part up to date with the latest version of the model.
79
+ * Snapshot processors must be pure and deterministic. The model's canonical output snapshot must round-trip
80
+ * through this processor to the same model data. Reconciliation may skip invoking it when the input already
81
+ * equals the model's current canonical output snapshot.
79
82
  *
80
83
  * @param sn The custom input snapshot.
81
84
  * @returns An input snapshot that must match the expected model input snapshot.
@@ -83,6 +86,8 @@ export interface ModelOptions<TProps extends ModelProps, FS, TS> {
83
86
  fromSnapshotProcessor?: (sn: FS) => FromSnapshotDefaultType<TProps>;
84
87
  /**
85
88
  * Optional transformation that will be run when converting the data part of the model into a snapshot.
89
+ * Snapshot processors must be pure and deterministic, and their canonical output must round-trip through the
90
+ * corresponding input processor to the same model data. Snapshot generation may cache processor results.
86
91
  *
87
92
  * @param sn The output snapshot.
88
93
  * @param modelInstance The model instance.
@@ -23,17 +23,18 @@ export interface ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TT
23
23
  _defaultFn: (() => TPropValue) | typeof noDefaultValue;
24
24
  _defaultValue: TPropValue | typeof noDefaultValue;
25
25
  _defaultValueIsTransformed: boolean;
26
+ _getDefaultValueIsTransformed?: () => boolean;
26
27
  _typeChecker: AnyStandardType | undefined;
27
28
  _setter: SetterMode;
28
29
  _setterValueTransform: ((value: unknown) => unknown) | undefined;
29
30
  _isId: boolean;
30
31
  _idGenerator?: (() => string) | undefined;
31
- _transform: {
32
- transform: (original: unknown, model: object, propName: PropertyKey, setOriginalValue: (newOriginalValue: unknown) => void) => unknown;
33
- untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown;
34
- } | undefined;
32
+ _transform: FullModelPropTransform | undefined;
33
+ _getTransform?: () => FullModelPropTransform | undefined;
35
34
  _fromSnapshotProcessor?: (sn: unknown) => unknown;
36
35
  _toSnapshotProcessor?: (sn: unknown) => unknown;
36
+ _getFromSnapshotProcessor?: () => ((sn: unknown) => unknown) | undefined;
37
+ _getToSnapshotProcessor?: () => ((sn: unknown) => unknown) | undefined;
37
38
  /**
38
39
  * Adds a setter to the property. The setter will be named `set${CapitalizedPropName}`
39
40
  * and will be available in the model instance.
@@ -66,6 +67,9 @@ export interface ModelProp<TPropValue, TPropCreationValue, TTransformedValue, TT
66
67
  *
67
68
  * `fromSnapshot` runs before assigning snapshot data into the model prop.
68
69
  * `toSnapshot` runs when exporting model data back to snapshot form.
70
+ * Both processors must be pure and deterministic, and their canonical output
71
+ * must round-trip to the same stored value. Reconciliation and snapshot
72
+ * generation may skip calls when that canonical snapshot is already current.
69
73
  */
70
74
  withSnapshotProcessor<FS = TFromSnapshotOverride, TS = TToSnapshotOverride, This extends AnyModelProp = this>(processor: {
71
75
  fromSnapshot?: (sn: FS) => ModelPropFromSnapshot<This>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobx-keystone",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "A MobX powered state management solution based on data trees with first class support for TypeScript, snapshots, patches and much more",
5
5
  "keywords": [
6
6
  "mobx",
@@ -58,9 +58,9 @@
58
58
  "test": "vitest run",
59
59
  "test:ci": "vitest run --coverage",
60
60
  "test:perf": "pnpm run build && pnpm run test:perf:run",
61
- "test:perf:run": "cd perf_bench && NODE_ENV=production /usr/bin/time node --expose-gc --require ts-node/register ./report.ts",
62
- "memtest": "cd perf_bench && NODE_ENV=production node --expose-gc --require ts-node/register ./memtest.ts",
63
- "build-docs": "shx rm -rf api-docs && typedoc --options ./typedocconfig.js src/index.ts && shx rm -rf ../../apps/site/generated-static/api && shx mkdir -p ../../apps/site/generated-static/api && shx cp -R ./api-docs/* ../../apps/site/generated-static/api"
61
+ "test:perf:run": "cd perf_bench && NODE_ENV=production TS_NODE_COMPILER=@typescript/typescript6 /usr/bin/time node --expose-gc --require ts-node/register ./report.ts",
62
+ "memtest": "cd perf_bench && NODE_ENV=production TS_NODE_COMPILER=@typescript/typescript6 node --expose-gc --require ts-node/register ./memtest.ts",
63
+ "build-docs": "shx rm -rf api-docs && node --import ./scripts/typedoc-typescript6-register.mjs ./node_modules/typedoc/dist/cli.js --options ./typedocconfig.js src/index.ts && shx rm -rf ../../apps/site/generated-static/api && shx mkdir -p ../../apps/site/generated-static/api && shx cp -R ./api-docs/* ../../apps/site/generated-static/api"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "mobx": "^6.0.0 || ^5.0.0 || ^4.0.0"
@@ -73,18 +73,19 @@
73
73
  "@babel/preset-env": "^8.0.2",
74
74
  "@swc/core": "^1.15.43",
75
75
  "@types/babel__core": "^7.20.5",
76
- "@types/node": "^26.0.1",
77
- "@vitest/coverage-v8": "^4.1.9",
76
+ "@types/node": "^26.1.1",
77
+ "@typescript/typescript6": "^6.0.2",
78
+ "@vitest/coverage-v8": "^4.1.10",
78
79
  "mobx-v4": "npm:mobx@^4.15.7",
79
80
  "mobx-v5": "npm:mobx@^5.15.7",
80
81
  "shx": "^0.4.0",
81
82
  "spec.ts": "^1.1.3",
82
83
  "ts-node": "^10.9.2",
83
- "typedoc": "^0.28.19",
84
- "typescript": "^6.0.3",
85
- "vite": "^8.1.0",
84
+ "typedoc": "^0.28.20",
85
+ "typescript": "^7.0.2",
86
+ "vite": "^8.1.4",
86
87
  "vite-plugin-dts": "^5.0.3",
87
- "vitest": "^4.1.9"
88
+ "vitest": "^4.1.10"
88
89
  },
89
90
  "dependencies": {
90
91
  "fast-deep-equal": "^3.1.3",
@@ -1,5 +1,4 @@
1
1
  import { fastGetParent } from "../parent/path"
2
- import { isChildOfParent } from "../parent/path2"
3
2
  import { assertTweakedObject } from "../tweaker/core"
4
3
  import { assertIsFunction, assertIsObject, deleteFromArray, failure } from "../utils"
5
4
  import type { ActionContext } from "./context"
@@ -31,7 +30,7 @@ export interface ActionMiddleware {
31
30
  */
32
31
  export type ActionMiddlewareDisposer = () => void
33
32
 
34
- type PartialActionMiddleware = Pick<ActionMiddleware, "filter" | "middleware">
33
+ type PartialActionMiddleware = Pick<ActionMiddleware, "subtreeRoot" | "filter" | "middleware">
35
34
 
36
35
  const perObjectActionMiddlewares = new WeakMap<object, PartialActionMiddleware[]>()
37
36
 
@@ -96,7 +95,7 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
96
95
  assertIsObject(mware, "middleware")
97
96
 
98
97
  const { middleware, subtreeRoot } = mware
99
- let { filter } = mware
98
+ const { filter } = mware
100
99
 
101
100
  assertTweakedObject(subtreeRoot, "middleware.subtreeRoot")
102
101
  assertIsFunction(middleware, "middleware.middleware")
@@ -107,21 +106,10 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
107
106
  // reminder: never turn middlewares into actions or else
108
107
  // reactions will not be picked up by the undo manager
109
108
 
110
- if (subtreeRoot) {
111
- const targetFilter = (ctx: ActionContext) =>
112
- ctx.target === subtreeRoot || isChildOfParent(ctx.target, subtreeRoot)
113
-
114
- if (filter) {
115
- const customFilter = filter
116
- filter = (ctx) => {
117
- return targetFilter(ctx) && customFilter(ctx)
118
- }
119
- } else {
120
- filter = targetFilter
121
- }
122
- }
109
+ // Middleware collection establishes the initial subtree match. The runner
110
+ // revalidates it after user middleware or filter code may have changed parentage.
123
111
 
124
- const actualMware = { middleware, filter }
112
+ const actualMware = { middleware, subtreeRoot, filter }
125
113
 
126
114
  let objMwares = perObjectActionMiddlewares.get(subtreeRoot)
127
115
  if (objMwares) {
@@ -130,7 +118,6 @@ export function addActionMiddleware(mware: ActionMiddleware): ActionMiddlewareDi
130
118
  objMwares = [actualMware]
131
119
  perObjectActionMiddlewares.set(subtreeRoot, objMwares)
132
120
  }
133
-
134
121
  return () => {
135
122
  deleteFromArray(objMwares, actualMware)
136
123
  }
@@ -0,0 +1,72 @@
1
+ let mutationBatchActive = false
2
+ let mutationBatchFinishers: (() => void) | (() => void)[] | undefined
3
+
4
+ /**
5
+ * Starts a synchronous mutation batch if one is not already active.
6
+ *
7
+ * @returns `true` if this call owns the batch and must finish it.
8
+ * @internal
9
+ */
10
+ export function startMutationBatch(): boolean {
11
+ if (mutationBatchActive) {
12
+ return false
13
+ }
14
+
15
+ mutationBatchActive = true
16
+ return true
17
+ }
18
+
19
+ /**
20
+ * @internal
21
+ */
22
+ export function isMutationBatchActive(): boolean {
23
+ return mutationBatchActive
24
+ }
25
+
26
+ /**
27
+ * Registers a callback to run when the outermost synchronous mutation batch
28
+ * ends. The first callback is stored directly so the common single-finisher
29
+ * case does not allocate an array.
30
+ *
31
+ * @internal
32
+ */
33
+ export function addMutationBatchFinisher(finisher: () => void): void {
34
+ const currentFinishers = mutationBatchFinishers
35
+ if (!currentFinishers) {
36
+ mutationBatchFinishers = finisher
37
+ } else if (typeof currentFinishers === "function") {
38
+ mutationBatchFinishers = [currentFinishers, finisher]
39
+ } else {
40
+ currentFinishers.push(finisher)
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Finishes a batch started by `startMutationBatch`. Every finisher runs even
46
+ * if an earlier one throws, so a finisher's cleanup of its own module state
47
+ * cannot be skipped by another consumer's exception.
48
+ *
49
+ * @internal
50
+ */
51
+ export function finishMutationBatch(): void {
52
+ mutationBatchActive = false
53
+
54
+ const finishers = mutationBatchFinishers
55
+ mutationBatchFinishers = undefined
56
+
57
+ if (typeof finishers === "function") {
58
+ finishers()
59
+ } else if (finishers) {
60
+ runFinishers(finishers, finishers.length - 1)
61
+ }
62
+ }
63
+
64
+ function runFinishers(finishers: (() => void)[], index: number): void {
65
+ try {
66
+ finishers[index]()
67
+ } finally {
68
+ if (index > 0) {
69
+ runFinishers(finishers, index - 1)
70
+ }
71
+ }
72
+ }
@@ -1,10 +1,13 @@
1
1
  import { action } from "mobx"
2
+ import { finishMutationBatch, startMutationBatch } from "./mutationBatch"
2
3
  import { tryRunPendingActions } from "./pendingActions"
3
4
  import { getActionProtection, setActionProtection } from "./protection"
4
5
 
5
6
  /**
6
- * Runs a block in unprocted mode, as if it were run inside a model action.
7
- * Consider using a proper model action instead since these kind of actions are not recorded.
7
+ * Runs a block in unprotected mode, as if it were run inside a model action.
8
+ * Structural mutations share one synchronous mutation batch, but the block is
9
+ * not recorded as an action. Consider using a proper model action when action
10
+ * recording is needed.
8
11
  *
9
12
  * @template T Return type.
10
13
  * @param name Mobx action name.
@@ -14,8 +17,10 @@ import { getActionProtection, setActionProtection } from "./protection"
14
17
  export function runUnprotected<T>(name: string, fn: () => T): T
15
18
 
16
19
  /**
17
- * Runs a block in unprocted mode, as if it were run inside a model action.
18
- * Consider using a proper model action instead since these kind of actions are not recorded.
20
+ * Runs a block in unprotected mode, as if it were run inside a model action.
21
+ * Structural mutations share one synchronous mutation batch, but the block is
22
+ * not recorded as an action. Consider using a proper model action when action
23
+ * recording is needed.
19
24
  *
20
25
  * @template T Return type.
21
26
  * @param fn Action block.
@@ -28,7 +33,9 @@ export function runUnprotected<T>(arg1: any, arg2?: any): T {
28
33
  const name = typeof arg1 === "string" ? arg1 : undefined
29
34
  const fn: () => T = typeof arg1 === "string" ? arg2 : arg1
30
35
 
36
+ let mutationBatchOwner = false
31
37
  const innerAction = () => {
38
+ mutationBatchOwner = startMutationBatch()
32
39
  const oldActionProtection = getActionProtection()
33
40
  setActionProtection(false)
34
41
 
@@ -41,9 +48,15 @@ export function runUnprotected<T>(arg1: any, arg2?: any): T {
41
48
  }
42
49
  }
43
50
 
44
- if (name) {
45
- return action(name, innerAction)()
46
- } else {
47
- return action(innerAction)()
51
+ try {
52
+ if (name) {
53
+ return action(name, innerAction)()
54
+ } else {
55
+ return action(innerAction)()
56
+ }
57
+ } finally {
58
+ if (mutationBatchOwner) {
59
+ finishMutationBatch()
60
+ }
48
61
  }
49
62
  }
@@ -2,6 +2,7 @@ import { action } from "mobx"
2
2
  import type { O } from "ts-toolbelt"
3
3
  import type { AnyDataModel } from "../dataModel/BaseDataModel"
4
4
  import type { AnyModel } from "../model/BaseModel"
5
+ import { fastGetParent } from "../parent/path"
5
6
  import type { AnyFunction } from "../utils/AnyFunction"
6
7
  import { copyFunctionMetadata } from "../utils/decorators"
7
8
  import {
@@ -13,8 +14,20 @@ import {
13
14
  import { isModelAction, modelActionSymbol } from "./isModelAction"
14
15
  import { getPerObjectActionMiddlewares } from "./middleware"
15
16
  import type { FlowFinisher } from "./modelFlow"
17
+ import { finishMutationBatch, startMutationBatch } from "./mutationBatch"
16
18
  import { tryRunPendingActions } from "./pendingActions"
17
19
 
20
+ function isActionTargetInSubtree(target: object, subtreeRoot: object): boolean {
21
+ let current: object | undefined = target
22
+ while (current) {
23
+ if (current === subtreeRoot) {
24
+ return true
25
+ }
26
+ current = fastGetParent(current, false)
27
+ }
28
+ return false
29
+ }
30
+
18
31
  /**
19
32
  * @internal
20
33
  */
@@ -77,29 +90,44 @@ export function wrapInAction<T extends Function>({
77
90
  setCurrentActionContext(context)
78
91
 
79
92
  const perObjectMiddlewares = getPerObjectActionMiddlewares(context.target)
80
-
81
93
  let objectIndex = perObjectMiddlewares.length - 1 // from topmost parent to the object itself
82
94
  let objectMwareIndex = 0
95
+ let scopeMayHaveChanged = false
83
96
 
84
97
  const runNextMiddleware = (): unknown => {
85
- const objectMwares = perObjectMiddlewares[objectIndex]
86
- if (!objectMwares) {
87
- // no more middlewares, run base action
88
- return fn.apply(this, args)
89
- }
90
- const mwareData = objectMwares[objectMwareIndex]
98
+ while (objectIndex >= 0) {
99
+ const objectMwares = perObjectMiddlewares[objectIndex]
100
+ const mwareData = objectMwares[objectMwareIndex]
101
+
102
+ // Advance before calling user code so next() continues with the following middleware.
103
+ objectMwareIndex++
104
+ if (objectMwareIndex >= objectMwares.length) {
105
+ objectMwareIndex = 0
106
+ objectIndex--
107
+ }
91
108
 
92
- // advance to the next middleware
93
- objectMwareIndex++
94
- if (objectMwareIndex >= objectMwares.length) {
95
- objectMwareIndex = 0
96
- objectIndex--
109
+ if (
110
+ scopeMayHaveChanged &&
111
+ !isActionTargetInSubtree(context.target, mwareData.subtreeRoot)
112
+ ) {
113
+ continue
114
+ }
115
+
116
+ if (mwareData.filter) {
117
+ scopeMayHaveChanged = true
118
+ if (!mwareData.filter(context)) {
119
+ continue
120
+ }
121
+ }
122
+
123
+ scopeMayHaveChanged = true
124
+ return mwareData.middleware(context, runNextMiddleware)
97
125
  }
98
126
 
99
- const filterPassed = mwareData.filter ? mwareData.filter(context) : true
100
- return filterPassed ? mwareData.middleware(context, runNextMiddleware) : runNextMiddleware()
127
+ return fn.apply(this, args)
101
128
  }
102
129
 
130
+ const mutationBatchOwner = startMutationBatch()
103
131
  try {
104
132
  const ret = runNextMiddleware()
105
133
 
@@ -117,7 +145,9 @@ export function wrapInAction<T extends Function>({
117
145
  }
118
146
  } finally {
119
147
  setCurrentActionContext(context.parentContext)
120
-
148
+ if (mutationBatchOwner) {
149
+ finishMutationBatch()
150
+ }
121
151
  tryRunPendingActions()
122
152
  }
123
153
  }
@@ -32,7 +32,7 @@ const dataModelInstanceCache = new WeakMap<ModelClass<AnyDataModel>, WeakMap<any
32
32
  */
33
33
  export abstract class BaseDataModel<TProps extends ModelProps> {
34
34
  // just to make typing work properly
35
- [propsTypeSymbol]!: TProps
35
+ declare [propsTypeSymbol]: TProps
36
36
 
37
37
  /**
38
38
  * Called after the instance is created when there's the first call to `fn(M, data)`.
@@ -105,9 +105,10 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
105
105
  let changed = false
106
106
 
107
107
  // apply untransform (if any)
108
- if (propData._transform) {
108
+ const transform = propData._getTransform ? propData._getTransform() : propData._transform
109
+ if (transform) {
109
110
  changed = true
110
- newValue = propData._transform.untransform(newValue, this, k)
111
+ newValue = transform.untransform(newValue, this, k)
111
112
  }
112
113
 
113
114
  // apply default value (if needed)
@@ -143,11 +144,6 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
143
144
 
144
145
  Object.setPrototypeOf(this, modelClass.prototype)
145
146
 
146
- const self = this as any
147
-
148
- // delete unnecessary props
149
- delete self[propsTypeSymbol]
150
-
151
147
  internalNewDataModel(this, tweakedData as any, {
152
148
  modelClass,
153
149
  })