palistor 0.0.25 → 0.0.26
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.
- package/README.md +574 -542
- package/README.ru.md +979 -0
- package/dist/index.d.ts +16 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -28
- package/dist/index.js.map +1 -1
- package/dist/react/createTrackingProxy.d.ts +27 -27
- package/dist/react/createTrackingProxy.d.ts.map +1 -1
- package/dist/react/createTrackingProxy.js +54 -53
- package/dist/react/createTrackingProxy.js.map +1 -1
- package/dist/react/useForm.d.ts +42 -43
- package/dist/react/useForm.d.ts.map +1 -1
- package/dist/react/useForm.js +38 -38
- package/dist/react/useForm.js.map +1 -1
- package/dist/react/usePersist.d.ts +14 -14
- package/dist/react/usePersist.js +18 -18
- package/dist/react/usePersist.js.map +1 -1
- package/dist/react/useStoreContext.d.ts +11 -11
- package/dist/react/useStoreContext.js +11 -11
- package/dist/react/useTranslator.d.ts +12 -12
- package/dist/react/useTranslator.js +12 -12
- package/dist/store/applyPatch/applyPatch.d.ts +8 -8
- package/dist/store/applyPatch/applyPatch.js +12 -12
- package/dist/store/applyPatch/applyPatch.js.map +1 -1
- package/dist/store/buildProxy/buildEntityProjectionProxy.js +6 -6
- package/dist/store/buildProxy/buildEntityProjectionProxy.js.map +1 -1
- package/dist/store/buildProxy/buildListProxy.d.ts +8 -8
- package/dist/store/buildProxy/buildListProxy.js +30 -30
- package/dist/store/buildProxy/buildListProxy.js.map +1 -1
- package/dist/store/buildProxy/buildProxy.d.ts +8 -8
- package/dist/store/buildProxy/buildProxy.js +39 -39
- package/dist/store/buildProxy/buildProxy.js.map +1 -1
- package/dist/store/buildProxy/computeProxyKeys.d.ts +8 -8
- package/dist/store/buildProxy/computeProxyKeys.js +9 -9
- package/dist/store/buildProxy/computeProxyKeys.js.map +1 -1
- package/dist/store/buildProxy/handleLazyResolve.d.ts +3 -3
- package/dist/store/buildProxy/handleLazyResolve.js +3 -3
- package/dist/store/buildProxy/initProxyCaches.d.ts +5 -5
- package/dist/store/buildProxy/initProxyCaches.js +5 -5
- package/dist/store/compute/computeFieldState.d.ts +3 -3
- package/dist/store/compute/computeFieldState.js +8 -8
- package/dist/store/compute/computeFieldState.js.map +1 -1
- package/dist/store/compute/fieldStateChanged.d.ts +1 -1
- package/dist/store/compute/fieldStateChanged.js +1 -1
- package/dist/store/compute/recompute/collectGroupComputeNodes.d.ts +4 -4
- package/dist/store/compute/recompute/collectGroupComputeNodes.js +7 -7
- package/dist/store/compute/recompute/collectGroupComputeNodes.js.map +1 -1
- package/dist/store/compute/recompute/recomputeAndNotify.d.ts +3 -3
- package/dist/store/compute/recompute/recomputeAndNotify.js +3 -3
- package/dist/store/compute/recompute/recomputeLeaves.d.ts +11 -10
- package/dist/store/compute/recompute/recomputeLeaves.d.ts.map +1 -1
- package/dist/store/compute/recompute/recomputeLeaves.js +26 -27
- package/dist/store/compute/recompute/recomputeLeaves.js.map +1 -1
- package/dist/store/compute/recompute/recomputeTargeted.d.ts +7 -7
- package/dist/store/compute/recompute/recomputeTargeted.js +12 -12
- package/dist/store/compute/recompute/recomputeTargeted.js.map +1 -1
- package/dist/store/compute/recompute/topologicalSortComputed.d.ts +4 -4
- package/dist/store/compute/recompute/topologicalSortComputed.d.ts.map +1 -1
- package/dist/store/compute/recompute/topologicalSortComputed.js +11 -13
- package/dist/store/compute/recompute/topologicalSortComputed.js.map +1 -1
- package/dist/store/compute/recompute/types.d.ts +4 -4
- package/dist/store/compute/resolveFlag.d.ts +2 -2
- package/dist/store/compute/resolveFlag.js +2 -2
- package/dist/store/compute/resolveString.d.ts +5 -5
- package/dist/store/compute/resolveString.js +6 -6
- package/dist/store/compute/resolveString.js.map +1 -1
- package/dist/store/compute/types.d.ts +3 -3
- package/dist/store/constants.d.ts +48 -48
- package/dist/store/constants.d.ts.map +1 -1
- package/dist/store/constants.js +45 -45
- package/dist/store/defineFieldMapping.d.ts +21 -19
- package/dist/store/defineFieldMapping.d.ts.map +1 -1
- package/dist/store/defineFieldMapping.js +21 -19
- package/dist/store/defineFieldMapping.js.map +1 -1
- package/dist/store/dirtyTracking/collectInitialSnapshot.js +1 -1
- package/dist/store/dirtyTracking/collectInitialSnapshot.js.map +1 -1
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.d.ts +3 -3
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.js +11 -11
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.js.map +1 -1
- package/dist/store/dirtyTracking/setGroupRevalidate.d.ts +6 -6
- package/dist/store/dirtyTracking/setGroupRevalidate.js +7 -7
- package/dist/store/dirtyTracking/setGroupRevalidate.js.map +1 -1
- package/dist/store/entityRegistry/entityRegistry.d.ts +72 -72
- package/dist/store/entityRegistry/entityRegistry.d.ts.map +1 -1
- package/dist/store/entityRegistry/entityRegistry.js +91 -91
- package/dist/store/entityRegistry/entityRegistry.js.map +1 -1
- package/dist/store/entityRegistry/generateId.d.ts +3 -3
- package/dist/store/entityRegistry/generateId.js +3 -3
- package/dist/store/entityRegistry/types.d.ts +17 -17
- package/dist/store/entityRegistry/types.d.ts.map +1 -1
- package/dist/store/flow/buildFlowStepsProxy.d.ts +9 -9
- package/dist/store/flow/buildFlowStepsProxy.js +13 -13
- package/dist/store/flow/buildFlowStepsProxy.js.map +1 -1
- package/dist/store/flow/defineFlow.d.ts +27 -25
- package/dist/store/flow/defineFlow.d.ts.map +1 -1
- package/dist/store/flow/defineFlow.js +15 -13
- package/dist/store/flow/defineFlow.js.map +1 -1
- package/dist/store/flow/flowApi.d.ts +3 -2
- package/dist/store/flow/flowApi.d.ts.map +1 -1
- package/dist/store/flow/flowApi.js +1 -1
- package/dist/store/flow/flowApi.js.map +1 -1
- package/dist/store/flow/flowNavigation.d.ts +65 -63
- package/dist/store/flow/flowNavigation.d.ts.map +1 -1
- package/dist/store/flow/flowNavigation.js +74 -72
- package/dist/store/flow/flowNavigation.js.map +1 -1
- package/dist/store/flow/flowState.d.ts +16 -16
- package/dist/store/flow/flowState.d.ts.map +1 -1
- package/dist/store/flow/flowState.js +5 -5
- package/dist/store/flow/flowState.js.map +1 -1
- package/dist/store/groupDeps/createGroupDeps.d.ts +3 -3
- package/dist/store/groupDeps/createGroupDeps.js +6 -6
- package/dist/store/groupDeps/createGroupDeps.js.map +1 -1
- package/dist/store/groupDeps/createTrackingValues.d.ts +9 -9
- package/dist/store/groupDeps/createTrackingValues.js +11 -11
- package/dist/store/groupDeps/createTrackingValues.js.map +1 -1
- package/dist/store/groupDeps/getNodeGroupPath.d.ts +3 -3
- package/dist/store/groupDeps/getNodeGroupPath.js +5 -5
- package/dist/store/groupDeps/getNodeGroupPath.js.map +1 -1
- package/dist/store/groupDeps/getRecipientGroups.d.ts +1 -1
- package/dist/store/groupDeps/getRecipientGroups.js +1 -1
- package/dist/store/groupDeps/groupDeps.d.ts +7 -7
- package/dist/store/groupDeps/groupDeps.js +7 -7
- package/dist/store/groupDeps/pairKey.d.ts +3 -3
- package/dist/store/groupDeps/pairKey.d.ts.map +1 -1
- package/dist/store/groupDeps/pairKey.js +4 -4
- package/dist/store/groupDeps/pairKey.js.map +1 -1
- package/dist/store/groupDeps/resolveGroupByPath.d.ts +3 -3
- package/dist/store/groupDeps/resolveGroupByPath.js +3 -3
- package/dist/store/init/createNotificationHub.d.ts +31 -31
- package/dist/store/init/createNotificationHub.d.ts.map +1 -1
- package/dist/store/init/createNotificationHub.js +36 -37
- package/dist/store/init/createNotificationHub.js.map +1 -1
- package/dist/store/init/createResolveManager.d.ts +72 -69
- package/dist/store/init/createResolveManager.d.ts.map +1 -1
- package/dist/store/init/createResolveManager.js +101 -98
- package/dist/store/init/createResolveManager.js.map +1 -1
- package/dist/store/init/initGroupSubmitting.d.ts +2 -2
- package/dist/store/init/initGroupSubmitting.js +4 -4
- package/dist/store/init/initGroupSubmitting.js.map +1 -1
- package/dist/store/normalizeConfig.d.ts +20 -18
- package/dist/store/normalizeConfig.d.ts.map +1 -1
- package/dist/store/normalizeConfig.js +31 -29
- package/dist/store/normalizeConfig.js.map +1 -1
- package/dist/store/onChangePipeline/computeFieldKey.d.ts +2 -2
- package/dist/store/onChangePipeline/computeFieldKey.js +2 -2
- package/dist/store/onChangePipeline/findOnChangeAncestors.d.ts +2 -2
- package/dist/store/onChangePipeline/findOnChangeAncestors.js +4 -4
- package/dist/store/onChangePipeline/findOnChangeAncestors.js.map +1 -1
- package/dist/store/onChangePipeline/onChangePipeline.d.ts +13 -12
- package/dist/store/onChangePipeline/onChangePipeline.d.ts.map +1 -1
- package/dist/store/onChangePipeline/onChangePipeline.js +14 -13
- package/dist/store/onChangePipeline/onChangePipeline.js.map +1 -1
- package/dist/store/persist/drivers.d.ts +7 -7
- package/dist/store/persist/drivers.js +8 -8
- package/dist/store/persist/drivers.js.map +1 -1
- package/dist/store/persist/index.d.ts +1 -1
- package/dist/store/persist/persistManager.d.ts +23 -23
- package/dist/store/persist/persistManager.d.ts.map +1 -1
- package/dist/store/persist/persistManager.js +51 -51
- package/dist/store/persist/persistManager.js.map +1 -1
- package/dist/store/persist/types.d.ts +23 -23
- package/dist/store/persist/types.d.ts.map +1 -1
- package/dist/store/persist/types.js +4 -4
- package/dist/store/resetPipeline/buildResetPatch.d.ts +5 -5
- package/dist/store/resetPipeline/buildResetPatch.js +5 -5
- package/dist/store/resetPipeline/collectDefaults.d.ts +5 -5
- package/dist/store/resetPipeline/collectDefaults.js +6 -6
- package/dist/store/resetPipeline/collectDefaults.js.map +1 -1
- package/dist/store/resetPipeline/resetPipeline.d.ts +7 -7
- package/dist/store/resetPipeline/resetPipeline.js +15 -15
- package/dist/store/resetPipeline/resetPipeline.js.map +1 -1
- package/dist/store/resolvePipeline/applyPendingWrites.d.ts +10 -10
- package/dist/store/resolvePipeline/applyPendingWrites.d.ts.map +1 -1
- package/dist/store/resolvePipeline/applyPendingWrites.js +35 -38
- package/dist/store/resolvePipeline/applyPendingWrites.js.map +1 -1
- package/dist/store/resolvePipeline/createContextTrackingProxy.d.ts +8 -7
- package/dist/store/resolvePipeline/createContextTrackingProxy.d.ts.map +1 -1
- package/dist/store/resolvePipeline/createContextTrackingProxy.js +6 -5
- package/dist/store/resolvePipeline/createContextTrackingProxy.js.map +1 -1
- package/dist/store/resolvePipeline/createValuesTrackingProxy.d.ts +43 -43
- package/dist/store/resolvePipeline/createValuesTrackingProxy.d.ts.map +1 -1
- package/dist/store/resolvePipeline/createValuesTrackingProxy.js +70 -70
- package/dist/store/resolvePipeline/createValuesTrackingProxy.js.map +1 -1
- package/dist/store/resolvePipeline/executeListResolve.d.ts +14 -14
- package/dist/store/resolvePipeline/executeListResolve.js +27 -27
- package/dist/store/resolvePipeline/executeListResolve.js.map +1 -1
- package/dist/store/resolvePipeline/executeResolve.d.ts +15 -15
- package/dist/store/resolvePipeline/executeResolve.js +46 -46
- package/dist/store/resolvePipeline/executeResolve.js.map +1 -1
- package/dist/store/store/NodeRegistry/nodeRegistry.d.ts +61 -61
- package/dist/store/store/NodeRegistry/nodeRegistry.d.ts.map +1 -1
- package/dist/store/store/NodeRegistry/nodeRegistry.js +85 -86
- package/dist/store/store/NodeRegistry/nodeRegistry.js.map +1 -1
- package/dist/store/store/NodeRegistry/nodeUtils.d.ts +5 -5
- package/dist/store/store/NodeRegistry/nodeUtils.d.ts.map +1 -1
- package/dist/store/store/NodeRegistry/nodeUtils.js +5 -5
- package/dist/store/store/NodeRegistry/nodeUtils.js.map +1 -1
- package/dist/store/store/groupDepsMap.d.ts +23 -23
- package/dist/store/store/groupDepsMap.d.ts.map +1 -1
- package/dist/store/store/groupDepsMap.js +35 -34
- package/dist/store/store/groupDepsMap.js.map +1 -1
- package/dist/store/store/hasComputedProps.d.ts +2 -2
- package/dist/store/store/hasComputedProps.js +2 -2
- package/dist/store/store/nodeMap.d.ts +5 -5
- package/dist/store/store/nodeMap.js +7 -7
- package/dist/store/store/nodeMap.js.map +1 -1
- package/dist/store/store/palistor.d.ts +80 -80
- package/dist/store/store/palistor.d.ts.map +1 -1
- package/dist/store/store/palistor.js +149 -169
- package/dist/store/store/palistor.js.map +1 -1
- package/dist/store/store/registerNodes.d.ts +10 -10
- package/dist/store/store/registerNodes.js +15 -15
- package/dist/store/store/registerNodes.js.map +1 -1
- package/dist/store/store/serviceRegistry.d.ts +8 -8
- package/dist/store/store/serviceRegistry.d.ts.map +1 -1
- package/dist/store/store/serviceRegistry.js +8 -8
- package/dist/store/store/serviceRegistry.js.map +1 -1
- package/dist/store/store/types.d.ts +236 -234
- package/dist/store/store/types.d.ts.map +1 -1
- package/dist/store/submitPipeline/applyLeafBeforeSubmit.d.ts +2 -2
- package/dist/store/submitPipeline/applyLeafBeforeSubmit.js +2 -2
- package/dist/store/submitPipeline/collectLeafStates.d.ts +2 -2
- package/dist/store/submitPipeline/collectLeafStates.js +2 -2
- package/dist/store/submitPipeline/submitPipeline.d.ts +8 -8
- package/dist/store/submitPipeline/submitPipeline.js +12 -12
- package/dist/store/submitPipeline/submitPipeline.js.map +1 -1
- package/dist/store/submitPipeline/types.d.ts +1 -1
- package/dist/store/submitPipeline/types.d.ts.map +1 -1
- package/dist/store/traversal/nodeClassifier.d.ts +9 -9
- package/dist/store/traversal/nodeClassifier.d.ts.map +1 -1
- package/dist/store/traversal/nodeClassifier.js +11 -11
- package/dist/store/traversal/nodeClassifier.js.map +1 -1
- package/dist/store/traversal/walkFull.d.ts +14 -14
- package/dist/store/traversal/walkFull.js +2 -2
- package/dist/store/valuesCache/valuesCache.d.ts +9 -9
- package/dist/store/valuesCache/valuesCache.d.ts.map +1 -1
- package/dist/store/valuesCache/valuesCache.js +6 -6
- package/dist/store/writePipeline/formatPatch.d.ts +5 -5
- package/dist/store/writePipeline/formatPatch.js +7 -7
- package/dist/store/writePipeline/formatPatch.js.map +1 -1
- package/dist/store/writePipeline/formatValue.d.ts +5 -5
- package/dist/store/writePipeline/formatValue.js +5 -5
- package/dist/store/writePipeline/mergeChanged.d.ts +4 -4
- package/dist/store/writePipeline/mergeChanged.js +4 -4
- package/dist/store/writePipeline/runSetter.d.ts +6 -6
- package/dist/store/writePipeline/runSetter.js +8 -8
- package/dist/store/writePipeline/storeValue.d.ts +3 -3
- package/dist/store/writePipeline/storeValue.js +3 -3
- package/dist/store/writePipeline/types.d.ts +4 -4
- package/dist/store/writePipeline/types.d.ts.map +1 -1
- package/dist/store/writePipeline/writePipeline.d.ts +7 -7
- package/dist/store/writePipeline/writePipeline.d.ts.map +1 -1
- package/dist/store/writePipeline/writePipeline.js +15 -15
- package/dist/store/writePipeline/writePipeline.js.map +1 -1
- package/package.json +11 -4
|
@@ -22,8 +22,8 @@ import { normalizeConfig } from "../normalizeConfig";
|
|
|
22
22
|
import { initFlows } from "../flow/flowNavigation";
|
|
23
23
|
// ─── Palistor ─────────────────────────────────────────────────────────────────
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* The public form class. Acts as the DI container for all internal
|
|
26
|
+
* subsystems and implements the public `ProxyStore` interface.
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
29
29
|
* const store = new Palistor({ config: myConfig, initialValues: {...} });
|
|
@@ -31,7 +31,7 @@ import { initFlows } from "../flow/flowNavigation";
|
|
|
31
31
|
* store.submit();
|
|
32
32
|
*/
|
|
33
33
|
export class Palistor {
|
|
34
|
-
// ───
|
|
34
|
+
// ─── Constructor ──────────────────────────────────────────────────────────
|
|
35
35
|
constructor(options) {
|
|
36
36
|
/**
|
|
37
37
|
* @internal Plain POJO mirrors for each entity — used in valuesCache.values
|
|
@@ -40,16 +40,16 @@ export class Palistor {
|
|
|
40
40
|
*/
|
|
41
41
|
this.entityProjectionObjs = new Map();
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* Non-reactive context — arbitrary data available via `store.context`.
|
|
44
|
+
* Set via `setContext()` or the `useStoreContext()` hook.
|
|
45
45
|
*/
|
|
46
46
|
this._context = {};
|
|
47
47
|
const { config, initialValues = {} } = options;
|
|
48
|
-
// ─── Field mapping (
|
|
49
|
-
// fwd: internal → external (
|
|
50
|
-
// externalToInternal: external → internal (
|
|
51
|
-
//
|
|
52
|
-
// →
|
|
48
|
+
// ─── Field mapping (two projections of the map) ──────────────────────────
|
|
49
|
+
// fwd: internal → external (for ownKeys/spread).
|
|
50
|
+
// externalToInternal: external → internal (for GET/SET/tracking).
|
|
51
|
+
// Both are empty when fieldMapping is not provided → `?? key` returns the
|
|
52
|
+
// key as-is → zero overhead by default.
|
|
53
53
|
const fwd = options.fieldMapping ?? {};
|
|
54
54
|
this.fieldMapping = fwd;
|
|
55
55
|
this.externalToInternal = {};
|
|
@@ -58,14 +58,14 @@ export class Palistor {
|
|
|
58
58
|
if (external !== undefined)
|
|
59
59
|
this.externalToInternal[external] = internal;
|
|
60
60
|
}
|
|
61
|
-
// ───
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// internal
|
|
65
|
-
//
|
|
61
|
+
// ─── Config normalization (external → internal) ──────────────────────────
|
|
62
|
+
// The config is authored in PUBLIC (mapped) names. A single pass converts
|
|
63
|
+
// it to internal names BEFORE init/compute/traversal — everything below
|
|
64
|
+
// works with internal names unchanged. Empty map → the original config is
|
|
65
|
+
// returned without copying (zero overhead).
|
|
66
66
|
const rootConfig = normalizeConfig(config, this.externalToInternal, fwd);
|
|
67
67
|
this.rootConfig = rootConfig;
|
|
68
|
-
// ───
|
|
68
|
+
// ─── Services ────────────────────────────────────────────────────────────
|
|
69
69
|
this.services = new ServiceRegistry();
|
|
70
70
|
const { translate, notify } = this.services;
|
|
71
71
|
// ─── NodeRegistry ────────────────────────────────────────────────────────
|
|
@@ -81,9 +81,9 @@ export class Palistor {
|
|
|
81
81
|
for (const ls of this.nodes.allListStates) {
|
|
82
82
|
this.entityRegistry.registerList(ls);
|
|
83
83
|
}
|
|
84
|
-
// ─── GroupDepsMap +
|
|
84
|
+
// ─── GroupDepsMap + first recompute ──────────────────────────────────────
|
|
85
85
|
this.groupDepsMap = new GroupDepsMap(rootConfig, nodePaths, nodeParents);
|
|
86
|
-
this.recompute(); //
|
|
86
|
+
this.recompute(); // first full recompute — builds the dependency map
|
|
87
87
|
this.dirty.capture(rootConfig, nodeState);
|
|
88
88
|
// ─── NotificationHub ────────────────────────────────────────────────────
|
|
89
89
|
this.hub = new NotificationHub({ computeNodes, nodePaths });
|
|
@@ -102,7 +102,7 @@ export class Palistor {
|
|
|
102
102
|
syncListValuesCache: (listState) => this.syncListValuesCache(listState),
|
|
103
103
|
entityRegistry: this.entityRegistry,
|
|
104
104
|
});
|
|
105
|
-
// ─── Pipeline
|
|
105
|
+
// ─── Pipeline classes ─────────────────────────────────────────────────────
|
|
106
106
|
this.writePipeline = new WritePipeline(this);
|
|
107
107
|
this.resetPipeline = new ResetPipeline(this);
|
|
108
108
|
this.submitPipeline = new SubmitPipeline(this);
|
|
@@ -115,26 +115,26 @@ export class Palistor {
|
|
|
115
115
|
const postNotifyHook = this.resolveManager.createPostNotifyHook();
|
|
116
116
|
if (postNotifyHook)
|
|
117
117
|
this.hub.setPostNotifyHook(postNotifyHook);
|
|
118
|
-
// ───
|
|
118
|
+
// ─── Initial context ─────────────────────────────────────────────────────
|
|
119
119
|
if (options.context) {
|
|
120
120
|
this._context = options.context;
|
|
121
121
|
}
|
|
122
|
-
// ─── Flow: entry lifecycle
|
|
123
|
-
//
|
|
124
|
-
// onEnter → resolve (eager) → onReady.
|
|
125
|
-
//
|
|
122
|
+
// ─── Flow: entry lifecycle of the first step ─────────────────────────────
|
|
123
|
+
// The first step of every flow is "entered" at store creation:
|
|
124
|
+
// onEnter → resolve (eager) → onReady. Runs before launchEager so the flow
|
|
125
|
+
// itself triggers the step's idle resolve and attaches onReady correctly.
|
|
126
126
|
initFlows(this);
|
|
127
127
|
// ─── Launch eager resolvers ──────────────────────────────────────────────
|
|
128
128
|
this.resolveManager.launchEager();
|
|
129
129
|
}
|
|
130
|
-
// ─── @internal
|
|
130
|
+
// ─── @internal facade methods ─────────────────────────────────────────────
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
132
|
+
* Recompute node state.
|
|
133
133
|
*
|
|
134
|
-
* - `changedNodes`
|
|
135
|
-
* -
|
|
134
|
+
* - `changedNodes` provided and non-empty → targeted recompute (fast)
|
|
135
|
+
* - otherwise → full recompute of the whole tree (init, reset, resolve completion)
|
|
136
136
|
*
|
|
137
|
-
*
|
|
137
|
+
* The first call without changedNodes builds the group dependency map.
|
|
138
138
|
*
|
|
139
139
|
* @internal
|
|
140
140
|
*/
|
|
@@ -163,8 +163,8 @@ export class Palistor {
|
|
|
163
163
|
return recomputeLeaves(computeNodes, nodeState, this.values, translate);
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
166
|
+
* Notify subscribers about changed nodes.
|
|
167
|
+
* Recomputes dirty flags and bumps versions.
|
|
168
168
|
*
|
|
169
169
|
* @internal
|
|
170
170
|
*/
|
|
@@ -178,14 +178,14 @@ export class Palistor {
|
|
|
178
178
|
nodePaths: this.nodes.nodePaths,
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
|
-
// ─── @internal pipeline
|
|
182
|
-
/** @internal
|
|
181
|
+
// ─── @internal pipeline methods ───────────────────────────────────────────
|
|
182
|
+
/** @internal Apply a bulk patch to a node (single recompute + notify). */
|
|
183
183
|
setValuesNode(node, patch) {
|
|
184
184
|
const formatted = formatPatch(node, patch, this.values.values);
|
|
185
185
|
const changed = applyPatch(node, this.nodes.nodeState, formatted, new Set(), this.values);
|
|
186
186
|
_recomputeAndNotify(changed, () => this.recompute(), (c) => this.notifyChanged(c));
|
|
187
187
|
}
|
|
188
|
-
// ─── ProxyStore —
|
|
188
|
+
// ─── ProxyStore — public API ──────────────────────────────────────────────
|
|
189
189
|
get proxy() {
|
|
190
190
|
return this._proxy;
|
|
191
191
|
}
|
|
@@ -241,11 +241,11 @@ export class Palistor {
|
|
|
241
241
|
this.setValuesNode(this.rootConfig, patch);
|
|
242
242
|
}
|
|
243
243
|
/**
|
|
244
|
-
*
|
|
244
|
+
* Create or update an entity (or an array of entities) in the registry.
|
|
245
245
|
*
|
|
246
|
-
* -
|
|
247
|
-
* -
|
|
248
|
-
* - Batch
|
|
246
|
+
* - If no entity with the id exists — it is created and its leaf nodes registered.
|
|
247
|
+
* - If it exists — recursive merge; updated leaf nodes are marked as changed.
|
|
248
|
+
* - Batch mode: an array of entities is processed in one recompute + notifyChanged.
|
|
249
249
|
*/
|
|
250
250
|
set(data) {
|
|
251
251
|
const items = Array.isArray(data) ? data : [data];
|
|
@@ -260,14 +260,14 @@ export class Palistor {
|
|
|
260
260
|
this.notifyChanged(recomputed);
|
|
261
261
|
}
|
|
262
262
|
/**
|
|
263
|
-
*
|
|
263
|
+
* Rename an entity: move it from oldId to newId.
|
|
264
264
|
*
|
|
265
|
-
* -
|
|
266
|
-
* -
|
|
267
|
-
* -
|
|
268
|
-
* -
|
|
265
|
+
* - Updates EntityRegistry (entities Map, bindings, resolvedCache, id leaf value).
|
|
266
|
+
* - Updates itemIds in all ListState objects.
|
|
267
|
+
* - Updates entityProjectionObjs (moves the POJO mirror).
|
|
268
|
+
* - Notifies subscribers about the id leaf change.
|
|
269
269
|
*
|
|
270
|
-
* No-op
|
|
270
|
+
* No-op when no entity with oldId exists.
|
|
271
271
|
*/
|
|
272
272
|
rekey(oldId, newId) {
|
|
273
273
|
const entity = this.entityRegistry.get(oldId);
|
|
@@ -297,57 +297,56 @@ export class Palistor {
|
|
|
297
297
|
this.notifyChanged(recomputed);
|
|
298
298
|
}
|
|
299
299
|
/**
|
|
300
|
-
*
|
|
300
|
+
* Delete an entity from the registry by ID.
|
|
301
301
|
*
|
|
302
|
-
* -
|
|
303
|
-
* -
|
|
304
|
-
* -
|
|
302
|
+
* - Removes the entity's leaf nodes from NodeRegistry (computeNodes, groupComputeMap).
|
|
303
|
+
* - Clears bindings and resolvedCache.
|
|
304
|
+
* - Notifies subscribers.
|
|
305
305
|
*
|
|
306
|
-
* No-op
|
|
306
|
+
* No-op when the entity does not exist.
|
|
307
307
|
*/
|
|
308
308
|
delete(id) {
|
|
309
309
|
const entityNode = this.entityRegistry.get(id);
|
|
310
310
|
if (!entityNode)
|
|
311
311
|
return;
|
|
312
|
-
//
|
|
313
|
-
//
|
|
312
|
+
// Cascade-delete child entities owned by this entity.
|
|
313
|
+
// Copy the set — childrenByOwner is mutated during the recursive delete.
|
|
314
314
|
const childIds = this.entityRegistry.getChildrenByOwner(id);
|
|
315
315
|
if (childIds && childIds.size > 0) {
|
|
316
316
|
for (const childId of [...childIds]) {
|
|
317
317
|
this.delete(childId);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
|
-
//
|
|
320
|
+
// Collect all leaf nodes of the entity
|
|
321
321
|
const deletedLeaves = new Set();
|
|
322
322
|
this.collectEntityLeaves(entityNode, deletedLeaves);
|
|
323
|
-
//
|
|
323
|
+
// Remove leaf nodes from NodeRegistry (prevents a memory leak)
|
|
324
324
|
for (const leaf of deletedLeaves) {
|
|
325
325
|
this.nodes.unregisterLeaf(leaf);
|
|
326
326
|
}
|
|
327
|
-
//
|
|
327
|
+
// Cleanup per-entity field resolve states
|
|
328
328
|
this.resolveManager.cleanupEntityResolveStates(id);
|
|
329
|
-
//
|
|
329
|
+
// Remove the entity from the registry (clears bindings + resolvedCache)
|
|
330
330
|
this.entityRegistry.delete(id);
|
|
331
|
-
// Уведомить подписчиков
|
|
332
331
|
this.notifyChanged(deletedLeaves);
|
|
333
332
|
}
|
|
334
333
|
/**
|
|
335
|
-
*
|
|
334
|
+
* Clear the resolved cache for an entity (all templates or a specific one).
|
|
336
335
|
*
|
|
337
|
-
* - `invalidate(id)` —
|
|
338
|
-
* - `invalidate(id, templateNode)` —
|
|
336
|
+
* - `invalidate(id)` — clear the whole cache for the entity
|
|
337
|
+
* - `invalidate(id, templateNode)` — clear only for that specific pair
|
|
339
338
|
*
|
|
340
|
-
*
|
|
339
|
+
* On the next mount of useForm(entity, template) the resolve re-runs.
|
|
341
340
|
*/
|
|
342
341
|
invalidate(id, templateNode) {
|
|
343
342
|
this.entityRegistry.clearResolved(id, templateNode);
|
|
344
343
|
}
|
|
345
344
|
/**
|
|
346
|
-
* Submit entity
|
|
347
|
-
*
|
|
345
|
+
* Submit an entity through a template.
|
|
346
|
+
* Called from EntityProjectionProxy.submit().
|
|
348
347
|
*
|
|
349
348
|
* 1. submitting: true → notify
|
|
350
|
-
* 2.
|
|
349
|
+
* 2. Validation via template field rules (validate)
|
|
351
350
|
* 3. templateNode.onSubmit(entityProxy, store)
|
|
352
351
|
* 4. templateNode.afterSubmit(result, { reset })
|
|
353
352
|
* 5. submitting: false → notify
|
|
@@ -355,7 +354,6 @@ export class Palistor {
|
|
|
355
354
|
* @internal
|
|
356
355
|
*/
|
|
357
356
|
async executeEntityTemplateSubmit(entityId, templateNode, entityProxy) {
|
|
358
|
-
// Шаг 1: Найти entity в реестре. Если не найдена — ранний выход с ошибкой.
|
|
359
357
|
const entityNode = this.entityRegistry.get(entityId);
|
|
360
358
|
if (!entityNode) {
|
|
361
359
|
return {
|
|
@@ -363,34 +361,25 @@ export class Palistor {
|
|
|
363
361
|
errors: [{ path: "", message: `Entity "${entityId}" not found` }],
|
|
364
362
|
};
|
|
365
363
|
}
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
// для отображения спиннера в UI.
|
|
364
|
+
// Binding state { loading, submitting } for the (entityId, templateNode)
|
|
365
|
+
// pair — read by EntityProjectionProxy to render a spinner in the UI.
|
|
369
366
|
const bindingState = this.resolveManager.entityStates.getOrCreate(entityId, templateNode);
|
|
370
367
|
const entityNodeObj = entityNode;
|
|
371
|
-
// Шаг 3: Поднять флаг submitting и уведомить подписчиков (React перерендерит
|
|
372
|
-
// компоненты, привязанные к этой entity — например, кнопка покажет спиннер).
|
|
373
368
|
bindingState.submitting = true;
|
|
374
369
|
this.notifyChanged(new Set([entityNodeObj]));
|
|
375
370
|
try {
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
// берутся из nodeState entity.
|
|
371
|
+
// Validation — recursively walk template fields and call validate() on
|
|
372
|
+
// every leaf that has one. Current values come from the entity's nodeState.
|
|
379
373
|
const errors = [];
|
|
380
374
|
this.collectEntityTemplateErrors(templateNode, entityNode, errors, "");
|
|
381
|
-
// Если есть ошибки валидации — вернуть их, не вызывая onSubmit.
|
|
382
375
|
if (errors.length > 0) {
|
|
383
376
|
return { success: false, errors };
|
|
384
377
|
}
|
|
385
|
-
//
|
|
386
|
-
// entityProxy — это Proxy entity с template-правилами, store — сам Palistor.
|
|
387
|
-
// Обычно здесь выполняется API-запрос на сервер.
|
|
378
|
+
// User onSubmit(entityProxy, store) — typically the API call.
|
|
388
379
|
let result;
|
|
389
380
|
if (typeof templateNode.onSubmit === "function") {
|
|
390
381
|
result = await templateNode.onSubmit(entityProxy, this);
|
|
391
382
|
}
|
|
392
|
-
// Шаг 6: Вызвать afterSubmit(result, { reset }) — хук после успешного submit.
|
|
393
|
-
// reset для entity template — no-op (у entity нет встроенного сброса, в отличие от form).
|
|
394
383
|
if (typeof templateNode.afterSubmit === "function") {
|
|
395
384
|
const reset = () => void 0; // entity template has no built-in reset
|
|
396
385
|
await templateNode.afterSubmit(result, { reset });
|
|
@@ -398,13 +387,12 @@ export class Palistor {
|
|
|
398
387
|
return { success: true, result };
|
|
399
388
|
}
|
|
400
389
|
finally {
|
|
401
|
-
//
|
|
402
|
-
// finally гарантирует сброс даже при ошибке в onSubmit/afterSubmit.
|
|
390
|
+
// Always clear submitting, even when onSubmit/afterSubmit throws.
|
|
403
391
|
bindingState.submitting = false;
|
|
404
392
|
this.notifyChanged(new Set([entityNodeObj]));
|
|
405
393
|
}
|
|
406
394
|
}
|
|
407
|
-
// ───
|
|
395
|
+
// ─── Private entity helpers ──────────────────────────────────────────────
|
|
408
396
|
/**
|
|
409
397
|
* Upsert entities in EntityRegistry and register/update their leaf nodes.
|
|
410
398
|
* Returns Set of changed leaf nodes. Does NOT call recompute or notifyChanged.
|
|
@@ -412,7 +400,7 @@ export class Palistor {
|
|
|
412
400
|
* Used internally by `set()` and by `executeListResolve` via the
|
|
413
401
|
* `setEntitiesRaw` callback in ResolveManagerDeps.
|
|
414
402
|
*
|
|
415
|
-
*
|
|
403
|
+
* When `listNode` is provided, triggers entity field resolves for
|
|
416
404
|
* each template field entry belonging to that list.
|
|
417
405
|
*
|
|
418
406
|
* @internal
|
|
@@ -420,22 +408,22 @@ export class Palistor {
|
|
|
420
408
|
_setEntitiesRaw(items, listNode) {
|
|
421
409
|
const changed = new Set();
|
|
422
410
|
for (const item of items) {
|
|
423
|
-
//
|
|
424
|
-
//
|
|
411
|
+
// Create a new EntityNode or update the existing one (recursive merge).
|
|
412
|
+
// When item.id is missing, EntityRegistry generates a temporary _tmp_* id.
|
|
425
413
|
const entityNode = this.entityRegistry.upsert(item);
|
|
426
414
|
const entityId = entityNode.id.value;
|
|
427
|
-
//
|
|
415
|
+
// All entity nodes live in the "_entity_." namespace, isolated from form config nodes.
|
|
428
416
|
const entityPrefix = `_entity_.${entityId}`;
|
|
429
|
-
// projectionObj —
|
|
430
|
-
//
|
|
431
|
-
//
|
|
417
|
+
// projectionObj — the entity's plain-POJO mirror { id, field1, field2 }.
|
|
418
|
+
// Used in valuesCache.values as a list array element.
|
|
419
|
+
// The POJO's referential identity survives upserts; only values change.
|
|
432
420
|
let projectionObj = this.entityProjectionObjs.get(entityId);
|
|
433
421
|
if (!projectionObj) {
|
|
434
422
|
projectionObj = {};
|
|
435
423
|
this.entityProjectionObjs.set(entityId, projectionObj);
|
|
436
424
|
}
|
|
437
|
-
// DFS
|
|
438
|
-
//
|
|
425
|
+
// DFS over the entity tree: register new leaf nodes
|
|
426
|
+
// or detect changes in existing ones.
|
|
439
427
|
this.walkAndSyncEntityNode(entityNode, entityPrefix, entityNode, changed, projectionObj);
|
|
440
428
|
// Entity field resolves are lazy-only: triggered when a component first reads
|
|
441
429
|
// field.value or field.loading (via queueMicrotask in buildEntityProjectionProxy).
|
|
@@ -444,19 +432,19 @@ export class Palistor {
|
|
|
444
432
|
return changed;
|
|
445
433
|
}
|
|
446
434
|
/**
|
|
447
|
-
*
|
|
435
|
+
* Sync valuesCache with list membership — a SINGLE method (root + per-entity).
|
|
448
436
|
*
|
|
449
|
-
*
|
|
450
|
-
* - `null` → root:
|
|
451
|
-
* (`valuesCache.values.users = [{id:"u1",…}, …]` —
|
|
452
|
-
* `proxy.users.value
|
|
453
|
-
* - entity → per-entity
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
437
|
+
* Branches on `listState.ownerEntity`:
|
|
438
|
+
* - `null` → root: write the array of POJO mirrors into the config node's
|
|
439
|
+
* `nodeSlot` (`valuesCache.values.users = [{id:"u1",…}, …]` — the same
|
|
440
|
+
* array that `proxy.users.value` reads, so React sees the update);
|
|
441
|
+
* - entity → per-entity: materialize the membership into the owner's
|
|
442
|
+
* projectionObj at the list path (`["contacts"]` or `["profile","contacts"]`).
|
|
443
|
+
* This includes the nested list in `store.getValues()` — the owner's
|
|
444
|
+
* projectionObj is referenced by the root list array, and child
|
|
445
|
+
* projectionObjs materialize their lists recursively.
|
|
458
446
|
*
|
|
459
|
-
* No-op
|
|
447
|
+
* No-op when the slot/path/owner projectionObj is missing.
|
|
460
448
|
*
|
|
461
449
|
* @internal
|
|
462
450
|
*/
|
|
@@ -465,14 +453,14 @@ export class Palistor {
|
|
|
465
453
|
.map((id) => this.entityProjectionObjs.get(id))
|
|
466
454
|
.filter((obj) => obj !== undefined);
|
|
467
455
|
if (listState.ownerEntity === null) {
|
|
468
|
-
// Root:
|
|
456
|
+
// Root: the valuesCache slot ({ parent, key } pair) keyed by config node.
|
|
469
457
|
const slot = this.values.nodeSlot.get(listState.listConfigNode);
|
|
470
458
|
if (!slot)
|
|
471
459
|
return;
|
|
472
460
|
slot.parent[slot.key] = materialized;
|
|
473
461
|
return;
|
|
474
462
|
}
|
|
475
|
-
// Per-entity:
|
|
463
|
+
// Per-entity: descend to the parent POJO along the path and write the membership.
|
|
476
464
|
const listConfigNode = listState.listConfigNode;
|
|
477
465
|
const path = this.nodes.listFieldKeys.get(listConfigNode);
|
|
478
466
|
if (!path || path.length === 0)
|
|
@@ -493,22 +481,22 @@ export class Palistor {
|
|
|
493
481
|
}
|
|
494
482
|
target[path[path.length - 1]] = materialized;
|
|
495
483
|
}
|
|
496
|
-
/** @internal
|
|
484
|
+
/** @internal Current entity id (accounts for rekey via nodeState). */
|
|
497
485
|
_entityId(entity) {
|
|
498
486
|
const idLeaf = entity.id;
|
|
499
487
|
return (this.nodes.nodeState.get(idLeaf)?.value ??
|
|
500
488
|
entity.id.value);
|
|
501
489
|
}
|
|
502
490
|
/**
|
|
503
|
-
*
|
|
504
|
-
* (
|
|
491
|
+
* Restore the membership of root AND per-entity lists from a values
|
|
492
|
+
* snapshot (persist hydrate).
|
|
505
493
|
*
|
|
506
|
-
* `applyPatch`
|
|
507
|
-
*
|
|
508
|
-
* owner
|
|
509
|
-
*
|
|
494
|
+
* `applyPatch` skips list nodes, so list membership is restored in a
|
|
495
|
+
* separate pass: for every list field we create child entities, set the
|
|
496
|
+
* owner reference (for nested ones), fill `itemIds`/`initialItemIds` and
|
|
497
|
+
* sync valuesCache. Handles nested-of-nested recursively.
|
|
510
498
|
*
|
|
511
|
-
*
|
|
499
|
+
* Returns the set of changed nodes for a subsequent notify.
|
|
512
500
|
*
|
|
513
501
|
* @internal
|
|
514
502
|
*/
|
|
@@ -522,7 +510,7 @@ export class Palistor {
|
|
|
522
510
|
const child = configNode[key];
|
|
523
511
|
if (!child || typeof child !== "object")
|
|
524
512
|
continue;
|
|
525
|
-
//
|
|
513
|
+
// Nested group (not a list): recurse to reach the lists inside it.
|
|
526
514
|
if (!Array.isArray(child)) {
|
|
527
515
|
if (isGroupNode(child)) {
|
|
528
516
|
const nested = valueObj?.[key];
|
|
@@ -555,7 +543,7 @@ export class Palistor {
|
|
|
555
543
|
if (ownerEntity) {
|
|
556
544
|
this.entityRegistry.setEntityOwner(childEntity, this._entityId(ownerEntity), listConfigNode);
|
|
557
545
|
}
|
|
558
|
-
//
|
|
546
|
+
// Recurse into this item's nested lists.
|
|
559
547
|
this._restoreListsRec(template, itemObj, childEntity, changed);
|
|
560
548
|
}
|
|
561
549
|
}
|
|
@@ -572,7 +560,7 @@ export class Palistor {
|
|
|
572
560
|
listState.itemIds = ids;
|
|
573
561
|
listState.initialItemIds = [...ids];
|
|
574
562
|
this.syncListValuesCache(listState);
|
|
575
|
-
//
|
|
563
|
+
// ListState is the tracking key; listConfigNode is the backward-compat bridge.
|
|
576
564
|
changed.add(listState);
|
|
577
565
|
changed.add(listConfigNode);
|
|
578
566
|
}
|
|
@@ -580,57 +568,57 @@ export class Palistor {
|
|
|
580
568
|
}
|
|
581
569
|
}
|
|
582
570
|
/**
|
|
583
|
-
*
|
|
584
|
-
* (
|
|
585
|
-
* `createEntityNode`
|
|
571
|
+
* Return a shallow copy of an entity data object without list fields
|
|
572
|
+
* (their membership is restored separately via EntityListState). Otherwise
|
|
573
|
+
* `createEntityNode` would ingest the array as a regular leaf value.
|
|
586
574
|
*/
|
|
587
575
|
_stripListFields(itemObj, template) {
|
|
588
576
|
const result = {};
|
|
589
577
|
for (const key of Object.keys(itemObj)) {
|
|
590
578
|
const tField = template?.[key];
|
|
591
579
|
if (Array.isArray(tField))
|
|
592
|
-
continue; // list
|
|
580
|
+
continue; // skip list fields
|
|
593
581
|
result[key] = itemObj[key];
|
|
594
582
|
}
|
|
595
583
|
return result;
|
|
596
584
|
}
|
|
597
585
|
/**
|
|
598
|
-
*
|
|
599
|
-
* `registerDynamicLeaf`
|
|
586
|
+
* Recursive walk over an entity node: registers new leaf nodes via
|
|
587
|
+
* `registerDynamicLeaf` and accumulates changed ones in `changed`.
|
|
600
588
|
*
|
|
601
|
-
*
|
|
602
|
-
* -
|
|
603
|
-
* -
|
|
589
|
+
* Also maintains the entity projection POJO (projectionObj):
|
|
590
|
+
* - New leaf nodes: register nodeSlot → projectionObj
|
|
591
|
+
* - Existing leaf nodes: updated via updateValuesCacheEntry
|
|
604
592
|
*
|
|
605
|
-
* @param node
|
|
606
|
-
* @param prefix Dot
|
|
607
|
-
* @param parent
|
|
608
|
-
* @param changed
|
|
593
|
+
* @param node Current entity node being walked
|
|
594
|
+
* @param prefix Dot-path of the current node (e.g. "_entity_.u1")
|
|
595
|
+
* @param parent Parent object (for leaf registration)
|
|
596
|
+
* @param changed Accumulated set of changed nodes
|
|
609
597
|
* @param projectionObj Plain POJO at the current nesting level for valuesCache
|
|
610
598
|
*/
|
|
611
599
|
walkAndSyncEntityNode(node, prefix, parent, changed, projectionObj) {
|
|
612
|
-
//
|
|
613
|
-
// nodePaths
|
|
614
|
-
// (getNodeGroupPath)
|
|
600
|
+
// Register the group node's dot-path (e.g. "_entity_.u1", "_entity_.u1.address").
|
|
601
|
+
// nodePaths is used by the compute subsystem to determine a leaf's "group"
|
|
602
|
+
// (getNodeGroupPath) and during targeted recompute.
|
|
615
603
|
if (!this.nodes.nodePaths.has(parent)) {
|
|
616
604
|
this.nodes.nodePaths.set(parent, prefix);
|
|
617
605
|
}
|
|
618
606
|
for (const key of Object.keys(node)) {
|
|
619
607
|
const child = node[key];
|
|
620
|
-
//
|
|
608
|
+
// Skip primitives (not part of the entity tree)
|
|
621
609
|
if (!child || typeof child !== "object")
|
|
622
610
|
continue;
|
|
623
611
|
const childObj = child;
|
|
624
612
|
const childPath = `${prefix}.${key}`;
|
|
625
613
|
if (isLeafNode(childObj)) {
|
|
626
|
-
// ──
|
|
614
|
+
// ── Leaf node (EntityLeafNode): { value: <current value> } ──
|
|
627
615
|
const leaf = childObj;
|
|
628
616
|
if (!this.nodes.nodeState.has(childObj)) {
|
|
629
|
-
//
|
|
630
|
-
// - nodeState:
|
|
631
|
-
// - nodePaths:
|
|
632
|
-
// - nodeParents:
|
|
633
|
-
//
|
|
617
|
+
// First encounter of this leaf — register in NodeRegistry:
|
|
618
|
+
// - nodeState: holds FieldState (value, isVisible, dirty, etc.)
|
|
619
|
+
// - nodePaths: node → dot-path mapping
|
|
620
|
+
// - nodeParents: node → parent group mapping
|
|
621
|
+
// Entity leaves are always visible, never required/disabled.
|
|
634
622
|
this.nodes.registerDynamicLeaf(childObj, childPath, parent, {
|
|
635
623
|
value: leaf.value,
|
|
636
624
|
isVisible: true,
|
|
@@ -643,9 +631,9 @@ export class Palistor {
|
|
|
643
631
|
// Seed initialValueMap so recomputeDirtyTargeted compares against the entity's
|
|
644
632
|
// loaded value instead of `undefined` — prevents spurious dirty=true on first load.
|
|
645
633
|
this.dirty.initialValueMap.set(childObj, leaf.value);
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
//
|
|
634
|
+
// Bind the leaf to the POJO mirror via nodeSlot. Subsequent
|
|
635
|
+
// updateValuesCacheEntry(node, newValue) calls update the
|
|
636
|
+
// projectionObj value automatically in O(1).
|
|
649
637
|
if (projectionObj !== undefined) {
|
|
650
638
|
this.values.nodeSlot.set(childObj, { parent: projectionObj, key });
|
|
651
639
|
projectionObj[key] = leaf.value;
|
|
@@ -653,24 +641,22 @@ export class Palistor {
|
|
|
653
641
|
changed.add(childObj);
|
|
654
642
|
}
|
|
655
643
|
else {
|
|
656
|
-
// Leaf
|
|
657
|
-
//
|
|
644
|
+
// Leaf already registered — check whether the value changed.
|
|
645
|
+
// Happens on repeated upserts (entity refresh from the server).
|
|
658
646
|
const state = this.nodes.nodeState.get(childObj);
|
|
659
647
|
if (state.value !== leaf.value) {
|
|
660
|
-
//
|
|
648
|
+
// Raw write to nodeState (bypasses writePipeline on purpose)
|
|
661
649
|
state.value = leaf.value;
|
|
662
|
-
// Обновить projectionObj через nodeSlot: O(1), не нужно искать POJO.
|
|
663
650
|
updateValuesCacheEntry(this.values, childObj, leaf.value);
|
|
664
651
|
changed.add(childObj);
|
|
665
652
|
}
|
|
666
|
-
//
|
|
667
|
-
// подписчики не будут уведомлены (оптимизация).
|
|
653
|
+
// Unchanged values stay out of `changed` — subscribers are not notified.
|
|
668
654
|
}
|
|
669
655
|
}
|
|
670
656
|
else {
|
|
671
|
-
// ──
|
|
672
|
-
//
|
|
673
|
-
//
|
|
657
|
+
// ── Group node (EntityGroupNode): nested object without "value" ──
|
|
658
|
+
// e.g. address: { city: { value: "Moscow" } }
|
|
659
|
+
// Create a nested POJO in projectionObj (if needed) and recurse.
|
|
674
660
|
let nestedProjectionObj;
|
|
675
661
|
if (projectionObj !== undefined) {
|
|
676
662
|
if (!projectionObj[key] || typeof projectionObj[key] !== "object") {
|
|
@@ -683,8 +669,8 @@ export class Palistor {
|
|
|
683
669
|
}
|
|
684
670
|
}
|
|
685
671
|
/**
|
|
686
|
-
*
|
|
687
|
-
*
|
|
672
|
+
* Recursively collect all leaf nodes from an entity node tree.
|
|
673
|
+
* Used by `delete()` to clean up NodeRegistry.
|
|
688
674
|
*/
|
|
689
675
|
collectEntityLeaves(node, result) {
|
|
690
676
|
for (const key of Object.keys(node)) {
|
|
@@ -692,11 +678,9 @@ export class Palistor {
|
|
|
692
678
|
if (!child || typeof child !== "object")
|
|
693
679
|
continue;
|
|
694
680
|
if (isLeafNode(child)) {
|
|
695
|
-
// Leaf-нода: объект с полем "value" → добавить в результат
|
|
696
681
|
result.add(child);
|
|
697
682
|
}
|
|
698
683
|
else {
|
|
699
|
-
// Группа — рекурсивно обойти вложенные узлы
|
|
700
684
|
this.collectEntityLeaves(child, result);
|
|
701
685
|
}
|
|
702
686
|
}
|
|
@@ -710,37 +694,34 @@ export class Palistor {
|
|
|
710
694
|
*/
|
|
711
695
|
collectEntityTemplateErrors(templateNode, entityNode, errors, parentPath) {
|
|
712
696
|
const translate = this.services.translate;
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
697
|
+
// Plain values object passed as the second argument to every validate(),
|
|
698
|
+
// so validators can check cross-field dependencies, e.g.:
|
|
699
|
+
// validate: (v, vals) => vals.password !== vals.confirmPassword ? "Mismatch" : undefined
|
|
716
700
|
const entityValues = this.buildEntityValuesForTemplate(entityNode);
|
|
717
701
|
for (const key of configKeys(templateNode)) {
|
|
718
702
|
const templateField = templateNode[key];
|
|
719
703
|
if (!templateField || typeof templateField !== "object")
|
|
720
704
|
continue;
|
|
721
|
-
//
|
|
705
|
+
// Dot-path for the error message (e.g. "address.city")
|
|
722
706
|
const path = parentPath ? `${parentPath}.${key}` : key;
|
|
723
707
|
if (isLeafNode(templateField)) {
|
|
724
|
-
// Leaf-поле template — проверяем, есть ли validate() функция
|
|
725
708
|
if (typeof templateField.validate === "function") {
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
// 2. fallback на entityField.value (если node ещё не зарегистрирован)
|
|
709
|
+
// Current value: nodeState first (post write-pipeline), falling back
|
|
710
|
+
// to entityField.value when the node is not registered yet.
|
|
729
711
|
const entityField = entityNode[key];
|
|
730
712
|
const currentValue = entityField && typeof entityField === "object"
|
|
731
713
|
? this.nodes.nodeState.get(entityField)?.value ?? entityField.value
|
|
732
714
|
: undefined;
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
// undefined / false = поле валидно.
|
|
715
|
+
// validate(currentValue, allEntityValues, translateFn):
|
|
716
|
+
// a string is an error message; undefined/false means valid.
|
|
736
717
|
const result = templateField.validate(currentValue, entityValues, translate);
|
|
737
718
|
if (result)
|
|
738
719
|
errors.push({ path, message: result });
|
|
739
720
|
}
|
|
740
721
|
}
|
|
741
722
|
else {
|
|
742
|
-
//
|
|
743
|
-
// Template
|
|
723
|
+
// Template group — recurse into the matching nested entity node.
|
|
724
|
+
// Template and entity share the same nesting structure.
|
|
744
725
|
const entityField = entityNode[key];
|
|
745
726
|
if (entityField && typeof entityField === "object") {
|
|
746
727
|
this.collectEntityTemplateErrors(templateField, entityField, errors, path);
|
|
@@ -755,19 +736,18 @@ export class Palistor {
|
|
|
755
736
|
* @internal
|
|
756
737
|
*/
|
|
757
738
|
buildEntityValuesForTemplate(entityNode) {
|
|
758
|
-
//
|
|
739
|
+
// Result shape: { id: "u1", name: "Alice", address: { city: "Moscow" } }
|
|
759
740
|
const values = {};
|
|
760
741
|
for (const key of Object.keys(entityNode)) {
|
|
761
742
|
const field = entityNode[key];
|
|
762
743
|
if (field && typeof field === "object") {
|
|
763
744
|
if (isLeafNode(field)) {
|
|
764
|
-
// Leaf:
|
|
745
|
+
// Leaf: read value from nodeState (current), fall back to field.value
|
|
765
746
|
values[key] =
|
|
766
747
|
this.nodes.nodeState.get(field)?.value ??
|
|
767
748
|
field.value;
|
|
768
749
|
}
|
|
769
750
|
else {
|
|
770
|
-
// Группа: рекурсивно собрать вложенный объект
|
|
771
751
|
values[key] = this.buildEntityValuesForTemplate(field);
|
|
772
752
|
}
|
|
773
753
|
}
|