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.
Files changed (278) hide show
  1. package/README.md +574 -542
  2. package/README.ru.md +979 -0
  3. package/dist/index.d.ts +19 -28
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +21 -28
  6. package/dist/index.js.map +1 -1
  7. package/dist/react/createTrackingProxy.d.ts +27 -27
  8. package/dist/react/createTrackingProxy.d.ts.map +1 -1
  9. package/dist/react/createTrackingProxy.js +92 -42
  10. package/dist/react/createTrackingProxy.js.map +1 -1
  11. package/dist/react/useForm.d.ts +42 -43
  12. package/dist/react/useForm.d.ts.map +1 -1
  13. package/dist/react/useForm.js +38 -38
  14. package/dist/react/useForm.js.map +1 -1
  15. package/dist/react/usePersist.d.ts +14 -14
  16. package/dist/react/usePersist.js +18 -18
  17. package/dist/react/usePersist.js.map +1 -1
  18. package/dist/react/useStoreContext.d.ts +11 -11
  19. package/dist/react/useStoreContext.js +11 -11
  20. package/dist/react/useTranslator.d.ts +12 -12
  21. package/dist/react/useTranslator.js +12 -12
  22. package/dist/store/applyPatch/applyPatch.d.ts +8 -8
  23. package/dist/store/applyPatch/applyPatch.js +12 -12
  24. package/dist/store/applyPatch/applyPatch.js.map +1 -1
  25. package/dist/store/buildProxy/buildEntityProjectionProxy.js +6 -6
  26. package/dist/store/buildProxy/buildEntityProjectionProxy.js.map +1 -1
  27. package/dist/store/buildProxy/buildListProxy.d.ts +8 -8
  28. package/dist/store/buildProxy/buildListProxy.js +30 -30
  29. package/dist/store/buildProxy/buildListProxy.js.map +1 -1
  30. package/dist/store/buildProxy/buildProxy.d.ts +8 -8
  31. package/dist/store/buildProxy/buildProxy.d.ts.map +1 -1
  32. package/dist/store/buildProxy/buildProxy.js +84 -33
  33. package/dist/store/buildProxy/buildProxy.js.map +1 -1
  34. package/dist/store/buildProxy/computeProxyKeys.d.ts +8 -8
  35. package/dist/store/buildProxy/computeProxyKeys.d.ts.map +1 -1
  36. package/dist/store/buildProxy/computeProxyKeys.js +18 -13
  37. package/dist/store/buildProxy/computeProxyKeys.js.map +1 -1
  38. package/dist/store/buildProxy/handleLazyResolve.d.ts +3 -3
  39. package/dist/store/buildProxy/handleLazyResolve.js +3 -3
  40. package/dist/store/buildProxy/initProxyCaches.d.ts +5 -5
  41. package/dist/store/buildProxy/initProxyCaches.js +5 -5
  42. package/dist/store/compute/computeFieldState.d.ts +3 -3
  43. package/dist/store/compute/computeFieldState.js +8 -8
  44. package/dist/store/compute/computeFieldState.js.map +1 -1
  45. package/dist/store/compute/fieldStateChanged.d.ts +1 -1
  46. package/dist/store/compute/fieldStateChanged.js +1 -1
  47. package/dist/store/compute/recompute/collectGroupComputeNodes.d.ts +4 -4
  48. package/dist/store/compute/recompute/collectGroupComputeNodes.js +7 -7
  49. package/dist/store/compute/recompute/collectGroupComputeNodes.js.map +1 -1
  50. package/dist/store/compute/recompute/recomputeAndNotify.d.ts +3 -3
  51. package/dist/store/compute/recompute/recomputeAndNotify.js +3 -3
  52. package/dist/store/compute/recompute/recomputeLeaves.d.ts +11 -10
  53. package/dist/store/compute/recompute/recomputeLeaves.d.ts.map +1 -1
  54. package/dist/store/compute/recompute/recomputeLeaves.js +26 -27
  55. package/dist/store/compute/recompute/recomputeLeaves.js.map +1 -1
  56. package/dist/store/compute/recompute/recomputeTargeted.d.ts +7 -7
  57. package/dist/store/compute/recompute/recomputeTargeted.js +12 -12
  58. package/dist/store/compute/recompute/recomputeTargeted.js.map +1 -1
  59. package/dist/store/compute/recompute/topologicalSortComputed.d.ts +4 -4
  60. package/dist/store/compute/recompute/topologicalSortComputed.d.ts.map +1 -1
  61. package/dist/store/compute/recompute/topologicalSortComputed.js +11 -13
  62. package/dist/store/compute/recompute/topologicalSortComputed.js.map +1 -1
  63. package/dist/store/compute/recompute/types.d.ts +4 -4
  64. package/dist/store/compute/resolveFlag.d.ts +2 -2
  65. package/dist/store/compute/resolveFlag.js +2 -2
  66. package/dist/store/compute/resolveString.d.ts +5 -5
  67. package/dist/store/compute/resolveString.js +6 -6
  68. package/dist/store/compute/resolveString.js.map +1 -1
  69. package/dist/store/compute/types.d.ts +3 -3
  70. package/dist/store/constants.d.ts +57 -38
  71. package/dist/store/constants.d.ts.map +1 -1
  72. package/dist/store/constants.js +70 -35
  73. package/dist/store/constants.js.map +1 -1
  74. package/dist/store/defineFieldMapping.d.ts +21 -19
  75. package/dist/store/defineFieldMapping.d.ts.map +1 -1
  76. package/dist/store/defineFieldMapping.js +21 -19
  77. package/dist/store/defineFieldMapping.js.map +1 -1
  78. package/dist/store/dirtyTracking/collectInitialSnapshot.js +1 -1
  79. package/dist/store/dirtyTracking/collectInitialSnapshot.js.map +1 -1
  80. package/dist/store/dirtyTracking/recomputeDirtyTargeted.d.ts +3 -3
  81. package/dist/store/dirtyTracking/recomputeDirtyTargeted.js +11 -11
  82. package/dist/store/dirtyTracking/recomputeDirtyTargeted.js.map +1 -1
  83. package/dist/store/dirtyTracking/setGroupRevalidate.d.ts +6 -6
  84. package/dist/store/dirtyTracking/setGroupRevalidate.js +7 -7
  85. package/dist/store/dirtyTracking/setGroupRevalidate.js.map +1 -1
  86. package/dist/store/entityRegistry/entityRegistry.d.ts +72 -72
  87. package/dist/store/entityRegistry/entityRegistry.d.ts.map +1 -1
  88. package/dist/store/entityRegistry/entityRegistry.js +91 -91
  89. package/dist/store/entityRegistry/entityRegistry.js.map +1 -1
  90. package/dist/store/entityRegistry/generateId.d.ts +3 -3
  91. package/dist/store/entityRegistry/generateId.js +3 -3
  92. package/dist/store/entityRegistry/types.d.ts +17 -17
  93. package/dist/store/entityRegistry/types.d.ts.map +1 -1
  94. package/dist/store/flow/buildFlowStepsProxy.d.ts +17 -0
  95. package/dist/store/flow/buildFlowStepsProxy.d.ts.map +1 -0
  96. package/dist/store/flow/buildFlowStepsProxy.js +72 -0
  97. package/dist/store/flow/buildFlowStepsProxy.js.map +1 -0
  98. package/dist/store/flow/defineFlow.d.ts +92 -0
  99. package/dist/store/flow/defineFlow.d.ts.map +1 -0
  100. package/dist/store/flow/defineFlow.js +99 -0
  101. package/dist/store/flow/defineFlow.js.map +1 -0
  102. package/dist/store/flow/flowApi.d.ts +18 -0
  103. package/dist/store/flow/flowApi.d.ts.map +1 -0
  104. package/dist/store/flow/flowApi.js +18 -0
  105. package/dist/store/flow/flowApi.js.map +1 -0
  106. package/dist/store/flow/flowNavigation.d.ts +137 -0
  107. package/dist/store/flow/flowNavigation.d.ts.map +1 -0
  108. package/dist/store/flow/flowNavigation.js +384 -0
  109. package/dist/store/flow/flowNavigation.js.map +1 -0
  110. package/dist/store/flow/flowState.d.ts +41 -0
  111. package/dist/store/flow/flowState.d.ts.map +1 -0
  112. package/dist/store/flow/flowState.js +51 -0
  113. package/dist/store/flow/flowState.js.map +1 -0
  114. package/dist/store/flow/index.d.ts +10 -0
  115. package/dist/store/flow/index.d.ts.map +1 -0
  116. package/dist/store/flow/index.js +6 -0
  117. package/dist/store/flow/index.js.map +1 -0
  118. package/dist/store/groupDeps/createGroupDeps.d.ts +3 -3
  119. package/dist/store/groupDeps/createGroupDeps.js +6 -6
  120. package/dist/store/groupDeps/createGroupDeps.js.map +1 -1
  121. package/dist/store/groupDeps/createTrackingValues.d.ts +9 -9
  122. package/dist/store/groupDeps/createTrackingValues.js +11 -11
  123. package/dist/store/groupDeps/createTrackingValues.js.map +1 -1
  124. package/dist/store/groupDeps/getNodeGroupPath.d.ts +3 -3
  125. package/dist/store/groupDeps/getNodeGroupPath.js +5 -5
  126. package/dist/store/groupDeps/getNodeGroupPath.js.map +1 -1
  127. package/dist/store/groupDeps/getRecipientGroups.d.ts +1 -1
  128. package/dist/store/groupDeps/getRecipientGroups.js +1 -1
  129. package/dist/store/groupDeps/groupDeps.d.ts +7 -7
  130. package/dist/store/groupDeps/groupDeps.js +7 -7
  131. package/dist/store/groupDeps/pairKey.d.ts +3 -3
  132. package/dist/store/groupDeps/pairKey.d.ts.map +1 -1
  133. package/dist/store/groupDeps/pairKey.js +4 -4
  134. package/dist/store/groupDeps/pairKey.js.map +1 -1
  135. package/dist/store/groupDeps/resolveGroupByPath.d.ts +3 -3
  136. package/dist/store/groupDeps/resolveGroupByPath.js +3 -3
  137. package/dist/store/init/createNotificationHub.d.ts +31 -31
  138. package/dist/store/init/createNotificationHub.d.ts.map +1 -1
  139. package/dist/store/init/createNotificationHub.js +36 -37
  140. package/dist/store/init/createNotificationHub.js.map +1 -1
  141. package/dist/store/init/createResolveManager.d.ts +72 -69
  142. package/dist/store/init/createResolveManager.d.ts.map +1 -1
  143. package/dist/store/init/createResolveManager.js +101 -98
  144. package/dist/store/init/createResolveManager.js.map +1 -1
  145. package/dist/store/init/initGroupSubmitting.d.ts +2 -2
  146. package/dist/store/init/initGroupSubmitting.js +4 -4
  147. package/dist/store/init/initGroupSubmitting.js.map +1 -1
  148. package/dist/store/normalizeConfig.d.ts +20 -18
  149. package/dist/store/normalizeConfig.d.ts.map +1 -1
  150. package/dist/store/normalizeConfig.js +31 -29
  151. package/dist/store/normalizeConfig.js.map +1 -1
  152. package/dist/store/onChangePipeline/computeFieldKey.d.ts +2 -2
  153. package/dist/store/onChangePipeline/computeFieldKey.js +2 -2
  154. package/dist/store/onChangePipeline/findOnChangeAncestors.d.ts +2 -2
  155. package/dist/store/onChangePipeline/findOnChangeAncestors.js +4 -4
  156. package/dist/store/onChangePipeline/findOnChangeAncestors.js.map +1 -1
  157. package/dist/store/onChangePipeline/onChangePipeline.d.ts +13 -12
  158. package/dist/store/onChangePipeline/onChangePipeline.d.ts.map +1 -1
  159. package/dist/store/onChangePipeline/onChangePipeline.js +14 -13
  160. package/dist/store/onChangePipeline/onChangePipeline.js.map +1 -1
  161. package/dist/store/persist/drivers.d.ts +7 -7
  162. package/dist/store/persist/drivers.js +8 -8
  163. package/dist/store/persist/drivers.js.map +1 -1
  164. package/dist/store/persist/index.d.ts +1 -1
  165. package/dist/store/persist/persistManager.d.ts +23 -23
  166. package/dist/store/persist/persistManager.d.ts.map +1 -1
  167. package/dist/store/persist/persistManager.js +73 -42
  168. package/dist/store/persist/persistManager.js.map +1 -1
  169. package/dist/store/persist/types.d.ts +23 -23
  170. package/dist/store/persist/types.d.ts.map +1 -1
  171. package/dist/store/persist/types.js +4 -4
  172. package/dist/store/resetPipeline/buildResetPatch.d.ts +5 -5
  173. package/dist/store/resetPipeline/buildResetPatch.js +5 -5
  174. package/dist/store/resetPipeline/collectDefaults.d.ts +5 -5
  175. package/dist/store/resetPipeline/collectDefaults.js +6 -6
  176. package/dist/store/resetPipeline/collectDefaults.js.map +1 -1
  177. package/dist/store/resetPipeline/resetPipeline.d.ts +7 -7
  178. package/dist/store/resetPipeline/resetPipeline.d.ts.map +1 -1
  179. package/dist/store/resetPipeline/resetPipeline.js +17 -12
  180. package/dist/store/resetPipeline/resetPipeline.js.map +1 -1
  181. package/dist/store/resolvePipeline/applyPendingWrites.d.ts +10 -10
  182. package/dist/store/resolvePipeline/applyPendingWrites.d.ts.map +1 -1
  183. package/dist/store/resolvePipeline/applyPendingWrites.js +35 -38
  184. package/dist/store/resolvePipeline/applyPendingWrites.js.map +1 -1
  185. package/dist/store/resolvePipeline/createContextTrackingProxy.d.ts +8 -7
  186. package/dist/store/resolvePipeline/createContextTrackingProxy.d.ts.map +1 -1
  187. package/dist/store/resolvePipeline/createContextTrackingProxy.js +6 -5
  188. package/dist/store/resolvePipeline/createContextTrackingProxy.js.map +1 -1
  189. package/dist/store/resolvePipeline/createValuesTrackingProxy.d.ts +43 -43
  190. package/dist/store/resolvePipeline/createValuesTrackingProxy.d.ts.map +1 -1
  191. package/dist/store/resolvePipeline/createValuesTrackingProxy.js +70 -70
  192. package/dist/store/resolvePipeline/createValuesTrackingProxy.js.map +1 -1
  193. package/dist/store/resolvePipeline/executeListResolve.d.ts +14 -14
  194. package/dist/store/resolvePipeline/executeListResolve.js +27 -27
  195. package/dist/store/resolvePipeline/executeListResolve.js.map +1 -1
  196. package/dist/store/resolvePipeline/executeResolve.d.ts +15 -15
  197. package/dist/store/resolvePipeline/executeResolve.js +46 -46
  198. package/dist/store/resolvePipeline/executeResolve.js.map +1 -1
  199. package/dist/store/store/NodeRegistry/nodeRegistry.d.ts +69 -56
  200. package/dist/store/store/NodeRegistry/nodeRegistry.d.ts.map +1 -1
  201. package/dist/store/store/NodeRegistry/nodeRegistry.js +94 -78
  202. package/dist/store/store/NodeRegistry/nodeRegistry.js.map +1 -1
  203. package/dist/store/store/NodeRegistry/nodeUtils.d.ts +5 -5
  204. package/dist/store/store/NodeRegistry/nodeUtils.d.ts.map +1 -1
  205. package/dist/store/store/NodeRegistry/nodeUtils.js +5 -5
  206. package/dist/store/store/NodeRegistry/nodeUtils.js.map +1 -1
  207. package/dist/store/store/groupDepsMap.d.ts +23 -23
  208. package/dist/store/store/groupDepsMap.d.ts.map +1 -1
  209. package/dist/store/store/groupDepsMap.js +40 -34
  210. package/dist/store/store/groupDepsMap.js.map +1 -1
  211. package/dist/store/store/hasComputedProps.d.ts +2 -2
  212. package/dist/store/store/hasComputedProps.js +2 -2
  213. package/dist/store/store/nodeMap.d.ts +5 -5
  214. package/dist/store/store/nodeMap.js +7 -7
  215. package/dist/store/store/nodeMap.js.map +1 -1
  216. package/dist/store/store/palistor.d.ts +80 -80
  217. package/dist/store/store/palistor.d.ts.map +1 -1
  218. package/dist/store/store/palistor.js +151 -165
  219. package/dist/store/store/palistor.js.map +1 -1
  220. package/dist/store/store/registerNodes.d.ts +10 -10
  221. package/dist/store/store/registerNodes.d.ts.map +1 -1
  222. package/dist/store/store/registerNodes.js +33 -18
  223. package/dist/store/store/registerNodes.js.map +1 -1
  224. package/dist/store/store/serviceRegistry.d.ts +8 -8
  225. package/dist/store/store/serviceRegistry.d.ts.map +1 -1
  226. package/dist/store/store/serviceRegistry.js +8 -8
  227. package/dist/store/store/serviceRegistry.js.map +1 -1
  228. package/dist/store/store/types.d.ts +277 -223
  229. package/dist/store/store/types.d.ts.map +1 -1
  230. package/dist/store/submitPipeline/applyLeafBeforeSubmit.d.ts +2 -2
  231. package/dist/store/submitPipeline/applyLeafBeforeSubmit.js +2 -2
  232. package/dist/store/submitPipeline/collectLeafStates.d.ts +2 -2
  233. package/dist/store/submitPipeline/collectLeafStates.js +2 -2
  234. package/dist/store/submitPipeline/submitPipeline.d.ts +8 -8
  235. package/dist/store/submitPipeline/submitPipeline.d.ts.map +1 -1
  236. package/dist/store/submitPipeline/submitPipeline.js +15 -11
  237. package/dist/store/submitPipeline/submitPipeline.js.map +1 -1
  238. package/dist/store/submitPipeline/types.d.ts +1 -1
  239. package/dist/store/submitPipeline/types.d.ts.map +1 -1
  240. package/dist/store/traversal/nodeClassifier.d.ts +9 -9
  241. package/dist/store/traversal/nodeClassifier.d.ts.map +1 -1
  242. package/dist/store/traversal/nodeClassifier.js +11 -11
  243. package/dist/store/traversal/nodeClassifier.js.map +1 -1
  244. package/dist/store/traversal/walkFull.d.ts +14 -14
  245. package/dist/store/traversal/walkFull.js +2 -2
  246. package/dist/store/valuesCache/valuesCache.d.ts +9 -9
  247. package/dist/store/valuesCache/valuesCache.d.ts.map +1 -1
  248. package/dist/store/valuesCache/valuesCache.js +6 -6
  249. package/dist/store/writePipeline/formatPatch.d.ts +5 -5
  250. package/dist/store/writePipeline/formatPatch.js +7 -7
  251. package/dist/store/writePipeline/formatPatch.js.map +1 -1
  252. package/dist/store/writePipeline/formatValue.d.ts +5 -5
  253. package/dist/store/writePipeline/formatValue.js +5 -5
  254. package/dist/store/writePipeline/mergeChanged.d.ts +4 -4
  255. package/dist/store/writePipeline/mergeChanged.js +4 -4
  256. package/dist/store/writePipeline/runSetter.d.ts +6 -6
  257. package/dist/store/writePipeline/runSetter.js +8 -8
  258. package/dist/store/writePipeline/storeValue.d.ts +3 -3
  259. package/dist/store/writePipeline/storeValue.js +3 -3
  260. package/dist/store/writePipeline/types.d.ts +4 -4
  261. package/dist/store/writePipeline/types.d.ts.map +1 -1
  262. package/dist/store/writePipeline/writePipeline.d.ts +7 -7
  263. package/dist/store/writePipeline/writePipeline.d.ts.map +1 -1
  264. package/dist/store/writePipeline/writePipeline.js +15 -15
  265. package/dist/store/writePipeline/writePipeline.js.map +1 -1
  266. package/package.json +11 -4
  267. package/dist/store/compute/recompute/collectGroupLeafNodes.d.ts +0 -11
  268. package/dist/store/compute/recompute/collectGroupLeafNodes.d.ts.map +0 -1
  269. package/dist/store/compute/recompute/collectGroupLeafNodes.js +0 -26
  270. package/dist/store/compute/recompute/collectGroupLeafNodes.js.map +0 -1
  271. package/dist/store/fieldMapping.typecheck.d.ts +0 -2
  272. package/dist/store/fieldMapping.typecheck.d.ts.map +0 -1
  273. package/dist/store/fieldMapping.typecheck.js +0 -71
  274. package/dist/store/fieldMapping.typecheck.js.map +0 -1
  275. package/dist/store/submitPipeline/getSubValues.d.ts +0 -9
  276. package/dist/store/submitPipeline/getSubValues.d.ts.map +0 -1
  277. package/dist/store/submitPipeline/getSubValues.js +0 -26
  278. 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
