@abloatai/ablo 0.34.1 → 0.36.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/AGENTS.md +2 -1
- package/CHANGELOG.md +758 -5
- package/README.md +56 -502
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +176 -48
- package/dist/BaseSyncedStore.js +346 -214
- package/dist/Database.d.ts +17 -44
- package/dist/Database.js +96 -79
- package/dist/InstanceCache.d.ts +31 -6
- package/dist/InstanceCache.js +65 -30
- package/dist/LazyReferenceCollection.d.ts +3 -3
- package/dist/LazyReferenceCollection.js +4 -4
- package/dist/Model.d.ts +23 -13
- package/dist/Model.js +27 -17
- package/dist/ModelRegistry.d.ts +8 -4
- package/dist/ModelRegistry.js +20 -18
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
- package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
- package/dist/SyncClient.d.ts +47 -47
- package/dist/SyncClient.js +215 -156
- package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
- package/dist/ai-sdk/coordinatedTool.js +1 -1
- package/dist/ai-sdk/coordinationContext.d.ts +2 -2
- package/dist/ai-sdk/coordinationContext.js +1 -1
- package/dist/ai-sdk/wrap.d.ts +3 -3
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/index.d.ts +1 -156
- package/dist/auth/index.js +8 -301
- package/dist/client/Ablo.d.ts +42 -287
- package/dist/client/Ablo.js +129 -963
- package/dist/client/abloClient.d.ts +309 -0
- package/dist/client/abloClient.js +13 -0
- package/dist/client/clientPrelude.d.ts +52 -0
- package/dist/client/clientPrelude.js +60 -0
- package/dist/client/consoleLogger.d.ts +2 -2
- package/dist/client/coreClient.d.ts +60 -0
- package/dist/client/coreClient.js +118 -0
- package/dist/client/createInternalComponents.d.ts +8 -4
- package/dist/client/createInternalComponents.js +17 -10
- package/dist/client/createModelProxy.d.ts +98 -373
- package/dist/client/createModelProxy.js +233 -139
- package/dist/client/humans.d.ts +69 -0
- package/dist/client/humans.js +78 -0
- package/dist/client/modelRegistration.d.ts +1 -1
- package/dist/client/modelRegistration.js +9 -9
- package/dist/client/options.d.ts +73 -17
- package/dist/client/reactiveEngine.d.ts +53 -0
- package/dist/client/reactiveEngine.js +688 -0
- package/dist/client/resourceTypes.d.ts +9 -250
- package/dist/client/resourceTypes.js +8 -5
- package/dist/client/schemaConfig.d.ts +4 -4
- package/dist/client/schemaConfig.js +6 -2
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/client/validateAbloOptions.d.ts +3 -2
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +3 -3
- package/dist/client/wsMutationExecutor.js +3 -3
- package/dist/context.d.ts +22 -9
- package/dist/context.js +33 -9
- package/dist/coordination/ClaimLog.d.ts +26 -0
- package/dist/coordination/ClaimLog.js +32 -0
- package/dist/coordination/index.d.ts +1 -15
- package/dist/coordination/index.js +8 -31
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +2 -2
- package/dist/docs/catalog.d.ts +72 -0
- package/dist/docs/catalog.js +230 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +10 -0
- package/dist/environment.d.ts +1 -40
- package/dist/environment.js +8 -37
- package/dist/index.d.ts +44 -36
- package/dist/index.js +30 -22
- package/dist/interfaces/index.d.ts +44 -134
- package/dist/keys/index.d.ts +1 -77
- package/dist/keys/index.js +8 -190
- package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
- package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
- package/dist/mutators/Transaction.d.ts +1 -1
- package/dist/mutators/Transaction.js +1 -1
- package/dist/mutators/UndoManager.d.ts +6 -6
- package/dist/mutators/UndoManager.js +5 -5
- package/dist/mutators/defineMutators.d.ts +3 -3
- package/dist/mutators/defineMutators.js +1 -1
- package/dist/mutators/inverseOp.js +2 -2
- package/dist/mutators/mutateActions.d.ts +3 -3
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +1 -1
- package/dist/mutators/undoApply.js +1 -1
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +1 -1
- package/dist/query/client.d.ts +5 -2
- package/dist/query/client.js +10 -9
- package/dist/query/types.d.ts +6 -41
- package/dist/query/types.js +2 -2
- package/dist/react/AbloProvider.d.ts +18 -8
- package/dist/react/AbloProvider.js +10 -9
- package/dist/react/context.d.ts +3 -3
- package/dist/react/context.js +1 -1
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +6 -5
- package/dist/react/index.js +6 -3
- package/dist/react/internalContext.d.ts +1 -1
- package/dist/react/useAblo.d.ts +12 -5
- package/dist/react/useAblo.js +26 -8
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.d.ts +2 -2
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.d.ts +3 -3
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useUndoScope.d.ts +5 -5
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +69 -10
- package/dist/schema/coordination.js +90 -9
- package/dist/schema/ddl.js +2 -2
- package/dist/schema/diff.d.ts +1 -1
- package/dist/schema/generate.js +1 -1
- package/dist/schema/index.d.ts +11 -10
- package/dist/schema/index.js +22 -18
- package/dist/schema/queries.d.ts +27 -27
- package/dist/schema/queries.js +23 -23
- package/dist/schema/select.d.ts +3 -3
- package/dist/schema/select.js +6 -3
- package/dist/schema/serialize.d.ts +15 -6
- package/dist/schema/serialize.js +20 -3
- package/dist/schema/sugar.d.ts +6 -7
- package/dist/schema/sugar.js +9 -12
- package/dist/schema/syncDeltaRow.d.ts +4 -152
- package/dist/schema/syncDeltaRow.js +4 -105
- package/dist/server/adapter.d.ts +18 -1
- package/dist/server/commit.d.ts +10 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +1 -1
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/adapters/drizzle.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +2 -2
- package/dist/source/adapters/kysely.d.ts +1 -1
- package/dist/source/adapters/kysely.js +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +2 -2
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.d.ts +8 -3
- package/dist/source/adapters/prisma.js +1 -1
- package/dist/source/connector.js +1 -1
- package/dist/source/connectorProtocol.d.ts +2 -8
- package/dist/source/connectorProtocol.js +3 -2
- package/dist/source/contract.d.ts +29 -17
- package/dist/source/contract.js +27 -22
- package/dist/source/factory.d.ts +1 -1
- package/dist/source/idempotency.js +2 -2
- package/dist/source/index.d.ts +1 -0
- package/dist/source/index.js +3 -0
- package/dist/source/next.d.ts +1 -1
- package/dist/source/signing.d.ts +9 -2
- package/dist/source/signing.js +4 -1
- package/dist/source/types.d.ts +6 -4
- package/dist/source/types.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +6 -6
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +14 -13
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/{core → stores}/StoreManager.d.ts +9 -26
- package/dist/{core → stores}/StoreManager.js +29 -77
- package/dist/stores/SyncActionStore.d.ts +4 -2
- package/dist/stores/SyncActionStore.js +11 -17
- package/dist/stores/syncAction.d.ts +26 -0
- package/dist/stores/syncAction.js +16 -0
- package/dist/surface.d.ts +3 -3
- package/dist/surface.js +6 -4
- package/dist/sync/BootstrapFetcher.d.ts +127 -6
- package/dist/sync/BootstrapFetcher.js +511 -83
- package/dist/sync/ConnectionManager.d.ts +6 -198
- package/dist/sync/ConnectionManager.js +6 -677
- package/dist/sync/OnDemandLoader.d.ts +5 -2
- package/dist/sync/OnDemandLoader.js +61 -21
- package/dist/sync/SubscriptionManager.d.ts +13 -2
- package/dist/sync/SubscriptionManager.js +23 -5
- package/dist/sync/SyncWebSocket.d.ts +27 -510
- package/dist/sync/SyncWebSocket.js +76 -954
- package/dist/sync/awaitClaimGrant.d.ts +4 -44
- package/dist/sync/awaitClaimGrant.js +4 -109
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/commitFrames.d.ts +6 -40
- package/dist/sync/commitFrames.js +6 -97
- package/dist/sync/contextPorts.d.ts +18 -0
- package/dist/sync/contextPorts.js +31 -0
- package/dist/sync/createClaimStream.d.ts +5 -49
- package/dist/sync/createClaimStream.js +5 -469
- package/dist/sync/createPresenceStream.d.ts +26 -4
- package/dist/sync/createPresenceStream.js +28 -20
- package/dist/sync/createSnapshot.d.ts +2 -2
- package/dist/sync/createSnapshot.js +1 -1
- package/dist/sync/credentialLifecycle.d.ts +5 -173
- package/dist/sync/credentialLifecycle.js +5 -320
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +24 -6
- package/dist/sync/participants.js +32 -23
- package/dist/sync/schemaDrift.d.ts +55 -0
- package/dist/sync/schemaDrift.js +53 -0
- package/dist/sync/schemas.d.ts +23 -33
- package/dist/sync/schemas.js +29 -20
- package/dist/sync/syncPlan.d.ts +3 -3
- package/dist/sync/wsFrameHandlers.d.ts +6 -114
- package/dist/sync/wsFrameHandlers.js +6 -392
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/ablo.d.ts +88 -0
- package/dist/transaction/ablo.js +33 -0
- package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
- package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
- package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
- package/dist/transaction/auth/bootstrapScope.js +1 -0
- package/dist/transaction/auth/capability.d.ts +212 -0
- package/dist/transaction/auth/capability.js +224 -0
- package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
- package/dist/{client → transaction/auth}/identity.d.ts +8 -7
- package/dist/{client → transaction/auth}/identity.js +1 -1
- package/dist/transaction/auth/index.d.ts +162 -0
- package/dist/transaction/auth/index.js +304 -0
- package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
- package/dist/{auth → transaction/auth}/schemas.js +13 -13
- package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
- package/dist/{client → transaction/auth}/sessionMint.js +4 -7
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +56 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +124 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +34 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/claimMeta.d.ts +49 -0
- package/dist/transaction/coordination/claimMeta.js +52 -0
- package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
- package/dist/transaction/coordination/createClaimStream.js +475 -0
- package/dist/transaction/coordination/events.d.ts +74 -0
- package/dist/transaction/coordination/events.js +7 -0
- package/dist/transaction/coordination/index.d.ts +19 -0
- package/dist/transaction/coordination/index.js +45 -0
- package/dist/transaction/coordination/locator.d.ts +104 -0
- package/dist/transaction/coordination/locator.js +102 -0
- package/dist/transaction/coordination/schema.d.ts +1536 -0
- package/dist/transaction/coordination/schema.js +1177 -0
- package/dist/transaction/coordination/targetConflict.d.ts +2 -0
- package/dist/transaction/coordination/targetConflict.js +107 -0
- package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
- package/dist/{coordination → transaction/coordination}/trace.js +18 -25
- package/dist/transaction/durableWrites.d.ts +62 -0
- package/dist/{client → transaction}/durableWrites.js +28 -3
- package/dist/transaction/environment.d.ts +105 -0
- package/dist/transaction/environment.js +108 -0
- package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +12 -12
- package/dist/{errorCodes.js → transaction/errorCodes.js} +45 -18
- package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
- package/dist/{errors.js → transaction/errors.js} +85 -16
- package/dist/transaction/footprint.d.ts +111 -0
- package/dist/transaction/footprint.js +0 -0
- package/dist/transaction/index.d.ts +20 -0
- package/dist/transaction/index.js +20 -0
- package/dist/transaction/keys/index.d.ts +87 -0
- package/dist/transaction/keys/index.js +207 -0
- package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
- package/dist/transaction/log/syncDeltaRow.js +95 -0
- package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
- package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
- package/dist/transaction/logger.d.ts +16 -0
- package/dist/transaction/logger.js +7 -0
- package/dist/transaction/observability.d.ts +53 -0
- package/dist/transaction/observability.js +19 -0
- package/dist/transaction/plugin.d.ts +285 -0
- package/dist/transaction/plugin.js +106 -0
- package/dist/{policy → transaction/policy}/types.d.ts +3 -3
- package/dist/{policy → transaction/policy}/types.js +2 -0
- package/dist/transaction/resources/httpResources.d.ts +321 -0
- package/dist/transaction/resources/httpResources.js +7 -0
- package/dist/transaction/resources/modelOperations.d.ts +427 -0
- package/dist/transaction/resources/modelOperations.js +12 -0
- package/dist/transaction/resources/mutationOptions.d.ts +66 -0
- package/dist/transaction/resources/mutationOptions.js +9 -0
- package/dist/transaction/resources/where.d.ts +101 -0
- package/dist/transaction/resources/where.js +115 -0
- package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
- package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
- package/dist/{schema → transaction/schema}/field.d.ts +17 -23
- package/dist/{schema → transaction/schema}/field.js +5 -5
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/loadStrategy.d.ts +45 -0
- package/dist/transaction/schema/loadStrategy.js +46 -0
- package/dist/{schema → transaction/schema}/model.d.ts +50 -35
- package/dist/{schema → transaction/schema}/model.js +30 -20
- package/dist/transaction/schema/openapi.d.ts +58 -0
- package/dist/transaction/schema/openapi.js +501 -0
- package/dist/{schema → transaction/schema}/relation.d.ts +21 -16
- package/dist/{schema → transaction/schema}/relation.js +7 -7
- package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
- package/dist/{schema → transaction/schema}/residency.js +0 -5
- package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
- package/dist/{schema → transaction/schema}/roles.js +5 -5
- package/dist/{schema → transaction/schema}/schema.d.ts +39 -10
- package/dist/{schema → transaction/schema}/schema.js +24 -3
- package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
- package/dist/{schema → transaction/schema}/tenancy.js +7 -4
- package/dist/transaction/transactionLayer.d.ts +82 -0
- package/dist/transaction/transactionLayer.js +24 -0
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +5 -6
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
- package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +11 -37
- package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
- package/dist/transaction/transport/commitFrames.d.ts +90 -0
- package/dist/transaction/transport/commitFrames.js +134 -0
- package/dist/transaction/transport/connectionManager.d.ts +215 -0
- package/dist/transaction/transport/connectionManager.js +673 -0
- package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
- package/dist/transaction/transport/credentialLifecycle.js +324 -0
- package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
- package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
- package/dist/transaction/transport/httpClient.d.ts +131 -0
- package/dist/{client → transaction/transport}/httpClient.js +6 -5
- package/dist/transaction/transport/httpOptions.d.ts +33 -0
- package/dist/transaction/transport/httpOptions.js +12 -0
- package/dist/{client → transaction/transport}/httpTransport.js +295 -97
- package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
- package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
- package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
- package/dist/transaction/transport/wsFrameHandlers.js +429 -0
- package/dist/transaction/transport/wsTransport.d.ts +574 -0
- package/dist/transaction/transport/wsTransport.js +1023 -0
- package/dist/transaction/types/assertExact.d.ts +17 -0
- package/dist/transaction/types/assertExact.js +1 -0
- package/dist/{types → transaction/types}/global.d.ts +17 -2
- package/dist/{types → transaction/types}/global.js +2 -1
- package/dist/{types → transaction/types}/index.d.ts +14 -46
- package/dist/{types → transaction/types}/index.js +7 -16
- package/dist/{types → transaction/types}/streams.d.ts +73 -45
- package/dist/transaction/utils/duration.d.ts +50 -0
- package/dist/{utils → transaction/utils}/duration.js +32 -0
- package/dist/{utils → transaction/utils}/json.d.ts +18 -0
- package/dist/transaction/utils/json.js +276 -0
- package/dist/transaction/wire/accountResponses.d.ts +420 -0
- package/dist/transaction/wire/accountResponses.js +290 -0
- package/dist/transaction/wire/auth.d.ts +56 -0
- package/dist/transaction/wire/auth.js +63 -0
- package/dist/transaction/wire/claimEvent.d.ts +76 -0
- package/dist/transaction/wire/claimEvent.js +73 -0
- package/dist/transaction/wire/claims.d.ts +530 -0
- package/dist/transaction/wire/claims.js +327 -0
- package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
- package/dist/{wire → transaction/wire}/commit.js +68 -47
- package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
- package/dist/{wire → transaction/wire}/delta.js +37 -13
- package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
- package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
- package/dist/transaction/wire/feedCursor.d.ts +60 -0
- package/dist/transaction/wire/feedCursor.js +82 -0
- package/dist/transaction/wire/feedEvent.d.ts +204 -0
- package/dist/transaction/wire/feedEvent.js +65 -0
- package/dist/transaction/wire/frames.d.ts +194 -0
- package/dist/transaction/wire/frames.js +50 -0
- package/dist/transaction/wire/inboundFrames.d.ts +562 -0
- package/dist/transaction/wire/inboundFrames.js +116 -0
- package/dist/transaction/wire/index.d.ts +54 -0
- package/dist/transaction/wire/index.js +83 -0
- package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
- package/dist/transaction/wire/listEnvelope.js +42 -0
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelResponses.d.ts +85 -0
- package/dist/transaction/wire/modelResponses.js +43 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +85 -38
- package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +141 -80
- package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
- package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
- package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
- package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
- package/dist/transactions/mutations/commitLatency.d.ts +52 -0
- package/dist/transactions/mutations/commitLatency.js +130 -0
- package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
- package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
- package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +18 -16
- package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +15 -15
- package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +15 -11
- package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +14 -12
- package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
- package/dist/transactions/mutations/durableWriteStore.js +12 -0
- package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
- package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +4 -3
- package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +7 -5
- package/dist/utils/mobxSetup.d.ts +1 -1
- package/dist/utils/mobxSetup.js +5 -2
- package/dist/{core → views}/QueryView.d.ts +2 -2
- package/dist/{core → views}/QueryView.js +2 -2
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/dist/webhooks/events.d.ts +2 -2
- package/dist/wire/index.d.ts +1 -34
- package/dist/wire/index.js +8 -49
- package/docs/agent-messaging.md +3 -3
- package/docs/agents.md +20 -13
- package/docs/api-keys.md +14 -10
- package/docs/api.md +27 -61
- package/docs/audit.md +6 -3
- package/docs/cli.md +41 -13
- package/docs/client-behavior.md +11 -9
- package/docs/concurrency-convention.md +49 -57
- package/docs/coordination.md +283 -121
- package/docs/data-sources.md +7 -5
- package/docs/debugging.md +39 -15
- package/docs/deployment.md +267 -0
- package/docs/examples/agent-human.md +49 -42
- package/docs/examples/ai-sdk-tool.md +69 -44
- package/docs/examples/existing-python-backend.md +8 -6
- package/docs/examples/nextjs.md +129 -47
- package/docs/examples/scoped-agent.md +46 -45
- package/docs/examples/server-agent.md +46 -26
- package/docs/groups.md +87 -30
- package/docs/guarantees.md +41 -12
- package/docs/how-it-works.md +38 -12
- package/docs/idempotency.md +126 -0
- package/docs/identity.md +77 -74
- package/docs/index.md +172 -86
- package/docs/integration-guide.md +31 -19
- package/docs/mcp.md +46 -21
- package/docs/migration.md +95 -18
- package/docs/operating-on-your-database.md +3 -1
- package/docs/projects.md +3 -1
- package/docs/quickstart.md +22 -5
- package/docs/react.md +31 -18
- package/docs/schema-contract.md +5 -3
- package/docs/session-settings.md +108 -0
- package/docs/sessions.md +4 -2
- package/docs/webhooks.md +12 -10
- package/llms.txt +48 -18
- package/package.json +21 -26
- package/dist/agent/Agent.d.ts +0 -366
- package/dist/agent/Agent.js +0 -514
- package/dist/agent/index.d.ts +0 -115
- package/dist/agent/index.js +0 -128
- package/dist/agent/session.d.ts +0 -93
- package/dist/agent/session.js +0 -149
- package/dist/agent/types.d.ts +0 -68
- package/dist/agent/types.js +0 -9
- package/dist/cli.cjs +0 -286329
- package/dist/client/durableWrites.d.ts +0 -21
- package/dist/client/httpClient.d.ts +0 -80
- package/dist/coordination/schema.d.ts +0 -722
- package/dist/coordination/schema.js +0 -578
- package/dist/schema/openapi.d.ts +0 -29
- package/dist/schema/openapi.js +0 -124
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -192
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -71
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/dist/transactions/durableWriteStore.js +0 -30
- package/dist/utils/duration.d.ts +0 -25
- package/dist/utils/json.js +0 -88
- package/dist/wire/errorEnvelope.d.ts +0 -55
- package/dist/wire/frames.d.ts +0 -197
- package/dist/wire/frames.js +0 -49
- package/dist/wire/listEnvelope.js +0 -18
- package/docs/interaction-model.md +0 -97
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
- /package/dist/{client → transaction}/persistence.d.ts +0 -0
- /package/dist/{client → transaction}/persistence.js +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
- /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.js +0 -0
- /package/dist/{types → transaction/types}/participant.d.ts +0 -0
- /package/dist/{types → transaction/types}/participant.js +0 -0
- /package/dist/{types → transaction/types}/streams.js +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
- /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocol.js +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
- /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
- /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
- /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* MutationQueue manages the lifecycle of local writes on their way to the
|
|
3
3
|
* server: it applies each change optimistically, batches the writes made in one
|
|
4
4
|
* event-loop tick into a single commit, retries transient failures, and rolls
|
|
5
5
|
* back on permanent rejection.
|
|
@@ -13,25 +13,25 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { EventEmitter } from 'events';
|
|
15
15
|
import { v4 as uuid } from 'uuid';
|
|
16
|
-
import { Model } from '
|
|
17
|
-
import {
|
|
18
|
-
import { AbloError, AbloConnectionError, AbloIdempotencyError, AbloNotFoundError, AbloValidationError, errorCodeSpec, } from '
|
|
19
|
-
import {
|
|
20
|
-
import { mutationCommitResultSchema, } from '
|
|
16
|
+
import { Model } from '../../Model.js';
|
|
17
|
+
import { globalRuntime } from '../../context.js';
|
|
18
|
+
import { AbloError, AbloConnectionError, AbloIdempotencyError, AbloNotFoundError, AbloValidationError, errorCodeSpec, } from '../../transaction/errors.js';
|
|
19
|
+
import { LogPosition } from '../../transaction/logPosition.js';
|
|
20
|
+
import { mutationCommitResultSchema, } from '../../transaction/wire/commit.js';
|
|
21
21
|
import { projectCommitPayload, computePriorityScore, normalizeModelKey,
|
|
22
22
|
// Includes stale guards as well as request identity/audit barriers.
|
|
23
23
|
hasCommitCoalescingBarrier, applyWriteOptions, asTransportError, extractStatusCode, TX_TYPE_TO_MUTATION_OP, } from './commitPayload.js';
|
|
24
|
-
import {
|
|
24
|
+
import { MutationStore } from './MutationStore.js';
|
|
25
25
|
import { entityKey, mergeUpdateData, takeUnsentCreateForModel, findCreateBarrierForDelete, deferDeleteUntilCreateSettles, releaseDeferredDeletesForCreate, } from './coalesceRules.js';
|
|
26
26
|
import { DeltaConfirmationTracker } from './deltaConfirmation.js';
|
|
27
27
|
import { deserializePersistedTransaction, isNonReplayablePersistedRow, pendingMutationRecordId, } from './replayValidation.js';
|
|
28
|
-
import { createCommitEnvelopeMember, createDurableCommitEnvelope, commitEnvelopeRecordId, durableCommitEnvelopeSchema, } from '
|
|
29
|
-
import { stableStringify } from '
|
|
28
|
+
import { createCommitEnvelopeMember, createDurableCommitEnvelope, commitEnvelopeRecordId, durableCommitEnvelopeSchema, } from '../../transaction/transactions/settlement/commitEnvelope.js';
|
|
29
|
+
import { stableStringify } from '../../transaction/utils/json.js';
|
|
30
30
|
import { applyOptimisticCreate, applyOptimisticUpdate, applyOptimisticDelete, rollbackOptimistic, } from './optimisticApply.js';
|
|
31
|
-
export class
|
|
31
|
+
export class MutationQueue extends EventEmitter {
|
|
32
32
|
// Keep one hour of clock/network margin inside the server's 24-hour ledger.
|
|
33
33
|
static DURABLE_REPLAY_WINDOW_MS = 23 * 60 * 60 * 1000;
|
|
34
|
-
store = new
|
|
34
|
+
store = new MutationStore();
|
|
35
35
|
// Signature of the last permanent-error we logged at `warn`. A `create`
|
|
36
36
|
// whose id already exists (`unique_violation`) is a permanent rejection
|
|
37
37
|
// that the offline queue re-drives on every reconnect/bootstrap — without
|
|
@@ -51,8 +51,9 @@ export class TransactionQueue extends EventEmitter {
|
|
|
51
51
|
// queue treats as transient and retries endlessly.
|
|
52
52
|
_mutationExecutor = null;
|
|
53
53
|
get mutationExecutor() {
|
|
54
|
-
return this._mutationExecutor ??
|
|
54
|
+
return this._mutationExecutor ?? this.runtime.mutationExecutor;
|
|
55
55
|
}
|
|
56
|
+
runtime;
|
|
56
57
|
executionQueue = [];
|
|
57
58
|
isProcessing = false;
|
|
58
59
|
processTimer;
|
|
@@ -100,7 +101,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
100
101
|
this.emit(event, payload);
|
|
101
102
|
}
|
|
102
103
|
catch (error) {
|
|
103
|
-
|
|
104
|
+
this.runtime.observability.captureMutationFailure({
|
|
104
105
|
context: `commit-lifecycle-listener:${event}`,
|
|
105
106
|
error: error instanceof Error ? error : String(error),
|
|
106
107
|
});
|
|
@@ -114,13 +115,19 @@ export class TransactionQueue extends EventEmitter {
|
|
|
114
115
|
this.assertDurableReplayOpen();
|
|
115
116
|
if (envelope.acceptedAt === undefined &&
|
|
116
117
|
Date.now() - envelope.sealedAt >=
|
|
117
|
-
|
|
118
|
+
MutationQueue.DURABLE_REPLAY_WINDOW_MS) {
|
|
118
119
|
this.durableReplayBlock = new AbloIdempotencyError('A pending commit is older than the server idempotency window; newer writes are blocked until it is reviewed.', { code: 'idempotency_conflict' });
|
|
120
|
+
// This gate stops EVERY subsequent write on this client, and each of
|
|
121
|
+
// those rejections is captured to observability rather than surfaced to
|
|
122
|
+
// the caller — without this line the session degrades into "nothing
|
|
123
|
+
// saves and nothing errors". One loud line at the moment the block
|
|
124
|
+
// engages is the only visible trace.
|
|
125
|
+
this.runtime.logger.warn('sync paused: a saved write from an earlier session is older than the server replay window, so newer writes are held until it is reviewed', { sealedAt: envelope.sealedAt });
|
|
119
126
|
throw this.durableReplayBlock;
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
computePriorityScore(type, modelName) {
|
|
123
|
-
return computePriorityScore(type, modelName);
|
|
130
|
+
return computePriorityScore(type, modelName, this.runtime);
|
|
124
131
|
}
|
|
125
132
|
ensureDerivedFields(transaction) {
|
|
126
133
|
if (!transaction.modelKey) {
|
|
@@ -256,6 +263,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
256
263
|
deltaConfirmationTimeout: 30000,
|
|
257
264
|
retryBackoff: { baseMs: 200, capMs: 1500 },
|
|
258
265
|
commitOfflineGraceMs: 30_000,
|
|
266
|
+
commitDispatchTimeoutMs: 30_000,
|
|
259
267
|
};
|
|
260
268
|
// Track executing transactions for backpressure
|
|
261
269
|
executingCount = 0;
|
|
@@ -264,7 +272,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
264
272
|
// completion paths, `getStats`, and `dispose` all read it.
|
|
265
273
|
optimisticUpdates = new Map();
|
|
266
274
|
// Stale-context notifications, keyed by transaction id. When the server
|
|
267
|
-
// accepts a commit but reports that an operation's
|
|
275
|
+
// accepts a commit but reports that an operation's premise had moved,
|
|
268
276
|
// the notification lands here from the commit acknowledgement and is drained
|
|
269
277
|
// by `waitForCommitReceipt`, so the receipt can carry it back to the caller.
|
|
270
278
|
commitNotifications = new Map();
|
|
@@ -287,7 +295,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
287
295
|
* shared: the client injects one, and a standalone queue creates its own. The
|
|
288
296
|
* queue advances the `acked` cursor as commit responses arrive, the store
|
|
289
297
|
* advances `applied` and `persisted`, and snapshots and claims read
|
|
290
|
-
* `readFloor`. See `../
|
|
298
|
+
* `readFloor`. See `../logPosition.js` for the full contract.
|
|
291
299
|
*/
|
|
292
300
|
position;
|
|
293
301
|
/** Applied-cursor alias, kept so the many internal read sites stay legible. */
|
|
@@ -390,9 +398,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
390
398
|
operations: [...input.operations],
|
|
391
399
|
sourceMutationIds,
|
|
392
400
|
commitOptions: {
|
|
393
|
-
...(input.commitOptions?.causedByTaskId !== undefined
|
|
394
|
-
? { causedByTaskId: input.commitOptions.causedByTaskId }
|
|
395
|
-
: {}),
|
|
396
401
|
...(input.commitOptions?.reads !== undefined
|
|
397
402
|
? {
|
|
398
403
|
reads: input.commitOptions.reads === null
|
|
@@ -440,7 +445,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
440
445
|
await this.commitOutbox.remove(commitEnvelopeRecordId(idempotencyKey));
|
|
441
446
|
}
|
|
442
447
|
catch (error) {
|
|
443
|
-
|
|
448
|
+
this.runtime.logger.debug('[MutationQueue] Durable-write cleanup deferred', {
|
|
444
449
|
idempotencyKey,
|
|
445
450
|
error: error instanceof Error ? error.message : String(error),
|
|
446
451
|
});
|
|
@@ -489,6 +494,35 @@ export class TransactionQueue extends EventEmitter {
|
|
|
489
494
|
return parsed.data;
|
|
490
495
|
throw new AbloConnectionError('The mutation transport returned an invalid commit receipt; its outcome remains pending and is safe to retry.', { code: 'commit_no_result', cause: parsed.error });
|
|
491
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Dispatch a sealed envelope to the transport, bounded by
|
|
499
|
+
* `commitDispatchTimeoutMs`. Every executor call site routes through here:
|
|
500
|
+
* a transport that never answers must become a retryable no-receipt failure
|
|
501
|
+
* (the same class as a malformed receipt) instead of an eternally in-flight
|
|
502
|
+
* commit, because an unanswered commit holds the staged-batch lock and
|
|
503
|
+
* silently stalls every later write in the session. The abandoned commit's
|
|
504
|
+
* eventual result, if it ever arrives, is discarded; the retry re-sends the
|
|
505
|
+
* identical idempotency key, so the server deduplicates an already-applied
|
|
506
|
+
* write.
|
|
507
|
+
*/
|
|
508
|
+
dispatchCommitBounded(...args) {
|
|
509
|
+
const timeoutMs = this.config.commitDispatchTimeoutMs;
|
|
510
|
+
const dispatched = this.mutationExecutor.commit(...args);
|
|
511
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
512
|
+
return dispatched;
|
|
513
|
+
return new Promise((resolve, reject) => {
|
|
514
|
+
const timer = setTimeout(() => {
|
|
515
|
+
reject(new AbloConnectionError('The mutation transport did not acknowledge the commit in time; its outcome remains pending and is safe to retry.', { code: 'commit_no_result' }));
|
|
516
|
+
}, timeoutMs);
|
|
517
|
+
dispatched.then((value) => {
|
|
518
|
+
clearTimeout(timer);
|
|
519
|
+
resolve(value);
|
|
520
|
+
}, (error) => {
|
|
521
|
+
clearTimeout(timer);
|
|
522
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
}
|
|
492
526
|
clearReplicationLagState(transactionId) {
|
|
493
527
|
const timeout = this.replicationLagTimeouts.get(transactionId);
|
|
494
528
|
if (timeout)
|
|
@@ -620,13 +654,14 @@ export class TransactionQueue extends EventEmitter {
|
|
|
620
654
|
* Resolvers for per-transaction `confirmation` promises. Populated in
|
|
621
655
|
* `attachConfirmation` at staging time, consumed by the constructor-time
|
|
622
656
|
* listeners on `transaction:completed` / `transaction:failed`. Kept off
|
|
623
|
-
* the
|
|
657
|
+
* the QueuedMutation row so the store's iteration order stays plain-data
|
|
624
658
|
* and serialization-friendly.
|
|
625
659
|
*/
|
|
626
660
|
confirmationResolvers = new Map();
|
|
627
661
|
constructor(config) {
|
|
628
662
|
super();
|
|
629
|
-
this.
|
|
663
|
+
this.runtime = config?.runtime ?? globalRuntime;
|
|
664
|
+
this.position = config?.position ?? new LogPosition();
|
|
630
665
|
// Bind the confirmation tracker to this queue's store/ledger/events.
|
|
631
666
|
// `isConnected` closes over `isConnectedFn` so `setConnectionChecker`
|
|
632
667
|
// swaps stay visible to in-flight timeouts.
|
|
@@ -638,6 +673,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
638
673
|
},
|
|
639
674
|
isConnected: () => this.isConnectedFn(),
|
|
640
675
|
position: this.position,
|
|
676
|
+
runtime: this.runtime,
|
|
641
677
|
});
|
|
642
678
|
if (config) {
|
|
643
679
|
this.config = { ...this.config, ...config };
|
|
@@ -683,7 +719,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
683
719
|
* be rolled back — `pending`, `executing`, and `awaiting_delta` — and ignores
|
|
684
720
|
* `completed` (already settled) and `failed`/`rolled_back` (already
|
|
685
721
|
* rejected). This complements the `confirmation` promise carried on a known
|
|
686
|
-
* {@link
|
|
722
|
+
* {@link QueuedMutation}: use this method at call sites that hold a model
|
|
687
723
|
* returned by `ablo.<model>.create()` but never see the underlying
|
|
688
724
|
* transaction.
|
|
689
725
|
*/
|
|
@@ -771,7 +807,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
771
807
|
return;
|
|
772
808
|
// Each failed commit reaches the consumer through its own rejection path,
|
|
773
809
|
// so this aggregate line is forensic and logged at debug rather than warn.
|
|
774
|
-
|
|
810
|
+
this.runtime.logger.debug(`[MutationQueue] WS disconnected > ${graceMs}ms; failing ${inFlight.length} in-flight commit(s) with AbloConnectionError`, { inFlightIds: inFlight.map((id) => id.slice(0, 8)) });
|
|
775
811
|
for (const id of inFlight) {
|
|
776
812
|
const tx = this.commitStore.get(id);
|
|
777
813
|
if (!tx)
|
|
@@ -844,7 +880,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
844
880
|
this.batchIndex++;
|
|
845
881
|
const currentBatchIndex = this.batchIndex;
|
|
846
882
|
// Log batch commit for performance monitoring
|
|
847
|
-
|
|
883
|
+
this.runtime.logger.debug('[MutationQueue] commitCreatedTransactions', {
|
|
848
884
|
count: this.createdTransactions.length,
|
|
849
885
|
batchIndex: currentBatchIndex,
|
|
850
886
|
types: this.createdTransactions.map((t) => `${t.type}:${t.modelName}`),
|
|
@@ -907,7 +943,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
907
943
|
sequence: batch[0]?.commitEnvelope?.sequence,
|
|
908
944
|
});
|
|
909
945
|
this.assertEnvelopeInsideReplayWindow(durableEnvelope);
|
|
910
|
-
const result = this.parseMutationCommitResult(await this.
|
|
946
|
+
const result = this.parseMutationCommitResult(await this.dispatchCommitBounded(durableEnvelope.operations, {
|
|
911
947
|
idempotencyKey,
|
|
912
948
|
}));
|
|
913
949
|
await this.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
@@ -948,7 +984,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
948
984
|
this.optimisticUpdates.delete(tx.id);
|
|
949
985
|
}
|
|
950
986
|
}
|
|
951
|
-
|
|
987
|
+
this.runtime.logger.debug('txn:commit', 0, {
|
|
952
988
|
count: batch.length,
|
|
953
989
|
lastSyncId: result.lastSyncId,
|
|
954
990
|
});
|
|
@@ -957,13 +993,13 @@ export class TransactionQueue extends EventEmitter {
|
|
|
957
993
|
catch (err) {
|
|
958
994
|
// If one request fails, hand it and every later request back to the
|
|
959
995
|
// normal lane. Their envelopes stay attached for safe retry.
|
|
960
|
-
const isOffline = !
|
|
996
|
+
const isOffline = !this.runtime.onlineStatus.isOnline();
|
|
961
997
|
const isNetworkError = err instanceof Error &&
|
|
962
998
|
(err.message.includes('Failed to fetch') ||
|
|
963
999
|
err.message.includes('Network request failed') ||
|
|
964
1000
|
err.message.includes('NetworkError'));
|
|
965
1001
|
if (!isOffline || !isNetworkError) {
|
|
966
|
-
|
|
1002
|
+
this.runtime.observability.breadcrumb('Batch flush fallback failed', 'sync.transaction', 'warning', {
|
|
967
1003
|
error: err instanceof Error ? err.message : String(err),
|
|
968
1004
|
});
|
|
969
1005
|
}
|
|
@@ -976,7 +1012,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
976
1012
|
}
|
|
977
1013
|
/**
|
|
978
1014
|
* Records a create and applies it optimistically, then stages it for the next
|
|
979
|
-
* batched commit. Returns the {@link
|
|
1015
|
+
* batched commit. Returns the {@link QueuedMutation}, whose `confirmation`
|
|
980
1016
|
* promise settles once the server confirms the write.
|
|
981
1017
|
*/
|
|
982
1018
|
async create(model, context, writeOptions, sourceMutationId) {
|
|
@@ -1080,7 +1116,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1080
1116
|
const actualModelName = model.getModelName();
|
|
1081
1117
|
// Skip Activity delete transactions - activities are permanent audit records
|
|
1082
1118
|
if (actualModelName === 'Activity') {
|
|
1083
|
-
|
|
1119
|
+
this.runtime.logger.debug('MutationQueue.delete() skipping Activity deletion - permanent audit records', { modelId: model.id });
|
|
1084
1120
|
const modelKey = normalizeModelKey(actualModelName);
|
|
1085
1121
|
const priorityScore = this.computePriorityScore('delete', actualModelName);
|
|
1086
1122
|
const mockTransaction = {
|
|
@@ -1147,7 +1183,14 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1147
1183
|
transaction.sourceMutationIds = [
|
|
1148
1184
|
...new Set([
|
|
1149
1185
|
...(transaction.sourceMutationIds ?? []),
|
|
1150
|
-
|
|
1186
|
+
// Absorb journal sources only from updates that never sealed an
|
|
1187
|
+
// envelope. A sealed update's seal already CONSUMED its journal rows
|
|
1188
|
+
// — they belong to that envelope forever — so listing them here
|
|
1189
|
+
// poisons this delete's own seal: the new-envelope guard finds its
|
|
1190
|
+
// sources missing, rejects with `idempotency_conflict`, and the
|
|
1191
|
+
// optimistic delete reverts. That was a user-visible lost delete
|
|
1192
|
+
// (edit a layer, then delete it while the edit was on the wire).
|
|
1193
|
+
...canceledUpdates.flatMap((candidate) => candidate.commitEnvelope ? [] : (candidate.sourceMutationIds ?? [])),
|
|
1151
1194
|
...(pendingMerge?.sourceMutationIds ?? []),
|
|
1152
1195
|
]),
|
|
1153
1196
|
];
|
|
@@ -1319,7 +1362,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1319
1362
|
// are already executing, so the server is not flooded with concurrent
|
|
1320
1363
|
// requests.
|
|
1321
1364
|
if (this.executingCount >= this.config.maxExecutingTransactions) {
|
|
1322
|
-
|
|
1365
|
+
this.runtime.logger.debug('[MutationQueue] Backpressure: delaying batch, too many executing', {
|
|
1323
1366
|
executingCount: this.executingCount,
|
|
1324
1367
|
max: this.config.maxExecutingTransactions,
|
|
1325
1368
|
});
|
|
@@ -1360,7 +1403,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1360
1403
|
this.isProcessing = true;
|
|
1361
1404
|
// Declare batch outside try so it's accessible in finally for backpressure tracking
|
|
1362
1405
|
let batch = [];
|
|
1363
|
-
await
|
|
1406
|
+
await this.runtime.observability.startSpanAsync('sync.batch', 'sync.transaction.batch', async () => {
|
|
1364
1407
|
try {
|
|
1365
1408
|
// Sort the execution queue by foreign-key priority before selecting a
|
|
1366
1409
|
// batch, so a parent row is always committed before its children, even
|
|
@@ -1432,7 +1475,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1432
1475
|
// the exact key assigned before the first transport attempt.
|
|
1433
1476
|
this.assertEnvelopeInsideReplayWindow(durableEnvelope);
|
|
1434
1477
|
dispatchStarted = true;
|
|
1435
|
-
const result = this.parseMutationCommitResult(await this.
|
|
1478
|
+
const result = this.parseMutationCommitResult(await this.dispatchCommitBounded(operations, {
|
|
1436
1479
|
idempotencyKey: commitIdempotencyKey,
|
|
1437
1480
|
}));
|
|
1438
1481
|
await this.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
@@ -1496,7 +1539,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1496
1539
|
}
|
|
1497
1540
|
this.store.updateStatus(tx.id, 'awaiting_delta');
|
|
1498
1541
|
this.scheduleReplicationLagTimeout(tx.id, commitIdempotencyKey, result.correlationId);
|
|
1499
|
-
|
|
1542
|
+
this.runtime.logger.debug('tx:awaiting_delta', {
|
|
1500
1543
|
txId: tx.id.slice(0, 8),
|
|
1501
1544
|
model: tx.modelName,
|
|
1502
1545
|
reason: 'queued_forward_waiting_for_correlated_echo',
|
|
@@ -1514,7 +1557,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1514
1557
|
// A lastSyncId of 0 means the mutation succeeded but the server
|
|
1515
1558
|
// emitted no sync delta; record that anomaly for observability.
|
|
1516
1559
|
if (lastSyncId === 0) {
|
|
1517
|
-
|
|
1560
|
+
this.runtime.observability.captureCommitZeroSyncId({
|
|
1518
1561
|
operationCount: operations.length,
|
|
1519
1562
|
operations: operations.map((op) => `${op.type}:${op.model}:${op.id?.slice(0, 8) ?? '?'}`),
|
|
1520
1563
|
});
|
|
@@ -1532,7 +1575,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1532
1575
|
this.emit('transaction:completed', tx);
|
|
1533
1576
|
this.emit(`transaction:completed:${tx.id}`, tx);
|
|
1534
1577
|
this.optimisticUpdates.delete(tx.id);
|
|
1535
|
-
|
|
1578
|
+
this.runtime.logger.debug('tx:confirm_delete_zero_syncid', {
|
|
1536
1579
|
txId: tx.id.slice(0, 8),
|
|
1537
1580
|
model: tx.modelName,
|
|
1538
1581
|
reason: 'delete_idempotent_no_delta',
|
|
@@ -1547,7 +1590,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1547
1590
|
this.emit('transaction:completed', tx);
|
|
1548
1591
|
this.emit(`transaction:completed:${tx.id}`, tx);
|
|
1549
1592
|
this.optimisticUpdates.delete(tx.id);
|
|
1550
|
-
|
|
1593
|
+
this.runtime.logger.debug('tx:confirm_ack', {
|
|
1551
1594
|
txId: tx.id.slice(0, 8),
|
|
1552
1595
|
model: tx.modelName,
|
|
1553
1596
|
serverSyncId: lastSyncId,
|
|
@@ -1556,7 +1599,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1556
1599
|
}
|
|
1557
1600
|
else {
|
|
1558
1601
|
this.store.updateStatus(tx.id, 'awaiting_delta');
|
|
1559
|
-
|
|
1602
|
+
this.runtime.logger.debug('tx:awaiting_delta', {
|
|
1560
1603
|
txId: tx.id.slice(0, 8),
|
|
1561
1604
|
model: tx.modelName,
|
|
1562
1605
|
neededSyncId: lastSyncId,
|
|
@@ -1603,7 +1646,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1603
1646
|
// authoritative `warn` with the same typed cause) — passes
|
|
1604
1647
|
// through here. Logging it at `warn` made one rejected write
|
|
1605
1648
|
// surface three identical dumps; keep it at `debug`.
|
|
1606
|
-
|
|
1649
|
+
this.runtime.logger.debug('[MutationQueue] Batch commit rejected', {
|
|
1607
1650
|
batchSize: batchOps.length,
|
|
1608
1651
|
models: batchOps.map(({ op }) => `${op.type}:${op.model}`),
|
|
1609
1652
|
errorType: abloErr?.type ?? error?.name,
|
|
@@ -1621,7 +1664,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1621
1664
|
if (dispatchStarted) {
|
|
1622
1665
|
await this.removeDurableCommit(commitIdempotencyKey);
|
|
1623
1666
|
}
|
|
1624
|
-
|
|
1667
|
+
this.runtime.logger.info('[MutationQueue] Graceful handling: entity already deleted', {
|
|
1625
1668
|
batchSize: batchOps.length,
|
|
1626
1669
|
});
|
|
1627
1670
|
for (const { tx, op } of batchOps) {
|
|
@@ -1629,7 +1672,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1629
1672
|
// Row already gone: the intended state holds, mark completed.
|
|
1630
1673
|
this.store.updateStatus(tx.id, 'completed');
|
|
1631
1674
|
this.emit('transaction:completed', tx);
|
|
1632
|
-
|
|
1675
|
+
this.runtime.logger.debug('[MutationQueue] Orphaned transaction treated as success', {
|
|
1633
1676
|
txId: tx.id.slice(0, 12),
|
|
1634
1677
|
model: tx.modelName,
|
|
1635
1678
|
type: op.type,
|
|
@@ -1689,7 +1732,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1689
1732
|
this.scheduleProcessing(true);
|
|
1690
1733
|
}
|
|
1691
1734
|
const batchEnd = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
1692
|
-
|
|
1735
|
+
this.runtime.logger.debug('txn:batch', batchEnd - batchStart, {
|
|
1693
1736
|
maxBatchSize: this.config.maxBatchSize,
|
|
1694
1737
|
remaining: this.executionQueue.length,
|
|
1695
1738
|
executingCount: this.executingCount,
|
|
@@ -1834,13 +1877,11 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1834
1877
|
await existing.sealPromise;
|
|
1835
1878
|
const existingIntent = stableStringify({
|
|
1836
1879
|
operations: existing.operations,
|
|
1837
|
-
causedByTaskId: existing.causedByTaskId ?? null,
|
|
1838
1880
|
reads: existing.reads ?? null,
|
|
1839
1881
|
track: existing.track ?? null,
|
|
1840
1882
|
});
|
|
1841
1883
|
const incomingIntent = stableStringify({
|
|
1842
1884
|
operations,
|
|
1843
|
-
causedByTaskId: options.causedByTaskId ?? null,
|
|
1844
1885
|
reads: options.reads ?? null,
|
|
1845
1886
|
track: options.track ?? null,
|
|
1846
1887
|
});
|
|
@@ -1871,7 +1912,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1871
1912
|
id: clientTxId,
|
|
1872
1913
|
kind: 'commit',
|
|
1873
1914
|
operations: [...operations],
|
|
1874
|
-
causedByTaskId: options.causedByTaskId ?? null,
|
|
1875
1915
|
...(options.reads ? { reads: options.reads } : {}),
|
|
1876
1916
|
...(options.track ? { track: options.track } : {}),
|
|
1877
1917
|
status: 'pending',
|
|
@@ -1886,7 +1926,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1886
1926
|
origin: 'atomic_commit',
|
|
1887
1927
|
operations: tx.operations,
|
|
1888
1928
|
commitOptions: {
|
|
1889
|
-
causedByTaskId: tx.causedByTaskId ?? null,
|
|
1890
1929
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
1891
1930
|
...(tx.track ? { track: tx.track } : {}),
|
|
1892
1931
|
},
|
|
@@ -1950,7 +1989,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1950
1989
|
operations: tx.operations,
|
|
1951
1990
|
sourceMutationIds: tx.sourceMutationIds,
|
|
1952
1991
|
commitOptions: {
|
|
1953
|
-
causedByTaskId: tx.causedByTaskId ?? null,
|
|
1954
1992
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
1955
1993
|
...(tx.track ? { track: tx.track } : {}),
|
|
1956
1994
|
},
|
|
@@ -1961,9 +1999,8 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1961
1999
|
tx.durableEnvelope = durableEnvelope;
|
|
1962
2000
|
this.assertEnvelopeInsideReplayWindow(durableEnvelope);
|
|
1963
2001
|
dispatchStarted = true;
|
|
1964
|
-
const result = this.parseMutationCommitResult(await this.
|
|
2002
|
+
const result = this.parseMutationCommitResult(await this.dispatchCommitBounded(durableEnvelope.operations, {
|
|
1965
2003
|
idempotencyKey: tx.id,
|
|
1966
|
-
causedByTaskId: durableEnvelope.commitOptions.causedByTaskId ?? undefined,
|
|
1967
2004
|
...(durableEnvelope.commitOptions.reads
|
|
1968
2005
|
? { reads: durableEnvelope.commitOptions.reads }
|
|
1969
2006
|
: {}),
|
|
@@ -1991,7 +2028,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1991
2028
|
}
|
|
1992
2029
|
else {
|
|
1993
2030
|
this.scheduleReplicationLagTimeout(tx.id, tx.id, result.correlationId);
|
|
1994
|
-
|
|
2031
|
+
this.runtime.logger.debug('[MutationQueue] commit lane awaiting source echo', {
|
|
1995
2032
|
txId: tx.id.slice(0, 12),
|
|
1996
2033
|
});
|
|
1997
2034
|
}
|
|
@@ -2012,14 +2049,29 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2012
2049
|
if (dispatchStarted && this.isDefinitiveRejection(error)) {
|
|
2013
2050
|
await this.removeDurableCommit(tx.id);
|
|
2014
2051
|
}
|
|
2015
|
-
|
|
2052
|
+
// A transport that is DOWN is not a failing write: the envelope is
|
|
2053
|
+
// meant to wait for reconnect, which is what makes a commit survive a
|
|
2054
|
+
// dropped connection. A transient error the SERVER keeps returning is
|
|
2055
|
+
// a different thing, and this lane has no attempt bound of its own —
|
|
2056
|
+
// so a 5xx carrying no wire code reads as transient on every kick,
|
|
2057
|
+
// sits at the head of the lane, and `waitForCommitReceipt` never
|
|
2058
|
+
// settles. The caller sees a write that neither lands nor fails.
|
|
2059
|
+
//
|
|
2060
|
+
// Counting only the non-connection failures keeps offline waiting
|
|
2061
|
+
// unbounded while giving a repeating server rejection an end.
|
|
2062
|
+
if (!(error instanceof AbloConnectionError)) {
|
|
2063
|
+
tx.transientAttempts = (tx.transientAttempts ?? 0) + 1;
|
|
2064
|
+
}
|
|
2065
|
+
const exhausted = (tx.transientAttempts ?? 0) > this.config.maxRetries;
|
|
2066
|
+
if (!this.isPermanentError(error) && !exhausted) {
|
|
2016
2067
|
// Transient: leave it at the head and retry on the next kick
|
|
2017
2068
|
// (reconnect or the next enqueueCommit) rather than tight-looping
|
|
2018
2069
|
// while the connection is down.
|
|
2019
2070
|
tx.status = 'pending';
|
|
2020
|
-
|
|
2071
|
+
this.runtime.logger.debug('[MutationQueue] commit lane transient', {
|
|
2021
2072
|
txId: tx.id.slice(0, 12),
|
|
2022
2073
|
attempts: tx.attempts,
|
|
2074
|
+
transientAttempts: tx.transientAttempts ?? 0,
|
|
2023
2075
|
message: error.message,
|
|
2024
2076
|
});
|
|
2025
2077
|
break;
|
|
@@ -2030,7 +2082,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2030
2082
|
// Internal bookkeeping; the consumer-facing rejection is emitted on
|
|
2031
2083
|
// 'transaction:failed' and surfaced by the permanent-error headline,
|
|
2032
2084
|
// so this line stays at debug.
|
|
2033
|
-
|
|
2085
|
+
this.runtime.logger.debug('[MutationQueue] commit lane permanent error', {
|
|
2034
2086
|
txId: tx.id.slice(0, 12),
|
|
2035
2087
|
attempts: tx.attempts,
|
|
2036
2088
|
message: error.message,
|
|
@@ -2240,7 +2292,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2240
2292
|
const sig = `${details.type}:${details.model}:${details.modelId}:${details.errorCode ?? details.errorType}`;
|
|
2241
2293
|
const isRepeat = sig === this.lastPermanentErrorSig;
|
|
2242
2294
|
this.lastPermanentErrorSig = sig;
|
|
2243
|
-
const logger =
|
|
2295
|
+
const logger = this.runtime.logger;
|
|
2244
2296
|
// Two registers from one call site, split by log level (the default
|
|
2245
2297
|
// logger is gated at `warn`, so `debug` stays hidden unless
|
|
2246
2298
|
// ABLO_LOG_LEVEL=debug is set to inspect the engine):
|
|
@@ -2256,7 +2308,17 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2256
2308
|
: '';
|
|
2257
2309
|
const reason = abloErr?.message ? ` — ${abloErr.message}` : '';
|
|
2258
2310
|
const code = abloErr?.code ? ` (code: ${abloErr.code})` : '';
|
|
2259
|
-
|
|
2311
|
+
// An optimistic write resolves before the server answers, so a later
|
|
2312
|
+
// rejection has no caller left to return to and this log is the only
|
|
2313
|
+
// place it appears. That reads to an application developer as their own
|
|
2314
|
+
// save silently failing — the write showed, then vanished — and sends
|
|
2315
|
+
// them into their editor instead of here. Name the subscription that
|
|
2316
|
+
// hands them the same typed error, so the application can say what
|
|
2317
|
+
// happened rather than only the console.
|
|
2318
|
+
const channelNote = this.config.enableOptimistic
|
|
2319
|
+
? ' To surface this in your app, subscribe with `ablo.onMutationFailure(…)`.'
|
|
2320
|
+
: '';
|
|
2321
|
+
const headline = `Your ${transaction.type} to "${transaction.modelName}" was not saved${reason}${code}.${revertNote}${channelNote}`;
|
|
2260
2322
|
if (isRepeat) {
|
|
2261
2323
|
// Same write rejected for the same reason on each reconnect replay —
|
|
2262
2324
|
// log the forensics once, stay quiet after.
|
|
@@ -2388,7 +2450,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2388
2450
|
}
|
|
2389
2451
|
}
|
|
2390
2452
|
catch (error) {
|
|
2391
|
-
|
|
2453
|
+
this.runtime.observability.captureMutationFailure({
|
|
2392
2454
|
context: 'load-persisted-transactions',
|
|
2393
2455
|
error: error instanceof Error ? error : String(error),
|
|
2394
2456
|
});
|
|
@@ -2418,8 +2480,8 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2418
2480
|
envelopes.push(parsed.data);
|
|
2419
2481
|
}
|
|
2420
2482
|
else {
|
|
2421
|
-
|
|
2422
|
-
|
|
2483
|
+
this.runtime.logger.warn('A saved local write is unreadable and was held for review.');
|
|
2484
|
+
this.runtime.observability.captureMutationFailure({
|
|
2423
2485
|
context: 'restore-commit-envelope',
|
|
2424
2486
|
error: parsed.error,
|
|
2425
2487
|
});
|
|
@@ -2435,9 +2497,9 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2435
2497
|
}
|
|
2436
2498
|
if (envelope.acceptedAt === undefined &&
|
|
2437
2499
|
Date.now() - envelope.sealedAt >=
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2500
|
+
MutationQueue.DURABLE_REPLAY_WINDOW_MS) {
|
|
2501
|
+
this.runtime.logger.warn('A saved local write is too old to retry safely and was held for review.');
|
|
2502
|
+
this.runtime.observability.captureMutationFailure({
|
|
2441
2503
|
context: 'quarantine-expired-commit-envelope',
|
|
2442
2504
|
error: `Envelope ${envelope.idempotencyKey} is too old to replay safely`,
|
|
2443
2505
|
});
|
|
@@ -2448,7 +2510,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2448
2510
|
envelope.scope.organizationId !== this.commitOutboxScope.organizationId ||
|
|
2449
2511
|
envelope.scope.participantId !== this.commitOutboxScope.participantId ||
|
|
2450
2512
|
envelope.scope.namespace !== this.commitOutboxScope.namespace)) {
|
|
2451
|
-
|
|
2513
|
+
this.runtime.logger.warn('A saved local write belongs to a different account or server and was held for review.');
|
|
2452
2514
|
continue;
|
|
2453
2515
|
}
|
|
2454
2516
|
if (this.commitStore.has(envelope.idempotencyKey))
|
|
@@ -2457,7 +2519,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2457
2519
|
id: envelope.idempotencyKey,
|
|
2458
2520
|
kind: 'commit',
|
|
2459
2521
|
operations: envelope.operations.map((operation) => ({ ...operation })),
|
|
2460
|
-
causedByTaskId: envelope.commitOptions.causedByTaskId ?? null,
|
|
2461
2522
|
...(envelope.commitOptions.reads
|
|
2462
2523
|
? { reads: [...envelope.commitOptions.reads] }
|
|
2463
2524
|
: {}),
|
|
@@ -2482,10 +2543,10 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2482
2543
|
void this.processCommitLane();
|
|
2483
2544
|
}
|
|
2484
2545
|
catch (error) {
|
|
2485
|
-
|
|
2546
|
+
this.runtime.logger.debug('[MutationQueue] Failed to restore durable writes', {
|
|
2486
2547
|
error: error instanceof Error ? error.message : String(error),
|
|
2487
2548
|
});
|
|
2488
|
-
|
|
2549
|
+
this.runtime.observability.captureMutationFailure({
|
|
2489
2550
|
context: 'restore-commit-envelopes',
|
|
2490
2551
|
error: error instanceof Error ? error : String(error),
|
|
2491
2552
|
});
|
|
@@ -2502,15 +2563,15 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2502
2563
|
deserializeTransaction(data) {
|
|
2503
2564
|
if (isNonReplayablePersistedRow(data))
|
|
2504
2565
|
return null;
|
|
2505
|
-
const transaction = deserializePersistedTransaction(data);
|
|
2566
|
+
const transaction = deserializePersistedTransaction(data, this.runtime);
|
|
2506
2567
|
if (!transaction) {
|
|
2507
2568
|
const rowId = typeof data === 'object' && data !== null && typeof data.id === 'string'
|
|
2508
2569
|
? data.id
|
|
2509
2570
|
: undefined;
|
|
2510
|
-
|
|
2571
|
+
this.runtime.logger.debug('[MutationQueue] Dropping malformed persisted transaction', {
|
|
2511
2572
|
rowId,
|
|
2512
2573
|
});
|
|
2513
|
-
|
|
2574
|
+
this.runtime.observability.captureMutationFailure({
|
|
2514
2575
|
context: 'deserialize-persisted-transaction',
|
|
2515
2576
|
error: `Persisted transaction failed schema validation${rowId ? ` (id: ${rowId})` : ''}`,
|
|
2516
2577
|
});
|
|
@@ -2538,7 +2599,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2538
2599
|
// listener) must surface, not vanish — the status flip above is
|
|
2539
2600
|
// already committed either way.
|
|
2540
2601
|
void this.rollbackOptimistic(transaction, 'model_cancelled').catch((error) => {
|
|
2541
|
-
|
|
2602
|
+
this.runtime.observability.captureMutationFailure({
|
|
2542
2603
|
context: 'rollback-model-cancelled',
|
|
2543
2604
|
error: error instanceof Error ? error : String(error),
|
|
2544
2605
|
});
|
|
@@ -2553,8 +2614,8 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2553
2614
|
* used to cascade a parent deletion. The caller supplies the foreign-key
|
|
2554
2615
|
* relationship; this method performs the cancellation.
|
|
2555
2616
|
*
|
|
2556
|
-
* @param childModelName - The child model type (for example '
|
|
2557
|
-
* @param foreignKey - The foreign-key property name (for example '
|
|
2617
|
+
* @param childModelName - The child model type (for example 'Block').
|
|
2618
|
+
* @param foreignKey - The foreign-key property name (for example 'sectionId').
|
|
2558
2619
|
* @param parentId - The deleted parent's id.
|
|
2559
2620
|
* @returns The number of transactions cancelled.
|
|
2560
2621
|
*/
|
|
@@ -2572,13 +2633,13 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2572
2633
|
if (fkValue === parentId) {
|
|
2573
2634
|
this.store.updateStatus(transaction.id, 'rolled_back');
|
|
2574
2635
|
void this.rollbackOptimistic(transaction, 'cascade_parent_deleted').catch((error) => {
|
|
2575
|
-
|
|
2636
|
+
this.runtime.observability.captureMutationFailure({
|
|
2576
2637
|
context: 'rollback-cascade-parent-deleted',
|
|
2577
2638
|
error: error instanceof Error ? error : String(error),
|
|
2578
2639
|
});
|
|
2579
2640
|
});
|
|
2580
2641
|
cancelled++;
|
|
2581
|
-
|
|
2642
|
+
this.runtime.logger.debug('[MutationQueue] Cascade cancelled orphaned transaction', {
|
|
2582
2643
|
txId: transaction.id.slice(0, 12),
|
|
2583
2644
|
model: childModelName,
|
|
2584
2645
|
foreignKey,
|
|
@@ -2603,13 +2664,13 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2603
2664
|
return { ...(remote || {}), ...(local || {}) };
|
|
2604
2665
|
}
|
|
2605
2666
|
extractCreateData(model) {
|
|
2606
|
-
return projectCommitPayload(model.getModelName(), model.toJSON(), { dropUndefined: false });
|
|
2667
|
+
return projectCommitPayload(model.getModelName(), model.toJSON(), { dropUndefined: false }, this.runtime);
|
|
2607
2668
|
}
|
|
2608
2669
|
mapChangesToInput(modelName, changes) {
|
|
2609
|
-
return projectCommitPayload(modelName, changes, { dropUndefined: true });
|
|
2670
|
+
return projectCommitPayload(modelName, changes, { dropUndefined: true }, this.runtime);
|
|
2610
2671
|
}
|
|
2611
2672
|
extractUpdateData(model) {
|
|
2612
|
-
return projectCommitPayload(model.getModelName(), model.getChanges(), { dropUndefined: true });
|
|
2673
|
+
return projectCommitPayload(model.getModelName(), model.getChanges(), { dropUndefined: true }, this.runtime);
|
|
2613
2674
|
}
|
|
2614
2675
|
// Derive previous values for changed fields to support accurate rollback.
|
|
2615
2676
|
// Model-specific special cases do not belong here; a model that needs to
|
|
@@ -2624,7 +2685,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2624
2685
|
// inverse rather than inventing one. With no `updateInput` (a full extract)
|
|
2625
2686
|
// it falls back to every tracked field. `Model.capturePreviousValues` is the
|
|
2626
2687
|
// single before-image source, shared with
|
|
2627
|
-
// `
|
|
2688
|
+
// `RecordingMutation.snapshotFields`.
|
|
2628
2689
|
const keys = updateInput
|
|
2629
2690
|
? Object.keys(updateInput)
|
|
2630
2691
|
: [...(model.modifiedProperties instanceof Map ? model.modifiedProperties.keys() : [])];
|