@abloatai/humans 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/Ablo.d.ts +208 -0
- package/dist/Ablo.js +120 -0
- package/dist/client.d.ts +317 -0
- package/dist/client.js +13 -0
- package/dist/core.d.ts +35 -0
- package/dist/core.js +48 -0
- package/dist/humans.d.ts +28 -0
- package/dist/humans.js +34 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +6 -0
- package/dist/local/BaseSyncedStore.d.ts +807 -0
- package/dist/local/BaseSyncedStore.js +1516 -0
- package/dist/local/Database.d.ts +322 -0
- package/dist/local/Database.js +1589 -0
- package/dist/local/InstanceCache.d.ts +255 -0
- package/dist/local/InstanceCache.js +1263 -0
- package/dist/local/LazyReferenceCollection.d.ts +177 -0
- package/dist/local/LazyReferenceCollection.js +461 -0
- package/dist/local/Model.d.ts +475 -0
- package/dist/local/Model.js +950 -0
- package/dist/local/ModelRegistry.d.ts +225 -0
- package/dist/local/ModelRegistry.js +539 -0
- package/dist/local/NetworkMonitor.d.ts +28 -0
- package/dist/local/NetworkMonitor.js +79 -0
- package/dist/local/RuntimeContext.d.ts +52 -0
- package/dist/local/RuntimeContext.js +80 -0
- package/dist/local/SyncClient.d.ts +516 -0
- package/dist/local/SyncClient.js +1754 -0
- package/dist/local/adapters/alwaysOnline.d.ts +14 -0
- package/dist/local/adapters/alwaysOnline.js +17 -0
- package/dist/local/adapters/inMemoryStorage.d.ts +37 -0
- package/dist/local/adapters/inMemoryStorage.js +122 -0
- package/dist/local/client/clientPrelude.d.ts +52 -0
- package/dist/local/client/clientPrelude.js +60 -0
- package/dist/local/client/consoleLogger.d.ts +35 -0
- package/dist/local/client/consoleLogger.js +44 -0
- package/dist/local/client/createInternalComponents.d.ts +50 -0
- package/dist/local/client/createInternalComponents.js +98 -0
- package/dist/local/client/createModelProxy.d.ts +248 -0
- package/dist/local/client/createModelProxy.js +880 -0
- package/dist/local/client/modelRegistration.d.ts +10 -0
- package/dist/local/client/modelRegistration.js +316 -0
- package/dist/local/client/options.d.ts +440 -0
- package/dist/local/client/options.js +7 -0
- package/dist/local/client/reactiveEngine.d.ts +53 -0
- package/dist/local/client/reactiveEngine.js +705 -0
- package/dist/local/client/resourceTypes.d.ts +12 -0
- package/dist/local/client/resourceTypes.js +10 -0
- package/dist/local/client/schemaConfig.d.ts +44 -0
- package/dist/local/client/schemaConfig.js +185 -0
- package/dist/local/client/storeCluster.d.ts +46 -0
- package/dist/local/client/storeCluster.js +133 -0
- package/dist/local/client/storeLifecycle.d.ts +61 -0
- package/dist/local/client/storeLifecycle.js +236 -0
- package/dist/local/client/validateAbloOptions.d.ts +42 -0
- package/dist/local/client/validateAbloOptions.js +43 -0
- package/dist/local/client/wsMutationExecutor.d.ts +27 -0
- package/dist/local/client/wsMutationExecutor.js +72 -0
- package/dist/local/context.d.ts +42 -0
- package/dist/local/context.js +81 -0
- package/dist/local/coordination/ClaimLog.d.ts +26 -0
- package/dist/local/coordination/ClaimLog.js +32 -0
- package/dist/local/interfaces/index.d.ts +311 -0
- package/dist/local/interfaces/index.js +9 -0
- package/dist/local/localModelContract.d.ts +14 -0
- package/dist/local/localModelContract.js +1 -0
- package/dist/local/logPosition.d.ts +31 -0
- package/dist/local/logPosition.js +53 -0
- package/dist/local/mutationPersistence.d.ts +6 -0
- package/dist/local/mutationPersistence.js +1 -0
- package/dist/local/mutators/RecordingMutation.d.ts +36 -0
- package/dist/local/mutators/RecordingMutation.js +182 -0
- package/dist/local/mutators/Transaction.d.ts +40 -0
- package/dist/local/mutators/Transaction.js +58 -0
- package/dist/local/mutators/UndoManager.d.ts +258 -0
- package/dist/local/mutators/UndoManager.js +665 -0
- package/dist/local/mutators/defineMutators.d.ts +60 -0
- package/dist/local/mutators/defineMutators.js +18 -0
- package/dist/local/mutators/inverseOp.d.ts +126 -0
- package/dist/local/mutators/inverseOp.js +71 -0
- package/dist/local/mutators/mutateActions.d.ts +45 -0
- package/dist/local/mutators/mutateActions.js +105 -0
- package/dist/local/mutators/readerActions.d.ts +33 -0
- package/dist/local/mutators/readerActions.js +57 -0
- package/dist/local/mutators/undoApply.d.ts +51 -0
- package/dist/local/mutators/undoApply.js +117 -0
- package/dist/local/persistence.d.ts +7 -0
- package/dist/local/persistence.js +9 -0
- package/dist/local/query/QueryProcessor.d.ts +75 -0
- package/dist/local/query/QueryProcessor.js +255 -0
- package/dist/local/query/client.d.ts +64 -0
- package/dist/local/query/client.js +138 -0
- package/dist/local/query/types.d.ts +85 -0
- package/dist/local/query/types.js +16 -0
- package/dist/local/schema/serialize.d.ts +1 -0
- package/dist/local/schema/serialize.js +1 -0
- package/dist/local/store/queryApi.d.ts +13 -0
- package/dist/local/store/queryApi.js +35 -0
- package/dist/local/storeContract.d.ts +145 -0
- package/dist/local/storeContract.js +12 -0
- package/dist/local/stores/DatabaseManager.d.ts +112 -0
- package/dist/local/stores/DatabaseManager.js +400 -0
- package/dist/local/stores/ObjectStore.d.ts +115 -0
- package/dist/local/stores/ObjectStore.js +393 -0
- package/dist/local/stores/ObjectStoreContract.d.ts +38 -0
- package/dist/local/stores/ObjectStoreContract.js +1 -0
- package/dist/local/stores/StoreManager.d.ts +114 -0
- package/dist/local/stores/StoreManager.js +304 -0
- package/dist/local/stores/SyncActionStore.d.ts +99 -0
- package/dist/local/stores/SyncActionStore.js +506 -0
- package/dist/local/stores/openIDBWithTimeout.d.ts +65 -0
- package/dist/local/stores/openIDBWithTimeout.js +153 -0
- package/dist/local/stores/persistenceCleanup.d.ts +7 -0
- package/dist/local/stores/persistenceCleanup.js +26 -0
- package/dist/local/stores/persistenceIdentity.d.ts +27 -0
- package/dist/local/stores/persistenceIdentity.js +38 -0
- package/dist/local/stores/syncAction.d.ts +26 -0
- package/dist/local/stores/syncAction.js +16 -0
- package/dist/local/stores/v1PersistenceDeletion.d.ts +8 -0
- package/dist/local/stores/v1PersistenceDeletion.js +16 -0
- package/dist/local/sync/BootstrapFetcher.d.ts +284 -0
- package/dist/local/sync/BootstrapFetcher.js +964 -0
- package/dist/local/sync/ConnectionManager.d.ts +8 -0
- package/dist/local/sync/ConnectionManager.js +8 -0
- package/dist/local/sync/OnDemandLoader.d.ts +231 -0
- package/dist/local/sync/OnDemandLoader.js +743 -0
- package/dist/local/sync/SubscriptionManager.d.ts +159 -0
- package/dist/local/sync/SubscriptionManager.js +243 -0
- package/dist/local/sync/SyncWebSocket.d.ts +173 -0
- package/dist/local/sync/SyncWebSocket.js +438 -0
- package/dist/local/sync/bootstrapApply.d.ts +73 -0
- package/dist/local/sync/bootstrapApply.js +73 -0
- package/dist/local/sync/commitFrames.d.ts +8 -0
- package/dist/local/sync/commitFrames.js +8 -0
- package/dist/local/sync/connectionManagerLifecycle.d.ts +23 -0
- package/dist/local/sync/connectionManagerLifecycle.js +126 -0
- package/dist/local/sync/contextPorts.d.ts +18 -0
- package/dist/local/sync/contextPorts.js +31 -0
- package/dist/local/sync/createClaimStream.d.ts +64 -0
- package/dist/local/sync/createClaimStream.js +475 -0
- package/dist/local/sync/createSnapshot.d.ts +29 -0
- package/dist/local/sync/createSnapshot.js +116 -0
- package/dist/local/sync/credentialLifecycle.d.ts +7 -0
- package/dist/local/sync/credentialLifecycle.js +7 -0
- package/dist/local/sync/deltaPipeline.d.ts +116 -0
- package/dist/local/sync/deltaPipeline.js +357 -0
- package/dist/local/sync/groupChange.d.ts +116 -0
- package/dist/local/sync/groupChange.js +244 -0
- package/dist/local/sync/initialize.d.ts +27 -0
- package/dist/local/sync/initialize.js +137 -0
- package/dist/local/sync/participants.d.ts +132 -0
- package/dist/local/sync/participants.js +342 -0
- package/dist/local/sync/persistedPrefix.d.ts +12 -0
- package/dist/local/sync/persistedPrefix.js +22 -0
- package/dist/local/sync/reconnect.d.ts +23 -0
- package/dist/local/sync/reconnect.js +55 -0
- package/dist/local/sync/schemaDrift.d.ts +55 -0
- package/dist/local/sync/schemaDrift.js +53 -0
- package/dist/local/sync/schemas.d.ts +71 -0
- package/dist/local/sync/schemas.js +94 -0
- package/dist/local/sync/socketEventWiring.d.ts +31 -0
- package/dist/local/sync/socketEventWiring.js +130 -0
- package/dist/local/sync/syncCursor.d.ts +40 -0
- package/dist/local/sync/syncCursor.js +55 -0
- package/dist/local/sync/syncPlan.d.ts +54 -0
- package/dist/local/sync/syncPlan.js +50 -0
- package/dist/local/sync/terminalSessionLifecycle.d.ts +20 -0
- package/dist/local/sync/terminalSessionLifecycle.js +50 -0
- package/dist/local/sync/wsFrameHandlers.d.ts +8 -0
- package/dist/local/sync/wsFrameHandlers.js +8 -0
- package/dist/local/transactions/databaseCommitOutbox.d.ts +15 -0
- package/dist/local/transactions/databaseCommitOutbox.js +16 -0
- package/dist/local/transactions/localMutation.d.ts +10 -0
- package/dist/local/transactions/localMutation.js +37 -0
- package/dist/local/transactions/mutations/MutationQueue.d.ts +511 -0
- package/dist/local/transactions/mutations/MutationQueue.js +1498 -0
- package/dist/local/transactions/mutations/MutationStore.d.ts +20 -0
- package/dist/local/transactions/mutations/MutationStore.js +53 -0
- package/dist/local/transactions/mutations/UnconfirmedWrites.d.ts +82 -0
- package/dist/local/transactions/mutations/UnconfirmedWrites.js +104 -0
- package/dist/local/transactions/mutations/batchProcessing.d.ts +64 -0
- package/dist/local/transactions/mutations/batchProcessing.js +349 -0
- package/dist/local/transactions/mutations/coalesceRules.d.ts +58 -0
- package/dist/local/transactions/mutations/coalesceRules.js +140 -0
- package/dist/local/transactions/mutations/commitApi.d.ts +19 -0
- package/dist/local/transactions/mutations/commitApi.js +74 -0
- package/dist/local/transactions/mutations/commitLane.d.ts +70 -0
- package/dist/local/transactions/mutations/commitLane.js +140 -0
- package/dist/local/transactions/mutations/commitLatency.d.ts +52 -0
- package/dist/local/transactions/mutations/commitLatency.js +130 -0
- package/dist/local/transactions/mutations/commitPayload.d.ts +165 -0
- package/dist/local/transactions/mutations/commitPayload.js +152 -0
- package/dist/local/transactions/mutations/commitTransport.d.ts +36 -0
- package/dist/local/transactions/mutations/commitTransport.js +104 -0
- package/dist/local/transactions/mutations/deltaConfirmation.d.ts +63 -0
- package/dist/local/transactions/mutations/deltaConfirmation.js +235 -0
- package/dist/local/transactions/mutations/durableCommitRestore.d.ts +17 -0
- package/dist/local/transactions/mutations/durableCommitRestore.js +95 -0
- package/dist/local/transactions/mutations/durableWriteStore.d.ts +14 -0
- package/dist/local/transactions/mutations/durableWriteStore.js +12 -0
- package/dist/local/transactions/mutations/executionSelection.d.ts +6 -0
- package/dist/local/transactions/mutations/executionSelection.js +42 -0
- package/dist/local/transactions/mutations/failureHandling.d.ts +16 -0
- package/dist/local/transactions/mutations/failureHandling.js +130 -0
- package/dist/local/transactions/mutations/failurePolicy.d.ts +13 -0
- package/dist/local/transactions/mutations/failurePolicy.js +48 -0
- package/dist/local/transactions/mutations/localMutation.d.ts +58 -0
- package/dist/local/transactions/mutations/localMutation.js +75 -0
- package/dist/local/transactions/mutations/modelOperations.d.ts +44 -0
- package/dist/local/transactions/mutations/modelOperations.js +144 -0
- package/dist/local/transactions/mutations/mutationPersistence.d.ts +25 -0
- package/dist/local/transactions/mutations/mutationPersistence.js +188 -0
- package/dist/local/transactions/mutations/pendingDrain.d.ts +33 -0
- package/dist/local/transactions/mutations/pendingDrain.js +112 -0
- package/dist/local/transactions/mutations/processingScheduler.d.ts +14 -0
- package/dist/local/transactions/mutations/processingScheduler.js +24 -0
- package/dist/local/transactions/mutations/queueCoalescing.d.ts +13 -0
- package/dist/local/transactions/mutations/queueCoalescing.js +35 -0
- package/dist/local/transactions/mutations/replayValidation.d.ts +187 -0
- package/dist/local/transactions/mutations/replayValidation.js +164 -0
- package/dist/local/transactions/reconnectDrain.d.ts +11 -0
- package/dist/local/transactions/reconnectDrain.js +13 -0
- package/dist/local/utils/mobxSetup.d.ts +53 -0
- package/dist/local/utils/mobxSetup.js +330 -0
- package/dist/local/views/QueryView.d.ts +79 -0
- package/dist/local/views/QueryView.js +218 -0
- package/dist/local/views/ViewRegistry.d.ts +20 -0
- package/dist/local/views/ViewRegistry.js +57 -0
- package/dist/local/views/incrementalView.d.ts +45 -0
- package/dist/local/views/incrementalView.js +69 -0
- package/dist/plugin.d.ts +285 -0
- package/dist/plugin.js +106 -0
- package/dist/presenceStream.d.ts +69 -0
- package/dist/presenceStream.js +200 -0
- package/dist/react/AbloProvider.d.ts +242 -0
- package/dist/react/AbloProvider.js +456 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/context.d.ts +55 -0
- package/dist/react/context.js +29 -0
- package/dist/react/createAbloReact.d.ts +50 -0
- package/dist/react/createAbloReact.js +48 -0
- package/dist/react/internalContext.d.ts +33 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +96 -0
- package/dist/react/useAblo.js +120 -0
- package/dist/react/useCurrentUserId.d.ts +2 -0
- package/dist/react/useCurrentUserId.js +12 -0
- package/dist/react/useErrorListener.d.ts +2 -0
- package/dist/react/useErrorListener.js +14 -0
- package/dist/react/useMutationFailureListener.d.ts +8 -0
- package/dist/react/useMutationFailureListener.js +19 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +84 -0
- package/dist/react/useSyncStatus.d.ts +19 -0
- package/dist/react/useSyncStatus.js +37 -0
- package/dist/react/useUndoScope.d.ts +34 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/react.d.ts +18 -0
- package/dist/react.js +14 -0
- package/dist/reactRuntime.d.ts +4 -0
- package/dist/reactRuntime.js +2 -0
- package/dist/surface.d.ts +36 -0
- package/dist/surface.js +77 -0
- package/dist/useReactive.d.ts +6 -0
- package/dist/useReactive.js +43 -0
- package/package.json +119 -0
- package/src/Ablo.ts +456 -0
- package/src/client.ts +374 -0
- package/src/core.ts +104 -0
- package/src/humans.ts +61 -0
- package/src/index.ts +40 -0
- package/src/local/BaseSyncedStore.ts +1991 -0
- package/src/local/Database.ts +2052 -0
- package/src/local/InstanceCache.ts +1503 -0
- package/src/local/LazyReferenceCollection.ts +563 -0
- package/src/local/Model.ts +1124 -0
- package/src/local/ModelRegistry.ts +762 -0
- package/src/local/NetworkMonitor.ts +88 -0
- package/src/local/RuntimeContext.ts +141 -0
- package/src/local/SyncClient.ts +2131 -0
- package/src/local/adapters/alwaysOnline.ts +20 -0
- package/src/local/adapters/inMemoryStorage.ts +141 -0
- package/src/local/client/clientPrelude.ts +112 -0
- package/src/local/client/consoleLogger.ts +60 -0
- package/src/local/client/createInternalComponents.ts +163 -0
- package/src/local/client/createModelProxy.ts +1390 -0
- package/src/local/client/modelRegistration.ts +350 -0
- package/src/local/client/options.ts +526 -0
- package/src/local/client/reactiveEngine.ts +935 -0
- package/src/local/client/resourceTypes.ts +37 -0
- package/src/local/client/schemaConfig.ts +194 -0
- package/src/local/client/storeCluster.ts +172 -0
- package/src/local/client/storeLifecycle.ts +343 -0
- package/src/local/client/validateAbloOptions.ts +95 -0
- package/src/local/client/wsMutationExecutor.ts +110 -0
- package/src/local/context.ts +96 -0
- package/src/local/coordination/ClaimLog.ts +39 -0
- package/src/local/interfaces/index.ts +468 -0
- package/src/local/localModelContract.ts +15 -0
- package/src/local/logPosition.ts +84 -0
- package/src/local/mutationPersistence.ts +7 -0
- package/src/local/mutators/RecordingMutation.ts +222 -0
- package/src/local/mutators/Transaction.ts +97 -0
- package/src/local/mutators/UndoManager.ts +741 -0
- package/src/local/mutators/defineMutators.ts +76 -0
- package/src/local/mutators/inverseOp.ts +83 -0
- package/src/local/mutators/mutateActions.ts +167 -0
- package/src/local/mutators/readerActions.ts +99 -0
- package/src/local/mutators/undoApply.ts +141 -0
- package/src/local/persistence.ts +16 -0
- package/src/local/query/QueryProcessor.ts +347 -0
- package/src/local/query/client.ts +197 -0
- package/src/local/query/types.ts +102 -0
- package/src/local/schema/serialize.ts +1 -0
- package/src/local/store/queryApi.ts +56 -0
- package/src/local/storeContract.ts +146 -0
- package/src/local/stores/DatabaseManager.ts +507 -0
- package/src/local/stores/ObjectStore.ts +449 -0
- package/src/local/stores/ObjectStoreContract.ts +48 -0
- package/src/local/stores/StoreManager.ts +388 -0
- package/src/local/stores/SyncActionStore.ts +579 -0
- package/src/local/stores/openIDBWithTimeout.ts +195 -0
- package/src/local/stores/persistenceCleanup.ts +43 -0
- package/src/local/stores/persistenceIdentity.ts +83 -0
- package/src/local/stores/syncAction.ts +21 -0
- package/src/local/stores/v1PersistenceDeletion.ts +21 -0
- package/src/local/sync/BootstrapFetcher.ts +1224 -0
- package/src/local/sync/ConnectionManager.ts +15 -0
- package/src/local/sync/OnDemandLoader.ts +927 -0
- package/src/local/sync/SubscriptionManager.ts +300 -0
- package/src/local/sync/SyncWebSocket.ts +584 -0
- package/src/local/sync/bootstrapApply.ts +130 -0
- package/src/local/sync/commitFrames.ts +16 -0
- package/src/local/sync/connectionManagerLifecycle.ts +158 -0
- package/src/local/sync/contextPorts.ts +37 -0
- package/src/local/sync/createClaimStream.ts +668 -0
- package/src/local/sync/createSnapshot.ts +160 -0
- package/src/local/sync/credentialLifecycle.ts +18 -0
- package/src/local/sync/deltaPipeline.ts +473 -0
- package/src/local/sync/groupChange.ts +343 -0
- package/src/local/sync/initialize.ts +205 -0
- package/src/local/sync/participants.ts +564 -0
- package/src/local/sync/persistedPrefix.ts +27 -0
- package/src/local/sync/reconnect.ts +88 -0
- package/src/local/sync/schemaDrift.ts +86 -0
- package/src/local/sync/schemas.ts +118 -0
- package/src/local/sync/socketEventWiring.ts +196 -0
- package/src/local/sync/syncCursor.ts +62 -0
- package/src/local/sync/syncPlan.ts +89 -0
- package/src/local/sync/terminalSessionLifecycle.ts +66 -0
- package/src/local/sync/wsFrameHandlers.ts +20 -0
- package/src/local/transactions/databaseCommitOutbox.ts +33 -0
- package/src/local/transactions/localMutation.ts +58 -0
- package/src/local/transactions/mutations/MutationQueue.ts +1998 -0
- package/src/local/transactions/mutations/MutationStore.ts +65 -0
- package/src/local/transactions/mutations/UnconfirmedWrites.ts +133 -0
- package/src/local/transactions/mutations/batchProcessing.ts +469 -0
- package/src/local/transactions/mutations/coalesceRules.ts +192 -0
- package/src/local/transactions/mutations/commitApi.ts +97 -0
- package/src/local/transactions/mutations/commitLane.ts +191 -0
- package/src/local/transactions/mutations/commitLatency.ts +164 -0
- package/src/local/transactions/mutations/commitPayload.ts +281 -0
- package/src/local/transactions/mutations/commitTransport.ts +174 -0
- package/src/local/transactions/mutations/deltaConfirmation.ts +298 -0
- package/src/local/transactions/mutations/durableCommitRestore.ts +128 -0
- package/src/local/transactions/mutations/durableWriteStore.ts +21 -0
- package/src/local/transactions/mutations/executionSelection.ts +42 -0
- package/src/local/transactions/mutations/failureHandling.ts +154 -0
- package/src/local/transactions/mutations/failurePolicy.ts +61 -0
- package/src/local/transactions/mutations/localMutation.ts +135 -0
- package/src/local/transactions/mutations/modelOperations.ts +210 -0
- package/src/local/transactions/mutations/mutationPersistence.ts +231 -0
- package/src/local/transactions/mutations/pendingDrain.ts +160 -0
- package/src/local/transactions/mutations/processingScheduler.ts +35 -0
- package/src/local/transactions/mutations/queueCoalescing.ts +45 -0
- package/src/local/transactions/mutations/replayValidation.ts +192 -0
- package/src/local/transactions/reconnectDrain.ts +24 -0
- package/src/local/utils/mobxSetup.ts +388 -0
- package/src/local/views/QueryView.ts +311 -0
- package/src/local/views/ViewRegistry.ts +61 -0
- package/src/local/views/incrementalView.ts +92 -0
- package/src/plugin.ts +396 -0
- package/src/presenceStream.ts +279 -0
- package/src/react/AbloProvider.tsx +744 -0
- package/src/react/ClientSideSuspense.tsx +57 -0
- package/src/react/DefaultFallback.tsx +60 -0
- package/src/react/context.ts +89 -0
- package/src/react/createAbloReact.ts +116 -0
- package/src/react/internalContext.ts +38 -0
- package/src/react/useAblo.ts +280 -0
- package/src/react/useCurrentUserId.ts +17 -0
- package/src/react/useErrorListener.ts +22 -0
- package/src/react/useMutationFailureListener.ts +34 -0
- package/src/react/useMutators.ts +184 -0
- package/src/react/useSyncStatus.ts +42 -0
- package/src/react/useUndoScope.ts +143 -0
- package/src/react.ts +69 -0
- package/src/reactRuntime.ts +10 -0
- package/src/surface.ts +106 -0
- package/src/useReactive.ts +51 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MutationInput, QueuedMutation } from './commitPayload.js';
|
|
2
|
+
import { asTransportError, extractStatusCode } from './commitPayload.js';
|
|
3
|
+
import { mergeUpdateData } from './coalesceRules.js';
|
|
4
|
+
import { AbloConnectionError, errorCodeSpec } from '@abloatai/transaction/errors';
|
|
5
|
+
import type { MutationQueueConfig } from './MutationQueue.js';
|
|
6
|
+
import type { MutationStore } from './MutationStore.js';
|
|
7
|
+
|
|
8
|
+
export function isPermanentError(error: Error): boolean {
|
|
9
|
+
if (error instanceof AbloConnectionError) return false;
|
|
10
|
+
const code = (error as { code?: string }).code;
|
|
11
|
+
const spec = code ? errorCodeSpec(code) : undefined;
|
|
12
|
+
if (spec) return !spec.retryable;
|
|
13
|
+
const message = error.message.toLowerCase();
|
|
14
|
+
if (['failed to fetch', 'network error', 'networkerror', 'connection refused', 'connection reset', 'timeout', 'econnrefused', 'econnreset', 'etimedout', 'socket hang up'].some((value) => message.includes(value))) return false;
|
|
15
|
+
const status = extractStatusCode(error);
|
|
16
|
+
if (status !== undefined) return status < 500 && status !== 429;
|
|
17
|
+
return !(asTransportError(error).response?.errors?.length);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isDefinitiveRejection(error: Error): boolean {
|
|
21
|
+
const code = (error as { code?: string }).code;
|
|
22
|
+
const spec = code ? errorCodeSpec(code) : undefined;
|
|
23
|
+
if (spec) return !spec.retryable;
|
|
24
|
+
const status = extractStatusCode(error);
|
|
25
|
+
return status !== undefined && status >= 400 && status < 500 && status !== 429;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ConflictPolicyContext {
|
|
29
|
+
readonly config: Pick<MutationQueueConfig, 'conflictResolution'>;
|
|
30
|
+
readonly store: MutationStore;
|
|
31
|
+
readonly rollbackOptimistic: (transaction: QueuedMutation, reason: string) => Promise<void>;
|
|
32
|
+
readonly mergeData: (local: MutationInput | undefined, remote: MutationInput) => MutationInput;
|
|
33
|
+
readonly enqueue: (transaction: QueuedMutation) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function handleConflict(
|
|
37
|
+
ctx: ConflictPolicyContext,
|
|
38
|
+
transaction: QueuedMutation,
|
|
39
|
+
serverData: MutationInput,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
const { strategy, resolver } = ctx.config.conflictResolution;
|
|
42
|
+
switch (strategy) {
|
|
43
|
+
case 'last-write-wins':
|
|
44
|
+
ctx.store.updateStatus(transaction.id, 'rolled_back');
|
|
45
|
+
await ctx.rollbackOptimistic(transaction, 'conflict_server_wins');
|
|
46
|
+
break;
|
|
47
|
+
case 'merge':
|
|
48
|
+
transaction.data = ctx.mergeData(transaction.data, serverData);
|
|
49
|
+
ctx.enqueue(transaction);
|
|
50
|
+
break;
|
|
51
|
+
case 'reject':
|
|
52
|
+
ctx.enqueue(transaction);
|
|
53
|
+
break;
|
|
54
|
+
case 'custom':
|
|
55
|
+
if (resolver) {
|
|
56
|
+
transaction.data = resolver(transaction.data, serverData);
|
|
57
|
+
ctx.enqueue(transaction);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-first apply and rollback bookkeeping for pending mutations.
|
|
3
|
+
*
|
|
4
|
+
* When a change is written before the server confirms it, these functions
|
|
5
|
+
* record it in a ledger and emit an `optimistic:*` event; separate listeners
|
|
6
|
+
* apply the change to the in-memory data and, on rollback, restore the value
|
|
7
|
+
* it replaced. Each function takes the ledger and a small event emitter rather
|
|
8
|
+
* than any larger object, so the rules have no dependencies of their own and
|
|
9
|
+
* can be tested in isolation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { LocalModel as Model } from '../../localModelContract.js';
|
|
13
|
+
import type { MutationInput, QueuedMutation } from './commitPayload.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One tracked optimistic mutation: the live model plus the value it held
|
|
17
|
+
* before the change, kept so a rollback can restore it.
|
|
18
|
+
*/
|
|
19
|
+
export interface OptimisticUpdateEntry {
|
|
20
|
+
model: Model;
|
|
21
|
+
previousState: MutationInput | null | undefined;
|
|
22
|
+
transaction: QueuedMutation;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The event emitter these functions use to announce each optimistic change and
|
|
27
|
+
* its rollback. You supply the implementation.
|
|
28
|
+
*/
|
|
29
|
+
export interface OptimisticEmitter {
|
|
30
|
+
emit(event: string, payload: unknown): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Materializer capability consumed by settlement; implementation belongs to the local layer. */
|
|
34
|
+
export interface LocalMutationPort {
|
|
35
|
+
readonly updates: Map<string, OptimisticUpdateEntry>;
|
|
36
|
+
applyCreate(model: Model, transaction: QueuedMutation): void;
|
|
37
|
+
applyUpdate(model: Model, transaction: QueuedMutation): void;
|
|
38
|
+
applyDelete(model: Model, transaction: QueuedMutation): void;
|
|
39
|
+
rollback(transaction: QueuedMutation, reason?: string, error?: Error): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createLocalMutationPort(emitter: OptimisticEmitter): LocalMutationPort {
|
|
43
|
+
const updates = new Map<string, OptimisticUpdateEntry>();
|
|
44
|
+
return {
|
|
45
|
+
updates,
|
|
46
|
+
applyCreate: (model, transaction) => applyOptimisticCreate(updates, emitter, model, transaction),
|
|
47
|
+
applyUpdate: (model, transaction) => applyOptimisticUpdate(updates, emitter, model, transaction),
|
|
48
|
+
applyDelete: (model, transaction) => applyOptimisticDelete(updates, emitter, model, transaction),
|
|
49
|
+
rollback: (transaction, reason, error) => rollbackOptimistic(updates, emitter, transaction, reason, error),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Record an optimistic create and announce it. There is no prior value to
|
|
55
|
+
* restore, so the rollback pre-image is `null`.
|
|
56
|
+
*/
|
|
57
|
+
export function applyOptimisticCreate(
|
|
58
|
+
optimisticUpdates: Map<string, OptimisticUpdateEntry>,
|
|
59
|
+
emitter: OptimisticEmitter,
|
|
60
|
+
model: Model,
|
|
61
|
+
transaction: QueuedMutation,
|
|
62
|
+
): void {
|
|
63
|
+
optimisticUpdates.set(transaction.id, {
|
|
64
|
+
model,
|
|
65
|
+
previousState: null,
|
|
66
|
+
transaction,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
emitter.emit('optimistic:create', { model, transaction });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Record an optimistic update and announce it, keeping the row's prior value so
|
|
74
|
+
* a rollback can put it back.
|
|
75
|
+
*/
|
|
76
|
+
export function applyOptimisticUpdate(
|
|
77
|
+
optimisticUpdates: Map<string, OptimisticUpdateEntry>,
|
|
78
|
+
emitter: OptimisticEmitter,
|
|
79
|
+
model: Model,
|
|
80
|
+
transaction: QueuedMutation,
|
|
81
|
+
): void {
|
|
82
|
+
optimisticUpdates.set(transaction.id, {
|
|
83
|
+
model,
|
|
84
|
+
previousState: transaction.previousData,
|
|
85
|
+
transaction,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
emitter.emit('optimistic:update', { model, transaction });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Record an optimistic delete and announce it, keeping the deleted row so a
|
|
93
|
+
* rollback can restore it.
|
|
94
|
+
*/
|
|
95
|
+
export function applyOptimisticDelete(
|
|
96
|
+
optimisticUpdates: Map<string, OptimisticUpdateEntry>,
|
|
97
|
+
emitter: OptimisticEmitter,
|
|
98
|
+
model: Model,
|
|
99
|
+
transaction: QueuedMutation,
|
|
100
|
+
): void {
|
|
101
|
+
optimisticUpdates.set(transaction.id, {
|
|
102
|
+
model,
|
|
103
|
+
previousState: transaction.previousData,
|
|
104
|
+
transaction,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
emitter.emit('optimistic:delete', { model, transaction });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Undo an optimistic mutation by its transaction id: emit `optimistic:rollback`
|
|
112
|
+
* with the saved pre-image so listeners can restore the prior value, then drop
|
|
113
|
+
* the ledger entry. Does nothing if the transaction was never tracked.
|
|
114
|
+
*/
|
|
115
|
+
export function rollbackOptimistic(
|
|
116
|
+
optimisticUpdates: Map<string, OptimisticUpdateEntry>,
|
|
117
|
+
emitter: OptimisticEmitter,
|
|
118
|
+
transaction: QueuedMutation,
|
|
119
|
+
reason?: string,
|
|
120
|
+
error?: Error,
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
const optimistic = optimisticUpdates.get(transaction.id);
|
|
123
|
+
if (!optimistic) return Promise.resolve();
|
|
124
|
+
|
|
125
|
+
emitter.emit('optimistic:rollback', {
|
|
126
|
+
model: optimistic.model,
|
|
127
|
+
previousState: optimistic.previousState,
|
|
128
|
+
transaction,
|
|
129
|
+
reason: reason ?? 'unknown',
|
|
130
|
+
error,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
optimisticUpdates.delete(transaction.id);
|
|
134
|
+
return Promise.resolve();
|
|
135
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { WriteOptions } from '../../interfaces/index.js';
|
|
2
|
+
import type { LocalModel as Model } from '../../localModelContract.js';
|
|
3
|
+
import type { QueuedMutation, UserContext, MutationInput } from './commitPayload.js';
|
|
4
|
+
|
|
5
|
+
export interface ModelMutationContext {
|
|
6
|
+
readonly enableOptimistic: boolean;
|
|
7
|
+
readonly persistenceReady: boolean;
|
|
8
|
+
readonly assertDurableReplayOpen: () => void;
|
|
9
|
+
readonly generateId: () => string;
|
|
10
|
+
readonly normalizeModelKey: (modelName: string) => string;
|
|
11
|
+
readonly computePriorityScore: (type: QueuedMutation['type'], modelName: string) => number;
|
|
12
|
+
readonly extractCreateData: (model: Model) => Record<string, unknown>;
|
|
13
|
+
readonly extractUpdateData: (model: Model) => MutationInput;
|
|
14
|
+
readonly extractPreviousData: (model: Model, input: MutationInput) => Record<string, unknown> | null;
|
|
15
|
+
readonly mapChangesToInput: (modelName: string, changes: Record<string, unknown>) => MutationInput;
|
|
16
|
+
readonly isReorderPayload: (input: MutationInput) => boolean;
|
|
17
|
+
readonly attachConfirmation: (transaction: QueuedMutation) => void;
|
|
18
|
+
readonly add: (transaction: QueuedMutation) => void;
|
|
19
|
+
readonly applyOptimisticCreate: (model: Model, transaction: QueuedMutation) => void;
|
|
20
|
+
readonly applyOptimisticUpdate: (model: Model, transaction: QueuedMutation) => void;
|
|
21
|
+
readonly applyOptimisticDelete: (model: Model, transaction: QueuedMutation) => void;
|
|
22
|
+
readonly takeUnsentCreateForModel: (modelName: string, modelId: string) => QueuedMutation | undefined;
|
|
23
|
+
readonly cancelUnsentCreateForDelete: (transaction: QueuedMutation) => Promise<void>;
|
|
24
|
+
readonly completeLocalDelete: (model: Model, context: UserContext, writeOptions?: WriteOptions, sourceMutationIds?: string[]) => QueuedMutation | Promise<QueuedMutation>;
|
|
25
|
+
readonly cancelTransactionsForModel: (modelId: string, type: 'update') => QueuedMutation[];
|
|
26
|
+
readonly pendingMergeByModel: Map<string, { data: MutationInput; sourceMutationIds: string[] }>;
|
|
27
|
+
readonly inFlightByModel: Set<string>;
|
|
28
|
+
readonly findCreateBarrierForDelete: (modelName: string, modelId: string) => QueuedMutation | undefined;
|
|
29
|
+
readonly deferDeleteUntilCreateSettles: (create: QueuedMutation, transaction: QueuedMutation) => void;
|
|
30
|
+
readonly logger: { debug: (message: string, fields?: Record<string, string>) => void };
|
|
31
|
+
readonly persistAndStage: (transaction: QueuedMutation, modelData: Record<string, unknown>) => Promise<void>;
|
|
32
|
+
readonly persistQueuedTransaction: (transaction: QueuedMutation, modelData: Record<string, unknown>) => Promise<void>;
|
|
33
|
+
readonly stageTransaction: (transaction: QueuedMutation) => void;
|
|
34
|
+
readonly emit: (event: string, payload: object) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function remove(
|
|
38
|
+
ctx: ModelMutationContext,
|
|
39
|
+
model: Model,
|
|
40
|
+
context: UserContext,
|
|
41
|
+
writeOptions?: WriteOptions,
|
|
42
|
+
sourceMutationId?: string,
|
|
43
|
+
): Promise<QueuedMutation> {
|
|
44
|
+
ctx.assertDurableReplayOpen();
|
|
45
|
+
const modelName = model.getModelName();
|
|
46
|
+
if (modelName === 'Activity') {
|
|
47
|
+
ctx.logger.debug('MutationQueue.delete() skipping Activity deletion - permanent audit records', { modelId: model.id });
|
|
48
|
+
const transaction: QueuedMutation = {
|
|
49
|
+
id: ctx.generateId(), type: 'delete', modelName, modelId: model.id,
|
|
50
|
+
modelKey: ctx.normalizeModelKey(modelName), priorityScore: ctx.computePriorityScore('delete', modelName),
|
|
51
|
+
previousData: model.toJSON(), context, status: 'completed', createdAt: Date.now(), attempts: 0,
|
|
52
|
+
priority: 'high', writeOptions, localOnly: true, confirmation: Promise.resolve(),
|
|
53
|
+
...(sourceMutationId ? { sourceMutationIds: [sourceMutationId] } : {}),
|
|
54
|
+
};
|
|
55
|
+
if (ctx.persistenceReady) transaction.sourceMutationIds ??= [transaction.id];
|
|
56
|
+
if (ctx.enableOptimistic) ctx.applyOptimisticDelete(model, transaction);
|
|
57
|
+
ctx.emit('transaction:created', transaction);
|
|
58
|
+
ctx.emit('transaction:completed', transaction);
|
|
59
|
+
return transaction;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const unsentCreate = ctx.takeUnsentCreateForModel(modelName, model.id);
|
|
63
|
+
if (unsentCreate) {
|
|
64
|
+
await ctx.cancelUnsentCreateForDelete(unsentCreate);
|
|
65
|
+
return ctx.completeLocalDelete(model, context, writeOptions, [
|
|
66
|
+
...(unsentCreate.sourceMutationIds ?? []),
|
|
67
|
+
...(sourceMutationId ? [sourceMutationId] : []),
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const transaction: QueuedMutation = {
|
|
72
|
+
id: ctx.generateId(), type: 'delete', modelName, modelId: model.id,
|
|
73
|
+
modelKey: ctx.normalizeModelKey(modelName), priorityScore: ctx.computePriorityScore('delete', modelName),
|
|
74
|
+
previousData: model.toJSON(), context, status: 'pending', createdAt: Date.now(), attempts: 0,
|
|
75
|
+
priority: 'high', writeOptions,
|
|
76
|
+
...(sourceMutationId ? { sourceMutationIds: [sourceMutationId] } : {}),
|
|
77
|
+
};
|
|
78
|
+
if (ctx.persistenceReady) transaction.sourceMutationIds ??= [transaction.id];
|
|
79
|
+
ctx.attachConfirmation(transaction);
|
|
80
|
+
ctx.add(transaction);
|
|
81
|
+
const canceledUpdates = ctx.cancelTransactionsForModel(model.id, 'update');
|
|
82
|
+
const key = `${modelName}:${model.id}`;
|
|
83
|
+
const pendingMerge = ctx.pendingMergeByModel.get(key);
|
|
84
|
+
transaction.sourceMutationIds = [...new Set([
|
|
85
|
+
...(transaction.sourceMutationIds ?? []),
|
|
86
|
+
...canceledUpdates.flatMap((candidate) => candidate.commitEnvelope ? [] : (candidate.sourceMutationIds ?? [])),
|
|
87
|
+
...(pendingMerge?.sourceMutationIds ?? []),
|
|
88
|
+
])];
|
|
89
|
+
ctx.pendingMergeByModel.delete(key);
|
|
90
|
+
ctx.inFlightByModel.delete(key);
|
|
91
|
+
if (ctx.enableOptimistic) ctx.applyOptimisticDelete(model, transaction);
|
|
92
|
+
await ctx.persistQueuedTransaction(transaction, model.toJSON());
|
|
93
|
+
const createBarrier = ctx.findCreateBarrierForDelete(modelName, model.id);
|
|
94
|
+
if (createBarrier) ctx.deferDeleteUntilCreateSettles(createBarrier, transaction);
|
|
95
|
+
else ctx.stageTransaction(transaction);
|
|
96
|
+
ctx.emit('transaction:created', transaction);
|
|
97
|
+
return transaction;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function stage(ctx: ModelMutationContext, transaction: QueuedMutation, model: Model): Promise<void> | void {
|
|
101
|
+
return ctx.persistenceReady
|
|
102
|
+
? ctx.persistAndStage(transaction, model.toJSON())
|
|
103
|
+
: ctx.stageTransaction(transaction);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function create(
|
|
107
|
+
ctx: ModelMutationContext,
|
|
108
|
+
model: Model,
|
|
109
|
+
context: UserContext,
|
|
110
|
+
writeOptions?: WriteOptions,
|
|
111
|
+
sourceMutationId?: string,
|
|
112
|
+
): Promise<QueuedMutation> | QueuedMutation {
|
|
113
|
+
ctx.assertDurableReplayOpen();
|
|
114
|
+
const modelName = model.getModelName();
|
|
115
|
+
const transaction: QueuedMutation = {
|
|
116
|
+
id: ctx.generateId(), type: 'create', modelName, modelId: model.id,
|
|
117
|
+
modelKey: ctx.normalizeModelKey(modelName),
|
|
118
|
+
priorityScore: ctx.computePriorityScore('create', modelName),
|
|
119
|
+
data: ctx.extractCreateData(model), previousData: null, context,
|
|
120
|
+
status: 'pending', createdAt: Date.now(), attempts: 0, priority: 'normal', writeOptions,
|
|
121
|
+
...(sourceMutationId ? { sourceMutationIds: [sourceMutationId] } : {}),
|
|
122
|
+
};
|
|
123
|
+
if (ctx.persistenceReady) transaction.sourceMutationIds ??= [transaction.id];
|
|
124
|
+
ctx.attachConfirmation(transaction);
|
|
125
|
+
ctx.add(transaction);
|
|
126
|
+
if (ctx.enableOptimistic) ctx.applyOptimisticCreate(model, transaction);
|
|
127
|
+
const staged = stage(ctx, transaction, model);
|
|
128
|
+
if (staged) return staged.then(() => { ctx.emit('transaction:created', transaction); return transaction; });
|
|
129
|
+
ctx.emit('transaction:created', transaction);
|
|
130
|
+
return transaction;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function update(
|
|
134
|
+
ctx: ModelMutationContext,
|
|
135
|
+
model: Model,
|
|
136
|
+
context: UserContext,
|
|
137
|
+
precomputedChanges?: Record<string, unknown>,
|
|
138
|
+
writeOptions?: WriteOptions,
|
|
139
|
+
sourceMutationId?: string,
|
|
140
|
+
): Promise<QueuedMutation> | QueuedMutation {
|
|
141
|
+
ctx.assertDurableReplayOpen();
|
|
142
|
+
const modelName = model.getModelName();
|
|
143
|
+
const input = precomputedChanges
|
|
144
|
+
? ctx.mapChangesToInput(modelName, precomputedChanges)
|
|
145
|
+
: ctx.extractUpdateData(model);
|
|
146
|
+
const previousData = ctx.extractPreviousData(model, input);
|
|
147
|
+
model.consumeModifiedFields(Object.keys(input));
|
|
148
|
+
const transaction: QueuedMutation = {
|
|
149
|
+
id: ctx.generateId(), type: 'update', modelName, modelId: model.id,
|
|
150
|
+
modelKey: ctx.normalizeModelKey(modelName),
|
|
151
|
+
priorityScore: ctx.computePriorityScore('update', modelName),
|
|
152
|
+
data: input, previousData, context, status: 'pending', createdAt: Date.now(), attempts: 0,
|
|
153
|
+
priority: ctx.isReorderPayload(input) ? 'high' : 'normal', writeOptions,
|
|
154
|
+
...(sourceMutationId ? { sourceMutationIds: [sourceMutationId] } : {}),
|
|
155
|
+
};
|
|
156
|
+
if (ctx.persistenceReady) transaction.sourceMutationIds ??= [transaction.id];
|
|
157
|
+
ctx.attachConfirmation(transaction);
|
|
158
|
+
ctx.add(transaction);
|
|
159
|
+
if (ctx.enableOptimistic) ctx.applyOptimisticUpdate(model, transaction);
|
|
160
|
+
const staged = stage(ctx, transaction, model);
|
|
161
|
+
if (staged) return staged.then(() => { ctx.emit('transaction:created', transaction); return transaction; });
|
|
162
|
+
ctx.emit('transaction:created', transaction);
|
|
163
|
+
return transaction;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function archive(
|
|
167
|
+
ctx: ModelMutationContext,
|
|
168
|
+
model: Model,
|
|
169
|
+
context: UserContext,
|
|
170
|
+
writeOptions?: WriteOptions,
|
|
171
|
+
sourceMutationId?: string,
|
|
172
|
+
): Promise<QueuedMutation> | QueuedMutation {
|
|
173
|
+
return simpleStatusMutation(ctx, 'archive', model, context, writeOptions, sourceMutationId);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function unarchive(
|
|
177
|
+
ctx: ModelMutationContext,
|
|
178
|
+
model: Model,
|
|
179
|
+
context: UserContext,
|
|
180
|
+
): Promise<QueuedMutation> | QueuedMutation {
|
|
181
|
+
return simpleStatusMutation(ctx, 'unarchive', model, context);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function simpleStatusMutation(
|
|
185
|
+
ctx: ModelMutationContext,
|
|
186
|
+
type: 'archive' | 'unarchive',
|
|
187
|
+
model: Model,
|
|
188
|
+
context: UserContext,
|
|
189
|
+
writeOptions?: WriteOptions,
|
|
190
|
+
sourceMutationId?: string,
|
|
191
|
+
): Promise<QueuedMutation> | QueuedMutation {
|
|
192
|
+
ctx.assertDurableReplayOpen();
|
|
193
|
+
const modelName = model.getModelName();
|
|
194
|
+
const transaction: QueuedMutation = {
|
|
195
|
+
id: ctx.generateId(), type, modelName, modelId: model.id,
|
|
196
|
+
modelKey: ctx.normalizeModelKey(modelName),
|
|
197
|
+
priorityScore: ctx.computePriorityScore(type, modelName),
|
|
198
|
+
previousData: model.toJSON(), context, status: 'pending', createdAt: Date.now(), attempts: 0,
|
|
199
|
+
priority: 'normal',
|
|
200
|
+
...(writeOptions ? { writeOptions } : {}),
|
|
201
|
+
...(sourceMutationId ? { sourceMutationIds: [sourceMutationId] } : {}),
|
|
202
|
+
};
|
|
203
|
+
if (ctx.persistenceReady) transaction.sourceMutationIds ??= [transaction.id];
|
|
204
|
+
ctx.attachConfirmation(transaction);
|
|
205
|
+
ctx.add(transaction);
|
|
206
|
+
const staged = stage(ctx, transaction, model);
|
|
207
|
+
if (staged) return staged.then(() => { ctx.emit('transaction:created', transaction); return transaction; });
|
|
208
|
+
ctx.emit('transaction:created', transaction);
|
|
209
|
+
return transaction;
|
|
210
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
2
|
+
import type { MutationPersistencePort } from '../../mutationPersistence.js';
|
|
3
|
+
import type { MutationQueueConfig } from './MutationQueue.js';
|
|
4
|
+
import type { CommitOutboxScope } from '@abloatai/transaction/transactions/settlement/commitEnvelope';
|
|
5
|
+
import type { QueuedMutation } from './commitPayload.js';
|
|
6
|
+
import { MutationStore } from './MutationStore.js';
|
|
7
|
+
import { normalizeModelKey } from './commitPayload.js';
|
|
8
|
+
import {
|
|
9
|
+
deserializePersistedTransaction,
|
|
10
|
+
legacyPendingMutationRecordSchema,
|
|
11
|
+
pendingMutationRecordId,
|
|
12
|
+
pendingMutationRecordSchema,
|
|
13
|
+
persistedMutationSchema,
|
|
14
|
+
} from './replayValidation.js';
|
|
15
|
+
|
|
16
|
+
export interface MutationPersistenceContext {
|
|
17
|
+
readonly runtime: RuntimeContext;
|
|
18
|
+
readonly persistence: MutationPersistencePort | null;
|
|
19
|
+
readonly commitOutboxScope: CommitOutboxScope | null;
|
|
20
|
+
readonly config: Pick<MutationQueueConfig, 'enablePersistence'>;
|
|
21
|
+
readonly store: MutationStore;
|
|
22
|
+
readonly enqueue: (transaction: QueuedMutation) => void;
|
|
23
|
+
readonly computePriorityScore: (type: QueuedMutation['type'], modelName: string) => number;
|
|
24
|
+
readonly deserializeTransaction: (data: Parameters<typeof deserializePersistedTransaction>[0]) => QueuedMutation | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function persistQueuedTransaction(
|
|
28
|
+
ctx: MutationPersistenceContext,
|
|
29
|
+
transaction: QueuedMutation,
|
|
30
|
+
modelData?: Record<string, unknown>,
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
if (!ctx.persistence) return;
|
|
33
|
+
const mutationId = transaction.sourceMutationIds?.[0] ?? transaction.id;
|
|
34
|
+
if (modelData && ctx.commitOutboxScope) {
|
|
35
|
+
await ctx.persistence.saveTransaction({
|
|
36
|
+
id: pendingMutationRecordId(mutationId),
|
|
37
|
+
type: 'pending_mutation',
|
|
38
|
+
storageVersion: 2,
|
|
39
|
+
mutation: {
|
|
40
|
+
mutationId,
|
|
41
|
+
type: transaction.type,
|
|
42
|
+
modelData,
|
|
43
|
+
modelName: transaction.modelName,
|
|
44
|
+
timestamp: new Date(transaction.createdAt).toISOString(),
|
|
45
|
+
...(transaction.type === 'update' && transaction.data
|
|
46
|
+
? { capturedChanges: transaction.data }
|
|
47
|
+
: {}),
|
|
48
|
+
...(transaction.writeOptions !== undefined
|
|
49
|
+
? { writeOptions: transaction.writeOptions }
|
|
50
|
+
: {}),
|
|
51
|
+
},
|
|
52
|
+
scope: ctx.commitOutboxScope,
|
|
53
|
+
timestamp: transaction.createdAt,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await ctx.persistence.saveTransaction({
|
|
58
|
+
id: transaction.id,
|
|
59
|
+
type: transaction.type,
|
|
60
|
+
modelName: transaction.modelName,
|
|
61
|
+
modelId: transaction.modelId,
|
|
62
|
+
modelKey: transaction.modelKey,
|
|
63
|
+
...(transaction.data !== undefined ? { data: transaction.data } : {}),
|
|
64
|
+
...(transaction.previousData !== undefined ? { previousData: transaction.previousData } : {}),
|
|
65
|
+
context: transaction.context,
|
|
66
|
+
createdAt: transaction.createdAt,
|
|
67
|
+
...(transaction.writeOptions !== undefined ? { writeOptions: transaction.writeOptions } : {}),
|
|
68
|
+
...(transaction.sourceMutationIds !== undefined
|
|
69
|
+
? { sourceMutationIds: transaction.sourceMutationIds }
|
|
70
|
+
: {}),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
export function removePersistedTransaction(ctx: MutationPersistenceContext, transactionId: string): void {
|
|
76
|
+
const ids = [transactionId, pendingMutationRecordId(transactionId), ...(
|
|
77
|
+
ctx.store.get(transactionId)?.sourceMutationIds ?? []
|
|
78
|
+
).map(pendingMutationRecordId)];
|
|
79
|
+
for (const id of ids) {
|
|
80
|
+
void ctx.persistence?.removeTransaction(id).catch(() => undefined);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export function settlePersistedFailure(ctx: MutationPersistenceContext, transaction: { id?: string; sourceMutationIds?: string[] }): void {
|
|
86
|
+
const sourceIds = new Set(transaction.sourceMutationIds ?? []);
|
|
87
|
+
const queued = transaction.id
|
|
88
|
+
? ctx.store.get(transaction.id)
|
|
89
|
+
: undefined;
|
|
90
|
+
const match = queued ?? ctx.store.getAll().find((candidate) =>
|
|
91
|
+
candidate.id === transaction.id ||
|
|
92
|
+
sourceIds.has(candidate.id) ||
|
|
93
|
+
(candidate.sourceMutationIds ?? []).some((id) => sourceIds.has(id)),
|
|
94
|
+
);
|
|
95
|
+
if (match) {
|
|
96
|
+
ctx.store.updateStatus(match.id, 'failed');
|
|
97
|
+
removePersistedTransaction(ctx, match.id);
|
|
98
|
+
}
|
|
99
|
+
for (const sourceId of sourceIds) removePersistedTransaction(ctx, sourceId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
export function deserializeLegacyPendingMutation(
|
|
104
|
+
ctx: MutationPersistenceContext,
|
|
105
|
+
row: object,
|
|
106
|
+
fallbackMutationId?: string,
|
|
107
|
+
): QueuedMutation | null {
|
|
108
|
+
const rowType = (row as { type?: string }).type;
|
|
109
|
+
const parsed = persistedMutationSchema.safeParse(
|
|
110
|
+
rowType === 'pending_mutation'
|
|
111
|
+
? (pendingMutationRecordSchema.safeParse(row).success
|
|
112
|
+
? pendingMutationRecordSchema.parse(row).mutation
|
|
113
|
+
: legacyPendingMutationRecordSchema.safeParse(row).success
|
|
114
|
+
? legacyPendingMutationRecordSchema.parse(row).mutation
|
|
115
|
+
: null)
|
|
116
|
+
: row,
|
|
117
|
+
);
|
|
118
|
+
if (!parsed.success) return null;
|
|
119
|
+
const mutation = parsed.data;
|
|
120
|
+
const writtenAt = Date.parse(mutation.timestamp);
|
|
121
|
+
if (!Number.isFinite(writtenAt) || Date.now() - writtenAt >= 23 * 60 * 60 * 1000) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const mutationId = mutation.mutationId ?? fallbackMutationId;
|
|
125
|
+
const modelId = mutation.modelData.id;
|
|
126
|
+
if (!mutationId || typeof modelId !== 'string') return null;
|
|
127
|
+
const scope = ctx.commitOutboxScope;
|
|
128
|
+
if (!scope) return null;
|
|
129
|
+
const type = mutation.type;
|
|
130
|
+
return {
|
|
131
|
+
id: mutationId,
|
|
132
|
+
type,
|
|
133
|
+
modelName: mutation.modelName,
|
|
134
|
+
modelId,
|
|
135
|
+
modelKey: normalizeModelKey(mutation.modelName),
|
|
136
|
+
...(type === 'create'
|
|
137
|
+
? { data: mutation.modelData }
|
|
138
|
+
: type === 'update'
|
|
139
|
+
? { data: mutation.capturedChanges ?? mutation.modelData }
|
|
140
|
+
: {}),
|
|
141
|
+
...(type === 'delete' || type === 'archive'
|
|
142
|
+
? { previousData: mutation.modelData }
|
|
143
|
+
: { previousData: null }),
|
|
144
|
+
context: {
|
|
145
|
+
userId: scope.participantId,
|
|
146
|
+
organizationId: scope.organizationId,
|
|
147
|
+
},
|
|
148
|
+
status: 'pending',
|
|
149
|
+
createdAt: Date.parse(mutation.timestamp) || Date.now(),
|
|
150
|
+
attempts: 0,
|
|
151
|
+
priority: 'normal',
|
|
152
|
+
priorityScore: ctx.computePriorityScore(type, mutation.modelName),
|
|
153
|
+
...(mutation.writeOptions !== undefined ? { writeOptions: mutation.writeOptions } : {}),
|
|
154
|
+
sourceMutationIds: [mutationId],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
export async function loadPersistedTransactions(
|
|
160
|
+
ctx: MutationPersistenceContext,
|
|
161
|
+
persistence: MutationPersistencePort,
|
|
162
|
+
sealedMutationIds: ReadonlySet<string> = new Set(),
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
if (!ctx.config.enablePersistence) return;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const persisted = await persistence.getPersistedTransactions();
|
|
168
|
+
|
|
169
|
+
for (const data of persisted) {
|
|
170
|
+
if (data.type === 'pending_mutation') {
|
|
171
|
+
const legacy = deserializeLegacyPendingMutation(ctx, data);
|
|
172
|
+
if (legacy) {
|
|
173
|
+
const v1 = legacyPendingMutationRecordSchema.safeParse(data);
|
|
174
|
+
if (v1.success && ctx.commitOutboxScope) {
|
|
175
|
+
await persistence.saveTransaction({
|
|
176
|
+
...data,
|
|
177
|
+
storageVersion: 2,
|
|
178
|
+
scope: ctx.commitOutboxScope,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
legacy.id = String(data.id);
|
|
182
|
+
ctx.store.add(legacy);
|
|
183
|
+
ctx.enqueue(legacy);
|
|
184
|
+
} else {
|
|
185
|
+
ctx.runtime.logger.debug('[SyncClient] Dropping malformed persisted mutation', {
|
|
186
|
+
rowId: data.id,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (data.id === 'mutation-queue' && Array.isArray(data.mutations)) {
|
|
192
|
+
if (data.mutations.length === 0) continue;
|
|
193
|
+
let migrated = true;
|
|
194
|
+
for (const [index, mutation] of data.mutations.entries()) {
|
|
195
|
+
if (typeof mutation !== 'object' || mutation === null) {
|
|
196
|
+
migrated = false;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const transaction = deserializeLegacyPendingMutation(ctx,
|
|
200
|
+
mutation,
|
|
201
|
+
`legacy_mutation_${index}`,
|
|
202
|
+
);
|
|
203
|
+
if (!transaction) {
|
|
204
|
+
migrated = false;
|
|
205
|
+
ctx.runtime.logger.debug('[SyncClient] Dropping malformed persisted mutation', {
|
|
206
|
+
rowId: `mutation-queue:${index}`,
|
|
207
|
+
});
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
await persistQueuedTransaction(ctx, transaction);
|
|
211
|
+
ctx.store.add(transaction);
|
|
212
|
+
ctx.enqueue(transaction);
|
|
213
|
+
}
|
|
214
|
+
if (migrated) await persistence.removeTransaction(data.id);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const transaction = ctx.deserializeTransaction(data);
|
|
218
|
+
if (!transaction) continue;
|
|
219
|
+
if ((transaction.sourceMutationIds ?? []).some((id) => sealedMutationIds.has(id))) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
ctx.store.add(transaction);
|
|
223
|
+
ctx.enqueue(transaction);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
ctx.runtime.observability.captureMutationFailure({
|
|
227
|
+
context: 'load-persisted-transactions',
|
|
228
|
+
error: error instanceof Error ? error : String(error),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|