- * Публичный класс формы. Одновременно является DI-контейнером для всех
25
- * внутренних подсистем и реализует публичный интерфейс `ProxyStore`.
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
- * Нереактивный контекстпроизвольные данные, доступные через `store.context`.
43
- * Устанавливается через `setContext()` или хук `useStoreContext()`.
43
+ * Non-reactive contextarbitrary 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 (для ownKeys/spread).
49
- // externalToInternal: external → internal (для GET/SET/tracking).
50
- // Обе пусты, когда fieldMapping не передан → `?? key` возвращает ключ как есть
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
- // ─── Нормализация конфига (external → internal) ───────────────────────────
61
- // Конфиг пишется в ПУБЛИЧНЫХ (замапленных) именах. Один проход приводит его
62
- // к internal-именам ДО init/compute/traversal — всё ядро ниже работает с
63
- // internal-именами без изменений. Пустая картавозвращается исходный
64
- // config без копий (нулевой оверхед).
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 mapthe 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 + первый recompute ──────────────────────────────────────
84
+ // ─── GroupDepsMap + first recompute ──────────────────────────────────────
84
85
  this.groupDepsMap = new GroupDepsMap(rootConfig, nodePaths, nodeParents);
85
- this.recompute(); // первый полный пересчётстроит карту зависимостей
86
+ this.recompute(); // first full recomputebuilds 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
- // ─── Начальный контекст (Phase 4) ────────────────────────────────────────
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
- * - иначеполный пересчёт всего дерева (init, reset, resolve-завершение)
134
+ * - `changedNodes` provided and non-emptytargeted recompute (fast)
135
+ * - otherwisefull recompute of the whole tree (init, reset, resolve completion)
130
136
  *
