@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,11 +1,15 @@
|
|
|
1
1
|
# Server Agent
|
|
2
2
|
|
|
3
|
+
> A stateless schema-backed worker: wake, claim, commit, go idle.
|
|
4
|
+
|
|
3
5
|
A server agent is backend code — a cron job, a queue worker, an AI task — that
|
|
4
6
|
reads and writes your app's records outside the browser. The hard part is doing
|
|
5
|
-
it without racing
|
|
6
|
-
once, one write clobbers the other. This is what `claim()` is for.
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
it without racing whatever else is working: if two workers pick up the same task
|
|
8
|
+
at once, one write clobbers the other. This is what `claim()` is for.
|
|
9
|
+
|
|
10
|
+
Agents hold no socket, so pass `transport: 'http'` and import the same schema the
|
|
11
|
+
rest of the app uses. Below, a worker finishes a task by claiming it, writing the
|
|
12
|
+
result, and releasing it automatically when the claim goes out of scope.
|
|
9
13
|
|
|
10
14
|
`claim({ id })` takes the record for your worker and returns a disposable handle:
|
|
11
15
|
the fresh post-lease row is on `claim.data`, and holding the handle with
|
|
@@ -18,53 +22,69 @@ import Ablo from '@abloatai/ablo';
|
|
|
18
22
|
import { defineSchema, model, z } from '@abloatai/ablo/schema';
|
|
19
23
|
|
|
20
24
|
const schema = defineSchema({
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
status: z.enum(['
|
|
24
|
-
|
|
25
|
+
tasks: model({
|
|
26
|
+
title: z.string(),
|
|
27
|
+
status: z.enum(['todo', 'doing', 'done']),
|
|
28
|
+
summary: z.string().optional(),
|
|
25
29
|
}),
|
|
26
30
|
});
|
|
27
31
|
|
|
28
32
|
const ablo = Ablo({
|
|
29
33
|
schema,
|
|
30
34
|
apiKey: process.env.ABLO_API_KEY,
|
|
35
|
+
transport: 'http',
|
|
31
36
|
});
|
|
32
37
|
|
|
33
|
-
export async function
|
|
38
|
+
export async function completeTask(taskId: string) {
|
|
34
39
|
await ablo.ready();
|
|
35
40
|
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
41
|
+
const task = await ablo.tasks.retrieve({ id: taskId });
|
|
42
|
+
if (!task) return { status: 'not_found' };
|
|
38
43
|
|
|
39
|
-
await using claim = await ablo.
|
|
40
|
-
id:
|
|
44
|
+
await using claim = await ablo.tasks.claim({
|
|
45
|
+
id: taskId,
|
|
41
46
|
queue: false,
|
|
42
47
|
description: 'completing',
|
|
43
48
|
});
|
|
44
|
-
const claimed = claim.data;
|
|
45
49
|
|
|
46
|
-
const updated = await ablo.
|
|
47
|
-
id:
|
|
48
|
-
data: { status: '
|
|
50
|
+
const updated = await ablo.tasks.update({
|
|
51
|
+
id: claim.data.id,
|
|
52
|
+
data: { status: 'done' },
|
|
49
53
|
wait: 'confirmed',
|
|
50
54
|
});
|
|
51
55
|
|
|
52
|
-
return { status: '
|
|
56
|
+
return { status: 'done', task: updated };
|
|
57
|
+
// claim auto-releases as the function returns
|
|
53
58
|
}
|
|
54
59
|
```
|
|
55
60
|
|
|
56
61
|
`retrieve({ id })` is an async server read — it hits the server and returns the
|
|
57
|
-
row (or `
|
|
58
|
-
the claim is held, and `wait: 'confirmed'` makes
|
|
59
|
-
|
|
62
|
+
row (or `undefined`, which the early `not_found` guard handles). The update runs
|
|
63
|
+
while the claim is held, and `wait: 'confirmed'` makes it resolve only once your
|
|
64
|
+
database has confirmed the row landed.
|
|
60
65
|
|
|
61
66
|
The two options on the claim:
|
|
62
67
|
|
|
63
68
|
- `queue: false` — skip this record if another claim is already in progress,
|
|
64
|
-
rather than queueing behind it.
|
|
65
|
-
|
|
69
|
+
rather than queueing behind it. Fail-fast dedup: *if someone else has this job,
|
|
70
|
+
skip it.* (The default queues.)
|
|
71
|
+
- `description: 'completing'` — a readable label for what your worker is doing,
|
|
66
72
|
visible to anyone reading `claim.state({ id })`.
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
## Atomic batches
|
|
75
|
+
|
|
76
|
+
When several rows must change together, submit one atomic commit through the same
|
|
77
|
+
schema-backed client:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await ablo.commits.create({
|
|
81
|
+
operations: [
|
|
82
|
+
{ action: 'update', model: 'tasks', id: 'task_123', data: { status: 'done' } },
|
|
83
|
+
],
|
|
84
|
+
wait: 'confirmed',
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Because the worker uses the same schema and `claim()` as everything else, its
|
|
89
|
+
writes reach every connected client in real time and never collide with work
|
|
90
|
+
already in progress.
|
package/docs/groups.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Change Propagation
|
|
2
2
|
|
|
3
|
+
> How one row's change reaches the rows and actors that depend on it.
|
|
4
|
+
|
|
3
5
|
> How a change to one row reaches the rows and actors that depend on it, and how
|
|
4
6
|
> to keep a chain of dependent work fresh. This is the propagation half of sync
|
|
5
7
|
> groups; [`identity.md`](./identity.md) is the access half (who may read a
|
|
@@ -10,8 +12,8 @@
|
|
|
10
12
|
|
|
11
13
|
## Start from the problem
|
|
12
14
|
|
|
13
|
-
An agent reads
|
|
14
|
-
|
|
15
|
+
An agent reads workspace `A` to write document `B`. A moment later it reads `B` to write
|
|
16
|
+
block `C`. Between those steps someone else edits `A`. The agent is now building
|
|
15
17
|
`C` on a premise that has moved — and nothing about writing `C` looks wrong in
|
|
16
18
|
isolation. That is stale context, and it is the thing sync groups let you catch.
|
|
17
19
|
|
|
@@ -19,19 +21,19 @@ The recipe is one field on the commit: declare the group you read as a premise,
|
|
|
19
21
|
and say what should happen if it moved.
|
|
20
22
|
|
|
21
23
|
```ts
|
|
22
|
-
// The agent read everything under
|
|
23
|
-
await ablo.
|
|
24
|
-
id: '
|
|
24
|
+
// The agent read everything under workspace:abc to compose this write.
|
|
25
|
+
await ablo.blocks.update({
|
|
26
|
+
id: 'block-C',
|
|
25
27
|
data: { text: revised },
|
|
26
|
-
reads: [{ group: '
|
|
28
|
+
reads: [{ group: 'workspace:abc', readAt: watermark, onStale: 'notify' }],
|
|
27
29
|
});
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
At commit, inside the write transaction, the engine asks a single question: *did
|
|
31
|
-
any delta routed to `
|
|
33
|
+
any delta routed to `workspace:abc` land after `watermark`?* If nothing moved, the
|
|
32
34
|
write applies. If something moved, `onStale` decides — `notify` holds the write
|
|
33
35
|
and hands the agent a `StaleNotification` naming the group, so it re-reads
|
|
34
|
-
`
|
|
36
|
+
`workspace:abc` and regenerates; `reject` aborts the batch with a `409`. The agent
|
|
35
37
|
never persists work built on a premise it can no longer see.
|
|
36
38
|
|
|
37
39
|
---
|
|
@@ -43,13 +45,13 @@ for you and leaves the third to you — on purpose.
|
|
|
43
45
|
|
|
44
46
|
**Routing — who hears about a change.** Every row belongs to one or more sync
|
|
45
47
|
groups, and a write fans out to all of them. A row also inherits its ancestors'
|
|
46
|
-
groups: editing a
|
|
47
|
-
`
|
|
48
|
+
groups: editing a block stamps the delta with `block:…`, `document:…`, *and*
|
|
49
|
+
`workspace:…`, so everyone watching the workspace sees the block move. This is delivery,
|
|
48
50
|
resolved by walking the ownership tree at commit time. It routes the change; it
|
|
49
51
|
never recomputes a value.
|
|
50
52
|
|
|
51
|
-
**Structural cascade — what disappears with a change.** Deleting a
|
|
52
|
-
its
|
|
53
|
+
**Structural cascade — what disappears with a change.** Deleting a workspace removes
|
|
54
|
+
its documents and blocks. The database does that through `ON DELETE CASCADE`, but a
|
|
53
55
|
database-level cascade emits no delta, so open clients would quietly hold rows
|
|
54
56
|
that no longer exist. The engine closes that gap: before the delete it snapshots
|
|
55
57
|
the subtree and emits a tombstone for each descendant, routed to the right
|
|
@@ -85,8 +87,8 @@ reaches `C`.
|
|
|
85
87
|
|
|
86
88
|
The direction matters. The signal flows forward, A to B to C, and each hop is a
|
|
87
89
|
real write an actor chose to make. The engine supplies the edges (group
|
|
88
|
-
membership) and a stale signal on each edge (the
|
|
89
|
-
|
|
90
|
+
membership) and a stale signal on each edge (the premise check); the actors are
|
|
91
|
+
the runtime that walks them. It is closer to a spreadsheet an analyst
|
|
90
92
|
recalculates cell by cell than to a reactive engine that recomputes the whole
|
|
91
93
|
column for you.
|
|
92
94
|
|
|
@@ -102,16 +104,17 @@ Two consequences worth designing around:
|
|
|
102
104
|
|
|
103
105
|
---
|
|
104
106
|
|
|
105
|
-
## Declaring
|
|
107
|
+
## Declaring the batch premise
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
`reads[]` declares what the commit was based on. Each entry is a premise, and
|
|
110
|
+
each governs the *whole* commit: if one goes stale, its disposition applies to
|
|
111
|
+
every write in the batch, not just one operation. You choose the granularity per
|
|
109
112
|
entry.
|
|
110
113
|
|
|
111
114
|
```ts
|
|
112
115
|
reads: [
|
|
113
|
-
{ group: '
|
|
114
|
-
{ model: '
|
|
116
|
+
{ group: 'workspace:abc', readAt: N, onStale: 'notify' }, // did anything in the workspace move?
|
|
117
|
+
{ model: 'Document', id: 's-1', readAt: N, fields: ['title'] }, // did this row (this field) move?
|
|
115
118
|
]
|
|
116
119
|
```
|
|
117
120
|
|
|
@@ -137,7 +140,7 @@ the same row don't collide.
|
|
|
137
140
|
|
|
138
141
|
## Staying subscribed across commits: `track`
|
|
139
142
|
|
|
140
|
-
A
|
|
143
|
+
A batch premise guards a single commit: you state what you read, the engine
|
|
141
144
|
checks it, the premise is gone. That fits an actor that reads and writes in one
|
|
142
145
|
breath. It does not fit a long-running one — an agent that reads a row now,
|
|
143
146
|
works for a few minutes, and writes much later. By the time it commits, the
|
|
@@ -152,10 +155,10 @@ you, arriving on the write you were going to make anyway.
|
|
|
152
155
|
|
|
153
156
|
```ts
|
|
154
157
|
// Register interest and walk away — no write required.
|
|
155
|
-
await ablo.
|
|
158
|
+
await ablo.documents.track({ id: 's-1' });
|
|
156
159
|
|
|
157
160
|
// …minutes of other work later, on your next commit…
|
|
158
|
-
const res = await ablo.
|
|
161
|
+
const res = await ablo.blocks.update({ id: 'block-C', data: { text: revised } });
|
|
159
162
|
res.notifications; // populated if s-1 moved under you in the meantime
|
|
160
163
|
```
|
|
161
164
|
|
|
@@ -169,11 +172,11 @@ You can also register a track as part of a write you are already making, the
|
|
|
169
172
|
persisted companion to `reads`:
|
|
170
173
|
|
|
171
174
|
```ts
|
|
172
|
-
await ablo.
|
|
175
|
+
await ablo.documents.update({
|
|
173
176
|
id: 's-1',
|
|
174
177
|
data: { title: revised },
|
|
175
|
-
reads: [{ group: '
|
|
176
|
-
track: [{ group: '
|
|
178
|
+
reads: [{ group: 'workspace:abc', readAt: N, onStale: 'notify' }], // guards THIS commit
|
|
179
|
+
track: [{ group: 'workspace:abc' }], // and keeps watching after it
|
|
177
180
|
});
|
|
178
181
|
```
|
|
179
182
|
|
|
@@ -194,8 +197,8 @@ broad wakes actors for changes they don't care about, and one that is too narrow
|
|
|
194
197
|
misses the dependency you meant to track.
|
|
195
198
|
|
|
196
199
|
The rule of thumb: **make a group the smallest set of rows that must stay
|
|
197
|
-
mutually consistent.** A
|
|
198
|
-
changes what the others mean; two unrelated
|
|
200
|
+
mutually consistent.** A workspace and its documents belong together because editing one
|
|
201
|
+
changes what the others mean; two unrelated workspaces do not. Reach for finer,
|
|
199
202
|
overlapping groups when you genuinely have a dependency chain to track, and keep
|
|
200
203
|
them coarse everywhere else.
|
|
201
204
|
|
|
@@ -204,7 +207,7 @@ them coarse everywhere else.
|
|
|
204
207
|
## Where this is defined
|
|
205
208
|
|
|
206
209
|
- **Access** — who may read or write a group — is [`identity.md`](./identity.md).
|
|
207
|
-
- **The convention** — non-coercion, the
|
|
210
|
+
- **The convention** — non-coercion, the premise, and the notification — is
|
|
208
211
|
[`concurrency-convention.md`](./concurrency-convention.md) (§4 and §5).
|
|
209
|
-
- **The mechanics** — the three coordination
|
|
212
|
+
- **The mechanics** — the three coordination blocks underneath — are
|
|
210
213
|
[`coordination.md`](./coordination.md).
|
package/docs/guarantees.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Guarantees
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
> Exactly what a confirmed write, a rejected stale write, and a held claim each promise.
|
|
4
|
+
|
|
5
|
+
When an Ablo write succeeds, the server has accepted it — and when two agents
|
|
6
|
+
touch the same row, Ablo coordinates them instead of letting one silently
|
|
5
7
|
overwrite the other. This page is the precise list of what you can count on:
|
|
6
8
|
confirmed writes, stale-write protection, claims, and the audit trail behind
|
|
7
9
|
every change.
|
package/docs/how-it-works.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# How Ablo Works
|
|
2
2
|
|
|
3
|
+
> You write through Ablo, Ablo writes to your Postgres, and the write-ahead log confirms it.
|
|
4
|
+
|
|
3
5
|
You write through Ablo, and Ablo writes to your Postgres. That one sentence is the
|
|
4
6
|
whole model — everything below explains what it means and how to use it.
|
|
5
7
|
|
|
@@ -8,14 +10,14 @@ whole model — everything below explains what it means and how to use it.
|
|
|
8
10
|
await ablo.tasks.update({ id: 'task_42', data: { status: 'done' }, wait: 'confirmed' });
|
|
9
11
|
|
|
10
12
|
// Reads come back live, kept current from your database.
|
|
11
|
-
const task = ablo.tasks.
|
|
13
|
+
const task = ablo.tasks.local.retrieve('task_42');
|
|
12
14
|
```
|
|
13
15
|
|
|
14
16
|
## The mental model — read this once
|
|
15
17
|
|
|
16
|
-
Ablo is a **coordination layer in front of your Postgres**.
|
|
17
|
-
|
|
18
|
-
makes sure their writes don't clobber each other.
|
|
18
|
+
Ablo is a **coordination layer in front of your Postgres**. Agents, background
|
|
19
|
+
jobs, and the people alongside them all change the same application data through
|
|
20
|
+
one API, and Ablo makes sure their writes don't clobber each other.
|
|
19
21
|
|
|
20
22
|
- **Writes go through Ablo.** `ablo.<model>.create / update / delete` enter Ablo's
|
|
21
23
|
commit chokepoint — where claims, ordering, and idempotency are enforced — and
|
|
@@ -57,7 +59,7 @@ Registering the database is the whole switch. There is no tier or flag to choose
|
|
|
57
59
|
npm install @abloatai/ablo
|
|
58
60
|
npx ablo init
|
|
59
61
|
|
|
60
|
-
# 2. Push your schema (the models
|
|
62
|
+
# 2. Push your schema (the models your agents edit together).
|
|
61
63
|
npx ablo push
|
|
62
64
|
|
|
63
65
|
# 3. Connect your database — one command, admin credential used once and discarded.
|
|
@@ -80,13 +82,13 @@ export const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
|
80
82
|
await ablo.tasks.update({ id: 'task_42', data: { status: 'done' }, wait: 'confirmed' });
|
|
81
83
|
|
|
82
84
|
// 6. Read — live, no fetch loop.
|
|
83
|
-
const task = ablo.tasks.
|
|
85
|
+
const task = ablo.tasks.local.retrieve('task_42');
|
|
84
86
|
|
|
85
87
|
// 7. Coordinate when more than one actor can touch a row. Hold a claim and Ablo
|
|
86
88
|
// serializes writes on that key against everyone else; read after claiming,
|
|
87
89
|
// then write. The lease releases automatically at the end of the scope.
|
|
88
90
|
await using _hold = await ablo.tasks.claim('task_42');
|
|
89
|
-
const latest = ablo.tasks.
|
|
91
|
+
const latest = ablo.tasks.local.retrieve('task_42'); // read after claiming, not from memory
|
|
90
92
|
await ablo.tasks.update({ id: 'task_42', data: { status: 'done' } });
|
|
91
93
|
```
|
|
92
94
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Idempotency
|
|
2
|
+
|
|
3
|
+
> Make a retried write safe: the same key never applies the same change twice.
|
|
4
|
+
|
|
5
|
+
An agent retries. A socket drops mid-commit, a worker restarts, a queue redelivers — and the write
|
|
6
|
+
you already sent arrives again. An idempotency key is how Ablo tells a retry from a new intention.
|
|
7
|
+
|
|
8
|
+
Every model write carries one. The SDK generates a key when you omit it, which makes an in-process
|
|
9
|
+
retry safe automatically. It cannot make a retry across a process restart safe, because a new
|
|
10
|
+
process generates a new key — so for anything that must survive a crash, supply your own.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
await ablo.tasks.update({
|
|
14
|
+
id: taskId,
|
|
15
|
+
data: { status: 'done' },
|
|
16
|
+
idempotencyKey: `task:${taskId}:mark-done:v1`,
|
|
17
|
+
wait: 'confirmed',
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## The one rule
|
|
22
|
+
|
|
23
|
+
**Derive the key from the business event, not from the attempt.** A key built from
|
|
24
|
+
`crypto.randomUUID()` at the call site is regenerated on every retry, so it protects nothing — each
|
|
25
|
+
attempt looks like a new intention and the write lands twice. A key built from the thing that
|
|
26
|
+
happened (`task:42:mark-done:v1`) is identical on every retry by construction, which is the whole
|
|
27
|
+
point.
|
|
28
|
+
|
|
29
|
+
The same rule stated as its failure: never derive a key from a timestamp, an attempt counter, or a
|
|
30
|
+
random value. If two retries of one operation can produce different keys, you have no idempotency.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
The key is not a lookup that happens before the write — it is the **execution lock on the write
|
|
35
|
+
itself**. Ablo inserts a pending row keyed by the caller and the key inside the same transaction as
|
|
36
|
+
the mutation, and a unique index makes that insert the lock:
|
|
37
|
+
|
|
38
|
+
- **Insert wins** — this transaction owns the execution and runs the write.
|
|
39
|
+
- **Insert conflicts** — someone else owns it. The second caller waits for the owner to finish and
|
|
40
|
+
then replays its recorded result.
|
|
41
|
+
- **Insert conflicts, different request** — the key was reused to mean something else. Rejected.
|
|
42
|
+
|
|
43
|
+
Because the lock and the write share a transaction, there is no window in which a write has happened
|
|
44
|
+
but its key has not been recorded.
|
|
45
|
+
|
|
46
|
+
Keys are scoped to **the organization and the participant**, not globally. Two agents can use the
|
|
47
|
+
same key string without colliding, and one agent can never replay another's result.
|
|
48
|
+
|
|
49
|
+
## The four outcomes
|
|
50
|
+
|
|
51
|
+
| You send | Ablo does |
|
|
52
|
+
|---|---|
|
|
53
|
+
| A new key | Runs the write. |
|
|
54
|
+
| The same key, the same request, already finished | Replays the recorded result. The write does not run again. |
|
|
55
|
+
| The same key, the same request, still running | Waits for the in-flight attempt, then replays its result. If the original is still running after a short wait, rejects with `idempotency_conflict` (409) — retry the same key. |
|
|
56
|
+
| The same key, a **different** request | Rejects with `idempotency_conflict` (409). A key is bound to the request it first arrived with. |
|
|
57
|
+
|
|
58
|
+
Both conflict cases return the same code, so tell them apart by what your own
|
|
59
|
+
client did. If you retried an identical request, the original is still in flight
|
|
60
|
+
— wait and retry the same key. If you changed the request, that is a client bug:
|
|
61
|
+
use a new key.
|
|
62
|
+
|
|
63
|
+
## Failures are not replayed — they re-run
|
|
64
|
+
|
|
65
|
+
This is where Ablo deliberately differs from Stripe and from most payment APIs, and it is the
|
|
66
|
+
behaviour most likely to surprise you.
|
|
67
|
+
|
|
68
|
+
**Only successful writes are recorded.** A write that failed leaves no idempotency record, so
|
|
69
|
+
retrying it with the same key **executes fresh** rather than replaying the error.
|
|
70
|
+
|
|
71
|
+
That is the right default here because most failures are ones you can fix and legitimately want to
|
|
72
|
+
re-attempt — a validation error, a stale premise, a claim held by someone else. Replaying the
|
|
73
|
+
original error for 24 hours would strand the caller behind a decision that is no longer true.
|
|
74
|
+
|
|
75
|
+
The consequence to hold onto: a retry after a failure is a real execution. If a write failed in a
|
|
76
|
+
way that leaves you unsure whether it landed — a timeout, a dropped socket — do not assume the retry
|
|
77
|
+
is a no-op. Retry with the **same key**: if the original did land, the recorded success replays; if
|
|
78
|
+
it did not, the write runs now. That is exactly the case idempotency exists for.
|
|
79
|
+
|
|
80
|
+
## The window
|
|
81
|
+
|
|
82
|
+
A recorded result is retained for **24 hours**, then expires. Within that window a repeated key
|
|
83
|
+
replays. After it, the key is forgotten and reusing it starts a genuinely new write.
|
|
84
|
+
|
|
85
|
+
Treat 24 hours as *how long a retry is guaranteed safe*, not as permanent deduplication. A nightly
|
|
86
|
+
job that reuses yesterday's key will execute again.
|
|
87
|
+
|
|
88
|
+
Writes routed to a registered data source are the exception: their intent is retained **permanently**
|
|
89
|
+
rather than expiring, because letting that record lapse would make a reused key indistinguishable
|
|
90
|
+
from old work against your database. A key whose retained intent has expired is rejected with
|
|
91
|
+
`idempotency_key_expired` (409) rather than being silently re-executed.
|
|
92
|
+
|
|
93
|
+
## Route pinning
|
|
94
|
+
|
|
95
|
+
A key is bound to the route its first attempt took. If an earlier attempt was applied through a
|
|
96
|
+
direct data source and a retry arrives when the endpoint fallback is active, Ablo rejects it with
|
|
97
|
+
`source_transport_pinned` (409) instead of switching.
|
|
98
|
+
|
|
99
|
+
That refusal is deliberate: switching routes on a retry risks applying a write that the first route
|
|
100
|
+
may already have committed. Restore the original route and retry the same key.
|
|
101
|
+
|
|
102
|
+
## When to retry
|
|
103
|
+
|
|
104
|
+
| Situation | Do |
|
|
105
|
+
|---|---|
|
|
106
|
+
| Timeout or dropped connection, no response | Retry with the **same** key, with backoff. You get the recorded success, or the write runs now. |
|
|
107
|
+
| `source_unreachable` (503) | Retry with the **same** key once connectivity recovers. The write stays pinned to its route. |
|
|
108
|
+
| `replication_lag_timeout` (504) | The write may have materialized. Retry with the **same** key, or wait for source ingestion to catch up. |
|
|
109
|
+
| `AbloStaleContextError` | Re-read the row, regenerate, then write under a **new** key — the new write is a new intention. |
|
|
110
|
+
| `AbloClaimedError` | Someone else holds the row. Wait or yield; the key is unused, so reuse it when you retry. |
|
|
111
|
+
| `idempotency_conflict` (409) after an identical retry | The original is still in flight. Wait, then retry the **same** key. |
|
|
112
|
+
| `idempotency_conflict` (409) after changing the request | A client bug: a key is bound to the first request sent under it. Use a **new** key. |
|
|
113
|
+
| `idempotency_key_too_long` (400) | The key exceeds 255 characters. A UUID or a short business string works. |
|
|
114
|
+
|
|
115
|
+
## Keys
|
|
116
|
+
|
|
117
|
+
- Up to **255 characters**. Longer is rejected, not truncated.
|
|
118
|
+
- Unique per logical operation, identical across every retry of that operation.
|
|
119
|
+
- Generate a new one only when a genuinely new operation begins.
|
|
120
|
+
- Never put secrets in a key — it is stored and appears in support diagnostics.
|
|
121
|
+
|
|
122
|
+
## Related
|
|
123
|
+
|
|
124
|
+
- [Client Behavior](./client-behavior.md) — every write option, and which errors retry.
|
|
125
|
+
- [Guarantees](./guarantees.md) — what `queued` and `confirmed` promise.
|
|
126
|
+
- [Errors](./errors.md) — the full code registry, including every code named above.
|