@abloatai/ablo 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +758 -5
- package/README.md +56 -502
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +176 -48
- package/dist/BaseSyncedStore.js +346 -214
- package/dist/Database.d.ts +17 -44
- package/dist/Database.js +96 -79
- package/dist/InstanceCache.d.ts +31 -6
- package/dist/InstanceCache.js +65 -30
- package/dist/LazyReferenceCollection.d.ts +3 -3
- package/dist/LazyReferenceCollection.js +4 -4
- package/dist/Model.d.ts +23 -13
- package/dist/Model.js +27 -17
- package/dist/ModelRegistry.d.ts +8 -4
- package/dist/ModelRegistry.js +20 -18
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
- package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
- package/dist/SyncClient.d.ts +47 -47
- package/dist/SyncClient.js +215 -156
- package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
- package/dist/ai-sdk/coordinatedTool.js +1 -1
- package/dist/ai-sdk/coordinationContext.d.ts +2 -2
- package/dist/ai-sdk/coordinationContext.js +1 -1
- package/dist/ai-sdk/wrap.d.ts +3 -3
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/index.d.ts +1 -156
- package/dist/auth/index.js +8 -301
- package/dist/client/Ablo.d.ts +42 -287
- package/dist/client/Ablo.js +129 -963
- package/dist/client/abloClient.d.ts +309 -0
- package/dist/client/abloClient.js +13 -0
- package/dist/client/clientPrelude.d.ts +52 -0
- package/dist/client/clientPrelude.js +60 -0
- package/dist/client/consoleLogger.d.ts +2 -2
- package/dist/client/coreClient.d.ts +60 -0
- package/dist/client/coreClient.js +118 -0
- package/dist/client/createInternalComponents.d.ts +8 -4
- package/dist/client/createInternalComponents.js +17 -10
- package/dist/client/createModelProxy.d.ts +98 -373
- package/dist/client/createModelProxy.js +233 -139
- package/dist/client/humans.d.ts +69 -0
- package/dist/client/humans.js +78 -0
- package/dist/client/modelRegistration.d.ts +1 -1
- package/dist/client/modelRegistration.js +9 -9
- package/dist/client/options.d.ts +73 -17
- package/dist/client/reactiveEngine.d.ts +53 -0
- package/dist/client/reactiveEngine.js +688 -0
- package/dist/client/resourceTypes.d.ts +9 -250
- package/dist/client/resourceTypes.js +8 -5
- package/dist/client/schemaConfig.d.ts +4 -4
- package/dist/client/schemaConfig.js +6 -2
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/client/validateAbloOptions.d.ts +3 -2
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +3 -3
- package/dist/client/wsMutationExecutor.js +3 -3
- package/dist/context.d.ts +22 -9
- package/dist/context.js +33 -9
- package/dist/coordination/ClaimLog.d.ts +26 -0
- package/dist/coordination/ClaimLog.js +32 -0
- package/dist/coordination/index.d.ts +1 -15
- package/dist/coordination/index.js +8 -31
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +2 -2
- package/dist/docs/catalog.d.ts +72 -0
- package/dist/docs/catalog.js +230 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +10 -0
- package/dist/environment.d.ts +1 -40
- package/dist/environment.js +8 -37
- package/dist/index.d.ts +44 -36
- package/dist/index.js +30 -22
- package/dist/interfaces/index.d.ts +44 -134
- package/dist/keys/index.d.ts +1 -77
- package/dist/keys/index.js +8 -190
- package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
- package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
- package/dist/mutators/Transaction.d.ts +1 -1
- package/dist/mutators/Transaction.js +1 -1
- package/dist/mutators/UndoManager.d.ts +6 -6
- package/dist/mutators/UndoManager.js +5 -5
- package/dist/mutators/defineMutators.d.ts +3 -3
- package/dist/mutators/defineMutators.js +1 -1
- package/dist/mutators/inverseOp.js +2 -2
- package/dist/mutators/mutateActions.d.ts +3 -3
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +1 -1
- package/dist/mutators/undoApply.js +1 -1
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +1 -1
- package/dist/query/client.d.ts +5 -2
- package/dist/query/client.js +10 -9
- package/dist/query/types.d.ts +6 -41
- package/dist/query/types.js +2 -2
- package/dist/react/AbloProvider.d.ts +18 -8
- package/dist/react/AbloProvider.js +10 -9
- package/dist/react/context.d.ts +3 -3
- package/dist/react/context.js +1 -1
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +6 -5
- package/dist/react/index.js +6 -3
- package/dist/react/internalContext.d.ts +1 -1
- package/dist/react/useAblo.d.ts +12 -5
- package/dist/react/useAblo.js +26 -8
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.d.ts +2 -2
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.d.ts +3 -3
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useUndoScope.d.ts +5 -5
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +69 -10
- package/dist/schema/coordination.js +90 -9
- package/dist/schema/ddl.js +2 -2
- package/dist/schema/diff.d.ts +1 -1
- package/dist/schema/generate.js +1 -1
- package/dist/schema/index.d.ts +11 -10
- package/dist/schema/index.js +22 -18
- package/dist/schema/queries.d.ts +27 -27
- package/dist/schema/queries.js +23 -23
- package/dist/schema/select.d.ts +3 -3
- package/dist/schema/select.js +6 -3
- package/dist/schema/serialize.d.ts +15 -6
- package/dist/schema/serialize.js +20 -3
- package/dist/schema/sugar.d.ts +6 -7
- package/dist/schema/sugar.js +9 -12
- package/dist/schema/syncDeltaRow.d.ts +4 -152
- package/dist/schema/syncDeltaRow.js +4 -105
- package/dist/server/adapter.d.ts +18 -1
- package/dist/server/commit.d.ts +10 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +1 -1
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/adapters/drizzle.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +2 -2
- package/dist/source/adapters/kysely.d.ts +1 -1
- package/dist/source/adapters/kysely.js +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +2 -2
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.d.ts +8 -3
- package/dist/source/adapters/prisma.js +1 -1
- package/dist/source/connector.js +1 -1
- package/dist/source/connectorProtocol.d.ts +2 -8
- package/dist/source/connectorProtocol.js +3 -2
- package/dist/source/contract.d.ts +29 -17
- package/dist/source/contract.js +27 -22
- package/dist/source/factory.d.ts +1 -1
- package/dist/source/idempotency.js +2 -2
- package/dist/source/index.d.ts +1 -0
- package/dist/source/index.js +3 -0
- package/dist/source/next.d.ts +1 -1
- package/dist/source/signing.d.ts +9 -2
- package/dist/source/signing.js +4 -1
- package/dist/source/types.d.ts +6 -4
- package/dist/source/types.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +6 -6
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +14 -13
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/{core → stores}/StoreManager.d.ts +9 -26
- package/dist/{core → stores}/StoreManager.js +29 -77
- package/dist/stores/SyncActionStore.d.ts +4 -2
- package/dist/stores/SyncActionStore.js +11 -17
- package/dist/stores/syncAction.d.ts +26 -0
- package/dist/stores/syncAction.js +16 -0
- package/dist/surface.d.ts +3 -3
- package/dist/surface.js +6 -4
- package/dist/sync/BootstrapFetcher.d.ts +127 -6
- package/dist/sync/BootstrapFetcher.js +511 -83
- package/dist/sync/ConnectionManager.d.ts +6 -198
- package/dist/sync/ConnectionManager.js +6 -677
- package/dist/sync/OnDemandLoader.d.ts +5 -2
- package/dist/sync/OnDemandLoader.js +61 -21
- package/dist/sync/SubscriptionManager.d.ts +13 -2
- package/dist/sync/SubscriptionManager.js +23 -5
- package/dist/sync/SyncWebSocket.d.ts +27 -510
- package/dist/sync/SyncWebSocket.js +76 -954
- package/dist/sync/awaitClaimGrant.d.ts +4 -44
- package/dist/sync/awaitClaimGrant.js +4 -109
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/commitFrames.d.ts +6 -40
- package/dist/sync/commitFrames.js +6 -97
- package/dist/sync/contextPorts.d.ts +18 -0
- package/dist/sync/contextPorts.js +31 -0
- package/dist/sync/createClaimStream.d.ts +5 -49
- package/dist/sync/createClaimStream.js +5 -469
- package/dist/sync/createPresenceStream.d.ts +26 -4
- package/dist/sync/createPresenceStream.js +28 -20
- package/dist/sync/createSnapshot.d.ts +2 -2
- package/dist/sync/createSnapshot.js +1 -1
- package/dist/sync/credentialLifecycle.d.ts +5 -173
- package/dist/sync/credentialLifecycle.js +5 -320
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +24 -6
- package/dist/sync/participants.js +32 -23
- package/dist/sync/schemaDrift.d.ts +55 -0
- package/dist/sync/schemaDrift.js +53 -0
- package/dist/sync/schemas.d.ts +23 -33
- package/dist/sync/schemas.js +29 -20
- package/dist/sync/syncPlan.d.ts +3 -3
- package/dist/sync/wsFrameHandlers.d.ts +6 -114
- package/dist/sync/wsFrameHandlers.js +6 -392
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/ablo.d.ts +88 -0
- package/dist/transaction/ablo.js +33 -0
- package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
- package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
- package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
- package/dist/transaction/auth/bootstrapScope.js +1 -0
- package/dist/transaction/auth/capability.d.ts +212 -0
- package/dist/transaction/auth/capability.js +224 -0
- package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
- package/dist/{client → transaction/auth}/identity.d.ts +8 -7
- package/dist/{client → transaction/auth}/identity.js +1 -1
- package/dist/transaction/auth/index.d.ts +162 -0
- package/dist/transaction/auth/index.js +304 -0
- package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
- package/dist/{auth → transaction/auth}/schemas.js +13 -13
- package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
- package/dist/{client → transaction/auth}/sessionMint.js +4 -7
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +56 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +124 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +34 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/claimMeta.d.ts +49 -0
- package/dist/transaction/coordination/claimMeta.js +52 -0
- package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
- package/dist/transaction/coordination/createClaimStream.js +475 -0
- package/dist/transaction/coordination/events.d.ts +74 -0
- package/dist/transaction/coordination/events.js +7 -0
- package/dist/transaction/coordination/index.d.ts +19 -0
- package/dist/transaction/coordination/index.js +45 -0
- package/dist/transaction/coordination/locator.d.ts +104 -0
- package/dist/transaction/coordination/locator.js +102 -0
- package/dist/transaction/coordination/schema.d.ts +1536 -0
- package/dist/transaction/coordination/schema.js +1177 -0
- package/dist/transaction/coordination/targetConflict.d.ts +2 -0
- package/dist/transaction/coordination/targetConflict.js +107 -0
- package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
- package/dist/{coordination → transaction/coordination}/trace.js +18 -25
- package/dist/transaction/durableWrites.d.ts +62 -0
- package/dist/{client → transaction}/durableWrites.js +28 -3
- package/dist/transaction/environment.d.ts +105 -0
- package/dist/transaction/environment.js +108 -0
- package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +12 -12
- package/dist/{errorCodes.js → transaction/errorCodes.js} +45 -18
- package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
- package/dist/{errors.js → transaction/errors.js} +85 -16
- package/dist/transaction/footprint.d.ts +111 -0
- package/dist/transaction/footprint.js +0 -0
- package/dist/transaction/index.d.ts +20 -0
- package/dist/transaction/index.js +20 -0
- package/dist/transaction/keys/index.d.ts +87 -0
- package/dist/transaction/keys/index.js +207 -0
- package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
- package/dist/transaction/log/syncDeltaRow.js +95 -0
- package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
- package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
- package/dist/transaction/logger.d.ts +16 -0
- package/dist/transaction/logger.js +7 -0
- package/dist/transaction/observability.d.ts +53 -0
- package/dist/transaction/observability.js +19 -0
- package/dist/transaction/plugin.d.ts +285 -0
- package/dist/transaction/plugin.js +106 -0
- package/dist/{policy → transaction/policy}/types.d.ts +3 -3
- package/dist/{policy → transaction/policy}/types.js +2 -0
- package/dist/transaction/resources/httpResources.d.ts +321 -0
- package/dist/transaction/resources/httpResources.js +7 -0
- package/dist/transaction/resources/modelOperations.d.ts +427 -0
- package/dist/transaction/resources/modelOperations.js +12 -0
- package/dist/transaction/resources/mutationOptions.d.ts +66 -0
- package/dist/transaction/resources/mutationOptions.js +9 -0
- package/dist/transaction/resources/where.d.ts +101 -0
- package/dist/transaction/resources/where.js +115 -0
- package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
- package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
- package/dist/{schema → transaction/schema}/field.d.ts +17 -23
- package/dist/{schema → transaction/schema}/field.js +5 -5
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/loadStrategy.d.ts +45 -0
- package/dist/transaction/schema/loadStrategy.js +46 -0
- package/dist/{schema → transaction/schema}/model.d.ts +50 -35
- package/dist/{schema → transaction/schema}/model.js +30 -20
- package/dist/transaction/schema/openapi.d.ts +58 -0
- package/dist/transaction/schema/openapi.js +501 -0
- package/dist/{schema → transaction/schema}/relation.d.ts +21 -16
- package/dist/{schema → transaction/schema}/relation.js +7 -7
- package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
- package/dist/{schema → transaction/schema}/residency.js +0 -5
- package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
- package/dist/{schema → transaction/schema}/roles.js +5 -5
- package/dist/{schema → transaction/schema}/schema.d.ts +39 -10
- package/dist/{schema → transaction/schema}/schema.js +24 -3
- package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
- package/dist/{schema → transaction/schema}/tenancy.js +7 -4
- package/dist/transaction/transactionLayer.d.ts +82 -0
- package/dist/transaction/transactionLayer.js +24 -0
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +5 -6
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
- package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +11 -37
- package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
- package/dist/transaction/transport/commitFrames.d.ts +90 -0
- package/dist/transaction/transport/commitFrames.js +134 -0
- package/dist/transaction/transport/connectionManager.d.ts +215 -0
- package/dist/transaction/transport/connectionManager.js +673 -0
- package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
- package/dist/transaction/transport/credentialLifecycle.js +324 -0
- package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
- package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
- package/dist/transaction/transport/httpClient.d.ts +131 -0
- package/dist/{client → transaction/transport}/httpClient.js +6 -5
- package/dist/transaction/transport/httpOptions.d.ts +33 -0
- package/dist/transaction/transport/httpOptions.js +12 -0
- package/dist/{client → transaction/transport}/httpTransport.js +295 -97
- package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
- package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
- package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
- package/dist/transaction/transport/wsFrameHandlers.js +429 -0
- package/dist/transaction/transport/wsTransport.d.ts +574 -0
- package/dist/transaction/transport/wsTransport.js +1023 -0
- package/dist/transaction/types/assertExact.d.ts +17 -0
- package/dist/transaction/types/assertExact.js +1 -0
- package/dist/{types → transaction/types}/global.d.ts +17 -2
- package/dist/{types → transaction/types}/global.js +2 -1
- package/dist/{types → transaction/types}/index.d.ts +14 -46
- package/dist/{types → transaction/types}/index.js +7 -16
- package/dist/{types → transaction/types}/streams.d.ts +73 -45
- package/dist/transaction/utils/duration.d.ts +50 -0
- package/dist/{utils → transaction/utils}/duration.js +32 -0
- package/dist/{utils → transaction/utils}/json.d.ts +18 -0
- package/dist/transaction/utils/json.js +276 -0
- package/dist/transaction/wire/accountResponses.d.ts +420 -0
- package/dist/transaction/wire/accountResponses.js +290 -0
- package/dist/transaction/wire/auth.d.ts +56 -0
- package/dist/transaction/wire/auth.js +63 -0
- package/dist/transaction/wire/claimEvent.d.ts +76 -0
- package/dist/transaction/wire/claimEvent.js +73 -0
- package/dist/transaction/wire/claims.d.ts +530 -0
- package/dist/transaction/wire/claims.js +327 -0
- package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
- package/dist/{wire → transaction/wire}/commit.js +68 -47
- package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
- package/dist/{wire → transaction/wire}/delta.js +37 -13
- package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
- package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
- package/dist/transaction/wire/feedCursor.d.ts +60 -0
- package/dist/transaction/wire/feedCursor.js +82 -0
- package/dist/transaction/wire/feedEvent.d.ts +204 -0
- package/dist/transaction/wire/feedEvent.js +65 -0
- package/dist/transaction/wire/frames.d.ts +194 -0
- package/dist/transaction/wire/frames.js +50 -0
- package/dist/transaction/wire/inboundFrames.d.ts +562 -0
- package/dist/transaction/wire/inboundFrames.js +116 -0
- package/dist/transaction/wire/index.d.ts +54 -0
- package/dist/transaction/wire/index.js +83 -0
- package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
- package/dist/transaction/wire/listEnvelope.js +42 -0
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelResponses.d.ts +85 -0
- package/dist/transaction/wire/modelResponses.js +43 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +85 -38
- package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +141 -80
- package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
- package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
- package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
- package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
- package/dist/transactions/mutations/commitLatency.d.ts +52 -0
- package/dist/transactions/mutations/commitLatency.js +130 -0
- package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
- package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
- package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +18 -16
- package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +15 -15
- package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +15 -11
- package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +14 -12
- package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
- package/dist/transactions/mutations/durableWriteStore.js +12 -0
- package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
- package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +4 -3
- package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +7 -5
- package/dist/utils/mobxSetup.d.ts +1 -1
- package/dist/utils/mobxSetup.js +5 -2
- package/dist/{core → views}/QueryView.d.ts +2 -2
- package/dist/{core → views}/QueryView.js +2 -2
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/dist/webhooks/events.d.ts +2 -2
- package/dist/wire/index.d.ts +1 -34
- package/dist/wire/index.js +8 -49
- package/docs/agent-messaging.md +3 -3
- package/docs/agents.md +20 -13
- package/docs/api-keys.md +14 -10
- package/docs/api.md +27 -61
- package/docs/audit.md +6 -3
- package/docs/cli.md +41 -13
- package/docs/client-behavior.md +11 -9
- package/docs/concurrency-convention.md +49 -57
- package/docs/coordination.md +283 -121
- package/docs/data-sources.md +7 -5
- package/docs/debugging.md +39 -15
- package/docs/deployment.md +267 -0
- package/docs/examples/agent-human.md +49 -42
- package/docs/examples/ai-sdk-tool.md +69 -44
- package/docs/examples/existing-python-backend.md +8 -6
- package/docs/examples/nextjs.md +129 -47
- package/docs/examples/scoped-agent.md +46 -45
- package/docs/examples/server-agent.md +46 -26
- package/docs/groups.md +87 -30
- package/docs/guarantees.md +41 -12
- package/docs/how-it-works.md +38 -12
- package/docs/idempotency.md +126 -0
- package/docs/identity.md +77 -74
- package/docs/index.md +172 -86
- package/docs/integration-guide.md +31 -19
- package/docs/mcp.md +46 -21
- package/docs/migration.md +95 -18
- package/docs/operating-on-your-database.md +3 -1
- package/docs/projects.md +3 -1
- package/docs/quickstart.md +22 -5
- package/docs/react.md +31 -18
- package/docs/schema-contract.md +5 -3
- package/docs/session-settings.md +108 -0
- package/docs/sessions.md +4 -2
- package/docs/webhooks.md +12 -10
- package/llms.txt +48 -18
- package/package.json +21 -26
- package/dist/agent/Agent.d.ts +0 -366
- package/dist/agent/Agent.js +0 -514
- package/dist/agent/index.d.ts +0 -115
- package/dist/agent/index.js +0 -128
- package/dist/agent/session.d.ts +0 -93
- package/dist/agent/session.js +0 -149
- package/dist/agent/types.d.ts +0 -68
- package/dist/agent/types.js +0 -9
- package/dist/cli.cjs +0 -286329
- package/dist/client/durableWrites.d.ts +0 -21
- package/dist/client/httpClient.d.ts +0 -80
- package/dist/coordination/schema.d.ts +0 -722
- package/dist/coordination/schema.js +0 -578
- package/dist/schema/openapi.d.ts +0 -29
- package/dist/schema/openapi.js +0 -124
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -192
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -71
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/dist/transactions/durableWriteStore.js +0 -30
- package/dist/utils/duration.d.ts +0 -25
- package/dist/utils/json.js +0 -88
- package/dist/wire/errorEnvelope.d.ts +0 -55
- package/dist/wire/frames.d.ts +0 -197
- package/dist/wire/frames.js +0 -49
- package/dist/wire/listEnvelope.js +0 -18
- package/docs/interaction-model.md +0 -97
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
- /package/dist/{client → transaction}/persistence.d.ts +0 -0
- /package/dist/{client → transaction}/persistence.js +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
- /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.js +0 -0
- /package/dist/{types → transaction/types}/participant.d.ts +0 -0
- /package/dist/{types → transaction/types}/participant.js +0 -0
- /package/dist/{types → transaction/types}/streams.js +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
- /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocol.js +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
- /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
- /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
- /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
package/docs/coordination.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Coordination Reference
|
|
2
2
|
|
|
3
|
+
> Claim mechanics and the API behind them: who holds a row, who is waiting, and how the line moves.
|
|
4
|
+
|
|
3
5
|
> **Governing convention:** [`concurrency-convention.md`](./concurrency-convention.md)
|
|
4
6
|
> — the non-coercion principle (surface state, let the actor decide), the full
|
|
5
|
-
> `onStale` taxonomy, the
|
|
6
|
-
> the *why* and the contract; this reference is the *how* (claim
|
|
7
|
+
> `onStale` taxonomy, the batch premise (`reads[]`), and the boundaries. Read
|
|
8
|
+
> that for the *why* and the contract; this reference is the *how* (claim
|
|
9
|
+
> mechanics + API).
|
|
7
10
|
|
|
8
|
-
Coordinate long-running work on a row so
|
|
9
|
-
other. Most writes need none of this — a plain `ablo.<model>.update({ id, data })`
|
|
11
|
+
Coordinate long-running work on a row so agents — and the people watching them —
|
|
12
|
+
don't clobber each other. Most writes need none of this — a plain `ablo.<model>.update({ id, data })`
|
|
10
13
|
is **last-write-wins** by default.
|
|
11
14
|
|
|
12
15
|
> **Read-modify-write under contention? Use the functional update — it owns all
|
|
@@ -35,21 +38,36 @@ Reads stay open: reading a claimed row is allowed unless the caller explicitly
|
|
|
35
38
|
asks for claimed gating. A claim carries a TTL so a crashed holder is
|
|
36
39
|
auto-released and the queue advances.
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
>
|
|
47
|
-
>
|
|
48
|
-
>
|
|
49
|
-
>
|
|
50
|
-
>
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
A claim is also as narrow as you make it. Name a part of the row — a `field`, a
|
|
42
|
+
`path` into a document, a `range` of text — and exclusion follows the target:
|
|
43
|
+
**two claims on non-overlapping parts of the same row are both granted**, and
|
|
44
|
+
only overlapping targets queue behind each other. See
|
|
45
|
+
[claiming part of a row](#claiming-part-of-a-row).
|
|
46
|
+
|
|
47
|
+
> **Transport: both wait — only the mechanism differs.** `claim({ id })` means
|
|
48
|
+
> "serialize me behind whoever holds this row" on every transport. The
|
|
49
|
+
> realtime client parks the promise on its socket and resolves it on the grant
|
|
50
|
+
> frame. The **stateless HTTP client** (`Ablo({ transport: 'http' })` — the
|
|
51
|
+
> transport server-side agents use) holds the same place in the same
|
|
52
|
+
> server-side FIFO line; under the hood it heartbeats its queued ticket until
|
|
53
|
+
> the line moves, then re-reads the row and resolves to the same held claim.
|
|
54
|
+
> The same snippet works on both.
|
|
55
|
+
>
|
|
56
|
+
> Shape the wait the same way on either transport: cap it with
|
|
57
|
+
> `waitTimeoutMs` (rejects `grant_timeout` and leaves the line), cancel it
|
|
58
|
+
> from outside with `signal` (an `AbortSignal`; rejects
|
|
59
|
+
> `claim_wait_aborted`), bound the line you'll join with `maxQueueDepth`
|
|
60
|
+
> (`queue_too_deep`), or skip waiting entirely with `queue: false` — the
|
|
61
|
+
> try-claim, which resolves `null` when the target is held (a declined try
|
|
62
|
+
> is not an error) and takes no place in line. For
|
|
63
|
+
> callers that manage the wait themselves, the ticket surface remains:
|
|
64
|
+
> `ablo.claims.retrieve({ claimId })` polls a ticket to its grant,
|
|
65
|
+
> `ablo.claims.heartbeat({ claimId })` keeps the slot, and
|
|
66
|
+
> `ablo.claims.release({ claimId })` leaves the line. And contention can be
|
|
67
|
+
> treated as a signal rather than a wait at all — catch the error, re-read
|
|
68
|
+
> fresh, regenerate, retry; see [Errors](#errors) for the loop sketch.
|
|
69
|
+
|
|
70
|
+
This reference opens with [the model](#the-model-three-layers-one-decision) — the
|
|
53
71
|
one answer to "how do two agents not clobber each other" — then covers the
|
|
54
72
|
[claim state object](#the-claim-state-object), the SDK [methods](#methods)
|
|
55
73
|
(`claim` · `claim.state` · `claim.queue` · `claim.release` · [writing under a
|
|
@@ -80,7 +98,7 @@ claim](#writing-under-a-claim)), and the [errors](#errors) you can catch.
|
|
|
80
98
|
|
|
81
99
|
---
|
|
82
100
|
|
|
83
|
-
## The model
|
|
101
|
+
## The model: three layers, one decision
|
|
84
102
|
|
|
85
103
|
Ablo has exactly **three** coordination layers. They are **not** three competing
|
|
86
104
|
answers to the same question — they stack, and only one of them is a decision you
|
|
@@ -88,9 +106,9 @@ make:
|
|
|
88
106
|
|
|
89
107
|
| layer | kind | what it does | enforces? |
|
|
90
108
|
|---|---|---|---|
|
|
91
|
-
| **Presence** (`claim.state`, observers) | observation | Broadcasts who is working where, live. Renders cursors / "agent X is editing." Reading or claiming a row auto-enrolls you in its sync group, so `claim.state({ id })` observes co-participants from any client (browser or Node agent) with no manual subscribe step. | **No.** Advisory only
|
|
92
|
-
| **Claim** (`claim`/`claim.queue`/`claim.release`) | pessimistic | Reserves a row for one participant. Foreign writers are rejected server-side; contenders join a fair FIFO queue. | **Yes**, between participants
|
|
93
|
-
| **Stale-context** (`readAt` + `onStale`) | optimistic (LWW) | On commit, rejects a write whose snapshot is older than the row's latest delta. Last-writer-wins detection. | **Yes**, against time
|
|
109
|
+
| **Presence** (`claim.state`, observers) | observation | Broadcasts who is working where, live. Renders cursors / "agent X is editing." Reading or claiming a row auto-enrolls you in its sync group, so `claim.state({ id })` observes co-participants from any client (browser or Node agent) with no manual subscribe step. | **No.** Advisory only: it never blocks or rejects a write. |
|
|
110
|
+
| **Claim** (`claim`/`claim.queue`/`claim.release`) | pessimistic | Reserves a row for one participant. Foreign writers are rejected server-side; contenders join a fair FIFO queue. | **Yes**, between participants: mutual exclusion. |
|
|
111
|
+
| **Stale-context** (`readAt` + `onStale`) | optimistic (LWW) | On commit, rejects a write whose snapshot is older than the row's latest delta. Last-writer-wins detection. | **Yes**, against time: lost-update detection. |
|
|
94
112
|
|
|
95
113
|
**The one decision: do you hold the row across a slow gap (read → LLM call →
|
|
96
114
|
write)?**
|
|
@@ -128,46 +146,53 @@ disposition once, in the schema, so every commit to that model is governed
|
|
|
128
146
|
without per-call wiring. This is the third coordination axis — orthogonal to
|
|
129
147
|
`policy` (who may read a row) and `groups` (which delta channels it fans into).
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
| value | meaning |
|
|
135
|
-
|---|---|
|
|
136
|
-
| `'overwrite'` | the write wins; that committer is never blocked. |
|
|
137
|
-
| `'reject'` | the write is refused; that committer yields to a held claim / stale snapshot. |
|
|
138
|
-
| `'notify'` | hold the write and hand back the current value so the committer re-reads and re-applies (stale writes only). |
|
|
149
|
+
Set a model's `conflict` stance with `coordination`, naming one rule per kind of
|
|
150
|
+
committer:
|
|
139
151
|
|
|
140
152
|
```ts
|
|
141
|
-
import { model,
|
|
142
|
-
|
|
143
|
-
export const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
import { coordination, model, z } from '@abloatai/ablo/schema';
|
|
154
|
+
|
|
155
|
+
export const cards = model(
|
|
156
|
+
{
|
|
157
|
+
title: z.string(),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
// "a human's edit always wins (never blocked); an agent yields"
|
|
161
|
+
conflict: coordination.humansOverwrite().agentsReject(),
|
|
162
|
+
}
|
|
163
|
+
);
|
|
149
164
|
```
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
Each rule pairs a committer with a disposition, drawn from the same `onStale`
|
|
167
|
+
vocabulary the write guards use:
|
|
168
|
+
|
|
169
|
+
| disposition | meaning |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `overwrite` | the write wins; that committer is never blocked. |
|
|
172
|
+
| `reject` | the write is refused; that committer yields to a held claim / stale snapshot. |
|
|
173
|
+
| `notify` | hold the write and hand back the current value so the committer re-reads and re-applies (stale writes only). |
|
|
174
|
+
|
|
175
|
+
That gives nine rules — `humansOverwrite` / `humansReject` / `humansNotify`,
|
|
176
|
+
`agentsOverwrite` / `agentsReject` / `agentsNotify`, `systemOverwrite` /
|
|
177
|
+
`systemReject` / `systemNotify` — and a chain may name as many as it needs. A
|
|
178
|
+
kind left unnamed falls through to the engine default, and a kind named twice
|
|
179
|
+
takes the later rule.
|
|
152
180
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
for conflict policy — later rules win on key collisions):
|
|
181
|
+
When the rules are assembled at runtime rather than written out, each one is
|
|
182
|
+
also a standalone function, and `coordination()` merges them:
|
|
156
183
|
|
|
157
184
|
```ts
|
|
158
|
-
import {
|
|
185
|
+
import { coordination, humansOverwrite, agentsReject } from '@abloatai/ablo/schema';
|
|
159
186
|
|
|
160
|
-
|
|
161
|
-
title: field.string(),
|
|
162
|
-
}, {
|
|
163
|
-
conflict: coordination(humansOverwrite(), agentsReject()),
|
|
164
|
-
// → { user: 'overwrite', agent: 'reject' }
|
|
165
|
-
});
|
|
187
|
+
const stance = coordination(humansOverwrite(), agentsReject());
|
|
166
188
|
```
|
|
167
189
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
Both forms produce the same thing: a map keyed by the committer's participant
|
|
191
|
+
kind, which is what travels to the server.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
{ user: 'overwrite', agent: 'reject' }
|
|
195
|
+
```
|
|
171
196
|
|
|
172
197
|
### How it relates to per-write coordination
|
|
173
198
|
|
|
@@ -195,14 +220,15 @@ a model row. It's what `claim.state()` returns and what observers render.
|
|
|
195
220
|
| field | type | description |
|
|
196
221
|
|---|---|---|
|
|
197
222
|
| `id` | `string` | The claim id (distinct from the target row id). |
|
|
198
|
-
| `status` | `ClaimStatus` | `'active' \| 'queued' \| 'committed' \| 'expired' \| 'canceled'`. `active` = the holder; `queued` = waiting in line behind it. The other three are terminal states you only see on a claim you just finished
|
|
199
|
-
| `target` | `EntityRef` | What is being coordinated (`{ model, id
|
|
200
|
-
| `description` | `string` | Peer-visible description of the work
|
|
223
|
+
| `status` | `ClaimStatus` | `'active' \| 'queued' \| 'committed' \| 'expired' \| 'canceled'`. `active` = the holder; `queued` = waiting in line behind it. The other three are terminal states you only see on a claim you just finished: `committed` (released after a successful write), `expired` (TTL lapsed), `canceled` (released early). |
|
|
224
|
+
| `target` | `EntityRef` | What is being coordinated: the row (`{ model, id }`) plus any sub-row narrowing the holder claimed: `path?`, `range?`, `field?`, `fields?`, and opaque `meta?`. A target with no narrowing covers the whole row. |
|
|
225
|
+
| `description` | `string` | Peer-visible description of the work: the sentence another participant reads to decide whether to wait or move on (`'rewriting the risk section'`). Defaults to `'editing'`. |
|
|
201
226
|
| `heldBy` | `string` | Participant holding (or waiting on) it (e.g. `'agent:forecaster'`). |
|
|
202
|
-
| `participantKind` | `'user' \| 'agent' \| 'system'` | Who's behind it
|
|
203
|
-
| `position` | `number?` | 0-based place in the FIFO line
|
|
204
|
-
| `createdAt` | `number?` | Ms-epoch the holder opened it. Optional
|
|
205
|
-
| `expiresAt` | `number` | Ms-epoch the server reclaims it if the holder goes **silent**. Renewed automatically while the holder's connection stays alive
|
|
227
|
+
| `participantKind` | `'user' \| 'agent' \| 'system'` | Who's behind it: a human (`user`), an AI (`agent`), or automated infrastructure (`system`). |
|
|
228
|
+
| `position` | `number?` | 0-based place in the FIFO line: present only when `status: 'queued'` (`0` = next behind the holder). |
|
|
229
|
+
| `createdAt` | `number?` | Ms-epoch the holder opened it. Optional: derived shapes may omit it. |
|
|
230
|
+
| `expiresAt` | `number` | Ms-epoch the server reclaims it if the holder goes **silent**. Renewed automatically while the holder's connection stays alive: a crash-cleanup floor, not a duration you size. |
|
|
231
|
+
| `meta` | `Record<string, unknown>?` | The claim's open metadata bag, as it stands on the wire. A [heartbeat](#heartbeat-holding-a-claim-for-long-running-work) writes its `details` here under `progress`: last beat wins, so an observer can read what a long hold is doing without the holder releasing it. Distinct from `target.meta`, which is the shape your program declared: a declared shape has no member for a key the coordinator wrote. |
|
|
206
232
|
|
|
207
233
|
```jsonc
|
|
208
234
|
{
|
|
@@ -213,23 +239,49 @@ a model row. It's what `claim.state()` returns and what observers render.
|
|
|
213
239
|
"heldBy": "agent:forecaster",
|
|
214
240
|
"participantKind": "agent",
|
|
215
241
|
"createdAt": 1748160000000,
|
|
216
|
-
"expiresAt": 1748160030000
|
|
242
|
+
"expiresAt": 1748160030000,
|
|
243
|
+
"meta": { "progress": { "phase": "writing", "done": 2, "of": 5 } }
|
|
217
244
|
}
|
|
218
245
|
```
|
|
219
246
|
|
|
247
|
+
### Lifecycle
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
claim({ id }) update({ id }) lands
|
|
251
|
+
(free) ───────────▶ active ───────────────────────▶ committed
|
|
252
|
+
│
|
|
253
|
+
┌───────────┴───────────┐
|
|
254
|
+
▼ ▼
|
|
255
|
+
canceled expired
|
|
256
|
+
(release w/o write) (TTL; holder died)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
A target is free when `ablo.<model>.claim.state({ id })` returns `null`. Terminal
|
|
260
|
+
states drop out of the live stream, so a claim you can see is either `active`
|
|
261
|
+
(the holder) or `queued` (waiting in the FIFO line behind it; see
|
|
262
|
+
[`claim.queue`](#claimqueue)).
|
|
263
|
+
|
|
264
|
+
Reading a holder's progress is the same synchronous read as everything else
|
|
265
|
+
here — no second subscription, and nothing to poll:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const held = ablo.documents.claim.state({ id: docId });
|
|
269
|
+
const phase = held?.meta?.progress?.phase ?? 'reading';
|
|
270
|
+
```
|
|
271
|
+
|
|
220
272
|
---
|
|
221
273
|
|
|
222
274
|
## Methods
|
|
223
275
|
|
|
224
276
|
One word — "claim" — names four distinct things; keep them separate as you read:
|
|
225
277
|
|
|
226
|
-
- **the lease (claim handle)
|
|
278
|
+
- **the lease (claim handle):** the *object* returned by `ablo.<model>.claim({ id })`
|
|
227
279
|
(`ClaimHandle`, an `AsyncDisposable` with `.data` and `.release()`).
|
|
228
|
-
- **acquiring a claim/lease
|
|
280
|
+
- **acquiring a claim/lease:** the *verb* `ablo.<model>.claim({ id })`, the call
|
|
229
281
|
that takes the lease.
|
|
230
|
-
- **`claim.state` / `claim.queue
|
|
282
|
+
- **`claim.state` / `claim.queue`:** the *inspection namespace* hanging off the
|
|
231
283
|
model, for reading who holds the row and who's lined up.
|
|
232
|
-
- **the write's `claim` param
|
|
284
|
+
- **the write's `claim` param:** `update({ id, data, claim })`, where you pass a
|
|
233
285
|
lease the proxy didn't take itself.
|
|
234
286
|
|
|
235
287
|
Each method below follows one fixed shape: **signature · what it does ·
|
|
@@ -249,16 +301,41 @@ then re-reads so the claimed snapshot reflects what the previous holder
|
|
|
249
301
|
committed. There's no polling and no race window — the server decides the order,
|
|
250
302
|
so two claimers can't both think they won.
|
|
251
303
|
|
|
252
|
-
**Parameters**
|
|
304
|
+
**Parameters** — every option is flat on the call, and each sits on one of
|
|
305
|
+
four axes. `claim({ id })` alone is a complete call; each axis is opt-in.
|
|
306
|
+
|
|
307
|
+
*What you claim* — the target, narrowed below the row:
|
|
308
|
+
|
|
309
|
+
| name | type | required | description |
|
|
310
|
+
|---|---|---|---|
|
|
311
|
+
| `id` | `string` | yes | The row id: same id as `retrieve` / `update`. |
|
|
312
|
+
| `options.field` | `ClaimField` | no | **Deprecated: say it as a set: `fields: ['title']`. Removed in 0.37.0.** One member for a set of one, beside another for a set of any size, is two ways to say one thing, and the singular is the one that misled: a caller needing two parts packed them into it. The wire still reads `field`, so this changes what you write, not what is understood. |
|
|
313
|
+
| `options.fields` | `ClaimField[]` | no | Claim named parts of the row instead of all of it: one or several. Prefer the selector form, `fields: (f) => [f.status]`, where the model hands you its own fields, so a field it does not have stops compiling and a rename is a compile error at every use. A bare name (`'status'`) autocompletes but is not checked; an app-defined part, a cell, a section, is named with `part('B2')`. Two sets conflict where they intersect, so holders of disjoint parts do not wait for each other, see [claiming part of a row](#claiming-part-of-a-row). A name containing a comma is refused: two names in one string compare as a single unrelated name, and both writers would be granted the same part. |
|
|
314
|
+
| `options.path` | `string` | no | A hierarchical position in a document-shaped row: `'/content/3'` claims one block. Paths conflict on containment: `/content` covers `/content/3`, while `/content/3` and `/content/7` are disjoint and both granted. |
|
|
315
|
+
| `options.range` | `TargetRange` | no | A span of the row's text: `{ startLine, endLine, startColumn?, endColumn? }`. Ranges conflict only where their line intervals overlap. An editor addressing by integer position maps it onto `startLine`/`endLine`: the test is plain interval intersection. |
|
|
316
|
+
|
|
317
|
+
*What others see* — the presence half:
|
|
253
318
|
|
|
254
319
|
| name | type | required | description |
|
|
255
320
|
|---|---|---|---|
|
|
256
|
-
| `id` | `string` | yes | The row id — same id as `retrieve` / `update`. |
|
|
257
321
|
| `options.description` | `string` | no | Peer-visible description of the work, shown to observers (default `'editing'`). |
|
|
258
|
-
| `options.
|
|
259
|
-
|
|
322
|
+
| `options.meta` | `object` | no | App-defined structured metadata, carried verbatim to every participant observing the claim. Declare its shape once on `Register`'s `ClaimMeta` slot. |
|
|
323
|
+
|
|
324
|
+
*How you wait* — admission to the line:
|
|
325
|
+
|
|
326
|
+
| name | type | required | description |
|
|
327
|
+
|---|---|---|---|
|
|
328
|
+
| `options.queue` | `boolean` | no | `true` (default) queues and waits for the lease. `false` is the try-claim: if another participant holds the row it resolves `null`: an expected outcome, not an error, so claim-or-skip dedup reads `if (!claim) return` (waiting would double-process). Who holds it stays readable via `claim.state`. A *write* to a held row still rejects `entity_claimed`. |
|
|
260
329
|
| `options.maxQueueDepth` | `number` | no | Backpressure: reject with `AbloClaimedError('queue_too_deep')` instead of joining a line already `>= maxQueueDepth` deep. Omit to wait however deep the queue is. |
|
|
261
|
-
| `options.
|
|
330
|
+
| `options.waitTimeoutMs` | `number` | no | Cap on how long a queued claim waits for its grant before rejecting with `AbloClaimedError('grant_timeout')`. Omit to wait as long as the line takes. Same meaning on both transports; over HTTP a timed-out wait also leaves the line. |
|
|
331
|
+
| `options.signal` | `AbortSignal` | no | Abort a pending wait from outside: a cancelled agent task or an unmounted component takes its queued claim with it. Rejects with `AbloClaimedError('claim_wait_aborted')`; over HTTP the abort also leaves the line. Ignored once the grant has arrived, release a held lease instead. |
|
|
332
|
+
|
|
333
|
+
*How long you hold* — the lease:
|
|
334
|
+
|
|
335
|
+
| name | type | required | description |
|
|
336
|
+
|---|---|---|---|
|
|
337
|
+
| `options.ttl` | `Duration` | no | Crash-cleanup floor. Rarely set: the lease renews while your connection is alive, so it only matters once you go silent. |
|
|
338
|
+
| `options.heartbeat` | `true \| Duration \| { every?, onBeat?, onLost? }` | no | Keep the lease alive for work that outlives the TTL: `true` beats every third of the TTL, a duration sets the cadence, and the structured form carries the cadence and both callbacks in one place: `onBeat` fires after every successful beat (chiefly `queueDepth`, the pressure signal), `onLost` once if a beat learns the lease is gone. The loop stops on release. |
|
|
262
339
|
|
|
263
340
|
The high-level `claim` queues by default, so on contention you either get the row
|
|
264
341
|
when your turn arrives or one of the [queue errors](#errors) (`claim_lost`,
|
|
@@ -290,10 +367,69 @@ committed — pass an idempotency key on the write if you replay the block.) The
|
|
|
290
367
|
lower-level [`claim.release`](#claimrelease) shows the manual `try/finally`
|
|
291
368
|
equivalent for when you hold a claim without `await using`.
|
|
292
369
|
|
|
370
|
+
### Claiming part of a row
|
|
371
|
+
|
|
372
|
+
Name a field and a typo cannot survive — the model is already bound by the call,
|
|
373
|
+
so it hands you its own fields:
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
await using mine = await ablo.tasks.claim({ id, fields: (f) => [f.status] });
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
`f.status` is checked against the model: a field it does not have stops
|
|
380
|
+
compiling, and renaming one is a compile error at every use. Nothing to import,
|
|
381
|
+
nothing to add to your schema file.
|
|
382
|
+
|
|
383
|
+
`fields: ['status']` still works and still autocompletes; it just accepts
|
|
384
|
+
`'titel'` too, which is granted, excludes nobody, and leaves the write of
|
|
385
|
+
`title` unguarded.
|
|
386
|
+
|
|
387
|
+
A claim covers the whole row only when you name nothing narrower. Name a
|
|
388
|
+
target — a `path` into a document, a `range` of text, a `field` or set of
|
|
389
|
+
`fields` — and exclusion follows it: **two claims on non-overlapping parts of
|
|
390
|
+
the same row are both granted**, and only overlapping targets queue behind
|
|
391
|
+
each other.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
// Agent A holds one block of the document…
|
|
395
|
+
await using intro = await ablo.documents.claim({
|
|
396
|
+
id: docId,
|
|
397
|
+
path: '/content/3',
|
|
398
|
+
description: 'rewriting the risk section',
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// …while agent B, in another process, holds a different block of the SAME
|
|
402
|
+
// row. Granted immediately — /content/3 and /content/7 do not overlap.
|
|
403
|
+
await using summary = await ablo.documents.claim({
|
|
404
|
+
id: docId,
|
|
405
|
+
path: '/content/7',
|
|
406
|
+
description: 'tightening the summary',
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Overlap is judged per axis:
|
|
411
|
+
|
|
412
|
+
- **`path`:** hierarchical containment, on a separator boundary. `/content`
|
|
413
|
+
covers `/content/3`, so the parent's holder queues behind (or is queued
|
|
414
|
+
behind by) the child's; `/content/3` and `/content/7` are disjoint, and
|
|
415
|
+
`/content` never collides with `/contentious`.
|
|
416
|
+
- **`field` / `fields`:** set intersection. Holders on `title` and `status`
|
|
417
|
+
proceed concurrently; naming no field covers all of them.
|
|
418
|
+
- **`range`:** interval intersection on `[startLine, endLine]`. An editor
|
|
419
|
+
that addresses text by integer position rather than by line maps that
|
|
420
|
+
position axis straight onto `startLine`/`endLine`: the server's test is
|
|
421
|
+
plain interval intersection and never assumes the units are lines of a
|
|
422
|
+
file.
|
|
423
|
+
|
|
424
|
+
A claim with no target is the widest parent: it covers every part of the row
|
|
425
|
+
and conflicts with any narrower claim on it. Region locking on a single
|
|
426
|
+
document row is therefore one claim per region — the row stays one row, and
|
|
427
|
+
the claims carve it up.
|
|
428
|
+
|
|
293
429
|
### Claim-gated reads
|
|
294
430
|
|
|
295
431
|
`claim.state({ id })` always returns immediately. Model reads such as
|
|
296
|
-
`ablo.<model>.
|
|
432
|
+
`ablo.<model>.local.retrieve(id)` are local reads and stay available while a claim is
|
|
297
433
|
held. Server/model reads can choose a claimed policy:
|
|
298
434
|
|
|
299
435
|
```ts
|
|
@@ -337,8 +473,15 @@ whole coordination API.
|
|
|
337
473
|
|---|---|---|---|
|
|
338
474
|
| `id` | `string` | yes | The row id. |
|
|
339
475
|
|
|
340
|
-
**Returns** —
|
|
341
|
-
is free.
|
|
476
|
+
**Returns** — an active [claim state object](#the-claim-state-object) on the row, or
|
|
477
|
+
`null` when the row is free.
|
|
478
|
+
|
|
479
|
+
**One holder, and a row can have several.** This reads a row, not a target, and
|
|
480
|
+
answers with a single claim. That is the whole story for a whole-row claim, and
|
|
481
|
+
only part of it once you [claim parts of a row](#claiming-part-of-a-row): three
|
|
482
|
+
agents holding `/content/3`, `/content/7`, and `title` are all active at once,
|
|
483
|
+
and this read surfaces one of them. To render every holder — a rail per claimed
|
|
484
|
+
block, a chip per participant — use [`claim.list`](#claimlist).
|
|
342
485
|
|
|
343
486
|
**Example**
|
|
344
487
|
|
|
@@ -361,6 +504,45 @@ Returns the active claim state when the row is held, or `null` when it's free:
|
|
|
361
504
|
}
|
|
362
505
|
```
|
|
363
506
|
|
|
507
|
+
### `claim.list`
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
ablo.<model>.claim.list({ id })
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Every holder of a row. Same synchronous, reactive read as `claim.state` — off
|
|
514
|
+
the same local snapshot, safe to call inline in a render — and the same list
|
|
515
|
+
envelope as [`claim.queue`](#claimqueue).
|
|
516
|
+
|
|
517
|
+
Reach for it whenever a row can be claimed in parts. One agent rewriting
|
|
518
|
+
`/content/3` while another tightens `/content/7` are two active claims on one
|
|
519
|
+
row, and only this read returns both.
|
|
520
|
+
|
|
521
|
+
**Parameters**
|
|
522
|
+
|
|
523
|
+
| name | type | required | description |
|
|
524
|
+
|---|---|---|---|
|
|
525
|
+
| `id` | `string` | yes | The row id. |
|
|
526
|
+
|
|
527
|
+
**Returns** — `{ object: 'list', data: Claim[] }`. Your own claim comes first
|
|
528
|
+
when this client holds one, then the other participants'. Empty `data` when the
|
|
529
|
+
row is free.
|
|
530
|
+
|
|
531
|
+
**Example** — a rail for every claimed block:
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
const { data: holders } = ablo.documents.claim.list({ id: docId });
|
|
535
|
+
|
|
536
|
+
return blocks.map((block) => {
|
|
537
|
+
const held = holders.find((c) => c.target.path === `/content/${block.index}`);
|
|
538
|
+
return <Block key={block.id} rail={held?.heldBy} note={held?.description} />;
|
|
539
|
+
});
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
`claim.state({ id })` remains the right read when a row is claimed whole, or
|
|
543
|
+
when all you need is "is anyone working here" — it answers with one claim and
|
|
544
|
+
`null` when the row is free.
|
|
545
|
+
|
|
364
546
|
### `claim.queue`
|
|
365
547
|
|
|
366
548
|
```ts
|
|
@@ -426,7 +608,7 @@ try {
|
|
|
426
608
|
}
|
|
427
609
|
```
|
|
428
610
|
|
|
429
|
-
### `heartbeat
|
|
611
|
+
### `heartbeat`: holding a claim for long-running work
|
|
430
612
|
|
|
431
613
|
```ts
|
|
432
614
|
held.heartbeat(ttl?: Duration): Promise<{ expiresAt: number }>
|
|
@@ -447,8 +629,7 @@ await using claim = await ablo.reports.claim({
|
|
|
447
629
|
id: 'report_q3',
|
|
448
630
|
description: 'generating',
|
|
449
631
|
ttl: '5m',
|
|
450
|
-
heartbeat:
|
|
451
|
-
onHeartbeatLost: () => abortWork(),
|
|
632
|
+
heartbeat: { onLost: () => abortWork() }, // `true` and '2m' are the shorthands
|
|
452
633
|
});
|
|
453
634
|
await runLongGeneration(claim.data); // lease held for the duration
|
|
454
635
|
// scope exit releases; the loop stops with it
|
|
@@ -463,11 +644,11 @@ failures (a connection blip) don't stop the loop — the next tick retries.
|
|
|
463
644
|
|
|
464
645
|
Each beat's answer carries two more things:
|
|
465
646
|
|
|
466
|
-
- **`queueDepth
|
|
647
|
+
- **`queueDepth`:** how many participants wait in line behind the lease.
|
|
467
648
|
This is the cooperative-yield pressure signal: a worker that can checkpoint
|
|
468
649
|
may release early when others wait. Read it from the resolved beat, or pass
|
|
469
|
-
`
|
|
470
|
-
- **progress `details
|
|
650
|
+
`heartbeat: { onBeat }` when claiming to observe every auto-beat.
|
|
651
|
+
- **progress `details`:** `held.heartbeat({ details: { pages: 42, of: 100 } })`
|
|
471
652
|
stores the payload as the claim's peer-visible `meta.progress` (last beat
|
|
472
653
|
wins, via `claim.state`). This is presence, not a checkpoint: it dies with
|
|
473
654
|
the lease. Durable progress belongs in the data itself — write a row, and
|
|
@@ -497,7 +678,7 @@ A stateless worker holding **many** rows beats them all in one round trip:
|
|
|
497
678
|
entry per extended lease. This is the socketless twin of the realtime
|
|
498
679
|
keepalive, which already renews every held lease on each ping.
|
|
499
680
|
|
|
500
|
-
### durability
|
|
681
|
+
### durability: what a claim survives
|
|
501
682
|
|
|
502
683
|
A lease belongs to your **identity** — the participant behind the credential —
|
|
503
684
|
not to the socket it was claimed on; the server keys each lease by participant
|
|
@@ -537,20 +718,20 @@ snapshot.
|
|
|
537
718
|
|
|
538
719
|
| the holder… | what happens to the claim |
|
|
539
720
|
| --- | --- |
|
|
540
|
-
| blips, then reconnects within the window | renewed automatically on reconnect
|
|
721
|
+
| blips, then reconnects within the window | renewed automatically on reconnect: no interruption |
|
|
541
722
|
| crashes or drops for good | released within one keepalive cycle; the queue advances |
|
|
542
|
-
| still has a second live connection | survives
|
|
723
|
+
| still has a second live connection | survives: release fires only on the last connection |
|
|
543
724
|
| loses the server to a restart | rides the TTL in the coordination store; re-announced on reconnect |
|
|
544
725
|
|
|
545
|
-
### `join
|
|
726
|
+
### `join`: presence for a set of rows
|
|
546
727
|
|
|
547
728
|
Reading or claiming a row auto-enrolls you in its sync group, which is enough for
|
|
548
729
|
`claim.state`/`claim.queue` to observe co-participants. When you want to *hold*
|
|
549
|
-
presence on a known set of rows — a
|
|
730
|
+
presence on a known set of rows — a workspace's documents, a board's cards — and react to
|
|
550
731
|
who joins or leaves, use `join`:
|
|
551
732
|
|
|
552
733
|
```ts
|
|
553
|
-
await using room = await ablo.
|
|
734
|
+
await using room = await ablo.documents.join(slideIds, { ttl: '5m' });
|
|
554
735
|
room.peers; // who else is here, live
|
|
555
736
|
```
|
|
556
737
|
|
|
@@ -615,19 +796,19 @@ inspect the `code`.
|
|
|
615
796
|
|
|
616
797
|
| error | `code` | thrown when | carries |
|
|
617
798
|
|---|---|---|---|
|
|
618
|
-
| `AbloClaimedError` | `claim_lost` | A held/queued claim was taken away
|
|
619
|
-
| `AbloClaimedError` | `claim_queued` | **HTTP transport only.** A contended `claim` (default `queue: true`) could not block-wait for the lease (no socket), so it rejected immediately instead of queueing. Retryable
|
|
799
|
+
| `AbloClaimedError` | `claim_lost` | A held/queued claim was taken away: the holder disconnected (reaped on the keepalive cycle), went silent past its TTL, was revoked, or was preempted (a privileged reorder, or a configured cumulative-hold ceiling reached while contenders waited), while you were holding or waiting. | `claims?` |
|
|
800
|
+
| `AbloClaimedError` | `claim_queued` | **HTTP transport only.** A contended `claim` (default `queue: true`) could not block-wait for the lease (no socket), so it rejected immediately instead of queueing. Retryable: re-attempt the claim. | `claims?` |
|
|
620
801
|
| `AbloClaimedError` | `grant_timeout` | The optional `timeoutMs` elapsed while you were still queued for a grant. | `claims?` |
|
|
621
|
-
| `AbloClaimedError` | `queue_too_deep` | `claim` was passed `maxQueueDepth` and the wait line was already that deep when you tried to join
|
|
622
|
-
| `AbloClaimedError` | `claim_conflict` | An `update`/`delete` targets a row another participant holds
|
|
623
|
-
| `AbloClaimedError` | `entity_claimed` | Same conflict, from the commit guard backstop.
|
|
624
|
-
| `AbloStaleContextError`
|
|
625
|
-
| `AbloValidationError` | `model_claim_not_configured` | `claim` called on a model proxy built without the collaboration runtime
|
|
626
|
-
| `AbloValidationError` | `entity_not_found` | The row id doesn't exist locally or on load.
|
|
802
|
+
| `AbloClaimedError` | `queue_too_deep` | `claim` was passed `maxQueueDepth` and the wait line was already that deep when you tried to join: fail-fast instead of waiting. | `claims?` |
|
|
803
|
+
| `AbloClaimedError` | `claim_conflict` | An `update`/`delete` targets a row another participant holds: the server's pre-commit check rejected it. |: |
|
|
804
|
+
| `AbloClaimedError` | `entity_claimed` | Same conflict, from the commit guard backstop. |: |
|
|
805
|
+
| `AbloStaleContextError` |: | A guarded `update` (under a claim, or any write carrying `readAt`) targets a row that received deltas since the snapshot: your reasoning is stale. | `readAt`, `conflicts[]` |
|
|
806
|
+
| `AbloValidationError` | `model_claim_not_configured` | `claim` called on a model proxy built without the collaboration runtime: an internal/advanced construction path. The standard `Ablo({ schema, apiKey })` client enables claiming for **every** model; there is no per-model claim config to add. |: |
|
|
807
|
+
| `AbloValidationError` | `entity_not_found` | The row id doesn't exist locally or on load. |: |
|
|
627
808
|
|
|
628
809
|
`AbloStaleContextError.conflicts` lists the `(model, id, observedSyncId)` rows
|
|
629
810
|
that moved during your generation window — use it for selective regeneration
|
|
630
|
-
(re-think only the
|
|
811
|
+
(re-think only the documents that changed, not the whole workspace) and for metrics.
|
|
631
812
|
|
|
632
813
|
```ts
|
|
633
814
|
try {
|
|
@@ -731,9 +912,9 @@ state**, which is the shape that races. The other two aren't read-modify-write:
|
|
|
731
912
|
|
|
732
913
|
| Verb | Functional form? | Why | Its "just works" property |
|
|
733
914
|
| --- | --- | --- | --- |
|
|
734
|
-
| `update` | **yes
|
|
735
|
-
| `create` | no
|
|
736
|
-
| `delete` | no
|
|
915
|
+
| `update` | **yes**: `update(id, current => next)` | next value depends on the current one (lost-update hazard) | compare-and-swap + reconcile |
|
|
916
|
+
| `create` | no: `create({ data, id? })` | no prior state to read; the hazard is *id collision*, a terminal `unique_violation`, not a lost update | **idempotency**: stable id / `idempotencyKey` makes a retried create safe |
|
|
917
|
+
| `delete` | no: `delete({ id })` | no resulting state to compute; "make it not exist" is unchanged by concurrent edits, and delete is idempotent | naturally idempotent |
|
|
737
918
|
|
|
738
919
|
The same reason React has `setState(prev => next)` but no functional mount /
|
|
739
920
|
unmount. A *conditional* delete ("only if unchanged since I read it") is the one
|
|
@@ -742,38 +923,19 @@ not a function.
|
|
|
742
923
|
|
|
743
924
|
---
|
|
744
925
|
|
|
745
|
-
## Observability
|
|
926
|
+
## Observability
|
|
746
927
|
|
|
747
928
|
Coordination you can't see is coordination you can't debug. Pass an
|
|
748
929
|
`observability` provider to `Ablo({ ... })` and the client reports every claim
|
|
749
930
|
lifecycle event and stale-write collision it sees. The batteries-included
|
|
750
|
-
provider is `ClaimLog
|
|
931
|
+
provider is `ClaimLog`, and `collisions()` is the eval primitive:
|
|
751
932
|
|
|
752
933
|
```ts
|
|
753
|
-
import Ablo, { ClaimLog } from '@abloatai/ablo';
|
|
754
|
-
|
|
755
934
|
const log = new ClaimLog();
|
|
756
935
|
const ablo = Ablo({ schema, apiKey, observability: log });
|
|
757
|
-
// …run your agents…
|
|
758
936
|
|
|
759
|
-
log.
|
|
760
|
-
log.collisions() // just the collisions: rejected/lost claims + stale writes
|
|
761
|
-
log.toString() // pretty, greppable timeline to print
|
|
762
|
-
log.onChange(fn) // reactive subscribe → drive a live activity feed / useSyncExternalStore
|
|
937
|
+
expect(log.collisions()).toHaveLength(0); // no one stepped on anyone
|
|
763
938
|
```
|
|
764
939
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
`conflict: tx … — 1 row(s) changed underneath: reports/r1`.
|
|
768
|
-
|
|
769
|
-
`ClaimLog` implements the full `SyncObservabilityProvider`, so it drops straight
|
|
770
|
-
into the `observability` slot; spread `noopObservability` if you only want to
|
|
771
|
-
override a few hooks (e.g. forward to Sentry/OTel). Exports: `ClaimLog`,
|
|
772
|
-
`formatClaim`, `formatConflict`, `noopObservability`, and the types `ClaimEvent`,
|
|
773
|
-
`ConflictEvent`, `ClaimLogEntry`, `SyncObservabilityProvider`.
|
|
774
|
-
|
|
775
|
-
> **Both transports, from 0.21.0.** Observability fires on the WebSocket **and**
|
|
776
|
-
> the stateless HTTP transport (claim acquired + coordination-conflict
|
|
777
|
-
> rejections, on every write door). Before 0.21.0 only WebSocket emitted, so a
|
|
778
|
-
> `ClaimLog` on an HTTP client — e.g. a headless server-agent eval — stayed
|
|
779
|
-
> silent even though coordination still worked.
|
|
940
|
+
See [Debugging & Logs](./debugging.md) for the setup, the event shapes, a
|
|
941
|
+
reactive activity feed, and routing events to your own backend.
|