131
- * При первом вызове без changedNodes строит карту групповых зависимостей.
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
- * Пересчитывает dirty-флаги и инкрементирует версии.
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 Применить bulk-патч к узлу (один recompute + notify). */
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 — публичный API ───────────────────────────────────────────
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
- * Создать или обновить entity (или массив entities) в реестре.
244
+ * Create or update an entity (or an array of entities) in the registry.
239
245
  *
240
- * - Если entity с таким id не существует создаётся и регистрируются leaf-ноды.
241
- * - Если существуетрекурсивный merge; обновлённые leaf-ноды маркируются как изменённые.
242
- * - Batch-режим: массив entities обрабатывается одним recompute + notifyChanged.
246
+ * - If no entity with the id existsit 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
- * Переименовать entity: перенести с oldId на newId.
263
+ * Rename an entity: move it from oldId to newId.
258
264
  *
259
- * - Обновляет EntityRegistry (entities Map, bindings, resolvedCache, id leaf value).
260
- * - Обновляет itemIds во всех ListState-объектах.
261
- * - Обновляет entityProjectionObjs (переносит POJO-зеркало).
262
- * - Уведомляет подписчиков об изменении id leaf.
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 если entity с oldId не существует.
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
- * Удалить entity из реестра по ID.
300
+ * Delete an entity from the registry by ID.
295
301
  *
