@abloatai/ablo 0.34.1 → 0.35.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 +674 -5
- package/README.md +39 -22
- package/dist/BaseSyncedStore.d.ts +152 -44
- package/dist/BaseSyncedStore.js +300 -184
- package/dist/Database.d.ts +9 -24
- package/dist/Database.js +37 -22
- package/dist/InstanceCache.d.ts +25 -4
- package/dist/InstanceCache.js +48 -15
- package/dist/LazyReferenceCollection.d.ts +3 -3
- package/dist/LazyReferenceCollection.js +4 -4
- package/dist/Model.d.ts +6 -6
- package/dist/Model.js +10 -10
- package/dist/ModelRegistry.d.ts +4 -4
- package/dist/ModelRegistry.js +3 -3
- package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
- package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
- package/dist/SyncClient.d.ts +42 -32
- package/dist/SyncClient.js +166 -110
- 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/cli.cjs +3344 -1073
- package/dist/client/Ablo.d.ts +42 -287
- package/dist/client/Ablo.js +118 -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 +4 -4
- package/dist/client/createInternalComponents.js +9 -8
- package/dist/client/createModelProxy.d.ts +78 -373
- package/dist/client/createModelProxy.js +114 -86
- package/dist/client/humans.d.ts +48 -0
- package/dist/client/humans.js +52 -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 +48 -0
- package/dist/client/reactiveEngine.js +910 -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/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 +9 -9
- package/dist/context.js +10 -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/DatabaseManager.js +1 -1
- package/dist/core/QueryView.d.ts +1 -1
- package/dist/core/QueryView.js +1 -1
- package/dist/core/StoreManager.d.ts +4 -23
- package/dist/core/StoreManager.js +5 -55
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/storeContract.d.ts +2 -2
- package/dist/docs/catalog.d.ts +72 -0
- package/dist/docs/catalog.js +227 -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 +40 -34
- package/dist/index.js +26 -20
- 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 +2 -2
- package/dist/query/client.js +4 -4
- package/dist/query/types.d.ts +6 -41
- package/dist/query/types.js +2 -2
- package/dist/react/AbloProvider.d.ts +6 -8
- package/dist/react/AbloProvider.js +5 -7
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js +1 -1
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.js +3 -3
- package/dist/react/internalContext.d.ts +1 -1
- package/dist/react/useAblo.d.ts +3 -3
- package/dist/react/useAblo.js +1 -1
- 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 +86 -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 +10 -10
- package/dist/schema/index.js +18 -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 +3 -3
- package/dist/schema/serialize.d.ts +15 -6
- package/dist/schema/serialize.js +17 -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/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/footprint.d.ts +111 -0
- package/dist/source/footprint.js +0 -0
- 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/stores/ObjectStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.js +2 -10
- 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 +123 -6
- package/dist/sync/BootstrapFetcher.js +492 -66
- package/dist/sync/ConnectionManager.d.ts +6 -198
- package/dist/sync/ConnectionManager.js +6 -677
- package/dist/sync/OnDemandLoader.d.ts +2 -2
- package/dist/sync/OnDemandLoader.js +60 -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/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 +1 -1
- package/dist/sync/participants.d.ts +5 -4
- package/dist/sync/participants.js +29 -22
- package/dist/sync/schemaDrift.d.ts +55 -0
- package/dist/sync/schemaDrift.js +53 -0
- package/dist/sync/schemas.d.ts +21 -32
- package/dist/sync/schemas.js +26 -17
- 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/testing/fixtures/bootstrap.d.ts +1 -1
- package/dist/testing/fixtures/deltas.d.ts +1 -1
- package/dist/testing/fixtures/httpResponses.d.ts +70 -0
- package/dist/testing/fixtures/httpResponses.js +90 -0
- package/dist/testing/fixtures/models.js +1 -1
- package/dist/testing/helpers/wait.js +1 -1
- package/dist/testing/mocks/MockMutationExecutor.d.ts +2 -2
- package/dist/testing/mocks/MockMutationExecutor.js +8 -14
- package/dist/testing/mocks/MockSyncContext.d.ts +11 -11
- package/dist/testing/mocks/MockSyncContext.js +10 -9
- package/dist/testing/mocks/MockSyncStore.js +1 -1
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
- 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 +177 -0
- package/dist/transaction/auth/capability.js +199 -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 +49 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +112 -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 +44 -0
- package/dist/transaction/coordination/locator.d.ts +83 -0
- package/dist/transaction/coordination/locator.js +82 -0
- package/dist/transaction/coordination/schema.d.ts +1473 -0
- package/dist/{coordination → transaction/coordination}/schema.js +490 -55
- package/dist/transaction/coordination/targetConflict.d.ts +2 -0
- package/dist/transaction/coordination/targetConflict.js +103 -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} +11 -11
- package/dist/{errorCodes.js → transaction/errorCodes.js} +35 -12
- package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
- package/dist/{errors.js → transaction/errors.js} +85 -16
- 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 +192 -0
- package/dist/transaction/plugin.js +87 -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 +266 -0
- package/dist/transaction/resources/httpResources.js +7 -0
- package/dist/transaction/resources/modelOperations.d.ts +319 -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 +85 -0
- package/dist/transaction/resources/where.js +70 -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 +5 -5
- package/dist/{schema → transaction/schema}/field.js +5 -5
- 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 +57 -0
- package/dist/transaction/schema/openapi.js +340 -0
- package/dist/{schema → transaction/schema}/relation.d.ts +14 -14
- 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 +12 -10
- package/dist/{schema → transaction/schema}/schema.js +4 -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 +4 -5
- 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} +10 -36
- 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/{client → transaction/transport}/httpClient.d.ts +59 -16
- package/dist/{client → transaction/transport}/httpClient.js +5 -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 +171 -85
- 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 +576 -0
- package/dist/transaction/transport/wsTransport.js +1017 -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 +63 -45
- 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 +351 -0
- package/dist/transaction/wire/accountResponses.js +255 -0
- package/dist/transaction/wire/auth.d.ts +49 -0
- package/dist/transaction/wire/auth.js +57 -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 +463 -0
- package/dist/transaction/wire/claims.js +229 -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 +177 -0
- package/dist/transaction/wire/feedEvent.js +39 -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 +552 -0
- package/dist/transaction/wire/inboundFrames.js +116 -0
- package/dist/transaction/wire/index.d.ts +50 -0
- package/dist/transaction/wire/index.js +74 -0
- package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
- package/dist/transaction/wire/listEnvelope.js +42 -0
- package/dist/transaction/wire/modelResponses.d.ts +85 -0
- package/dist/transaction/wire/modelResponses.js +43 -0
- package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +79 -38
- package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +110 -59
- 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} +16 -15
- package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +12 -12
- package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +11 -11
- package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +7 -7
- 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} +3 -3
- package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +4 -3
- package/dist/utils/mobxSetup.d.ts +1 -1
- package/dist/utils/mobxSetup.js +5 -2
- 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 +19 -12
- package/docs/api-keys.md +8 -4
- package/docs/api.md +22 -18
- package/docs/audit.md +2 -0
- package/docs/cli.md +31 -3
- package/docs/client-behavior.md +8 -6
- package/docs/concurrency-convention.md +30 -24
- package/docs/coordination.md +48 -38
- package/docs/data-sources.md +3 -1
- package/docs/debugging.md +5 -3
- 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 +45 -44
- package/docs/examples/server-agent.md +46 -26
- package/docs/groups.md +32 -29
- package/docs/guarantees.md +4 -2
- package/docs/how-it-works.md +9 -7
- package/docs/idempotency.md +126 -0
- package/docs/identity.md +58 -54
- package/docs/index.md +172 -86
- package/docs/integration-guide.md +17 -16
- package/docs/interaction-model.md +6 -4
- package/docs/mcp.md +41 -16
- package/docs/migration.md +63 -5
- package/docs/operating-on-your-database.md +3 -1
- package/docs/projects.md +2 -0
- package/docs/quickstart.md +22 -5
- package/docs/react.md +12 -10
- package/docs/schema-contract.md +5 -3
- package/docs/session-settings.md +108 -0
- package/docs/sessions.md +3 -1
- package/docs/webhooks.md +3 -1
- package/llms.txt +47 -17
- package/package.json +10 -8
- 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/client/durableWrites.d.ts +0 -21
- package/dist/coordination/schema.d.ts +0 -722
- package/dist/schema/openapi.d.ts +0 -29
- package/dist/schema/openapi.js +0 -124
- package/dist/transactions/durableWriteStore.js +0 -30
- 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/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/coordination}/claimHeartbeatLoop.d.ts +0 -0
- /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.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/{utils → transaction/utils}/duration.d.ts +0 -0
- /package/dist/{utils → transaction/utils}/duration.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
|
@@ -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 { getContext } from '
|
|
18
|
-
import { AbloError, AbloConnectionError, AbloIdempotencyError, AbloNotFoundError, AbloValidationError, errorCodeSpec, } from '
|
|
19
|
-
import {
|
|
20
|
-
import { mutationCommitResultSchema, } from '
|
|
16
|
+
import { Model } from '../../Model.js';
|
|
17
|
+
import { getContext } 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
|
|
@@ -100,7 +100,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
100
100
|
this.emit(event, payload);
|
|
101
101
|
}
|
|
102
102
|
catch (error) {
|
|
103
|
-
getContext().observability.
|
|
103
|
+
getContext().observability.captureMutationFailure({
|
|
104
104
|
context: `commit-lifecycle-listener:${event}`,
|
|
105
105
|
error: error instanceof Error ? error : String(error),
|
|
106
106
|
});
|
|
@@ -114,8 +114,14 @@ export class TransactionQueue extends EventEmitter {
|
|
|
114
114
|
this.assertDurableReplayOpen();
|
|
115
115
|
if (envelope.acceptedAt === undefined &&
|
|
116
116
|
Date.now() - envelope.sealedAt >=
|
|
117
|
-
|
|
117
|
+
MutationQueue.DURABLE_REPLAY_WINDOW_MS) {
|
|
118
118
|
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' });
|
|
119
|
+
// This gate stops EVERY subsequent write on this client, and each of
|
|
120
|
+
// those rejections is captured to observability rather than surfaced to
|
|
121
|
+
// the caller — without this line the session degrades into "nothing
|
|
122
|
+
// saves and nothing errors". One loud line at the moment the block
|
|
123
|
+
// engages is the only visible trace.
|
|
124
|
+
getContext().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
125
|
throw this.durableReplayBlock;
|
|
120
126
|
}
|
|
121
127
|
}
|
|
@@ -256,6 +262,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
256
262
|
deltaConfirmationTimeout: 30000,
|
|
257
263
|
retryBackoff: { baseMs: 200, capMs: 1500 },
|
|
258
264
|
commitOfflineGraceMs: 30_000,
|
|
265
|
+
commitDispatchTimeoutMs: 30_000,
|
|
259
266
|
};
|
|
260
267
|
// Track executing transactions for backpressure
|
|
261
268
|
executingCount = 0;
|
|
@@ -264,7 +271,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
264
271
|
// completion paths, `getStats`, and `dispose` all read it.
|
|
265
272
|
optimisticUpdates = new Map();
|
|
266
273
|
// Stale-context notifications, keyed by transaction id. When the server
|
|
267
|
-
// accepts a commit but reports that an operation's
|
|
274
|
+
// accepts a commit but reports that an operation's premise had moved,
|
|
268
275
|
// the notification lands here from the commit acknowledgement and is drained
|
|
269
276
|
// by `waitForCommitReceipt`, so the receipt can carry it back to the caller.
|
|
270
277
|
commitNotifications = new Map();
|
|
@@ -287,7 +294,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
287
294
|
* shared: the client injects one, and a standalone queue creates its own. The
|
|
288
295
|
* queue advances the `acked` cursor as commit responses arrive, the store
|
|
289
296
|
* advances `applied` and `persisted`, and snapshots and claims read
|
|
290
|
-
* `readFloor`. See `../
|
|
297
|
+
* `readFloor`. See `../logPosition.js` for the full contract.
|
|
291
298
|
*/
|
|
292
299
|
position;
|
|
293
300
|
/** Applied-cursor alias, kept so the many internal read sites stay legible. */
|
|
@@ -390,9 +397,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
390
397
|
operations: [...input.operations],
|
|
391
398
|
sourceMutationIds,
|
|
392
399
|
commitOptions: {
|
|
393
|
-
...(input.commitOptions?.causedByTaskId !== undefined
|
|
394
|
-
? { causedByTaskId: input.commitOptions.causedByTaskId }
|
|
395
|
-
: {}),
|
|
396
400
|
...(input.commitOptions?.reads !== undefined
|
|
397
401
|
? {
|
|
398
402
|
reads: input.commitOptions.reads === null
|
|
@@ -440,7 +444,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
440
444
|
await this.commitOutbox.remove(commitEnvelopeRecordId(idempotencyKey));
|
|
441
445
|
}
|
|
442
446
|
catch (error) {
|
|
443
|
-
getContext().logger.debug('[
|
|
447
|
+
getContext().logger.debug('[MutationQueue] Durable-write cleanup deferred', {
|
|
444
448
|
idempotencyKey,
|
|
445
449
|
error: error instanceof Error ? error.message : String(error),
|
|
446
450
|
});
|
|
@@ -489,6 +493,35 @@ export class TransactionQueue extends EventEmitter {
|
|
|
489
493
|
return parsed.data;
|
|
490
494
|
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
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Dispatch a sealed envelope to the transport, bounded by
|
|
498
|
+
* `commitDispatchTimeoutMs`. Every executor call site routes through here:
|
|
499
|
+
* a transport that never answers must become a retryable no-receipt failure
|
|
500
|
+
* (the same class as a malformed receipt) instead of an eternally in-flight
|
|
501
|
+
* commit, because an unanswered commit holds the staged-batch lock and
|
|
502
|
+
* silently stalls every later write in the session. The abandoned commit's
|
|
503
|
+
* eventual result, if it ever arrives, is discarded; the retry re-sends the
|
|
504
|
+
* identical idempotency key, so the server deduplicates an already-applied
|
|
505
|
+
* write.
|
|
506
|
+
*/
|
|
507
|
+
dispatchCommitBounded(...args) {
|
|
508
|
+
const timeoutMs = this.config.commitDispatchTimeoutMs;
|
|
509
|
+
const dispatched = this.mutationExecutor.commit(...args);
|
|
510
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
511
|
+
return dispatched;
|
|
512
|
+
return new Promise((resolve, reject) => {
|
|
513
|
+
const timer = setTimeout(() => {
|
|
514
|
+
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' }));
|
|
515
|
+
}, timeoutMs);
|
|
516
|
+
dispatched.then((value) => {
|
|
517
|
+
clearTimeout(timer);
|
|
518
|
+
resolve(value);
|
|
519
|
+
}, (error) => {
|
|
520
|
+
clearTimeout(timer);
|
|
521
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
}
|
|
492
525
|
clearReplicationLagState(transactionId) {
|
|
493
526
|
const timeout = this.replicationLagTimeouts.get(transactionId);
|
|
494
527
|
if (timeout)
|
|
@@ -620,13 +653,13 @@ export class TransactionQueue extends EventEmitter {
|
|
|
620
653
|
* Resolvers for per-transaction `confirmation` promises. Populated in
|
|
621
654
|
* `attachConfirmation` at staging time, consumed by the constructor-time
|
|
622
655
|
* listeners on `transaction:completed` / `transaction:failed`. Kept off
|
|
623
|
-
* the
|
|
656
|
+
* the QueuedMutation row so the store's iteration order stays plain-data
|
|
624
657
|
* and serialization-friendly.
|
|
625
658
|
*/
|
|
626
659
|
confirmationResolvers = new Map();
|
|
627
660
|
constructor(config) {
|
|
628
661
|
super();
|
|
629
|
-
this.position = config?.position ?? new
|
|
662
|
+
this.position = config?.position ?? new LogPosition();
|
|
630
663
|
// Bind the confirmation tracker to this queue's store/ledger/events.
|
|
631
664
|
// `isConnected` closes over `isConnectedFn` so `setConnectionChecker`
|
|
632
665
|
// swaps stay visible to in-flight timeouts.
|
|
@@ -683,7 +716,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
683
716
|
* be rolled back — `pending`, `executing`, and `awaiting_delta` — and ignores
|
|
684
717
|
* `completed` (already settled) and `failed`/`rolled_back` (already
|
|
685
718
|
* rejected). This complements the `confirmation` promise carried on a known
|
|
686
|
-
* {@link
|
|
719
|
+
* {@link QueuedMutation}: use this method at call sites that hold a model
|
|
687
720
|
* returned by `ablo.<model>.create()` but never see the underlying
|
|
688
721
|
* transaction.
|
|
689
722
|
*/
|
|
@@ -771,7 +804,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
771
804
|
return;
|
|
772
805
|
// Each failed commit reaches the consumer through its own rejection path,
|
|
773
806
|
// so this aggregate line is forensic and logged at debug rather than warn.
|
|
774
|
-
getContext().logger.debug(`[
|
|
807
|
+
getContext().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
808
|
for (const id of inFlight) {
|
|
776
809
|
const tx = this.commitStore.get(id);
|
|
777
810
|
if (!tx)
|
|
@@ -844,7 +877,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
844
877
|
this.batchIndex++;
|
|
845
878
|
const currentBatchIndex = this.batchIndex;
|
|
846
879
|
// Log batch commit for performance monitoring
|
|
847
|
-
getContext().logger.debug('[
|
|
880
|
+
getContext().logger.debug('[MutationQueue] commitCreatedTransactions', {
|
|
848
881
|
count: this.createdTransactions.length,
|
|
849
882
|
batchIndex: currentBatchIndex,
|
|
850
883
|
types: this.createdTransactions.map((t) => `${t.type}:${t.modelName}`),
|
|
@@ -907,7 +940,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
907
940
|
sequence: batch[0]?.commitEnvelope?.sequence,
|
|
908
941
|
});
|
|
909
942
|
this.assertEnvelopeInsideReplayWindow(durableEnvelope);
|
|
910
|
-
const result = this.parseMutationCommitResult(await this.
|
|
943
|
+
const result = this.parseMutationCommitResult(await this.dispatchCommitBounded(durableEnvelope.operations, {
|
|
911
944
|
idempotencyKey,
|
|
912
945
|
}));
|
|
913
946
|
await this.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
@@ -976,7 +1009,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
976
1009
|
}
|
|
977
1010
|
/**
|
|
978
1011
|
* Records a create and applies it optimistically, then stages it for the next
|
|
979
|
-
* batched commit. Returns the {@link
|
|
1012
|
+
* batched commit. Returns the {@link QueuedMutation}, whose `confirmation`
|
|
980
1013
|
* promise settles once the server confirms the write.
|
|
981
1014
|
*/
|
|
982
1015
|
async create(model, context, writeOptions, sourceMutationId) {
|
|
@@ -1080,7 +1113,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1080
1113
|
const actualModelName = model.getModelName();
|
|
1081
1114
|
// Skip Activity delete transactions - activities are permanent audit records
|
|
1082
1115
|
if (actualModelName === 'Activity') {
|
|
1083
|
-
getContext().logger.debug('
|
|
1116
|
+
getContext().logger.debug('MutationQueue.delete() skipping Activity deletion - permanent audit records', { modelId: model.id });
|
|
1084
1117
|
const modelKey = normalizeModelKey(actualModelName);
|
|
1085
1118
|
const priorityScore = this.computePriorityScore('delete', actualModelName);
|
|
1086
1119
|
const mockTransaction = {
|
|
@@ -1319,7 +1352,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1319
1352
|
// are already executing, so the server is not flooded with concurrent
|
|
1320
1353
|
// requests.
|
|
1321
1354
|
if (this.executingCount >= this.config.maxExecutingTransactions) {
|
|
1322
|
-
getContext().logger.debug('[
|
|
1355
|
+
getContext().logger.debug('[MutationQueue] Backpressure: delaying batch, too many executing', {
|
|
1323
1356
|
executingCount: this.executingCount,
|
|
1324
1357
|
max: this.config.maxExecutingTransactions,
|
|
1325
1358
|
});
|
|
@@ -1432,7 +1465,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1432
1465
|
// the exact key assigned before the first transport attempt.
|
|
1433
1466
|
this.assertEnvelopeInsideReplayWindow(durableEnvelope);
|
|
1434
1467
|
dispatchStarted = true;
|
|
1435
|
-
const result = this.parseMutationCommitResult(await this.
|
|
1468
|
+
const result = this.parseMutationCommitResult(await this.dispatchCommitBounded(operations, {
|
|
1436
1469
|
idempotencyKey: commitIdempotencyKey,
|
|
1437
1470
|
}));
|
|
1438
1471
|
await this.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
@@ -1603,7 +1636,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1603
1636
|
// authoritative `warn` with the same typed cause) — passes
|
|
1604
1637
|
// through here. Logging it at `warn` made one rejected write
|
|
1605
1638
|
// surface three identical dumps; keep it at `debug`.
|
|
1606
|
-
getContext().logger.debug('[
|
|
1639
|
+
getContext().logger.debug('[MutationQueue] Batch commit rejected', {
|
|
1607
1640
|
batchSize: batchOps.length,
|
|
1608
1641
|
models: batchOps.map(({ op }) => `${op.type}:${op.model}`),
|
|
1609
1642
|
errorType: abloErr?.type ?? error?.name,
|
|
@@ -1621,7 +1654,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1621
1654
|
if (dispatchStarted) {
|
|
1622
1655
|
await this.removeDurableCommit(commitIdempotencyKey);
|
|
1623
1656
|
}
|
|
1624
|
-
getContext().logger.info('[
|
|
1657
|
+
getContext().logger.info('[MutationQueue] Graceful handling: entity already deleted', {
|
|
1625
1658
|
batchSize: batchOps.length,
|
|
1626
1659
|
});
|
|
1627
1660
|
for (const { tx, op } of batchOps) {
|
|
@@ -1629,7 +1662,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1629
1662
|
// Row already gone: the intended state holds, mark completed.
|
|
1630
1663
|
this.store.updateStatus(tx.id, 'completed');
|
|
1631
1664
|
this.emit('transaction:completed', tx);
|
|
1632
|
-
getContext().logger.debug('[
|
|
1665
|
+
getContext().logger.debug('[MutationQueue] Orphaned transaction treated as success', {
|
|
1633
1666
|
txId: tx.id.slice(0, 12),
|
|
1634
1667
|
model: tx.modelName,
|
|
1635
1668
|
type: op.type,
|
|
@@ -1834,13 +1867,11 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1834
1867
|
await existing.sealPromise;
|
|
1835
1868
|
const existingIntent = stableStringify({
|
|
1836
1869
|
operations: existing.operations,
|
|
1837
|
-
causedByTaskId: existing.causedByTaskId ?? null,
|
|
1838
1870
|
reads: existing.reads ?? null,
|
|
1839
1871
|
track: existing.track ?? null,
|
|
1840
1872
|
});
|
|
1841
1873
|
const incomingIntent = stableStringify({
|
|
1842
1874
|
operations,
|
|
1843
|
-
causedByTaskId: options.causedByTaskId ?? null,
|
|
1844
1875
|
reads: options.reads ?? null,
|
|
1845
1876
|
track: options.track ?? null,
|
|
1846
1877
|
});
|
|
@@ -1871,7 +1902,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1871
1902
|
id: clientTxId,
|
|
1872
1903
|
kind: 'commit',
|
|
1873
1904
|
operations: [...operations],
|
|
1874
|
-
causedByTaskId: options.causedByTaskId ?? null,
|
|
1875
1905
|
...(options.reads ? { reads: options.reads } : {}),
|
|
1876
1906
|
...(options.track ? { track: options.track } : {}),
|
|
1877
1907
|
status: 'pending',
|
|
@@ -1886,7 +1916,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1886
1916
|
origin: 'atomic_commit',
|
|
1887
1917
|
operations: tx.operations,
|
|
1888
1918
|
commitOptions: {
|
|
1889
|
-
causedByTaskId: tx.causedByTaskId ?? null,
|
|
1890
1919
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
1891
1920
|
...(tx.track ? { track: tx.track } : {}),
|
|
1892
1921
|
},
|
|
@@ -1950,7 +1979,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1950
1979
|
operations: tx.operations,
|
|
1951
1980
|
sourceMutationIds: tx.sourceMutationIds,
|
|
1952
1981
|
commitOptions: {
|
|
1953
|
-
causedByTaskId: tx.causedByTaskId ?? null,
|
|
1954
1982
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
1955
1983
|
...(tx.track ? { track: tx.track } : {}),
|
|
1956
1984
|
},
|
|
@@ -1961,9 +1989,8 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1961
1989
|
tx.durableEnvelope = durableEnvelope;
|
|
1962
1990
|
this.assertEnvelopeInsideReplayWindow(durableEnvelope);
|
|
1963
1991
|
dispatchStarted = true;
|
|
1964
|
-
const result = this.parseMutationCommitResult(await this.
|
|
1992
|
+
const result = this.parseMutationCommitResult(await this.dispatchCommitBounded(durableEnvelope.operations, {
|
|
1965
1993
|
idempotencyKey: tx.id,
|
|
1966
|
-
causedByTaskId: durableEnvelope.commitOptions.causedByTaskId ?? undefined,
|
|
1967
1994
|
...(durableEnvelope.commitOptions.reads
|
|
1968
1995
|
? { reads: durableEnvelope.commitOptions.reads }
|
|
1969
1996
|
: {}),
|
|
@@ -1991,7 +2018,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
1991
2018
|
}
|
|
1992
2019
|
else {
|
|
1993
2020
|
this.scheduleReplicationLagTimeout(tx.id, tx.id, result.correlationId);
|
|
1994
|
-
getContext().logger.debug('[
|
|
2021
|
+
getContext().logger.debug('[MutationQueue] commit lane awaiting source echo', {
|
|
1995
2022
|
txId: tx.id.slice(0, 12),
|
|
1996
2023
|
});
|
|
1997
2024
|
}
|
|
@@ -2012,14 +2039,29 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2012
2039
|
if (dispatchStarted && this.isDefinitiveRejection(error)) {
|
|
2013
2040
|
await this.removeDurableCommit(tx.id);
|
|
2014
2041
|
}
|
|
2015
|
-
|
|
2042
|
+
// A transport that is DOWN is not a failing write: the envelope is
|
|
2043
|
+
// meant to wait for reconnect, which is what makes a commit survive a
|
|
2044
|
+
// dropped connection. A transient error the SERVER keeps returning is
|
|
2045
|
+
// a different thing, and this lane has no attempt bound of its own —
|
|
2046
|
+
// so a 5xx carrying no wire code reads as transient on every kick,
|
|
2047
|
+
// sits at the head of the lane, and `waitForCommitReceipt` never
|
|
2048
|
+
// settles. The caller sees a write that neither lands nor fails.
|
|
2049
|
+
//
|
|
2050
|
+
// Counting only the non-connection failures keeps offline waiting
|
|
2051
|
+
// unbounded while giving a repeating server rejection an end.
|
|
2052
|
+
if (!(error instanceof AbloConnectionError)) {
|
|
2053
|
+
tx.transientAttempts = (tx.transientAttempts ?? 0) + 1;
|
|
2054
|
+
}
|
|
2055
|
+
const exhausted = (tx.transientAttempts ?? 0) > this.config.maxRetries;
|
|
2056
|
+
if (!this.isPermanentError(error) && !exhausted) {
|
|
2016
2057
|
// Transient: leave it at the head and retry on the next kick
|
|
2017
2058
|
// (reconnect or the next enqueueCommit) rather than tight-looping
|
|
2018
2059
|
// while the connection is down.
|
|
2019
2060
|
tx.status = 'pending';
|
|
2020
|
-
getContext().logger.debug('[
|
|
2061
|
+
getContext().logger.debug('[MutationQueue] commit lane transient', {
|
|
2021
2062
|
txId: tx.id.slice(0, 12),
|
|
2022
2063
|
attempts: tx.attempts,
|
|
2064
|
+
transientAttempts: tx.transientAttempts ?? 0,
|
|
2023
2065
|
message: error.message,
|
|
2024
2066
|
});
|
|
2025
2067
|
break;
|
|
@@ -2030,7 +2072,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2030
2072
|
// Internal bookkeeping; the consumer-facing rejection is emitted on
|
|
2031
2073
|
// 'transaction:failed' and surfaced by the permanent-error headline,
|
|
2032
2074
|
// so this line stays at debug.
|
|
2033
|
-
getContext().logger.debug('[
|
|
2075
|
+
getContext().logger.debug('[MutationQueue] commit lane permanent error', {
|
|
2034
2076
|
txId: tx.id.slice(0, 12),
|
|
2035
2077
|
attempts: tx.attempts,
|
|
2036
2078
|
message: error.message,
|
|
@@ -2256,7 +2298,17 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2256
2298
|
: '';
|
|
2257
2299
|
const reason = abloErr?.message ? ` — ${abloErr.message}` : '';
|
|
2258
2300
|
const code = abloErr?.code ? ` (code: ${abloErr.code})` : '';
|
|
2259
|
-
|
|
2301
|
+
// An optimistic write resolves before the server answers, so a later
|
|
2302
|
+
// rejection has no caller left to return to and this log is the only
|
|
2303
|
+
// place it appears. That reads to an application developer as their own
|
|
2304
|
+
// save silently failing — the write showed, then vanished — and sends
|
|
2305
|
+
// them into their editor instead of here. Name the subscription that
|
|
2306
|
+
// hands them the same typed error, so the application can say what
|
|
2307
|
+
// happened rather than only the console.
|
|
2308
|
+
const channelNote = this.config.enableOptimistic
|
|
2309
|
+
? ' To surface this in your app, subscribe with `ablo.onMutationFailure(…)`.'
|
|
2310
|
+
: '';
|
|
2311
|
+
const headline = `Your ${transaction.type} to "${transaction.modelName}" was not saved${reason}${code}.${revertNote}${channelNote}`;
|
|
2260
2312
|
if (isRepeat) {
|
|
2261
2313
|
// Same write rejected for the same reason on each reconnect replay —
|
|
2262
2314
|
// log the forensics once, stay quiet after.
|
|
@@ -2388,7 +2440,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2388
2440
|
}
|
|
2389
2441
|
}
|
|
2390
2442
|
catch (error) {
|
|
2391
|
-
getContext().observability.
|
|
2443
|
+
getContext().observability.captureMutationFailure({
|
|
2392
2444
|
context: 'load-persisted-transactions',
|
|
2393
2445
|
error: error instanceof Error ? error : String(error),
|
|
2394
2446
|
});
|
|
@@ -2419,7 +2471,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2419
2471
|
}
|
|
2420
2472
|
else {
|
|
2421
2473
|
getContext().logger.warn('A saved local write is unreadable and was held for review.');
|
|
2422
|
-
getContext().observability.
|
|
2474
|
+
getContext().observability.captureMutationFailure({
|
|
2423
2475
|
context: 'restore-commit-envelope',
|
|
2424
2476
|
error: parsed.error,
|
|
2425
2477
|
});
|
|
@@ -2435,9 +2487,9 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2435
2487
|
}
|
|
2436
2488
|
if (envelope.acceptedAt === undefined &&
|
|
2437
2489
|
Date.now() - envelope.sealedAt >=
|
|
2438
|
-
|
|
2490
|
+
MutationQueue.DURABLE_REPLAY_WINDOW_MS) {
|
|
2439
2491
|
getContext().logger.warn('A saved local write is too old to retry safely and was held for review.');
|
|
2440
|
-
getContext().observability.
|
|
2492
|
+
getContext().observability.captureMutationFailure({
|
|
2441
2493
|
context: 'quarantine-expired-commit-envelope',
|
|
2442
2494
|
error: `Envelope ${envelope.idempotencyKey} is too old to replay safely`,
|
|
2443
2495
|
});
|
|
@@ -2457,7 +2509,6 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2457
2509
|
id: envelope.idempotencyKey,
|
|
2458
2510
|
kind: 'commit',
|
|
2459
2511
|
operations: envelope.operations.map((operation) => ({ ...operation })),
|
|
2460
|
-
causedByTaskId: envelope.commitOptions.causedByTaskId ?? null,
|
|
2461
2512
|
...(envelope.commitOptions.reads
|
|
2462
2513
|
? { reads: [...envelope.commitOptions.reads] }
|
|
2463
2514
|
: {}),
|
|
@@ -2482,10 +2533,10 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2482
2533
|
void this.processCommitLane();
|
|
2483
2534
|
}
|
|
2484
2535
|
catch (error) {
|
|
2485
|
-
getContext().logger.debug('[
|
|
2536
|
+
getContext().logger.debug('[MutationQueue] Failed to restore durable writes', {
|
|
2486
2537
|
error: error instanceof Error ? error.message : String(error),
|
|
2487
2538
|
});
|
|
2488
|
-
getContext().observability.
|
|
2539
|
+
getContext().observability.captureMutationFailure({
|
|
2489
2540
|
context: 'restore-commit-envelopes',
|
|
2490
2541
|
error: error instanceof Error ? error : String(error),
|
|
2491
2542
|
});
|
|
@@ -2507,10 +2558,10 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2507
2558
|
const rowId = typeof data === 'object' && data !== null && typeof data.id === 'string'
|
|
2508
2559
|
? data.id
|
|
2509
2560
|
: undefined;
|
|
2510
|
-
getContext().logger.debug('[
|
|
2561
|
+
getContext().logger.debug('[MutationQueue] Dropping malformed persisted transaction', {
|
|
2511
2562
|
rowId,
|
|
2512
2563
|
});
|
|
2513
|
-
getContext().observability.
|
|
2564
|
+
getContext().observability.captureMutationFailure({
|
|
2514
2565
|
context: 'deserialize-persisted-transaction',
|
|
2515
2566
|
error: `Persisted transaction failed schema validation${rowId ? ` (id: ${rowId})` : ''}`,
|
|
2516
2567
|
});
|
|
@@ -2538,7 +2589,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2538
2589
|
// listener) must surface, not vanish — the status flip above is
|
|
2539
2590
|
// already committed either way.
|
|
2540
2591
|
void this.rollbackOptimistic(transaction, 'model_cancelled').catch((error) => {
|
|
2541
|
-
getContext().observability.
|
|
2592
|
+
getContext().observability.captureMutationFailure({
|
|
2542
2593
|
context: 'rollback-model-cancelled',
|
|
2543
2594
|
error: error instanceof Error ? error : String(error),
|
|
2544
2595
|
});
|
|
@@ -2553,8 +2604,8 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2553
2604
|
* used to cascade a parent deletion. The caller supplies the foreign-key
|
|
2554
2605
|
* relationship; this method performs the cancellation.
|
|
2555
2606
|
*
|
|
2556
|
-
* @param childModelName - The child model type (for example '
|
|
2557
|
-
* @param foreignKey - The foreign-key property name (for example '
|
|
2607
|
+
* @param childModelName - The child model type (for example 'Block').
|
|
2608
|
+
* @param foreignKey - The foreign-key property name (for example 'sectionId').
|
|
2558
2609
|
* @param parentId - The deleted parent's id.
|
|
2559
2610
|
* @returns The number of transactions cancelled.
|
|
2560
2611
|
*/
|
|
@@ -2572,13 +2623,13 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2572
2623
|
if (fkValue === parentId) {
|
|
2573
2624
|
this.store.updateStatus(transaction.id, 'rolled_back');
|
|
2574
2625
|
void this.rollbackOptimistic(transaction, 'cascade_parent_deleted').catch((error) => {
|
|
2575
|
-
getContext().observability.
|
|
2626
|
+
getContext().observability.captureMutationFailure({
|
|
2576
2627
|
context: 'rollback-cascade-parent-deleted',
|
|
2577
2628
|
error: error instanceof Error ? error : String(error),
|
|
2578
2629
|
});
|
|
2579
2630
|
});
|
|
2580
2631
|
cancelled++;
|
|
2581
|
-
getContext().logger.debug('[
|
|
2632
|
+
getContext().logger.debug('[MutationQueue] Cascade cancelled orphaned transaction', {
|
|
2582
2633
|
txId: transaction.id.slice(0, 12),
|
|
2583
2634
|
model: childModelName,
|
|
2584
2635
|
foreignKey,
|
|
@@ -2624,7 +2675,7 @@ export class TransactionQueue extends EventEmitter {
|
|
|
2624
2675
|
// inverse rather than inventing one. With no `updateInput` (a full extract)
|
|
2625
2676
|
// it falls back to every tracked field. `Model.capturePreviousValues` is the
|
|
2626
2677
|
// single before-image source, shared with
|
|
2627
|
-
// `
|
|
2678
|
+
// `RecordingMutation.snapshotFields`.
|
|
2628
2679
|
const keys = updateInput
|
|
2629
2680
|
? Object.keys(updateInput)
|
|
2630
2681
|
: [...(model.modifiedProperties instanceof Map ? model.modifiedProperties.keys() : [])];
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* The status index keeps the queue's hot paths — such as `getByStatus('pending')`
|
|
4
4
|
* on every batch and coalesce decision — proportional to the number of
|
|
5
5
|
* transactions in that status rather than the total across all statuses.
|
|
6
|
-
* {@link
|
|
6
|
+
* {@link MutationQueue} owns an instance and routes every status change
|
|
7
7
|
* through {@link updateStatus}, which keeps the two indexes consistent.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
10
|
-
export declare class
|
|
9
|
+
import type { QueuedMutation } from './commitPayload.js';
|
|
10
|
+
export declare class MutationStore {
|
|
11
11
|
private transactions;
|
|
12
12
|
private byStatus;
|
|
13
|
-
add(transaction:
|
|
14
|
-
get(id: string):
|
|
15
|
-
updateStatus(id: string, newStatus:
|
|
16
|
-
getByStatus(status:
|
|
13
|
+
add(transaction: QueuedMutation): void;
|
|
14
|
+
get(id: string): QueuedMutation | undefined;
|
|
15
|
+
updateStatus(id: string, newStatus: QueuedMutation['status']): void;
|
|
16
|
+
getByStatus(status: QueuedMutation['status']): QueuedMutation[];
|
|
17
17
|
remove(id: string): void;
|
|
18
18
|
clear(): void;
|
|
19
|
-
getAll():
|
|
19
|
+
getAll(): QueuedMutation[];
|
|
20
20
|
}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* The status index keeps the queue's hot paths — such as `getByStatus('pending')`
|
|
4
4
|
* on every batch and coalesce decision — proportional to the number of
|
|
5
5
|
* transactions in that status rather than the total across all statuses.
|
|
6
|
-
* {@link
|
|
6
|
+
* {@link MutationQueue} owns an instance and routes every status change
|
|
7
7
|
* through {@link updateStatus}, which keeps the two indexes consistent.
|
|
8
8
|
*/
|
|
9
|
-
export class
|
|
9
|
+
export class MutationStore {
|
|
10
10
|
transactions = new Map();
|
|
11
11
|
byStatus = new Map();
|
|
12
12
|
add(transaction) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The queue's rules for coalescing operations that touch the same row, so their
|
|
3
|
-
* causal order is preserved. {@link
|
|
3
|
+
* causal order is preserved. {@link MutationQueue} calls into these through a
|
|
4
4
|
* small store-shaped interface:
|
|
5
5
|
*
|
|
6
6
|
* - Create-then-delete cancellation ({@link takeUnsentCreateForModel}):
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
* The queue keeps the `enqueue` and `delete` methods that orchestrate optimistic
|
|
17
17
|
* state and events; these functions hold only the coalescing rules.
|
|
18
18
|
*/
|
|
19
|
-
import type { MutationInput,
|
|
20
|
-
/** The subset of {@link
|
|
21
|
-
export interface
|
|
22
|
-
get(id: string):
|
|
23
|
-
getByStatus(status:
|
|
19
|
+
import type { MutationInput, QueuedMutation } from './commitPayload.js';
|
|
20
|
+
/** The subset of {@link MutationStore} that the coalescing rules read. */
|
|
21
|
+
export interface MutationStoreLike {
|
|
22
|
+
get(id: string): QueuedMutation | undefined;
|
|
23
|
+
getByStatus(status: QueuedMutation['status']): QueuedMutation[];
|
|
24
24
|
}
|
|
25
25
|
export declare const entityKey: (modelName: string, modelId: string) => string;
|
|
26
26
|
/**
|
|
@@ -30,25 +30,25 @@ export declare const entityKey: (modelName: string, modelId: string) => string;
|
|
|
30
30
|
* held it, so the caller can cancel it rather than send a create followed by a
|
|
31
31
|
* delete.
|
|
32
32
|
*/
|
|
33
|
-
export declare function takeUnsentCreateForModel(staged:
|
|
33
|
+
export declare function takeUnsentCreateForModel(staged: QueuedMutation[], queued: QueuedMutation[], store: Pick<MutationStoreLike, 'getByStatus'>, modelName: string, modelId: string): QueuedMutation | undefined;
|
|
34
34
|
/**
|
|
35
35
|
* Returns the most recent in-flight create for the given model and id that a
|
|
36
36
|
* delete must wait behind, or undefined if there is none. A pending create that
|
|
37
37
|
* has never been attempted is not a barrier, because it can be cancelled
|
|
38
38
|
* instead; once a create has been sent, even a retry-pending one is a barrier.
|
|
39
39
|
*/
|
|
40
|
-
export declare function findCreateBarrierForDelete(store: Pick<
|
|
40
|
+
export declare function findCreateBarrierForDelete(store: Pick<MutationStoreLike, 'getByStatus'>, modelName: string, modelId: string): QueuedMutation | undefined;
|
|
41
41
|
/**
|
|
42
42
|
* Parks a delete until the create for the same row settles, keyed by the
|
|
43
43
|
* create's model and id. {@link releaseDeferredDeletesForCreate} re-enqueues
|
|
44
44
|
* the parked deletes once that create completes.
|
|
45
45
|
*/
|
|
46
|
-
export declare function deferDeleteUntilCreateSettles(deferredDeletesByCreate: Map<string,
|
|
46
|
+
export declare function deferDeleteUntilCreateSettles(deferredDeletesByCreate: Map<string, QueuedMutation[]>, createTransaction: QueuedMutation, deleteTransaction: QueuedMutation): void;
|
|
47
47
|
/**
|
|
48
48
|
* Re-enqueues the deletes parked behind a create once that create settles,
|
|
49
49
|
* skipping any whose status is no longer pending.
|
|
50
50
|
*/
|
|
51
|
-
export declare function releaseDeferredDeletesForCreate(deferredDeletesByCreate: Map<string,
|
|
51
|
+
export declare function releaseDeferredDeletesForCreate(deferredDeletesByCreate: Map<string, QueuedMutation[]>, store: Pick<MutationStoreLike, 'get'>, enqueue: (transaction: QueuedMutation) => void, createTransaction: QueuedMutation): void;
|
|
52
52
|
/**
|
|
53
53
|
* Merges two update payloads for the same row into one. Later values win, with
|
|
54
54
|
* one exception: a `metadata` field is deep-merged as an object — parsing it
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The queue's rules for coalescing operations that touch the same row, so their
|
|
3
|
-
* causal order is preserved. {@link
|
|
3
|
+
* causal order is preserved. {@link MutationQueue} calls into these through a
|
|
4
4
|
* small store-shaped interface:
|
|
5
5
|
*
|
|
6
6
|
* - Create-then-delete cancellation ({@link takeUnsentCreateForModel}):
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit latency — how long a user's edit actually takes to land.
|
|
3
|
+
*
|
|
4
|
+
* The engine has never measured this. HUDs and dashboards reach for
|
|
5
|
+
* `window.fetch` timings, which the sync engine's WebSocket never touches, so
|
|
6
|
+
* the latency a user sees reported while editing has had nothing to do with
|
|
7
|
+
* the writes they are making. This module closes that gap without adding a
|
|
8
|
+
* single timestamp to the hot path: `MutationQueue` already emits the commit
|
|
9
|
+
* lifecycle, and the events already carry the correlation key.
|
|
10
|
+
*
|
|
11
|
+
* Three events, two intervals:
|
|
12
|
+
*
|
|
13
|
+
* commit:staging ──sealMs──▶ commit:created ──ackMs──▶ transaction:completed
|
|
14
|
+
*
|
|
15
|
+
* - `sealMs` is **local** — writing the durable envelope before the commit is
|
|
16
|
+
* allowed onto the wire. Slow here means storage (IndexedDB), not network.
|
|
17
|
+
* - `ackMs` is **remote** — dispatch, round-trip, and server work. For a
|
|
18
|
+
* commit routed at a connected source this also spans the wait for the
|
|
19
|
+
* correlated echo that promotes `queued` to `confirmed`, so it answers
|
|
20
|
+
* "when did my edit become real" rather than raw socket round-trip. Read a
|
|
21
|
+
* large `ackMs` against a small `sealMs` as a network or replication cost.
|
|
22
|
+
*
|
|
23
|
+
* Correlation is by `clientTxId`: `MutationQueue` uses it as the transaction
|
|
24
|
+
* id verbatim, so the staging event and the completion event share one key.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* The slice of an event emitter this module needs. Declared structurally so a
|
|
28
|
+
* plain object can stand in under test — `MutationQueue` satisfies it by
|
|
29
|
+
* extending `EventEmitter`.
|
|
30
|
+
*/
|
|
31
|
+
export interface CommitEventSource {
|
|
32
|
+
on(event: string, listener: (payload: unknown) => void): unknown;
|
|
33
|
+
off(event: string, listener: (payload: unknown) => void): unknown;
|
|
34
|
+
}
|
|
35
|
+
/** One completed commit, broken into its local and remote halves. */
|
|
36
|
+
export interface CommitLatencySample {
|
|
37
|
+
/** The commit's `clientTxId`, identical to its transaction id. */
|
|
38
|
+
clientTxId: string;
|
|
39
|
+
/** Milliseconds sealing the durable envelope locally. */
|
|
40
|
+
sealMs: number;
|
|
41
|
+
/** Milliseconds from sealed envelope to acknowledgement. */
|
|
42
|
+
ackMs: number;
|
|
43
|
+
/** Milliseconds from staging to acknowledgement — `sealMs + ackMs`. */
|
|
44
|
+
totalMs: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pair commit lifecycle events into latency samples. Returns an unsubscribe
|
|
48
|
+
* function that also drops any still-pending timings.
|
|
49
|
+
*
|
|
50
|
+
* `onSample` fires once per commit that completes, in completion order.
|
|
51
|
+
*/
|
|
52
|
+
export declare function observeCommitLatency(source: CommitEventSource, onSample: (sample: CommitLatencySample) => void): () => void;
|