palistor 0.0.24
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/LICENSE +21 -0
- package/README.md +947 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/react/createTrackingProxy.d.ts +55 -0
- package/dist/react/createTrackingProxy.d.ts.map +1 -0
- package/dist/react/createTrackingProxy.js +149 -0
- package/dist/react/createTrackingProxy.js.map +1 -0
- package/dist/react/useForm.d.ts +108 -0
- package/dist/react/useForm.d.ts.map +1 -0
- package/dist/react/useForm.js +180 -0
- package/dist/react/useForm.js.map +1 -0
- package/dist/react/useNotifier.d.ts +35 -0
- package/dist/react/useNotifier.d.ts.map +1 -0
- package/dist/react/useNotifier.js +41 -0
- package/dist/react/useNotifier.js.map +1 -0
- package/dist/react/usePersist.d.ts +45 -0
- package/dist/react/usePersist.d.ts.map +1 -0
- package/dist/react/usePersist.js +60 -0
- package/dist/react/usePersist.js.map +1 -0
- package/dist/react/useStoreContext.d.ts +42 -0
- package/dist/react/useStoreContext.d.ts.map +1 -0
- package/dist/react/useStoreContext.js +46 -0
- package/dist/react/useStoreContext.js.map +1 -0
- package/dist/react/useTranslator.d.ts +37 -0
- package/dist/react/useTranslator.d.ts.map +1 -0
- package/dist/react/useTranslator.js +43 -0
- package/dist/react/useTranslator.js.map +1 -0
- package/dist/store/applyPatch/applyPatch.d.ts +17 -0
- package/dist/store/applyPatch/applyPatch.d.ts.map +1 -0
- package/dist/store/applyPatch/applyPatch.js +44 -0
- package/dist/store/applyPatch/applyPatch.js.map +1 -0
- package/dist/store/buildProxy/buildEntityProjectionProxy.d.ts +52 -0
- package/dist/store/buildProxy/buildEntityProjectionProxy.d.ts.map +1 -0
- package/dist/store/buildProxy/buildEntityProjectionProxy.js +321 -0
- package/dist/store/buildProxy/buildEntityProjectionProxy.js.map +1 -0
- package/dist/store/buildProxy/buildListProxy.d.ts +33 -0
- package/dist/store/buildProxy/buildListProxy.d.ts.map +1 -0
- package/dist/store/buildProxy/buildListProxy.js +306 -0
- package/dist/store/buildProxy/buildListProxy.js.map +1 -0
- package/dist/store/buildProxy/buildProxy.d.ts +41 -0
- package/dist/store/buildProxy/buildProxy.d.ts.map +1 -0
- package/dist/store/buildProxy/buildProxy.js +380 -0
- package/dist/store/buildProxy/buildProxy.js.map +1 -0
- package/dist/store/buildProxy/computeProxyKeys.d.ts +15 -0
- package/dist/store/buildProxy/computeProxyKeys.d.ts.map +1 -0
- package/dist/store/buildProxy/computeProxyKeys.js +28 -0
- package/dist/store/buildProxy/computeProxyKeys.js.map +1 -0
- package/dist/store/buildProxy/handleLazyResolve.d.ts +10 -0
- package/dist/store/buildProxy/handleLazyResolve.d.ts.map +1 -0
- package/dist/store/buildProxy/handleLazyResolve.js +26 -0
- package/dist/store/buildProxy/handleLazyResolve.js.map +1 -0
- package/dist/store/buildProxy/initProxyCaches.d.ts +16 -0
- package/dist/store/buildProxy/initProxyCaches.d.ts.map +1 -0
- package/dist/store/buildProxy/initProxyCaches.js +17 -0
- package/dist/store/buildProxy/initProxyCaches.js.map +1 -0
- package/dist/store/compute/computeFieldState.d.ts +10 -0
- package/dist/store/compute/computeFieldState.d.ts.map +1 -0
- package/dist/store/compute/computeFieldState.js +53 -0
- package/dist/store/compute/computeFieldState.js.map +1 -0
- package/dist/store/compute/fieldStateChanged.d.ts +6 -0
- package/dist/store/compute/fieldStateChanged.d.ts.map +1 -0
- package/dist/store/compute/fieldStateChanged.js +20 -0
- package/dist/store/compute/fieldStateChanged.js.map +1 -0
- package/dist/store/compute/index.d.ts +7 -0
- package/dist/store/compute/index.d.ts.map +1 -0
- package/dist/store/compute/index.js +6 -0
- package/dist/store/compute/index.js.map +1 -0
- package/dist/store/compute/isEmpty.d.ts +6 -0
- package/dist/store/compute/isEmpty.d.ts.map +1 -0
- package/dist/store/compute/isEmpty.js +14 -0
- package/dist/store/compute/isEmpty.js.map +1 -0
- package/dist/store/compute/recompute/collectGroupComputeNodes.d.ts +11 -0
- package/dist/store/compute/recompute/collectGroupComputeNodes.d.ts.map +1 -0
- package/dist/store/compute/recompute/collectGroupComputeNodes.js +26 -0
- package/dist/store/compute/recompute/collectGroupComputeNodes.js.map +1 -0
- package/dist/store/compute/recompute/collectGroupLeafNodes.d.ts +11 -0
- package/dist/store/compute/recompute/collectGroupLeafNodes.d.ts.map +1 -0
- package/dist/store/compute/recompute/collectGroupLeafNodes.js +26 -0
- package/dist/store/compute/recompute/collectGroupLeafNodes.js.map +1 -0
- package/dist/store/compute/recompute/index.d.ts +7 -0
- package/dist/store/compute/recompute/index.d.ts.map +1 -0
- package/dist/store/compute/recompute/index.js +6 -0
- package/dist/store/compute/recompute/index.js.map +1 -0
- package/dist/store/compute/recompute/recomputeAndNotify.d.ts +8 -0
- package/dist/store/compute/recompute/recomputeAndNotify.d.ts.map +1 -0
- package/dist/store/compute/recompute/recomputeAndNotify.js +13 -0
- package/dist/store/compute/recompute/recomputeAndNotify.js.map +1 -0
- package/dist/store/compute/recompute/recomputeLeaves.d.ts +23 -0
- package/dist/store/compute/recompute/recomputeLeaves.d.ts.map +1 -0
- package/dist/store/compute/recompute/recomputeLeaves.js +77 -0
- package/dist/store/compute/recompute/recomputeLeaves.js.map +1 -0
- package/dist/store/compute/recompute/recomputeTargeted.d.ts +14 -0
- package/dist/store/compute/recompute/recomputeTargeted.d.ts.map +1 -0
- package/dist/store/compute/recompute/recomputeTargeted.js +50 -0
- package/dist/store/compute/recompute/recomputeTargeted.js.map +1 -0
- package/dist/store/compute/recompute/topologicalSortComputed.d.ts +16 -0
- package/dist/store/compute/recompute/topologicalSortComputed.d.ts.map +1 -0
- package/dist/store/compute/recompute/topologicalSortComputed.js +60 -0
- package/dist/store/compute/recompute/topologicalSortComputed.js.map +1 -0
- package/dist/store/compute/recompute/types.d.ts +24 -0
- package/dist/store/compute/recompute/types.d.ts.map +1 -0
- package/dist/store/compute/recompute/types.js +2 -0
- package/dist/store/compute/recompute/types.js.map +1 -0
- package/dist/store/compute/resolveFlag.d.ts +7 -0
- package/dist/store/compute/resolveFlag.d.ts.map +1 -0
- package/dist/store/compute/resolveFlag.js +12 -0
- package/dist/store/compute/resolveFlag.js.map +1 -0
- package/dist/store/compute/resolveString.d.ts +11 -0
- package/dist/store/compute/resolveString.d.ts.map +1 -0
- package/dist/store/compute/resolveString.js +18 -0
- package/dist/store/compute/resolveString.js.map +1 -0
- package/dist/store/compute/types.d.ts +37 -0
- package/dist/store/compute/types.d.ts.map +1 -0
- package/dist/store/compute/types.js +2 -0
- package/dist/store/compute/types.js.map +1 -0
- package/dist/store/constants.d.ts +83 -0
- package/dist/store/constants.d.ts.map +1 -0
- package/dist/store/constants.js +145 -0
- package/dist/store/constants.js.map +1 -0
- package/dist/store/defineFieldMapping.d.ts +41 -0
- package/dist/store/defineFieldMapping.d.ts.map +1 -0
- package/dist/store/defineFieldMapping.js +42 -0
- package/dist/store/defineFieldMapping.js.map +1 -0
- package/dist/store/defineList.d.ts +14 -0
- package/dist/store/defineList.d.ts.map +1 -0
- package/dist/store/defineList.js +7 -0
- package/dist/store/defineList.js.map +1 -0
- package/dist/store/dirtyTracking/captureInitialValues.d.ts +8 -0
- package/dist/store/dirtyTracking/captureInitialValues.d.ts.map +1 -0
- package/dist/store/dirtyTracking/captureInitialValues.js +16 -0
- package/dist/store/dirtyTracking/captureInitialValues.js.map +1 -0
- package/dist/store/dirtyTracking/collectInitialSnapshot.d.ts +10 -0
- package/dist/store/dirtyTracking/collectInitialSnapshot.d.ts.map +1 -0
- package/dist/store/dirtyTracking/collectInitialSnapshot.js +35 -0
- package/dist/store/dirtyTracking/collectInitialSnapshot.js.map +1 -0
- package/dist/store/dirtyTracking/index.d.ts +7 -0
- package/dist/store/dirtyTracking/index.d.ts.map +1 -0
- package/dist/store/dirtyTracking/index.js +6 -0
- package/dist/store/dirtyTracking/index.js.map +1 -0
- package/dist/store/dirtyTracking/isDirtyValue.d.ts +7 -0
- package/dist/store/dirtyTracking/isDirtyValue.d.ts.map +1 -0
- package/dist/store/dirtyTracking/isDirtyValue.js +25 -0
- package/dist/store/dirtyTracking/isDirtyValue.js.map +1 -0
- package/dist/store/dirtyTracking/mergeInitialValues.d.ts +11 -0
- package/dist/store/dirtyTracking/mergeInitialValues.d.ts.map +1 -0
- package/dist/store/dirtyTracking/mergeInitialValues.js +29 -0
- package/dist/store/dirtyTracking/mergeInitialValues.js.map +1 -0
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.d.ts +17 -0
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.d.ts.map +1 -0
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.js +90 -0
- package/dist/store/dirtyTracking/recomputeDirtyTargeted.js.map +1 -0
- package/dist/store/dirtyTracking/setGroupRevalidate.d.ts +13 -0
- package/dist/store/dirtyTracking/setGroupRevalidate.d.ts.map +1 -0
- package/dist/store/dirtyTracking/setGroupRevalidate.js +32 -0
- package/dist/store/dirtyTracking/setGroupRevalidate.js.map +1 -0
- package/dist/store/entityRegistry/entityRegistry.d.ts +166 -0
- package/dist/store/entityRegistry/entityRegistry.d.ts.map +1 -0
- package/dist/store/entityRegistry/entityRegistry.js +402 -0
- package/dist/store/entityRegistry/entityRegistry.js.map +1 -0
- package/dist/store/entityRegistry/generateId.d.ts +7 -0
- package/dist/store/entityRegistry/generateId.d.ts.map +1 -0
- package/dist/store/entityRegistry/generateId.js +18 -0
- package/dist/store/entityRegistry/generateId.js.map +1 -0
- package/dist/store/entityRegistry/index.d.ts +4 -0
- package/dist/store/entityRegistry/index.d.ts.map +1 -0
- package/dist/store/entityRegistry/index.js +3 -0
- package/dist/store/entityRegistry/index.js.map +1 -0
- package/dist/store/entityRegistry/types.d.ts +50 -0
- package/dist/store/entityRegistry/types.d.ts.map +1 -0
- package/dist/store/entityRegistry/types.js +2 -0
- package/dist/store/entityRegistry/types.js.map +1 -0
- package/dist/store/fieldMapping.typecheck.d.ts +2 -0
- package/dist/store/fieldMapping.typecheck.d.ts.map +1 -0
- package/dist/store/fieldMapping.typecheck.js +71 -0
- package/dist/store/fieldMapping.typecheck.js.map +1 -0
- package/dist/store/groupDeps/createGroupDeps.d.ts +9 -0
- package/dist/store/groupDeps/createGroupDeps.d.ts.map +1 -0
- package/dist/store/groupDeps/createGroupDeps.js +29 -0
- package/dist/store/groupDeps/createGroupDeps.js.map +1 -0
- package/dist/store/groupDeps/createTrackingValues.d.ts +15 -0
- package/dist/store/groupDeps/createTrackingValues.d.ts.map +1 -0
- package/dist/store/groupDeps/createTrackingValues.js +39 -0
- package/dist/store/groupDeps/createTrackingValues.js.map +1 -0
- package/dist/store/groupDeps/getNodeGroupPath.d.ts +9 -0
- package/dist/store/groupDeps/getNodeGroupPath.d.ts.map +1 -0
- package/dist/store/groupDeps/getNodeGroupPath.js +20 -0
- package/dist/store/groupDeps/getNodeGroupPath.js.map +1 -0
- package/dist/store/groupDeps/getRecipientGroups.d.ts +5 -0
- package/dist/store/groupDeps/getRecipientGroups.d.ts.map +1 -0
- package/dist/store/groupDeps/getRecipientGroups.js +15 -0
- package/dist/store/groupDeps/getRecipientGroups.js.map +1 -0
- package/dist/store/groupDeps/groupDeps.d.ts +19 -0
- package/dist/store/groupDeps/groupDeps.d.ts.map +1 -0
- package/dist/store/groupDeps/groupDeps.js +19 -0
- package/dist/store/groupDeps/groupDeps.js.map +1 -0
- package/dist/store/groupDeps/index.d.ts +7 -0
- package/dist/store/groupDeps/index.d.ts.map +1 -0
- package/dist/store/groupDeps/index.js +7 -0
- package/dist/store/groupDeps/index.js.map +1 -0
- package/dist/store/groupDeps/pairKey.d.ts +7 -0
- package/dist/store/groupDeps/pairKey.d.ts.map +1 -0
- package/dist/store/groupDeps/pairKey.js +12 -0
- package/dist/store/groupDeps/pairKey.js.map +1 -0
- package/dist/store/groupDeps/resolveGroupByPath.d.ts +8 -0
- package/dist/store/groupDeps/resolveGroupByPath.d.ts.map +1 -0
- package/dist/store/groupDeps/resolveGroupByPath.js +19 -0
- package/dist/store/groupDeps/resolveGroupByPath.js.map +1 -0
- package/dist/store/init/createNotificationHub.d.ts +81 -0
- package/dist/store/init/createNotificationHub.d.ts.map +1 -0
- package/dist/store/init/createNotificationHub.js +149 -0
- package/dist/store/init/createNotificationHub.js.map +1 -0
- package/dist/store/init/createResolveManager.d.ts +166 -0
- package/dist/store/init/createResolveManager.d.ts.map +1 -0
- package/dist/store/init/createResolveManager.js +502 -0
- package/dist/store/init/createResolveManager.js.map +1 -0
- package/dist/store/init/initGroupSubmitting.d.ts +8 -0
- package/dist/store/init/initGroupSubmitting.d.ts.map +1 -0
- package/dist/store/init/initGroupSubmitting.js +39 -0
- package/dist/store/init/initGroupSubmitting.js.map +1 -0
- package/dist/store/normalizeConfig.d.ts +28 -0
- package/dist/store/normalizeConfig.d.ts.map +1 -0
- package/dist/store/normalizeConfig.js +76 -0
- package/dist/store/normalizeConfig.js.map +1 -0
- package/dist/store/onChangePipeline/computeFieldKey.d.ts +11 -0
- package/dist/store/onChangePipeline/computeFieldKey.d.ts.map +1 -0
- package/dist/store/onChangePipeline/computeFieldKey.js +18 -0
- package/dist/store/onChangePipeline/computeFieldKey.js.map +1 -0
- package/dist/store/onChangePipeline/findOnChangeAncestors.d.ts +8 -0
- package/dist/store/onChangePipeline/findOnChangeAncestors.d.ts.map +1 -0
- package/dist/store/onChangePipeline/findOnChangeAncestors.js +22 -0
- package/dist/store/onChangePipeline/findOnChangeAncestors.js.map +1 -0
- package/dist/store/onChangePipeline/onChangePipeline.d.ts +29 -0
- package/dist/store/onChangePipeline/onChangePipeline.d.ts.map +1 -0
- package/dist/store/onChangePipeline/onChangePipeline.js +99 -0
- package/dist/store/onChangePipeline/onChangePipeline.js.map +1 -0
- package/dist/store/persist/drivers.d.ts +34 -0
- package/dist/store/persist/drivers.d.ts.map +1 -0
- package/dist/store/persist/drivers.js +89 -0
- package/dist/store/persist/drivers.js.map +1 -0
- package/dist/store/persist/index.d.ts +7 -0
- package/dist/store/persist/index.d.ts.map +1 -0
- package/dist/store/persist/index.js +3 -0
- package/dist/store/persist/index.js.map +1 -0
- package/dist/store/persist/persistManager.d.ts +67 -0
- package/dist/store/persist/persistManager.d.ts.map +1 -0
- package/dist/store/persist/persistManager.js +198 -0
- package/dist/store/persist/persistManager.js.map +1 -0
- package/dist/store/persist/types.d.ts +66 -0
- package/dist/store/persist/types.d.ts.map +1 -0
- package/dist/store/persist/types.js +9 -0
- package/dist/store/persist/types.js.map +1 -0
- package/dist/store/resetPipeline/buildResetPatch.d.ts +11 -0
- package/dist/store/resetPipeline/buildResetPatch.d.ts.map +1 -0
- package/dist/store/resetPipeline/buildResetPatch.js +22 -0
- package/dist/store/resetPipeline/buildResetPatch.js.map +1 -0
- package/dist/store/resetPipeline/collectDefaults.d.ts +12 -0
- package/dist/store/resetPipeline/collectDefaults.d.ts.map +1 -0
- package/dist/store/resetPipeline/collectDefaults.js +31 -0
- package/dist/store/resetPipeline/collectDefaults.js.map +1 -0
- package/dist/store/resetPipeline/resetPipeline.d.ts +19 -0
- package/dist/store/resetPipeline/resetPipeline.d.ts.map +1 -0
- package/dist/store/resetPipeline/resetPipeline.js +49 -0
- package/dist/store/resetPipeline/resetPipeline.js.map +1 -0
- package/dist/store/resolvePipeline/applyPendingWrites.d.ts +26 -0
- package/dist/store/resolvePipeline/applyPendingWrites.d.ts.map +1 -0
- package/dist/store/resolvePipeline/applyPendingWrites.js +68 -0
- package/dist/store/resolvePipeline/applyPendingWrites.js.map +1 -0
- package/dist/store/resolvePipeline/createContextTrackingProxy.d.ts +16 -0
- package/dist/store/resolvePipeline/createContextTrackingProxy.d.ts.map +1 -0
- package/dist/store/resolvePipeline/createContextTrackingProxy.js +29 -0
- package/dist/store/resolvePipeline/createContextTrackingProxy.js.map +1 -0
- package/dist/store/resolvePipeline/createValuesTrackingProxy.d.ts +75 -0
- package/dist/store/resolvePipeline/createValuesTrackingProxy.d.ts.map +1 -0
- package/dist/store/resolvePipeline/createValuesTrackingProxy.js +128 -0
- package/dist/store/resolvePipeline/createValuesTrackingProxy.js.map +1 -0
- package/dist/store/resolvePipeline/executeEntityFieldResolve.d.ts +29 -0
- package/dist/store/resolvePipeline/executeEntityFieldResolve.d.ts.map +1 -0
- package/dist/store/resolvePipeline/executeEntityFieldResolve.js +218 -0
- package/dist/store/resolvePipeline/executeEntityFieldResolve.js.map +1 -0
- package/dist/store/resolvePipeline/executeListResolve.d.ts +32 -0
- package/dist/store/resolvePipeline/executeListResolve.d.ts.map +1 -0
- package/dist/store/resolvePipeline/executeListResolve.js +157 -0
- package/dist/store/resolvePipeline/executeListResolve.js.map +1 -0
- package/dist/store/resolvePipeline/executeResolve.d.ts +26 -0
- package/dist/store/resolvePipeline/executeResolve.d.ts.map +1 -0
- package/dist/store/resolvePipeline/executeResolve.js +201 -0
- package/dist/store/resolvePipeline/executeResolve.js.map +1 -0
- package/dist/store/resolvePipeline/findResolvesToRetrigger.d.ts +10 -0
- package/dist/store/resolvePipeline/findResolvesToRetrigger.d.ts.map +1 -0
- package/dist/store/resolvePipeline/findResolvesToRetrigger.js +29 -0
- package/dist/store/resolvePipeline/findResolvesToRetrigger.js.map +1 -0
- package/dist/store/resolvePipeline/index.d.ts +22 -0
- package/dist/store/resolvePipeline/index.d.ts.map +1 -0
- package/dist/store/resolvePipeline/index.js +18 -0
- package/dist/store/resolvePipeline/index.js.map +1 -0
- package/dist/store/resolvePipeline/initResolveStates.d.ts +41 -0
- package/dist/store/resolvePipeline/initResolveStates.d.ts.map +1 -0
- package/dist/store/resolvePipeline/initResolveStates.js +95 -0
- package/dist/store/resolvePipeline/initResolveStates.js.map +1 -0
- package/dist/store/resolvePipeline/resetResolveState.d.ts +8 -0
- package/dist/store/resolvePipeline/resetResolveState.d.ts.map +1 -0
- package/dist/store/resolvePipeline/resetResolveState.js +18 -0
- package/dist/store/resolvePipeline/resetResolveState.js.map +1 -0
- package/dist/store/resolvePipeline/types.d.ts +134 -0
- package/dist/store/resolvePipeline/types.d.ts.map +1 -0
- package/dist/store/resolvePipeline/types.js +54 -0
- package/dist/store/resolvePipeline/types.js.map +1 -0
- package/dist/store/store/NodeRegistry/index.d.ts +3 -0
- package/dist/store/store/NodeRegistry/index.d.ts.map +1 -0
- package/dist/store/store/NodeRegistry/index.js +2 -0
- package/dist/store/store/NodeRegistry/index.js.map +1 -0
- package/dist/store/store/NodeRegistry/nodeRegistry.d.ts +139 -0
- package/dist/store/store/NodeRegistry/nodeRegistry.d.ts.map +1 -0
- package/dist/store/store/NodeRegistry/nodeRegistry.js +255 -0
- package/dist/store/store/NodeRegistry/nodeRegistry.js.map +1 -0
- package/dist/store/store/NodeRegistry/nodeUtils.d.ts +17 -0
- package/dist/store/store/NodeRegistry/nodeUtils.d.ts.map +1 -0
- package/dist/store/store/NodeRegistry/nodeUtils.js +21 -0
- package/dist/store/store/NodeRegistry/nodeUtils.js.map +1 -0
- package/dist/store/store/NodeRegistry/nodeView.d.ts +28 -0
- package/dist/store/store/NodeRegistry/nodeView.d.ts.map +1 -0
- package/dist/store/store/NodeRegistry/nodeView.js +16 -0
- package/dist/store/store/NodeRegistry/nodeView.js.map +1 -0
- package/dist/store/store/dirtyTracker.d.ts +27 -0
- package/dist/store/store/dirtyTracker.d.ts.map +1 -0
- package/dist/store/store/dirtyTracker.js +38 -0
- package/dist/store/store/dirtyTracker.js.map +1 -0
- package/dist/store/store/groupDepsMap.d.ts +52 -0
- package/dist/store/store/groupDepsMap.d.ts.map +1 -0
- package/dist/store/store/groupDepsMap.js +84 -0
- package/dist/store/store/groupDepsMap.js.map +1 -0
- package/dist/store/store/hasComputedProps.d.ts +7 -0
- package/dist/store/store/hasComputedProps.d.ts.map +1 -0
- package/dist/store/store/hasComputedProps.js +15 -0
- package/dist/store/store/hasComputedProps.js.map +1 -0
- package/dist/store/store/index.d.ts +6 -0
- package/dist/store/store/index.d.ts.map +1 -0
- package/dist/store/store/index.js +3 -0
- package/dist/store/store/index.js.map +1 -0
- package/dist/store/store/nodeMap.d.ts +12 -0
- package/dist/store/store/nodeMap.d.ts.map +1 -0
- package/dist/store/store/nodeMap.js +30 -0
- package/dist/store/store/nodeMap.js.map +1 -0
- package/dist/store/store/palistor.d.ts +256 -0
- package/dist/store/store/palistor.d.ts.map +1 -0
- package/dist/store/store/palistor.js +772 -0
- package/dist/store/store/palistor.js.map +1 -0
- package/dist/store/store/registerNodes.d.ts +33 -0
- package/dist/store/store/registerNodes.d.ts.map +1 -0
- package/dist/store/store/registerNodes.js +88 -0
- package/dist/store/store/registerNodes.js.map +1 -0
- package/dist/store/store/serviceRegistry.d.ts +24 -0
- package/dist/store/store/serviceRegistry.d.ts.map +1 -0
- package/dist/store/store/serviceRegistry.js +32 -0
- package/dist/store/store/serviceRegistry.js.map +1 -0
- package/dist/store/store/types.d.ts +622 -0
- package/dist/store/store/types.d.ts.map +1 -0
- package/dist/store/store/types.js +2 -0
- package/dist/store/store/types.js.map +1 -0
- package/dist/store/submitPipeline/applyLeafBeforeSubmit.d.ts +7 -0
- package/dist/store/submitPipeline/applyLeafBeforeSubmit.d.ts.map +1 -0
- package/dist/store/submitPipeline/applyLeafBeforeSubmit.js +26 -0
- package/dist/store/submitPipeline/applyLeafBeforeSubmit.js.map +1 -0
- package/dist/store/submitPipeline/collectLeafStates.d.ts +11 -0
- package/dist/store/submitPipeline/collectLeafStates.d.ts.map +1 -0
- package/dist/store/submitPipeline/collectLeafStates.js +17 -0
- package/dist/store/submitPipeline/collectLeafStates.js.map +1 -0
- package/dist/store/submitPipeline/getSubValues.d.ts +9 -0
- package/dist/store/submitPipeline/getSubValues.d.ts.map +1 -0
- package/dist/store/submitPipeline/getSubValues.js +26 -0
- package/dist/store/submitPipeline/getSubValues.js.map +1 -0
- package/dist/store/submitPipeline/submitPipeline.d.ts +32 -0
- package/dist/store/submitPipeline/submitPipeline.d.ts.map +1 -0
- package/dist/store/submitPipeline/submitPipeline.js +119 -0
- package/dist/store/submitPipeline/submitPipeline.js.map +1 -0
- package/dist/store/submitPipeline/types.d.ts +12 -0
- package/dist/store/submitPipeline/types.d.ts.map +1 -0
- package/dist/store/submitPipeline/types.js +2 -0
- package/dist/store/submitPipeline/types.js.map +1 -0
- package/dist/store/traversal/index.d.ts +4 -0
- package/dist/store/traversal/index.d.ts.map +1 -0
- package/dist/store/traversal/index.js +3 -0
- package/dist/store/traversal/index.js.map +1 -0
- package/dist/store/traversal/nodeClassifier.d.ts +23 -0
- package/dist/store/traversal/nodeClassifier.d.ts.map +1 -0
- package/dist/store/traversal/nodeClassifier.js +46 -0
- package/dist/store/traversal/nodeClassifier.js.map +1 -0
- package/dist/store/traversal/walkFull.d.ts +33 -0
- package/dist/store/traversal/walkFull.d.ts.map +1 -0
- package/dist/store/traversal/walkFull.js +28 -0
- package/dist/store/traversal/walkFull.js.map +1 -0
- package/dist/store/valuesCache/valuesCache.d.ts +27 -0
- package/dist/store/valuesCache/valuesCache.d.ts.map +1 -0
- package/dist/store/valuesCache/valuesCache.js +58 -0
- package/dist/store/valuesCache/valuesCache.js.map +1 -0
- package/dist/store/writePipeline/formatPatch.d.ts +14 -0
- package/dist/store/writePipeline/formatPatch.d.ts.map +1 -0
- package/dist/store/writePipeline/formatPatch.js +35 -0
- package/dist/store/writePipeline/formatPatch.js.map +1 -0
- package/dist/store/writePipeline/formatValue.d.ts +12 -0
- package/dist/store/writePipeline/formatValue.d.ts.map +1 -0
- package/dist/store/writePipeline/formatValue.js +15 -0
- package/dist/store/writePipeline/formatValue.js.map +1 -0
- package/dist/store/writePipeline/index.d.ts +8 -0
- package/dist/store/writePipeline/index.d.ts.map +1 -0
- package/dist/store/writePipeline/index.js +7 -0
- package/dist/store/writePipeline/index.js.map +1 -0
- package/dist/store/writePipeline/mergeChanged.d.ts +9 -0
- package/dist/store/writePipeline/mergeChanged.d.ts.map +1 -0
- package/dist/store/writePipeline/mergeChanged.js +15 -0
- package/dist/store/writePipeline/mergeChanged.js.map +1 -0
- package/dist/store/writePipeline/runSetter.d.ts +16 -0
- package/dist/store/writePipeline/runSetter.d.ts.map +1 -0
- package/dist/store/writePipeline/runSetter.js +35 -0
- package/dist/store/writePipeline/runSetter.js.map +1 -0
- package/dist/store/writePipeline/storeValue.d.ts +11 -0
- package/dist/store/writePipeline/storeValue.d.ts.map +1 -0
- package/dist/store/writePipeline/storeValue.js +17 -0
- package/dist/store/writePipeline/storeValue.js.map +1 -0
- package/dist/store/writePipeline/types.d.ts +10 -0
- package/dist/store/writePipeline/types.d.ts.map +1 -0
- package/dist/store/writePipeline/types.js +2 -0
- package/dist/store/writePipeline/types.js.map +1 -0
- package/dist/store/writePipeline/writePipeline.d.ts +31 -0
- package/dist/store/writePipeline/writePipeline.d.ts.map +1 -0
- package/dist/store/writePipeline/writePipeline.js +97 -0
- package/dist/store/writePipeline/writePipeline.js.map +1 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,947 @@
|
|
|
1
|
+
# Palistor
|
|
2
|
+
|
|
3
|
+
> Реактивный state manager для форм с гранулярным рендерингом
|
|
4
|
+
|
|
5
|
+
Palistor — библиотека управления состоянием форм для React. Построена на двухслойной proxy-архитектуре: framework-agnostic ядро и React-интеграция с точечным трекингом подписок. Компонент перерендеривается **только** при изменении тех полей, которые он реально читал.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Установка
|
|
10
|
+
|
|
11
|
+
Пакет публикуется в **публичный npm-реестр** под именем `palistor`.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install palistor
|
|
15
|
+
# или
|
|
16
|
+
yarn add palistor
|
|
17
|
+
# или
|
|
18
|
+
pnpm add palistor
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Peer-зависимости:** `react ^19`
|
|
22
|
+
|
|
23
|
+
> **Альтернатива — GitHub Packages.** Тот же пакет доступен под scoped-именем
|
|
24
|
+
> `@projectint/palistor`. Для установки добавьте в `.npmrc`:
|
|
25
|
+
> ```
|
|
26
|
+
> @projectint:registry=https://npm.pkg.github.com
|
|
27
|
+
> ```
|
|
28
|
+
> и (для приватного доступа) токен:
|
|
29
|
+
> ```
|
|
30
|
+
> //npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT
|
|
31
|
+
> ```
|
|
32
|
+
> затем `npm install @projectint/palistor`. Каноническим считается имя `palistor`.
|
|
33
|
+
|
|
34
|
+
### Импорт
|
|
35
|
+
|
|
36
|
+
Все публичные symbol'ы доступны из корневого модуля:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import {
|
|
40
|
+
Palistor,
|
|
41
|
+
useForm,
|
|
42
|
+
usePersist,
|
|
43
|
+
useTranslator,
|
|
44
|
+
useNotifier,
|
|
45
|
+
defineList,
|
|
46
|
+
localStorageDriver,
|
|
47
|
+
sessionStorageDriver,
|
|
48
|
+
} from "palistor";
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Содержание
|
|
54
|
+
|
|
55
|
+
- [Возможности](#возможности)
|
|
56
|
+
- [Быстрый старт](#быстрый-старт)
|
|
57
|
+
- [Концепции](#концепции)
|
|
58
|
+
- [API Reference](#api-reference)
|
|
59
|
+
- [Типизация](#типизация)
|
|
60
|
+
- [Рецепты](#рецепты)
|
|
61
|
+
- [Async Resolver](#async-resolver)
|
|
62
|
+
- [Списки и сущности](#списки-и-сущности)
|
|
63
|
+
- [Persist](#persist)
|
|
64
|
+
- [i18n](#i18n)
|
|
65
|
+
- [Уведомления](#уведомления)
|
|
66
|
+
- [Структура модуля](#структура-модуля)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Возможности
|
|
71
|
+
|
|
72
|
+
| | |
|
|
73
|
+
|---|---|
|
|
74
|
+
| **Гранулярные ре-рендеры** | Компонент подписывается только на те поля, которые он читал — больше ничего не триггерит ре-рендер |
|
|
75
|
+
| **Computed Field State** | `isVisible`, `isRequired`, `error`, `dirty` пересчитываются автоматически по конфигу |
|
|
76
|
+
| **Proxy API** | Нативный синтаксис: `field.value = x` вместо `dispatch({ type: "SET", field: "...", value: x })` |
|
|
77
|
+
| **Submit pipeline** | `beforeSubmit → validate → onSubmit → afterSubmit` с revalidate-семантикой |
|
|
78
|
+
| **Dirty tracking** | Per-field и per-group флаг изменений; baseline обновляется после resolve и reset |
|
|
79
|
+
| **Async resolvers** | Загрузка данных с auto-deps, retry, optimistic updates и React Suspense |
|
|
80
|
+
| **Списки / Entities** | Нормализованный реестр сущностей, list proxy с `add / remove / setItems` |
|
|
81
|
+
| **Persist** | Автосохранение в `localStorage`, `sessionStorage` или любом кастомном драйвере |
|
|
82
|
+
| **i18n** | Одна строка в layout — `label`, `placeholder`, `description` переводятся везде |
|
|
83
|
+
| **Тестируемость** | Framework-agnostic ядро — тестируется без React |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Быстрый старт
|
|
88
|
+
|
|
89
|
+
### 1. Создайте store
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { createProxyStore } from "palistor";
|
|
93
|
+
|
|
94
|
+
export const paymentStore = createProxyStore({
|
|
95
|
+
config: {
|
|
96
|
+
paymentType: {
|
|
97
|
+
value: "card",
|
|
98
|
+
label: "Способ оплаты",
|
|
99
|
+
},
|
|
100
|
+
cardNumber: {
|
|
101
|
+
value: "",
|
|
102
|
+
label: "Номер карты",
|
|
103
|
+
placeholder: "0000 0000 0000 0000",
|
|
104
|
+
isVisible: (v) => v.paymentType === "card",
|
|
105
|
+
isRequired: (v) => v.paymentType === "card",
|
|
106
|
+
validate: (value, v) =>
|
|
107
|
+
v.paymentType === "card" && value.length < 16
|
|
108
|
+
? "Введите 16 цифр"
|
|
109
|
+
: undefined,
|
|
110
|
+
},
|
|
111
|
+
passport: {
|
|
112
|
+
isVisible: (v) => v.paymentType === "bank",
|
|
113
|
+
number: { value: "", label: "Серия и номер", isRequired: true },
|
|
114
|
+
issueDate: { value: "", label: "Дата выдачи" },
|
|
115
|
+
},
|
|
116
|
+
amount: { value: 0, label: "Сумма", isRequired: true },
|
|
117
|
+
},
|
|
118
|
+
initialValues: { paymentType: "card" },
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2. Используйте в компоненте
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useForm } from "palistor";
|
|
126
|
+
|
|
127
|
+
function PaymentForm() {
|
|
128
|
+
const form = useForm(paymentStore);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<form onSubmit={() => paymentStore.submit()}>
|
|
132
|
+
<Select
|
|
133
|
+
value={form.paymentType.value}
|
|
134
|
+
onChange={(e) => (form.paymentType.value = e.target.value)}
|
|
135
|
+
label={form.paymentType.label}
|
|
136
|
+
/>
|
|
137
|
+
|
|
138
|
+
{form.cardNumber.isVisible && (
|
|
139
|
+
<Input
|
|
140
|
+
value={form.cardNumber.value}
|
|
141
|
+
onChange={(e) => (form.cardNumber.value = e.target.value)}
|
|
142
|
+
label={form.cardNumber.label}
|
|
143
|
+
isRequired={form.cardNumber.isRequired}
|
|
144
|
+
isInvalid={form.cardNumber.isInvalid}
|
|
145
|
+
errorMessage={form.cardNumber.errorMessage}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
{form.passport.isVisible && <PassportSection passport={form.passport} />}
|
|
150
|
+
|
|
151
|
+
<Button type="submit" isLoading={form.submitting}>
|
|
152
|
+
Оплатить
|
|
153
|
+
</Button>
|
|
154
|
+
</form>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. Изолированный ре-рендер дочернего компонента
|
|
160
|
+
|
|
161
|
+
Если дочернему компоненту нужен **независимый** трекинг, передайте поддерево в `useForm`:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
function PassportSection({ passport }) {
|
|
165
|
+
// Собственный tracking proxy — ре-рендер только при изменении полей passport
|
|
166
|
+
const p = useForm(passport);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<Input value={p.number.value} onChange={(e) => (p.number.value = e.target.value)} label={p.number.label} />
|
|
171
|
+
<Input value={p.issueDate.value} onChange={(e) => (p.issueDate.value = e.target.value)} label={p.issueDate.label} />
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
> **Без `useForm` в дочернем** — компонент рендерится каскадно вместе с родителем. Подходит для простых листовых компонентов.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Концепции
|
|
182
|
+
|
|
183
|
+
### Два узла: лист и группа
|
|
184
|
+
|
|
185
|
+
Тип узла определяется наличием свойства `value`:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Есть "value" → листовой узел (поле формы)
|
|
189
|
+
Нет "value" → групповой узел (контейнер или секция)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const config = {
|
|
194
|
+
// Листовой узел — управляет значением
|
|
195
|
+
email: { value: "", isRequired: true },
|
|
196
|
+
|
|
197
|
+
// Групповой узел — контейнер с вычисленными свойствами
|
|
198
|
+
address: {
|
|
199
|
+
isVisible: (v) => v.showAddress,
|
|
200
|
+
city: { value: "" },
|
|
201
|
+
country: { value: "RU" },
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Как работает трекинг
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Рендер: читаем form.email.value, form.phone.value
|
|
210
|
+
→ accessed = { emailNode, phoneNode }
|
|
211
|
+
|
|
212
|
+
SET form.city.value → cityNode++ → snapshot не изменился → нет ре-рендера ✓
|
|
213
|
+
SET form.email.value → emailNode++ → snapshot изменился → ре-рендер ✓
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Родитель, который только навигирует (`form.passport`), но не читает поля — **не ре-рендерится** при изменении полей внутри `passport`.
|
|
217
|
+
|
|
218
|
+
### Поток данных при SET
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
form.email.value = "user@example.com"
|
|
222
|
+
│
|
|
223
|
+
├─ 1. formatter(rawValue, allValues) → нормализованное значение
|
|
224
|
+
├─ 2. storeValue() → nodeState обновлён, valuesCache O(1)
|
|
225
|
+
├─ 3. setter(value, allValues)? → patch → applyPatch() смежных полей
|
|
226
|
+
├─ 4. recompute(changedNodes) → таргетированный пересчёт FieldState
|
|
227
|
+
├─ 5. recomputeDirtyTargeted() → dirty флаги вверх по дереву
|
|
228
|
+
├─ 6. notifyChanged() → version++ → useSyncExternalStore → ре-рендер
|
|
229
|
+
└─ 7. onChangePipeline.fire() → onChange callback (fire-and-forget)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## API Reference
|
|
235
|
+
|
|
236
|
+
### `createProxyStore(options)`
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { createProxyStore } from "palistor";
|
|
240
|
+
|
|
241
|
+
const store = createProxyStore({
|
|
242
|
+
config: { /* дерево ConfigNode */ },
|
|
243
|
+
initialValues?: { /* перекрывают значения из конфига */ },
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**`ProxyStore<TConfig>` — возвращаемое значение:**
|
|
248
|
+
|
|
249
|
+
| Свойство / метод | Тип | Описание |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `store.proxy` | `ConfigProxy<TConfig>` | Реактивный прокси — структура повторяет конфиг |
|
|
252
|
+
| `store.getValues()` | `DeepValues<TConfig>` | Все текущие значения вложенным объектом |
|
|
253
|
+
| `store.submit()` | `Promise<SubmitResult>` | Submit root-группы |
|
|
254
|
+
| `store.reset(values?)` | `void` | Сброс к defaults (или к переданным значениям) |
|
|
255
|
+
| `store.set(data)` | `string` | Upsert entity; возвращает `id` |
|
|
256
|
+
| `store.delete(id)` | `boolean` | Удалить entity из реестра |
|
|
257
|
+
| `store.rekey(oldId, newId)` | `void` | Переименовать entity во всех списках |
|
|
258
|
+
| `store.subscribe(node, fn)` | `() => void` | Подписка на изменения узла; возвращает unsubscribe |
|
|
259
|
+
| `store.subscribeGlobal(fn)` | `() => void` | Подписка на любые изменения |
|
|
260
|
+
| `store.getVersion()` | `number` | Глобальная версия (инкремент при каждом изменении) |
|
|
261
|
+
| `store.getNodeVersion(node)` | `number` | Версия конкретного узла |
|
|
262
|
+
| `store.setTranslator(fn \| null)` | `void` | Зарегистрировать i18n-функцию |
|
|
263
|
+
| `store.setNotifier(fn \| null)` | `void` | Зарегистрировать функцию уведомлений |
|
|
264
|
+
| `store.persist` | `PersistManager` | Менеджер персистенции |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### `useForm(source)`
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { useForm } from "palistor";
|
|
272
|
+
|
|
273
|
+
const form = useForm(store); // tracking поверх корневого proxy
|
|
274
|
+
const section = useForm(form.address); // свой tracking для поддерева
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Возвращает типизированный `ConfigProxy<TConfig>`. Ре-рендер только при изменении нод, к которым компонент обращался через `FIELD_STATE_PROPS`.
|
|
278
|
+
|
|
279
|
+
**Паттерны подписки:**
|
|
280
|
+
|
|
281
|
+
| Паттерн | Когда использовать |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `useForm(store)` + пропсы вниз | Простые формы, листовые UI-компоненты |
|
|
284
|
+
| `useForm(subtree)` в дочернем | Крупные секции с независимым ре-рендером |
|
|
285
|
+
| Проп без `useForm` | Pure UI-компоненты, рендерятся каскадно за родителем |
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### Листовой узел — доступные свойства
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Чтение (реактивно — добавляет ноду в tracking set)
|
|
293
|
+
form.email.value // → TValue
|
|
294
|
+
form.email.label // → string | undefined
|
|
295
|
+
form.email.placeholder // → string | undefined
|
|
296
|
+
form.email.description // → string | undefined
|
|
297
|
+
form.email.isRequired // → boolean
|
|
298
|
+
form.email.isReadOnly // → boolean
|
|
299
|
+
form.email.isDisabled // → boolean
|
|
300
|
+
form.email.isVisible // → boolean
|
|
301
|
+
form.email.isInvalid // → boolean | undefined
|
|
302
|
+
form.email.errorMessage // → string | undefined
|
|
303
|
+
form.email.dirty // → boolean
|
|
304
|
+
|
|
305
|
+
// Запись — триггерит formatter → setter → recompute → notify
|
|
306
|
+
form.email.value = "new@example.com";
|
|
307
|
+
form.email.onValueChange("new@example.com"); // эквивалентно
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Групповой узел — доступные свойства
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
form.passport.isVisible // → boolean
|
|
314
|
+
form.passport.isRequired // → boolean | undefined
|
|
315
|
+
form.passport.isReadOnly // → boolean | undefined
|
|
316
|
+
form.passport.isDisabled // → boolean | undefined
|
|
317
|
+
form.passport.isInvalid // → boolean | undefined
|
|
318
|
+
form.passport.errorMessage // → string | undefined
|
|
319
|
+
form.passport.submitting // → boolean
|
|
320
|
+
form.passport.loading // → boolean (async resolver)
|
|
321
|
+
form.passport.dirty // → boolean (хотя бы одно поле изменилось)
|
|
322
|
+
form.passport.revalidate // → boolean (true после первого неудачного submit)
|
|
323
|
+
form.passport.values // → Record<string, unknown> — live-снапшот значений группы
|
|
324
|
+
|
|
325
|
+
await form.passport.submit(); // → SubmitResult
|
|
326
|
+
form.passport.reset({ number: "" }); // сброс поддерева
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`values` — живая ссылка на вложенный объект `valuesCache`, отражающий текущие значения всех листовых полей группы (рекурсивно). Обновляется in-place при каждой записи — стабильная ссылка, безопасна для передачи в API:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
const vals = form.passport.values;
|
|
333
|
+
// → { number: "AB1234", issueDate: "2020-01-01" }
|
|
334
|
+
|
|
335
|
+
await api.submit(vals); // стабильная ссылка, всегда актуальна
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
### `ConfigNode` — схема поля
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Листовой узел (есть "value")
|
|
344
|
+
interface LeafNode<TValue, TValues> {
|
|
345
|
+
value?: TValue | ((values: TValues) => TValue);
|
|
346
|
+
validate?: (value: TValue, values: TValues) => string | undefined | false;
|
|
347
|
+
formatter?: (value: unknown, values: TValues) => TValue;
|
|
348
|
+
setter?: (value: TValue, values: TValues) => DeepPartialValues<TValues>;
|
|
349
|
+
beforeSubmit?: (value: TValue, values: TValues) => TValue;
|
|
350
|
+
dependencies?: readonly string[]; // для топологической сортировки computed
|
|
351
|
+
componentProps?: Record<string, unknown>;
|
|
352
|
+
|
|
353
|
+
label?: string | ((values: TValues) => string);
|
|
354
|
+
placeholder?: string | ((values: TValues) => string);
|
|
355
|
+
description?: string | ((values: TValues) => string);
|
|
356
|
+
isVisible?: boolean | ((values: TValues) => boolean); // default: true
|
|
357
|
+
isRequired?: boolean | ((values: TValues) => boolean); // default: false
|
|
358
|
+
isDisabled?: boolean | ((values: TValues) => boolean); // default: false
|
|
359
|
+
isReadOnly?: boolean | ((values: TValues) => boolean); // default: false
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Групповой узел (нет "value")
|
|
363
|
+
interface GroupNode<TValues> {
|
|
364
|
+
beforeSubmit?: (values: TValues) => TValues;
|
|
365
|
+
onSubmit?: (values: TValues) => Promise<unknown> | unknown;
|
|
366
|
+
afterSubmit?: (result: unknown, actions: { reset: () => void }) => void | Promise<void>;
|
|
367
|
+
reset?: (defaults: TValues) => TValues;
|
|
368
|
+
onChange?: (info: OnChangeInfo<TValues>) => DeepPartialValues<TValues> | void | Promise<...>;
|
|
369
|
+
resolve?: ResolveConfig<TValues>;
|
|
370
|
+
|
|
371
|
+
isVisible?: boolean | ((values: TValues) => boolean);
|
|
372
|
+
isRequired?: boolean | ((values: TValues) => boolean);
|
|
373
|
+
isDisabled?: boolean | ((values: TValues) => boolean);
|
|
374
|
+
isReadOnly?: boolean | ((values: TValues) => boolean);
|
|
375
|
+
|
|
376
|
+
[key: string]: LeafNode | GroupNode | ListNode | any;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Список (массив длиной 1 или 2)
|
|
380
|
+
type ListNode = [TemplateGroupNode] | [TemplateGroupNode, ListConfig];
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
### `SubmitResult`
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
type SubmitResult =
|
|
389
|
+
| { success: true; result?: unknown }
|
|
390
|
+
| { success: false; errors: Array<{ path: string; message: string }> };
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Типизация
|
|
396
|
+
|
|
397
|
+
### Вывод типа значений из конфига
|
|
398
|
+
|
|
399
|
+
`ExtractValues<typeof config>` извлекает плоский тип значений из конфига. Листовые узлы (`value`) — тип значения; групповые — вложенный объект; списки — массив:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
const config = {
|
|
403
|
+
name: { value: "" },
|
|
404
|
+
age: { value: 0 },
|
|
405
|
+
address: {
|
|
406
|
+
city: { value: "" },
|
|
407
|
+
country: { value: "RU" },
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
type FormValues = ExtractValues<typeof config>;
|
|
412
|
+
// → { name: string; age: number; address: { city: string; country: string } }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`DeepPartialValues<FormValues>` — глубокая optional-версия, используется для `initialValues`, `setter`, `setValues`:
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const initial: DeepPartialValues<FormValues> = { address: { city: "Москва" } };
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Типизация пропсов без импорта конфига
|
|
422
|
+
|
|
423
|
+
`Palistor<T>` маппит интерфейс значений на прокси-дерево. Используйте его для типизации пропсов дочерних компонентов:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import type { Palistor } from "palistor";
|
|
427
|
+
|
|
428
|
+
interface UserData { name: string; email: string; address: { city: string } }
|
|
429
|
+
|
|
430
|
+
type Props = { user: Palistor<UserData> };
|
|
431
|
+
|
|
432
|
+
function UserForm({ user }: Props) {
|
|
433
|
+
const u = useForm(user);
|
|
434
|
+
return <input value={u.name.value} onChange={e => { u.name.value = e.target.value }} />;
|
|
435
|
+
}
|
|
436
|
+
// u.name → FieldProxyNode<string>
|
|
437
|
+
// u.address → Palistor<{ city: string }> (GroupProxyNode + поля)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Массивы в `T` обрабатываются автоматически:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
interface FormData { users: Array<{ name: string; email: string }> }
|
|
444
|
+
// form.users → ListProxyNode<Palistor<{ name: string; email: string }>>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Типизированные entity-ссылки
|
|
448
|
+
|
|
449
|
+
При передаче entity-прокси через пропсы используйте `PalistorRef<TEntity>`:
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
import type { PalistorRef, InferEntity } from "palistor";
|
|
453
|
+
|
|
454
|
+
function UserRow({ user }: { user: PalistorRef<{ name: string; email: string }> }) {
|
|
455
|
+
const u = useForm(user, (s) => s.userTemplate);
|
|
456
|
+
return <span>{u.name.value}</span>;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Получить тип entity обратно из ссылки:
|
|
460
|
+
type UserEntity = InferEntity<PalistorRef<{ name: string; email: string }>>;
|
|
461
|
+
// → { name: string; email: string }
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
`PalistorList<TEntity>` — типизированный список: `ListProxyNode<PalistorRef<TEntity>>`.
|
|
465
|
+
|
|
466
|
+
### defineList — типизированный список
|
|
467
|
+
|
|
468
|
+
Предпочитайте `defineList<TEntity>()` вместо сырого массива. Он проверяет, что ключи `template` соответствуют entity, а `resolver` возвращает `Promise<TEntity[]>`:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { defineList } from "palistor";
|
|
472
|
+
|
|
473
|
+
interface User { id: string; name: string; email: string }
|
|
474
|
+
|
|
475
|
+
const users = defineList<User>({
|
|
476
|
+
template: {
|
|
477
|
+
id: { value: "" },
|
|
478
|
+
name: { value: "", isRequired: true },
|
|
479
|
+
email: { value: "" },
|
|
480
|
+
},
|
|
481
|
+
resolve: {
|
|
482
|
+
resolver: async (values) => api.getUsers(values.filter), // → Promise<User[]>
|
|
483
|
+
deps: ["filter"],
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const config = { filter: { value: "" }, users };
|
|
488
|
+
// store.proxy.users → ListProxyNode<PalistorRef<User>>
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Справочник типов
|
|
492
|
+
|
|
493
|
+
| Тип | Назначение |
|
|
494
|
+
|-----|-----------|
|
|
495
|
+
| `ExtractValues<TConfig>` | Плоский тип значений из конфига |
|
|
496
|
+
| `ConfigProxy<TConfig>` | Полный прокси — тип, возвращаемый `useForm(store)` |
|
|
497
|
+
| `Palistor<T>` | Proxy на основе интерфейса значений — для пропсов дочерних компонентов |
|
|
498
|
+
| `PalistorRef<TEntity>` | Непрозрачная ссылка на entity — для пропсов одного элемента |
|
|
499
|
+
| `PalistorList<TEntity>` | Типизированный список: `ListProxyNode<PalistorRef<TEntity>>` |
|
|
500
|
+
| `InferEntity<T>` | Извлечь тип entity из `PalistorRef<TEntity>` |
|
|
501
|
+
| `MaybeComputed<TResult, TValues>` | `isVisible`, `isRequired`, `value` — константа или `(values) => T` |
|
|
502
|
+
| `MaybeTranslatable<TResult, TValues>` | `label`, `placeholder` — константа или `(t, values) => string` |
|
|
503
|
+
| `DeepPartialValues<T>` | Глубокая partial-версия: `initialValues`, патчи `setter`, `setValues` |
|
|
504
|
+
| `TranslateFn` | Совместим с next-intl `t`, i18next `t` и любым `(...args) => string` |
|
|
505
|
+
| `TemplateConfig<TEntity>` | Типизированный шаблон — каждый ключ entity → `ConfigNode<TEntity[K]>` |
|
|
506
|
+
| `ListResolver<TEntity>` | Типизированный resolver — `(values) => Promise<TEntity[]>` |
|
|
507
|
+
|
|
508
|
+
### Полный паттерн типизации
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
import { Palistor, defineList } from "palistor";
|
|
512
|
+
import type { ExtractValues, PalistorRef, DeepPartialValues } from "palistor";
|
|
513
|
+
|
|
514
|
+
// 1. Вывести тип значений из конфига
|
|
515
|
+
const config = {
|
|
516
|
+
filter: { value: "" },
|
|
517
|
+
users: defineList<{ id: string; name: string }>({
|
|
518
|
+
template: { id: { value: "" }, name: { value: "" } },
|
|
519
|
+
}),
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
type Values = ExtractValues<typeof config>;
|
|
523
|
+
// → { filter: string; users: Array<{ id: string; name: string }> }
|
|
524
|
+
|
|
525
|
+
// 2. Создать store — TConfig выводится из конфига
|
|
526
|
+
const store = new Palistor({ config, initialValues: { filter: "active" } });
|
|
527
|
+
|
|
528
|
+
// 3. Типизировать пропсы через Palistor<Values>
|
|
529
|
+
type FormProps = { form: Palistor<Values> };
|
|
530
|
+
|
|
531
|
+
// 4. Типизировать entity-пропсы через PalistorRef
|
|
532
|
+
type UserRef = PalistorRef<{ id: string; name: string }>;
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Рецепты
|
|
538
|
+
|
|
539
|
+
### Computed value
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
createProxyStore({
|
|
543
|
+
config: {
|
|
544
|
+
price: { value: 100 },
|
|
545
|
+
quantity: { value: 2 },
|
|
546
|
+
total: { value: (v) => v.price * v.quantity, isReadOnly: true },
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Цепочка computed (топологическая сортировка)
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
config: {
|
|
555
|
+
price: { value: 100 },
|
|
556
|
+
tax: { value: (v) => v.price * 0.2, dependencies: ["price"] },
|
|
557
|
+
total: { value: (v) => v.price + v.tax, dependencies: ["price", "tax"] },
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### formatter — нормализация при записи
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
email: {
|
|
565
|
+
value: "",
|
|
566
|
+
formatter: (v) => String(v).trim().toLowerCase(),
|
|
567
|
+
},
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### setter — каскадное изменение нескольких полей
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
country: {
|
|
574
|
+
value: "RU",
|
|
575
|
+
setter: (value) => ({ city: "" }), // сбрасываем city при смене country
|
|
576
|
+
},
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### onChange — реакция на изменение поля
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
passport: {
|
|
583
|
+
onChange: ({ fieldKey, newValue, allValues }) => {
|
|
584
|
+
if (fieldKey === "number") return { issueDate: "" };
|
|
585
|
+
},
|
|
586
|
+
number: { value: "" },
|
|
587
|
+
issueDate: { value: "" },
|
|
588
|
+
},
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### beforeSubmit — трансформация перед отправкой
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
phone: {
|
|
595
|
+
value: "",
|
|
596
|
+
beforeSubmit: (value) => value.replace(/\D/g, ""), // не мутирует store
|
|
597
|
+
},
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Групповой submit с валидацией
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
const store = createProxyStore({
|
|
604
|
+
config: {
|
|
605
|
+
company: {
|
|
606
|
+
onSubmit: async (values) => api.saveCompany(values.company),
|
|
607
|
+
afterSubmit: (_result, { reset }) => {
|
|
608
|
+
showSuccessToast();
|
|
609
|
+
reset();
|
|
610
|
+
},
|
|
611
|
+
name: { value: "", isRequired: true },
|
|
612
|
+
taxId: { value: "" },
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const result = await store.proxy.company.submit();
|
|
618
|
+
if (!result.success) {
|
|
619
|
+
console.log(result.errors);
|
|
620
|
+
// [{ path: "company.name", message: "Обязательное поле" }]
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Получение значений для отправки
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
const values = store.getValues();
|
|
628
|
+
// → { paymentType: "bank", passport: { number: "AB123", issueDate: "2020-01-01" } }
|
|
629
|
+
await api.submit(values);
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## Async Resolver
|
|
635
|
+
|
|
636
|
+
Конфигурируется на групповом узле. Загружает данные асинхронно — с авто-трекингом зависимостей, retry и поддержкой React Suspense.
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
const store = createProxyStore({
|
|
640
|
+
config: {
|
|
641
|
+
userId: { value: "" },
|
|
642
|
+
|
|
643
|
+
userInfo: {
|
|
644
|
+
resolve: {
|
|
645
|
+
// values — tracking proxy: GET-доступы автоматически становятся зависимостями.
|
|
646
|
+
// При изменении userId resolver перезапустится.
|
|
647
|
+
resolver: async (values) => {
|
|
648
|
+
const data = await api.getUser(values.userId);
|
|
649
|
+
return { name: data.name, email: data.email };
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
// Мгновенный placeholder до завершения resolver
|
|
653
|
+
optimisticResolver: (values) => ({ name: "Загрузка..." }),
|
|
654
|
+
|
|
655
|
+
onError: (error, ctx) => {
|
|
656
|
+
ctx.notify("Не удалось загрузить данные", "USER_LOAD_ERROR");
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
options: {
|
|
660
|
+
lazy: true, // ждать первого обращения к узлу (default: true)
|
|
661
|
+
suspense: false, // throw promise для React Suspense (default: false)
|
|
662
|
+
retry: { attempts: 3, delay: 1000 },
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
name: { value: "" },
|
|
667
|
+
email: { value: "" },
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
const form = useForm(store);
|
|
675
|
+
|
|
676
|
+
// Без Suspense — ручная проверка loading
|
|
677
|
+
if (form.userInfo.loading) return <Spinner />;
|
|
678
|
+
|
|
679
|
+
// С Suspense — автоматически
|
|
680
|
+
<Suspense fallback={<Spinner />}>
|
|
681
|
+
<UserInfoSection />
|
|
682
|
+
</Suspense>
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Поведение при изменении зависимости:**
|
|
686
|
+
|
|
687
|
+
```
|
|
688
|
+
userId меняется
|
|
689
|
+
→ notifyChanged → findResolvesToRetrigger
|
|
690
|
+
→ resetResolveState(idle) → triggerResolve()
|
|
691
|
+
→ optimisticResolver применяется мгновенно
|
|
692
|
+
→ resolver запускается заново
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Списки и сущности
|
|
698
|
+
|
|
699
|
+
Списки объявляются как массив длиной 1 или 2, где `[0]` — шаблон элемента:
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
const store = createProxyStore({
|
|
703
|
+
config: {
|
|
704
|
+
users: [
|
|
705
|
+
// Шаблон — описывает поля каждого элемента
|
|
706
|
+
{
|
|
707
|
+
id: { value: "" },
|
|
708
|
+
name: { value: "", isRequired: true },
|
|
709
|
+
email: { value: "" },
|
|
710
|
+
},
|
|
711
|
+
// Опциональный конфиг списка
|
|
712
|
+
{
|
|
713
|
+
resolve: {
|
|
714
|
+
resolver: async () => {
|
|
715
|
+
const data = await api.getUsers();
|
|
716
|
+
return data; // Array<{ id, name, email }>
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### List Proxy API
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
const form = useForm(store);
|
|
729
|
+
|
|
730
|
+
// Чтение
|
|
731
|
+
form.users.items // ReadonlyArray<EntityProjectionProxy>
|
|
732
|
+
form.users.length // number
|
|
733
|
+
form.users.loading // boolean
|
|
734
|
+
form.users.dirty // boolean — состав списка изменился vs. baseline
|
|
735
|
+
|
|
736
|
+
// Итерация
|
|
737
|
+
form.users.map((item, index, id) => <Row key={id} item={item} />)
|
|
738
|
+
for (const item of form.users) { ... }
|
|
739
|
+
|
|
740
|
+
// Мутации
|
|
741
|
+
form.users.add({ name: "Alice", email: "alice@example.com" }); // объект → upsert + добавить
|
|
742
|
+
form.users.add("existing-id"); // строка → добавить существующую
|
|
743
|
+
form.users.remove("user-id");
|
|
744
|
+
form.users.setItems(["id1", "id2", "id3"]); // bulk замена
|
|
745
|
+
form.users.getById("user-id"); // → EntityProjectionProxy | undefined
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### EntityProjectionProxy — свойства элемента списка
|
|
749
|
+
|
|
750
|
+
Каждый элемент `form.users.items` — это `EntityProjectionProxy`. Доступные свойства:
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
const item = form.users.items[0];
|
|
754
|
+
|
|
755
|
+
item.id // string — идентификатор entity
|
|
756
|
+
item.name.value // значение поля через template (с formatter/validate/isRequired)
|
|
757
|
+
item.name.label // computed label из template
|
|
758
|
+
item.name.isRequired // computed isRequired из template
|
|
759
|
+
// ... все leaf props: value, label, placeholder, isRequired, isReadOnly, isDisabled,
|
|
760
|
+
// isVisible, isInvalid, errorMessage, dirty, onValueChange
|
|
761
|
+
|
|
762
|
+
item.loading // boolean — идёт ли resolve для этого entity
|
|
763
|
+
item.submitting // boolean — идёт ли submit для этого entity
|
|
764
|
+
item.values // Record<string, unknown> — plain объект значений entity
|
|
765
|
+
await item.submit(); // → SubmitResult — submit этого entity через template
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`item.values` — plain объект с текущими значениями полей entity, пригодный для передачи в API:
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
form.users.map((item) => {
|
|
772
|
+
console.log(item.values); // → { name: "Alice", email: "alice@example.com" }
|
|
773
|
+
});
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Работа с entity
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
// Создать / обновить entity — возвращает id
|
|
780
|
+
const id = store.set({ name: "Bob", email: "bob@example.com" });
|
|
781
|
+
|
|
782
|
+
// Если id не передан — генерируется временный (_tmp_...)
|
|
783
|
+
// После сохранения на сервере переименуйте:
|
|
784
|
+
store.rekey(tmpId, serverAssignedId);
|
|
785
|
+
|
|
786
|
+
// Удалить entity
|
|
787
|
+
store.delete(id);
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## Persist
|
|
793
|
+
|
|
794
|
+
Автосохранение состояния формы в любом хранилище.
|
|
795
|
+
|
|
796
|
+
### React-хук (рекомендуется)
|
|
797
|
+
|
|
798
|
+
```tsx
|
|
799
|
+
import { usePersist } from "palistor/react/usePersist";
|
|
800
|
+
|
|
801
|
+
function PaymentPage({ orderId }: { orderId: string }) {
|
|
802
|
+
usePersist(paymentStore, {
|
|
803
|
+
key: `payment-${orderId}`, // ключ может зависеть от пропсов
|
|
804
|
+
driver: localStorageDriver,
|
|
805
|
+
debounce: 500, // ms, default: 100
|
|
806
|
+
pick: ["cardNumber"], // персистировать только эти top-level поля
|
|
807
|
+
// omit: ["cvv"], // или исключить чувствительные поля
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
const form = useForm(paymentStore);
|
|
811
|
+
// ...
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Вне React
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
import { localStorageDriver, sessionStorageDriver } from "palistor/store/persist";
|
|
819
|
+
|
|
820
|
+
paymentStore.persist.enable({
|
|
821
|
+
key: "payment",
|
|
822
|
+
driver: localStorageDriver,
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
await paymentStore.persist.flush(); // принудительное сохранение
|
|
826
|
+
paymentStore.persist.disable();
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Кастомный драйвер
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
import type { PersistDriver } from "palistor/store/persist";
|
|
833
|
+
|
|
834
|
+
const myDriver: PersistDriver = {
|
|
835
|
+
getItem: (key) => myStorage.get(key),
|
|
836
|
+
setItem: (key, value) => myStorage.set(key, value),
|
|
837
|
+
removeItem: (key) => myStorage.delete(key),
|
|
838
|
+
};
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**`PersistOptions`:**
|
|
842
|
+
|
|
843
|
+
| Поле | Тип | По умолчанию | Описание |
|
|
844
|
+
|---|---|---|---|
|
|
845
|
+
| `key` | `string` | — | Ключ хранения |
|
|
846
|
+
| `driver` | `PersistDriver` | — | Реализация хранилища |
|
|
847
|
+
| `debounce` | `number` | `100` | Задержка записи, ms |
|
|
848
|
+
| `serialize` | `fn` | `JSON.stringify` | Кастомный сериализатор |
|
|
849
|
+
| `deserialize` | `fn` | `JSON.parse` | Кастомный десериализатор |
|
|
850
|
+
| `pick` | `string[]` | — | Персистировать только эти top-level ключи |
|
|
851
|
+
| `omit` | `string[]` | — | Исключить эти ключи (игнорируется если задан `pick`) |
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
## i18n
|
|
856
|
+
|
|
857
|
+
Регистрируйте функцию перевода один раз — в layout или провайдере. Все компоненты с `useForm` получат переведённые `label`, `placeholder`, `description` автоматически. При смене локали все компоненты перерендерятся.
|
|
858
|
+
|
|
859
|
+
```tsx
|
|
860
|
+
import { useTranslations } from "next-intl";
|
|
861
|
+
import { useTranslator } from "palistor/react/useTranslator";
|
|
862
|
+
|
|
863
|
+
function RootLayout({ children }: { children: React.ReactNode }) {
|
|
864
|
+
const t = useTranslations();
|
|
865
|
+
useTranslator(paymentStore, t);
|
|
866
|
+
return <>{children}</>;
|
|
867
|
+
}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
В конфиге `label` / `placeholder` задаются как функции-переводчики:
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
cardNumber: {
|
|
874
|
+
value: "",
|
|
875
|
+
label: (t) => t("fields.cardNumber"),
|
|
876
|
+
placeholder: (t) => t("fields.cardNumber.placeholder"),
|
|
877
|
+
},
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
> Без translator функции возвращают свой результат вызова как есть — удобно для тестов без i18n-окружения.
|
|
881
|
+
|
|
882
|
+
---
|
|
883
|
+
|
|
884
|
+
## Уведомления
|
|
885
|
+
|
|
886
|
+
Регистрирует функцию toast-уведомлений для использования внутри `resolve.onError`:
|
|
887
|
+
|
|
888
|
+
```tsx
|
|
889
|
+
import { useNotifier } from "palistor/react/useNotifier";
|
|
890
|
+
|
|
891
|
+
function RootLayout({ children }: { children: React.ReactNode }) {
|
|
892
|
+
const notifyError = useCallback((error: unknown, code?: string) => {
|
|
893
|
+
addToast({ title: code ?? "Ошибка", color: "danger" });
|
|
894
|
+
}, []);
|
|
895
|
+
|
|
896
|
+
useNotifier(paymentStore, notifyError);
|
|
897
|
+
return <>{children}</>;
|
|
898
|
+
}
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
## Структура модуля
|
|
904
|
+
|
|
905
|
+
```
|
|
906
|
+
palistor/
|
|
907
|
+
├── index.ts # Публичный API
|
|
908
|
+
├── store/
|
|
909
|
+
│ ├── store/
|
|
910
|
+
│ │ ├── palistor.ts # Palistor — ядро системы (kernel + ProxyStore)
|
|
911
|
+
│ │ ├── registerNodes.ts # Инициализация leafNodes + nodeState
|
|
912
|
+
│ │ └── types.ts # ConfigNode, ProxyStore, ListState и др.
|
|
913
|
+
│ ├── buildProxy/
|
|
914
|
+
│ │ ├── buildProxy.ts # Proxy слой 1: GET/SET traps
|
|
915
|
+
│ │ ├── buildListProxy.ts # List proxy: items, add, remove, map…
|
|
916
|
+
│ │ └── buildEntityProjectionProxy.ts # Entity proxy через template
|
|
917
|
+
│ ├── compute/
|
|
918
|
+
│ │ └── recompute/ # recomputeLeaves, recomputeTargeted, topologicalSort
|
|
919
|
+
│ ├── writePipeline/ # formatter → storeValue → setter
|
|
920
|
+
│ ├── submitPipeline/ # beforeSubmit → validate → onSubmit → afterSubmit
|
|
921
|
+
│ ├── resetPipeline/ # Сброс к defaults
|
|
922
|
+
│ ├── onChangePipeline/ # onChange callback
|
|
923
|
+
│ ├── resolvePipeline/ # Async resolver: deps, retry, optimistic, suspense
|
|
924
|
+
│ ├── dirtyTracking/ # captureInitialValues, recomputeDirtyTargeted
|
|
925
|
+
│ ├── entityRegistry/ # Нормализованный реестр сущностей
|
|
926
|
+
│ ├── groupDeps/ # Карта зависимостей между группами
|
|
927
|
+
│ ├── traversal/ # isLeaf / isGroup / isListNode / walkFull
|
|
928
|
+
│ ├── valuesCache/ # buildValuesCache + O(1) updateValuesCacheEntry
|
|
929
|
+
│ ├── init/ # NotificationHub, ResolveManager, initGroupSubmitting
|
|
930
|
+
│ └── persist/
|
|
931
|
+
│ ├── persistManager.ts # enable / disable / flush
|
|
932
|
+
│ ├── drivers.ts # localStorageDriver, sessionStorageDriver
|
|
933
|
+
│ └── types.ts # PersistDriver, PersistOptions
|
|
934
|
+
└── react/
|
|
935
|
+
├── useForm.ts # useSyncExternalStore + tracking proxy
|
|
936
|
+
├── createTrackingProxy.ts # Proxy слой 2: запись accessed нод
|
|
937
|
+
├── useTranslator.ts # Регистрация i18n-функции
|
|
938
|
+
├── useNotifier.ts # Регистрация функции уведомлений
|
|
939
|
+
└── usePersist.ts # React-интеграция persist
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
---
|
|
943
|
+
|
|
944
|
+
## Лицензия
|
|
945
|
+
|
|
946
|
+
MIT
|
|
947
|
+
|