296
- * - Удаляет leaf-ноды entity из NodeRegistry (computeNodes, groupComputeMap).
297
- * - Очищает bindings и resolvedCache.
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 если entity не существует.
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
- // C2: каскадное удаление child-entity, принадлежащих этой entity.
307
- // Снимаем копию множества — childrenByOwner мутируется при рекурсивном delete.
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
- // Собрать все leaf-ноды entity
320
+ // Collect all leaf nodes of the entity
315
321
  const deletedLeaves = new Set();
316
322
  this.collectEntityLeaves(entityNode, deletedLeaves);
317
- // Удалить leaf-ноды из NodeRegistry (предотвращает утечку памяти)
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
- // Phase 4: cleanup per-entity field resolve states
327
+ // Cleanup per-entity field resolve states
322
328
  this.resolveManager.cleanupEntityResolveStates(id);
323
- // Удалить entity из реестра (очищает bindings + resolvedCache)
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
- * Сбросить resolved-кэш для entity (все template или конкретный).
334
+ * Clear the resolved cache for an entity (all templates or a specific one).
330
335
  *
331
- * - `invalidate(id)` — очистить весь кэш для entity
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
- * При следующем mount useForm(entity, template) resolve будет перезапущен.
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 через template.
341
- * Вызывается из EntityProjectionProxy.submit().
345
+ * Submit an entity through a template.
346
+ * Called from EntityProjectionProxy.submit().
342
347
  *
