palistor 0.0.24 → 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 +19 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -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 +92 -42
- 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.d.ts.map +1 -1
- package/dist/store/buildProxy/buildProxy.js +84 -33
- package/dist/store/buildProxy/buildProxy.js.map +1 -1
- package/dist/store/buildProxy/computeProxyKeys.d.ts +8 -8
- package/dist/store/buildProxy/computeProxyKeys.d.ts.map +1 -1
- package/dist/store/buildProxy/computeProxyKeys.js +18 -13
- 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 +57 -38
- package/dist/store/constants.d.ts.map +1 -1
- package/dist/store/constants.js +70 -35
- package/dist/store/constants.js.map +1 -1
- 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 +17 -0
- package/dist/store/flow/buildFlowStepsProxy.d.ts.map +1 -0
- package/dist/store/flow/buildFlowStepsProxy.js +72 -0
- package/dist/store/flow/buildFlowStepsProxy.js.map +1 -0
- package/dist/store/flow/defineFlow.d.ts +92 -0
- package/dist/store/flow/defineFlow.d.ts.map +1 -0
- package/dist/store/flow/defineFlow.js +99 -0
- package/dist/store/flow/defineFlow.js.map +1 -0
- package/dist/store/flow/flowApi.d.ts +18 -0
- package/dist/store/flow/flowApi.d.ts.map +1 -0
- package/dist/store/flow/flowApi.js +18 -0
- package/dist/store/flow/flowApi.js.map +1 -0
- package/dist/store/flow/flowNavigation.d.ts +137 -0
- package/dist/store/flow/flowNavigation.d.ts.map +1 -0
- package/dist/store/flow/flowNavigation.js +384 -0
- package/dist/store/flow/flowNavigation.js.map +1 -0
- package/dist/store/flow/flowState.d.ts +41 -0
- package/dist/store/flow/flowState.d.ts.map +1 -0
- package/dist/store/flow/flowState.js +51 -0
- package/dist/store/flow/flowState.js.map +1 -0
- package/dist/store/flow/index.d.ts +10 -0
- package/dist/store/flow/index.d.ts.map +1 -0
- package/dist/store/flow/index.js +6 -0
- package/dist/store/flow/index.js.map +1 -0
- 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 +73 -42
- 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.d.ts.map +1 -1
- package/dist/store/resetPipeline/resetPipeline.js +17 -12
- 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 +69 -56
- package/dist/store/store/NodeRegistry/nodeRegistry.d.ts.map +1 -1
- package/dist/store/store/NodeRegistry/nodeRegistry.js +94 -78
- 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 +40 -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 +151 -165
- package/dist/store/store/palistor.js.map +1 -1
- package/dist/store/store/registerNodes.d.ts +10 -10
- package/dist/store/store/registerNodes.d.ts.map +1 -1
- package/dist/store/store/registerNodes.js +33 -18
- 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 +277 -223
- 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.d.ts.map +1 -1
- package/dist/store/submitPipeline/submitPipeline.js +15 -11
- 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
- package/dist/store/compute/recompute/collectGroupLeafNodes.d.ts +0 -11
- package/dist/store/compute/recompute/collectGroupLeafNodes.d.ts.map +0 -1
- package/dist/store/compute/recompute/collectGroupLeafNodes.js +0 -26
- package/dist/store/compute/recompute/collectGroupLeafNodes.js.map +0 -1
- package/dist/store/fieldMapping.typecheck.d.ts +0 -2
- package/dist/store/fieldMapping.typecheck.d.ts.map +0 -1
- package/dist/store/fieldMapping.typecheck.js +0 -71
- package/dist/store/fieldMapping.typecheck.js.map +0 -1
- package/dist/store/submitPipeline/getSubValues.d.ts +0 -9
- package/dist/store/submitPipeline/getSubValues.d.ts.map +0 -1
- package/dist/store/submitPipeline/getSubValues.js +0 -26
- package/dist/store/submitPipeline/getSubValues.js.map +0 -1
|
@@ -19,10 +19,11 @@ import { EntityRegistry } from "../entityRegistry";
|
|
|
19
19
|
import { generateTmpId } from "../entityRegistry";
|
|
20
20
|
import { isLeafNode, isListNode, isGroupNode, configKeys } from "../traversal";
|
|
21
21
|
import { normalizeConfig } from "../normalizeConfig";
|
|
22
|
+
import { initFlows } from "../flow/flowNavigation";
|
|
22
23
|
// ─── Palistor ─────────────────────────────────────────────────────────────────
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* The public form class. Acts as the DI container for all internal
|
|
26
|
+
* subsystems and implements the public `ProxyStore` interface.
|
|
26
27
|
*
|
|
27
28
|
* @example
|
|
28
29
|
* const store = new Palistor({ config: myConfig, initialValues: {...} });
|
|
@@ -30,7 +31,7 @@ import { normalizeConfig } from "../normalizeConfig";
|
|
|
30
31
|
* store.submit();
|
|
31
32
|
*/
|
|
32
33
|
export class Palistor {
|
|
33
|
-
// ───
|
|
34
|
+
// ─── Constructor ──────────────────────────────────────────────────────────
|
|
34
35
|
constructor(options) {
|
|
35
36
|
/**
|
|
36
37
|
* @internal Plain POJO mirrors for each entity — used in valuesCache.values
|
|
@@ -39,16 +40,16 @@ export class Palistor {
|
|
|
39
40
|
*/
|
|
40
41
|
this.entityProjectionObjs = new Map();
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* Non-reactive context — arbitrary data available via `store.context`.
|
|
44
|
+
* Set via `setContext()` or the `useStoreContext()` hook.
|
|
44
45
|
*/
|
|
45
46
|
this._context = {};
|
|
46
47
|
const { config, initialValues = {} } = options;
|
|
47
|
-
// ─── Field mapping (
|
|
48
|
-
// fwd: internal → external (
|
|
49
|
-
// externalToInternal: external → internal (
|
|
50
|
-
//
|
|
51
|
-
// →
|
|
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.
|
|
52
53
|
const fwd = options.fieldMapping ?? {};
|
|
53
54
|
this.fieldMapping = fwd;
|
|
54
55
|
this.externalToInternal = {};
|
|
@@ -57,14 +58,14 @@ export class Palistor {
|
|
|
57
58
|
if (external !== undefined)
|
|
58
59
|
this.externalToInternal[external] = internal;
|
|
59
60
|
}
|
|
60
|
-
// ───
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// internal
|
|
64
|
-
//
|
|
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).
|
|
65
66
|
const rootConfig = normalizeConfig(config, this.externalToInternal, fwd);
|
|
66
67
|
this.rootConfig = rootConfig;
|
|
67
|
-
// ───
|
|
68
|
+
// ─── Services ────────────────────────────────────────────────────────────
|
|
68
69
|
this.services = new ServiceRegistry();
|
|
69
70
|
const { translate, notify } = this.services;
|
|
70
71
|
// ─── NodeRegistry ────────────────────────────────────────────────────────
|
|
@@ -80,9 +81,9 @@ export class Palistor {
|
|
|
80
81
|
for (const ls of this.nodes.allListStates) {
|
|
81
82
|
this.entityRegistry.registerList(ls);
|
|
82
83
|
}
|
|
83
|
-
// ─── GroupDepsMap +
|
|
84
|
+
// ─── GroupDepsMap + first recompute ──────────────────────────────────────
|
|
84
85
|
this.groupDepsMap = new GroupDepsMap(rootConfig, nodePaths, nodeParents);
|
|
85
|
-
this.recompute(); //
|
|
86
|
+
this.recompute(); // first full recompute — builds the dependency map
|
|
86
87
|
this.dirty.capture(rootConfig, nodeState);
|
|
87
88
|
// ─── NotificationHub ────────────────────────────────────────────────────
|
|
88
89
|
this.hub = new NotificationHub({ computeNodes, nodePaths });
|
|
@@ -101,7 +102,7 @@ export class Palistor {
|
|
|
101
102
|
syncListValuesCache: (listState) => this.syncListValuesCache(listState),
|
|
102
103
|
entityRegistry: this.entityRegistry,
|
|
103
104
|
});
|
|
104
|
-
// ─── Pipeline
|
|
105
|
+
// ─── Pipeline classes ─────────────────────────────────────────────────────
|
|
105
106
|
this.writePipeline = new WritePipeline(this);
|
|
106
107
|
this.resetPipeline = new ResetPipeline(this);
|
|
107
108
|
this.submitPipeline = new SubmitPipeline(this);
|
|
@@ -114,21 +115,26 @@ export class Palistor {
|
|
|
114
115
|
const postNotifyHook = this.resolveManager.createPostNotifyHook();
|
|
115
116
|
if (postNotifyHook)
|
|
116
117
|
this.hub.setPostNotifyHook(postNotifyHook);
|
|
117
|
-
// ───
|
|
118
|
+
// ─── Initial context ─────────────────────────────────────────────────────
|
|
118
119
|
if (options.context) {
|
|
119
120
|
this._context = options.context;
|
|
120
121
|
}
|
|
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
|
+
initFlows(this);
|
|
121
127
|
// ─── Launch eager resolvers ──────────────────────────────────────────────
|
|
122
128
|
this.resolveManager.launchEager();
|
|
123
129
|
}
|
|
124
|
-
// ─── @internal
|
|
130
|
+
// ─── @internal facade methods ─────────────────────────────────────────────
|
|
125
131
|
/**
|
|
126
|
-
*
|
|
132
|
+
* Recompute node state.
|
|
127
133
|
*
|
|
128
|
-
* - `changedNodes`
|
|
129
|
-
* -
|
|
134
|
+
* - `changedNodes` provided and non-empty → targeted recompute (fast)
|
|
135
|
+
* - otherwise → full recompute of the whole tree (init, reset, resolve completion)
|
|
130
136
|
*
|
|
131
|
-
*
|
|
137
|
+
* The first call without changedNodes builds the group dependency map.
|
|
132
138
|
*
|
|
133
139
|
* @internal
|
|
134
140
|
*/
|
|
@@ -157,8 +163,8 @@ export class Palistor {
|
|
|
157
163
|
return recomputeLeaves(computeNodes, nodeState, this.values, translate);
|
|
158
164
|
}
|
|
159
165
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
166
|
+
* Notify subscribers about changed nodes.
|
|
167
|
+
* Recomputes dirty flags and bumps versions.
|
|
162
168
|
*
|
|
163
169
|
* @internal
|
|
164
170
|
*/
|
|
@@ -172,14 +178,14 @@ export class Palistor {
|
|
|
172
178
|
nodePaths: this.nodes.nodePaths,
|
|
173
179
|
});
|
|
174
180
|
}
|
|
175
|
-
// ─── @internal pipeline
|
|
176
|
-
/** @internal
|
|
181
|
+
// ─── @internal pipeline methods ───────────────────────────────────────────
|
|
182
|
+
/** @internal Apply a bulk patch to a node (single recompute + notify). */
|
|
177
183
|
setValuesNode(node, patch) {
|
|
178
184
|
const formatted = formatPatch(node, patch, this.values.values);
|
|
179
185
|
const changed = applyPatch(node, this.nodes.nodeState, formatted, new Set(), this.values);
|
|
180
186
|
_recomputeAndNotify(changed, () => this.recompute(), (c) => this.notifyChanged(c));
|
|
181
187
|
}
|
|
182
|
-
// ─── ProxyStore —
|
|
188
|
+
// ─── ProxyStore — public API ──────────────────────────────────────────────
|
|
183
189
|
get proxy() {
|
|
184
190
|
return this._proxy;
|
|
185
191
|
}
|
|
@@ -235,11 +241,11 @@ export class Palistor {
|
|
|
235
241
|
this.setValuesNode(this.rootConfig, patch);
|
|
236
242
|
}
|
|
237
243
|
/**
|
|
238
|
-
*
|
|
244
|
+
* Create or update an entity (or an array of entities) in the registry.
|
|
239
245
|
*
|
|
240
|
-
* -
|
|
241
|
-
* -
|
|
242
|
-
* - 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.
|
|
243
249
|
*/
|
|
244
250
|
set(data) {
|
|
245
251
|
const items = Array.isArray(data) ? data : [data];
|
|
@@ -254,14 +260,14 @@ export class Palistor {
|
|
|
254
260
|
this.notifyChanged(recomputed);
|
|
255
261
|
}
|
|
256
262
|
/**
|
|
257
|
-
*
|
|
263
|
+
* Rename an entity: move it from oldId to newId.
|
|
258
264
|
*
|
|
259
|
-
* -
|
|
260
|
-
* -
|
|
261
|
-
* -
|
|
262
|
-
* -
|
|
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.
|
|
263
269
|
*
|
|
264
|
-
* No-op
|
|
270
|
+
* No-op when no entity with oldId exists.
|
|
265
271
|
*/
|
|
266
272
|
rekey(oldId, newId) {
|
|
267
273
|
const entity = this.entityRegistry.get(oldId);
|
|
@@ -291,57 +297,56 @@ export class Palistor {
|
|
|
291
297
|
this.notifyChanged(recomputed);
|
|
292
298
|
}
|
|
293
299
|
/**
|
|
294
|
-
*
|
|
300
|
+
* Delete an entity from the registry by ID.
|
|
295
301
|
*
|
|
296
|
-
* -
|
|
297
|
-
* -
|
|
298
|
-
* -
|
|
302
|
+
* - Removes the entity's leaf nodes from NodeRegistry (computeNodes, groupComputeMap).
|
|
303
|
+
* - Clears bindings and resolvedCache.
|
|
304
|
+
* - Notifies subscribers.
|
|
299
305
|
*
|
|
300
|
-
* No-op
|
|
306
|
+
* No-op when the entity does not exist.
|
|
301
307
|
*/
|
|
302
308
|
delete(id) {
|
|
303
309
|
const entityNode = this.entityRegistry.get(id);
|
|
304
310
|
if (!entityNode)
|
|
305
311
|
return;
|
|
306
|
-
//
|
|
307
|
-
//
|
|
312
|
+
// Cascade-delete child entities owned by this entity.
|
|
313
|
+
// Copy the set — childrenByOwner is mutated during the recursive delete.
|
|
308
314
|
const childIds = this.entityRegistry.getChildrenByOwner(id);
|
|
309
315
|
if (childIds && childIds.size > 0) {
|
|
310
316
|
for (const childId of [...childIds]) {
|
|
311
317
|
this.delete(childId);
|
|
312
318
|
}
|
|
313
319
|
}
|
|
314
|
-
//
|
|
320
|
+
// Collect all leaf nodes of the entity
|
|
315
321
|
const deletedLeaves = new Set();
|
|
316
322
|
this.collectEntityLeaves(entityNode, deletedLeaves);
|
|
317
|
-
//
|
|
323
|
+
// Remove leaf nodes from NodeRegistry (prevents a memory leak)
|
|
318
324
|
for (const leaf of deletedLeaves) {
|
|
319
325
|
this.nodes.unregisterLeaf(leaf);
|
|
320
326
|
}
|
|
321
|
-
//
|
|
327
|
+
// Cleanup per-entity field resolve states
|
|
322
328
|
this.resolveManager.cleanupEntityResolveStates(id);
|
|
323
|
-
//
|
|
329
|
+
// Remove the entity from the registry (clears bindings + resolvedCache)
|
|
324
330
|
this.entityRegistry.delete(id);
|
|
325
|
-
// Уведомить подписчиков
|
|
326
331
|
this.notifyChanged(deletedLeaves);
|
|
327
332
|
}
|
|
328
333
|
/**
|
|
329
|
-
*
|
|
334
|
+
* Clear the resolved cache for an entity (all templates or a specific one).
|
|
330
335
|
*
|
|
331
|
-
* - `invalidate(id)` —
|
|
332
|
-
* - `invalidate(id, templateNode)` —
|
|
336
|
+
* - `invalidate(id)` — clear the whole cache for the entity
|
|
337
|
+
* - `invalidate(id, templateNode)` — clear only for that specific pair
|
|
333
338
|
*
|
|
334
|
-
*
|
|
339
|
+
* On the next mount of useForm(entity, template) the resolve re-runs.
|
|
335
340
|
*/
|
|
336
341
|
invalidate(id, templateNode) {
|
|
337
342
|
this.entityRegistry.clearResolved(id, templateNode);
|
|
338
343
|
}
|
|
339
344
|
/**
|
|
340
|
-
* Submit entity
|
|
341
|
-
*
|
|
345
|
+
* Submit an entity through a template.
|
|
346
|
+
* Called from EntityProjectionProxy.submit().
|
|
342
347
|
*
|
|
343
348
|
* 1. submitting: true → notify
|
|
344
|
-
* 2.
|
|
349
|
+
* 2. Validation via template field rules (validate)
|
|
345
350
|
* 3. templateNode.onSubmit(entityProxy, store)
|
|
346
351
|
* 4. templateNode.afterSubmit(result, { reset })
|
|
347
352
|
* 5. submitting: false → notify
|
|
@@ -349,7 +354,6 @@ export class Palistor {
|
|
|
349
354
|
* @internal
|
|
350
355
|
*/
|
|
351
356
|
async executeEntityTemplateSubmit(entityId, templateNode, entityProxy) {
|
|
352
|
-
// Шаг 1: Найти entity в реестре. Если не найдена — ранний выход с ошибкой.
|
|
353
357
|
const entityNode = this.entityRegistry.get(entityId);
|
|
354
358
|
if (!entityNode) {
|
|
355
359
|
return {
|
|
@@ -357,34 +361,25 @@ export class Palistor {
|
|
|
357
361
|
errors: [{ path: "", message: `Entity "${entityId}" not found` }],
|
|
358
362
|
};
|
|
359
363
|
}
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
// для отображения спиннера в UI.
|
|
364
|
+
// Binding state { loading, submitting } for the (entityId, templateNode)
|
|
365
|
+
// pair — read by EntityProjectionProxy to render a spinner in the UI.
|
|
363
366
|
const bindingState = this.resolveManager.entityStates.getOrCreate(entityId, templateNode);
|
|
364
367
|
const entityNodeObj = entityNode;
|
|
365
|
-
// Шаг 3: Поднять флаг submitting и уведомить подписчиков (React перерендерит
|
|
366
|
-
// компоненты, привязанные к этой entity — например, кнопка покажет спиннер).
|
|
367
368
|
bindingState.submitting = true;
|
|
368
369
|
this.notifyChanged(new Set([entityNodeObj]));
|
|
369
370
|
try {
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
// берутся из 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.
|
|
373
373
|
const errors = [];
|
|
374
374
|
this.collectEntityTemplateErrors(templateNode, entityNode, errors, "");
|
|
375
|
-
// Если есть ошибки валидации — вернуть их, не вызывая onSubmit.
|
|
376
375
|
if (errors.length > 0) {
|
|
377
376
|
return { success: false, errors };
|
|
378
377
|
}
|
|
379
|
-
//
|
|
380
|
-
// entityProxy — это Proxy entity с template-правилами, store — сам Palistor.
|
|
381
|
-
// Обычно здесь выполняется API-запрос на сервер.
|
|
378
|
+
// User onSubmit(entityProxy, store) — typically the API call.
|
|
382
379
|
let result;
|
|
383
380
|
if (typeof templateNode.onSubmit === "function") {
|
|
384
381
|
result = await templateNode.onSubmit(entityProxy, this);
|
|
385
382
|
}
|
|
386
|
-
// Шаг 6: Вызвать afterSubmit(result, { reset }) — хук после успешного submit.
|
|
387
|
-
// reset для entity template — no-op (у entity нет встроенного сброса, в отличие от form).
|
|
388
383
|
if (typeof templateNode.afterSubmit === "function") {
|
|
389
384
|
const reset = () => void 0; // entity template has no built-in reset
|
|
390
385
|
await templateNode.afterSubmit(result, { reset });
|
|
@@ -392,13 +387,12 @@ export class Palistor {
|
|
|
392
387
|
return { success: true, result };
|
|
393
388
|
}
|
|
394
389
|
finally {
|
|
395
|
-
//
|
|
396
|
-
// finally гарантирует сброс даже при ошибке в onSubmit/afterSubmit.
|
|
390
|
+
// Always clear submitting, even when onSubmit/afterSubmit throws.
|
|
397
391
|
bindingState.submitting = false;
|
|
398
392
|
this.notifyChanged(new Set([entityNodeObj]));
|
|
399
393
|
}
|
|
400
394
|
}
|
|
401
|
-
// ───
|
|
395
|
+
// ─── Private entity helpers ──────────────────────────────────────────────
|
|
402
396
|
/**
|
|
403
397
|
* Upsert entities in EntityRegistry and register/update their leaf nodes.
|
|
404
398
|
* Returns Set of changed leaf nodes. Does NOT call recompute or notifyChanged.
|
|
@@ -406,7 +400,7 @@ export class Palistor {
|
|
|
406
400
|
* Used internally by `set()` and by `executeListResolve` via the
|
|
407
401
|
* `setEntitiesRaw` callback in ResolveManagerDeps.
|
|
408
402
|
*
|
|
409
|
-
*
|
|
403
|
+
* When `listNode` is provided, triggers entity field resolves for
|
|
410
404
|
* each template field entry belonging to that list.
|
|
411
405
|
*
|
|
412
406
|
* @internal
|
|
@@ -414,22 +408,22 @@ export class Palistor {
|
|
|
414
408
|
_setEntitiesRaw(items, listNode) {
|
|
415
409
|
const changed = new Set();
|
|
416
410
|
for (const item of items) {
|
|
417
|
-
//
|
|
418
|
-
//
|
|
411
|
+
// Create a new EntityNode or update the existing one (recursive merge).
|
|
412
|
+
// When item.id is missing, EntityRegistry generates a temporary _tmp_* id.
|
|
419
413
|
const entityNode = this.entityRegistry.upsert(item);
|
|
420
414
|
const entityId = entityNode.id.value;
|
|
421
|
-
//
|
|
415
|
+
// All entity nodes live in the "_entity_." namespace, isolated from form config nodes.
|
|
422
416
|
const entityPrefix = `_entity_.${entityId}`;
|
|
423
|
-
// projectionObj —
|
|
424
|
-
//
|
|
425
|
-
//
|
|
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.
|
|
426
420
|
let projectionObj = this.entityProjectionObjs.get(entityId);
|
|
427
421
|
if (!projectionObj) {
|
|
428
422
|
projectionObj = {};
|
|
429
423
|
this.entityProjectionObjs.set(entityId, projectionObj);
|
|
430
424
|
}
|
|
431
|
-
// DFS
|
|
432
|
-
//
|
|
425
|
+
// DFS over the entity tree: register new leaf nodes
|
|
426
|
+
// or detect changes in existing ones.
|
|
433
427
|
this.walkAndSyncEntityNode(entityNode, entityPrefix, entityNode, changed, projectionObj);
|
|
434
428
|
// Entity field resolves are lazy-only: triggered when a component first reads
|
|
435
429
|
// field.value or field.loading (via queueMicrotask in buildEntityProjectionProxy).
|
|
@@ -438,19 +432,19 @@ export class Palistor {
|
|
|
438
432
|
return changed;
|
|
439
433
|
}
|
|
440
434
|
/**
|
|
441
|
-
*
|
|
435
|
+
* Sync valuesCache with list membership — a SINGLE method (root + per-entity).
|
|
442
436
|
*
|
|
443
|
-
*
|
|
444
|
-
* - `null` → root:
|
|
445
|
-
* (`valuesCache.values.users = [{id:"u1",…}, …]` —
|
|
446
|
-
* `proxy.users.value
|
|
447
|
-
* - entity → per-entity
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
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.
|
|
452
446
|
*
|
|
453
|
-
* No-op
|
|
447
|
+
* No-op when the slot/path/owner projectionObj is missing.
|
|
454
448
|
*
|
|
455
449
|
* @internal
|
|
456
450
|
*/
|
|
@@ -459,14 +453,14 @@ export class Palistor {
|
|
|
459
453
|
.map((id) => this.entityProjectionObjs.get(id))
|
|
460
454
|
.filter((obj) => obj !== undefined);
|
|
461
455
|
if (listState.ownerEntity === null) {
|
|
462
|
-
// Root:
|
|
456
|
+
// Root: the valuesCache slot ({ parent, key } pair) keyed by config node.
|
|
463
457
|
const slot = this.values.nodeSlot.get(listState.listConfigNode);
|
|
464
458
|
if (!slot)
|
|
465
459
|
return;
|
|
466
460
|
slot.parent[slot.key] = materialized;
|
|
467
461
|
return;
|
|
468
462
|
}
|
|
469
|
-
// Per-entity:
|
|
463
|
+
// Per-entity: descend to the parent POJO along the path and write the membership.
|
|
470
464
|
const listConfigNode = listState.listConfigNode;
|
|
471
465
|
const path = this.nodes.listFieldKeys.get(listConfigNode);
|
|
472
466
|
if (!path || path.length === 0)
|
|
@@ -487,22 +481,22 @@ export class Palistor {
|
|
|
487
481
|
}
|
|
488
482
|
target[path[path.length - 1]] = materialized;
|
|
489
483
|
}
|
|
490
|
-
/** @internal
|
|
484
|
+
/** @internal Current entity id (accounts for rekey via nodeState). */
|
|
491
485
|
_entityId(entity) {
|
|
492
486
|
const idLeaf = entity.id;
|
|
493
487
|
return (this.nodes.nodeState.get(idLeaf)?.value ??
|
|
494
488
|
entity.id.value);
|
|
495
489
|
}
|
|
496
490
|
/**
|
|
497
|
-
*
|
|
498
|
-
* (
|
|
491
|
+
* Restore the membership of root AND per-entity lists from a values
|
|
492
|
+
* snapshot (persist hydrate).
|
|
499
493
|
*
|
|
500
|
-
* `applyPatch`
|
|
501
|
-
*
|
|
502
|
-
* owner
|
|
503
|
-
*
|
|
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.
|
|
504
498
|
*
|
|
505
|
-
*
|
|
499
|
+
* Returns the set of changed nodes for a subsequent notify.
|
|
506
500
|
*
|
|
507
501
|
* @internal
|
|
508
502
|
*/
|
|
@@ -516,7 +510,7 @@ export class Palistor {
|
|
|
516
510
|
const child = configNode[key];
|
|
517
511
|
if (!child || typeof child !== "object")
|
|
518
512
|
continue;
|
|
519
|
-
//
|
|
513
|
+
// Nested group (not a list): recurse to reach the lists inside it.
|
|
520
514
|
if (!Array.isArray(child)) {
|
|
521
515
|
if (isGroupNode(child)) {
|
|
522
516
|
const nested = valueObj?.[key];
|
|
@@ -549,7 +543,7 @@ export class Palistor {
|
|
|
549
543
|
if (ownerEntity) {
|
|
550
544
|
this.entityRegistry.setEntityOwner(childEntity, this._entityId(ownerEntity), listConfigNode);
|
|
551
545
|
}
|
|
552
|
-
//
|
|
546
|
+
// Recurse into this item's nested lists.
|
|
553
547
|
this._restoreListsRec(template, itemObj, childEntity, changed);
|
|
554
548
|
}
|
|
555
549
|
}
|
|
@@ -566,7 +560,7 @@ export class Palistor {
|
|
|
566
560
|
listState.itemIds = ids;
|
|
567
561
|
listState.initialItemIds = [...ids];
|
|
568
562
|
this.syncListValuesCache(listState);
|
|
569
|
-
//
|
|
563
|
+
// ListState is the tracking key; listConfigNode is the backward-compat bridge.
|
|
570
564
|
changed.add(listState);
|
|
571
565
|
changed.add(listConfigNode);
|
|
572
566
|
}
|
|
@@ -574,57 +568,57 @@ export class Palistor {
|
|
|
574
568
|
}
|
|
575
569
|
}
|
|
576
570
|
/**
|
|
577
|
-
*
|
|
578
|
-
* (
|
|
579
|
-
* `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.
|
|
580
574
|
*/
|
|
581
575
|
_stripListFields(itemObj, template) {
|
|
582
576
|
const result = {};
|
|
583
577
|
for (const key of Object.keys(itemObj)) {
|
|
584
578
|
const tField = template?.[key];
|
|
585
579
|
if (Array.isArray(tField))
|
|
586
|
-
continue; // list
|
|
580
|
+
continue; // skip list fields
|
|
587
581
|
result[key] = itemObj[key];
|
|
588
582
|
}
|
|
589
583
|
return result;
|
|
590
584
|
}
|
|
591
585
|
/**
|
|
592
|
-
*
|
|
593
|
-
* `registerDynamicLeaf`
|
|
586
|
+
* Recursive walk over an entity node: registers new leaf nodes via
|
|
587
|
+
* `registerDynamicLeaf` and accumulates changed ones in `changed`.
|
|
594
588
|
*
|
|
595
|
-
*
|
|
596
|
-
* -
|
|
597
|
-
* -
|
|
589
|
+
* Also maintains the entity projection POJO (projectionObj):
|
|
590
|
+
* - New leaf nodes: register nodeSlot → projectionObj
|
|
591
|
+
* - Existing leaf nodes: updated via updateValuesCacheEntry
|
|
598
592
|
*
|
|
599
|
-
* @param node
|
|
600
|
-
* @param prefix Dot
|
|
601
|
-
* @param parent
|
|
602
|
-
* @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
|
|
603
597
|
* @param projectionObj Plain POJO at the current nesting level for valuesCache
|
|
604
598
|
*/
|
|
605
599
|
walkAndSyncEntityNode(node, prefix, parent, changed, projectionObj) {
|
|
606
|
-
//
|
|
607
|
-
// nodePaths
|
|
608
|
-
// (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.
|
|
609
603
|
if (!this.nodes.nodePaths.has(parent)) {
|
|
610
604
|
this.nodes.nodePaths.set(parent, prefix);
|
|
611
605
|
}
|
|
612
606
|
for (const key of Object.keys(node)) {
|
|
613
607
|
const child = node[key];
|
|
614
|
-
//
|
|
608
|
+
// Skip primitives (not part of the entity tree)
|
|
615
609
|
if (!child || typeof child !== "object")
|
|
616
610
|
continue;
|
|
617
611
|
const childObj = child;
|
|
618
612
|
const childPath = `${prefix}.${key}`;
|
|
619
613
|
if (isLeafNode(childObj)) {
|
|
620
|
-
// ──
|
|
614
|
+
// ── Leaf node (EntityLeafNode): { value: <current value> } ──
|
|
621
615
|
const leaf = childObj;
|
|
622
616
|
if (!this.nodes.nodeState.has(childObj)) {
|
|
623
|
-
//
|
|
624
|
-
// - nodeState:
|
|
625
|
-
// - nodePaths:
|
|
626
|
-
// - nodeParents:
|
|
627
|
-
//
|
|
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.
|
|
628
622
|
this.nodes.registerDynamicLeaf(childObj, childPath, parent, {
|
|
629
623
|
value: leaf.value,
|
|
630
624
|
isVisible: true,
|
|
@@ -637,9 +631,9 @@ export class Palistor {
|
|
|
637
631
|
// Seed initialValueMap so recomputeDirtyTargeted compares against the entity's
|
|
638
632
|
// loaded value instead of `undefined` — prevents spurious dirty=true on first load.
|
|
639
633
|
this.dirty.initialValueMap.set(childObj, leaf.value);
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
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).
|
|
643
637
|
if (projectionObj !== undefined) {
|
|
644
638
|
this.values.nodeSlot.set(childObj, { parent: projectionObj, key });
|
|
645
639
|
projectionObj[key] = leaf.value;
|
|
@@ -647,24 +641,22 @@ export class Palistor {
|
|
|
647
641
|
changed.add(childObj);
|
|
648
642
|
}
|
|
649
643
|
else {
|
|
650
|
-
// Leaf
|
|
651
|
-
//
|
|
644
|
+
// Leaf already registered — check whether the value changed.
|
|
645
|
+
// Happens on repeated upserts (entity refresh from the server).
|
|
652
646
|
const state = this.nodes.nodeState.get(childObj);
|
|
653
647
|
if (state.value !== leaf.value) {
|
|
654
|
-
//
|
|
648
|
+
// Raw write to nodeState (bypasses writePipeline on purpose)
|
|
655
649
|
state.value = leaf.value;
|
|
656
|
-
// Обновить projectionObj через nodeSlot: O(1), не нужно искать POJO.
|
|
657
650
|
updateValuesCacheEntry(this.values, childObj, leaf.value);
|
|
658
651
|
changed.add(childObj);
|
|
659
652
|
}
|
|
660
|
-
//
|
|
661
|
-
// подписчики не будут уведомлены (оптимизация).
|
|
653
|
+
// Unchanged values stay out of `changed` — subscribers are not notified.
|
|
662
654
|
}
|
|
663
655
|
}
|
|
664
656
|
else {
|
|
665
|
-
// ──
|
|
666
|
-
//
|
|
667
|
-
//
|
|
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.
|
|
668
660
|
let nestedProjectionObj;
|
|
669
661
|
if (projectionObj !== undefined) {
|
|
670
662
|
if (!projectionObj[key] || typeof projectionObj[key] !== "object") {
|
|
@@ -677,8 +669,8 @@ export class Palistor {
|
|
|
677
669
|
}
|
|
678
670
|
}
|
|
679
671
|
/**
|
|
680
|
-
*
|
|
681
|
-
*
|
|
672
|
+
* Recursively collect all leaf nodes from an entity node tree.
|
|
673
|
+
* Used by `delete()` to clean up NodeRegistry.
|
|
682
674
|
*/
|
|
683
675
|
collectEntityLeaves(node, result) {
|
|
684
676
|
for (const key of Object.keys(node)) {
|
|
@@ -686,11 +678,9 @@ export class Palistor {
|
|
|
686
678
|
if (!child || typeof child !== "object")
|
|
687
679
|
continue;
|
|
688
680
|
if (isLeafNode(child)) {
|
|
689
|
-
// Leaf-нода: объект с полем "value" → добавить в результат
|
|
690
681
|
result.add(child);
|
|
691
682
|
}
|
|
692
683
|
else {
|
|
693
|
-
// Группа — рекурсивно обойти вложенные узлы
|
|
694
684
|
this.collectEntityLeaves(child, result);
|
|
695
685
|
}
|
|
696
686
|
}
|
|
@@ -704,37 +694,34 @@ export class Palistor {
|
|
|
704
694
|
*/
|
|
705
695
|
collectEntityTemplateErrors(templateNode, entityNode, errors, parentPath) {
|
|
706
696
|
const translate = this.services.translate;
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
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
|
|
710
700
|
const entityValues = this.buildEntityValuesForTemplate(entityNode);
|
|
711
701
|
for (const key of configKeys(templateNode)) {
|
|
712
702
|
const templateField = templateNode[key];
|
|
713
703
|
if (!templateField || typeof templateField !== "object")
|
|
714
704
|
continue;
|
|
715
|
-
//
|
|
705
|
+
// Dot-path for the error message (e.g. "address.city")
|
|
716
706
|
const path = parentPath ? `${parentPath}.${key}` : key;
|
|
717
707
|
if (isLeafNode(templateField)) {
|
|
718
|
-
// Leaf-поле template — проверяем, есть ли validate() функция
|
|
719
708
|
if (typeof templateField.validate === "function") {
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
// 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.
|
|
723
711
|
const entityField = entityNode[key];
|
|
724
712
|
const currentValue = entityField && typeof entityField === "object"
|
|
725
713
|
? this.nodes.nodeState.get(entityField)?.value ?? entityField.value
|
|
726
714
|
: undefined;
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
// undefined / false = поле валидно.
|
|
715
|
+
// validate(currentValue, allEntityValues, translateFn):
|
|
716
|
+
// a string is an error message; undefined/false means valid.
|
|
730
717
|
const result = templateField.validate(currentValue, entityValues, translate);
|
|
731
718
|
if (result)
|
|
732
719
|
errors.push({ path, message: result });
|
|
733
720
|
}
|
|
734
721
|
}
|
|
735
722
|
else {
|
|
736
|
-
//
|
|
737
|
-
// Template
|
|
723
|
+
// Template group — recurse into the matching nested entity node.
|
|
724
|
+
// Template and entity share the same nesting structure.
|
|
738
725
|
const entityField = entityNode[key];
|
|
739
726
|
if (entityField && typeof entityField === "object") {
|
|
740
727
|
this.collectEntityTemplateErrors(templateField, entityField, errors, path);
|
|
@@ -749,19 +736,18 @@ export class Palistor {
|
|
|
749
736
|
* @internal
|
|
750
737
|
*/
|
|
751
738
|
buildEntityValuesForTemplate(entityNode) {
|
|
752
|
-
//
|
|
739
|
+
// Result shape: { id: "u1", name: "Alice", address: { city: "Moscow" } }
|
|
753
740
|
const values = {};
|
|
754
741
|
for (const key of Object.keys(entityNode)) {
|
|
755
742
|
const field = entityNode[key];
|
|
756
743
|
if (field && typeof field === "object") {
|
|
757
744
|
if (isLeafNode(field)) {
|
|
758
|
-
// Leaf:
|
|
745
|
+
// Leaf: read value from nodeState (current), fall back to field.value
|
|
759
746
|
values[key] =
|
|
760
747
|
this.nodes.nodeState.get(field)?.value ??
|
|
761
748
|
field.value;
|
|
762
749
|
}
|
|
763
750
|
else {
|
|
764
|
-
// Группа: рекурсивно собрать вложенный объект
|
|
765
751
|
values[key] = this.buildEntityValuesForTemplate(field);
|
|
766
752
|
}
|
|
767
753
|
}
|