343
348
  * 1. submitting: true → notify
344
- * 2. Валидация через template field rules (validate)
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
- // Шаг 2: Получить (или создать) объект состояния привязки { loading, submitting }
361
- // для пары (entityId, templateNode). Используется EntityProjectionProxy
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
- // Шаг 4: Валидация рекурсивно обойти template-поля и вызвать validate()
371
- // для каждого leaf-поля, у которого есть валидатор. Текущие значения
372
- // берутся из nodeState entity.
371
+ // Validationrecursively 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
- // Шаг 5: Вызвать пользовательский onSubmit(entityProxy, store).
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
- // Шаг 7 (always): Снять флаг submitting и уведомить подписчиков.
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
- // ─── Приватные helpers для entity ────────────────────────────────────────
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
- * Phase 4: when `listNode` is provided, triggers entity field resolves for
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
- // Создать новый EntityNode или обновить существующий (рекурсивный merge).
418
- // Если item.id отсутствует EntityRegistry сгенерирует временный _tmp_* id.
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
- // Все entity-ноды живут в namespace "_entity_." для изоляции от config-нод формы.
415
+ // All entity nodes live in the "_entity_." namespace, isolated from form config nodes.
422
416
  const entityPrefix = `_entity_.${entityId}`;
423
- // projectionObj — «зеркало» entity в виде plain POJO { id, field1, field2 }.
424
- // Используется в valuesCache.values как элемент массива списка.
425
- // Ссылочная идентичность POJO сохраняется между upsert-ами, меняются только значения.
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-обход entity-дерева: зарегистрировать новые leaf-ноды
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
- * Синхронизировать valuesCache с составом спискаЕДИНЫЙ метод (root + per-entity).
435
+ * Sync valuesCache with list membershipa SINGLE method (root + per-entity).
442
436
  *
443
- * Ветвится по `listState.ownerEntity`:
444
- * - `null` → root: пишем массив POJO-зеркал в `nodeSlot` config-узла
445
- * (`valuesCache.values.users = [{id:"u1",…}, …]` — тот же массив, что читает
446
- * `proxy.users.value`, поэтому React видит обновление);
447
- * - entity → per-entity (вариант C, C3/C4): материализуем состав в projectionObj
448
- * владельца по пути списка (`["contacts"]` или `["profile","contacts"]`). Это
449
- * включает вложенный список в `store.getValues()` — projectionObj владельца
450
- * входит в массив корневого списка по ссылке, а child-projectionObj-ы
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, если слот/путь/projectionObj владельца отсутствуют.
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: слот в valuesCache (пара { parent, key }) по config-узлу.
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: спускаемся к родительскому POJO по пути и пишем состав.
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 Текущий id entity (учитывает rekey через nodeState). */
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
- * Восстановить состав корневых И per-entity списков из снимка значений
498
- * (вариант C, C3 — persist hydrate).
491
+ * Restore the membership of root AND per-entity lists from a values
492
+ * snapshot (persist hydrate).
499
493
  *
500
- * `applyPatch` пропускает list-узлы, поэтому состав списков восстанавливается
501
- * отдельным проходом: для каждого list-поля создаём child-entity, проставляем
502
- * owner-ссылку (для вложенных), заполняем `itemIds`/`initialItemIds` и
503
- * синхронизируем valuesCache. Рекурсивно обрабатывает nested-of-nested.
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
- * Возвращает множество изменённых узлов для последующего notify.
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
- // Рекурсия во вложенные списки этого item.
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
- // U2: ListState ключ трекинга; listConfigNode мост обратной совместимости.
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
- * Вернуть поверхностную копию объекта данных entity без list-полей
578
- * (их состав восстанавливается отдельно через EntityListState). Иначе
579
- * `createEntityNode` затянул бы массив как обычный leaf-value.
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
- * Рекурсивный обход entity node: регистрирует новые leaf-ноды через
593
- * `registerDynamicLeaf` и собирает изменённые в `changed`.
586
+ * Recursive walk over an entity node: registers new leaf nodes via
587
+ * `registerDynamicLeaf` and accumulates changed ones in `changed`.
594
588
  *
595
- * Также поддерживает entity projection POJO (projectionObj):
596
- * - Новые leaf-ноды: регистрируют nodeSlot → projectionObj
597
- * - Существующие leaf-ноды: обновляют через updateValuesCacheEntry
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 Текущий узел entity для обхода
600
- * @param prefix Dot-путь текущего узла (e.g. "_entity_.u1")
601
- * @param parent Родительский объект (для регистрации leaf-нод)
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
- // Зарегистрировать dot-path группового узла (e.g. "_entity_.u1", "_entity_.u1.address").
607
- // nodePaths используется в compute-подсистеме для определения «группы» листа
608
- // (getNodeGroupPath) и при таргетированном recompute.
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
- // Пропускаем примитивы (не часть entity-дерева)
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
- // ── Листовой узел (EntityLeafNode): { value: <текущее значение> } ──
614
+ // ── Leaf node (EntityLeafNode): { value: <current value> } ──
621
615
  const leaf = childObj;
622
616
  if (!this.nodes.nodeState.has(childObj)) {
623
- // Первая встреча с этим leaf — зарегистрировать в NodeRegistry:
624
- // - nodeState: хранит FieldState (value, isVisible, dirty, etc.)
625
- // - nodePaths: маппинг node → dot-path
626
- // - nodeParents: маппинг node → parent group
627
- // Все entity leaf-ы всегда visible, не обязательны, не disabled.
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
- // Привязать leaf к POJO-зеркалу через nodeSlot.
641
- // При следующих updateValuesCacheEntry(node, newValue)
642
- // значение в projectionObj обновится автоматически за O(1).
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
- // Это происходит при повторном upsert (обновление entity с сервера).
644
+ // Leaf already registeredcheck 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
- // Обновляем nodeState напрямую (без writePipeline это raw-запись)
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
- // Если значение не изменилосьleaf не попадёт в changed,
661
- // подписчики не будут уведомлены (оптимизация).
653
+ // Unchanged values stay out of `changed` subscribers are not notified.
662
654
  }
663
655
  }
664
656
  else {
665
- // ── Групповой узел (EntityGroupNode): вложенный объект без "value" ──
666
- // Например: address: { city: { value: "Moscow" } }
667
- // Создаём вложенный POJO в projectionObj (если нужно) и рекурсируем.
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
- * Рекурсивно собрать все leaf-ноды из entity node tree.
681
- * Используется в `delete()` для очистки NodeRegistry.
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
- // Построить plain-объект значений entity передаётся вторым аргументом
708
- // в каждый validate(), чтобы валидатор мог проверять зависимости между полями.
709
- // Пример: validate: (v, vals) => vals.password !== vals.confirmPassword ? "Mismatch" : undefined
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
- // Формируем dot-path для сообщения об ошибке (e.g. "address.city")
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
- // Извлечь текущее значение из entity. Приоритет:
721
- // 1. nodeState (актуальное значение после write pipeline)
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
- // Вызвать validate(currentValue, allEntityValues, translateFn).
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
- // Группа templateрекурсия в соответствующий вложенный узел entity.
737
- // Template и entity должны иметь одинаковую структуру вложенности.
723
+ // Template grouprecurse 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
- // Результат: plain-объект вида { id: "u1", name: "Alice", address: { city: "Moscow" } }
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: читаем value из nodeState (актуальное), fallback на field.value
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
  }