@abloatai/ablo 0.34.1 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +758 -5
- package/README.md +56 -502
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +176 -48
- package/dist/BaseSyncedStore.js +346 -214
- package/dist/Database.d.ts +17 -44
- package/dist/Database.js +96 -79
- package/dist/InstanceCache.d.ts +31 -6
- package/dist/InstanceCache.js +65 -30
- package/dist/LazyReferenceCollection.d.ts +3 -3
- package/dist/LazyReferenceCollection.js +4 -4
- package/dist/Model.d.ts +23 -13
- package/dist/Model.js +27 -17
- package/dist/ModelRegistry.d.ts +8 -4
- package/dist/ModelRegistry.js +20 -18
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
- package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
- package/dist/SyncClient.d.ts +47 -47
- package/dist/SyncClient.js +215 -156
- package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
- package/dist/ai-sdk/coordinatedTool.js +1 -1
- package/dist/ai-sdk/coordinationContext.d.ts +2 -2
- package/dist/ai-sdk/coordinationContext.js +1 -1
- package/dist/ai-sdk/wrap.d.ts +3 -3
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/index.d.ts +1 -156
- package/dist/auth/index.js +8 -301
- package/dist/client/Ablo.d.ts +42 -287
- package/dist/client/Ablo.js +129 -963
- package/dist/client/abloClient.d.ts +309 -0
- package/dist/client/abloClient.js +13 -0
- package/dist/client/clientPrelude.d.ts +52 -0
- package/dist/client/clientPrelude.js +60 -0
- package/dist/client/consoleLogger.d.ts +2 -2
- package/dist/client/coreClient.d.ts +60 -0
- package/dist/client/coreClient.js +118 -0
- package/dist/client/createInternalComponents.d.ts +8 -4
- package/dist/client/createInternalComponents.js +17 -10
- package/dist/client/createModelProxy.d.ts +98 -373
- package/dist/client/createModelProxy.js +233 -139
- package/dist/client/humans.d.ts +69 -0
- package/dist/client/humans.js +78 -0
- package/dist/client/modelRegistration.d.ts +1 -1
- package/dist/client/modelRegistration.js +9 -9
- package/dist/client/options.d.ts +73 -17
- package/dist/client/reactiveEngine.d.ts +53 -0
- package/dist/client/reactiveEngine.js +688 -0
- package/dist/client/resourceTypes.d.ts +9 -250
- package/dist/client/resourceTypes.js +8 -5
- package/dist/client/schemaConfig.d.ts +4 -4
- package/dist/client/schemaConfig.js +6 -2
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/client/validateAbloOptions.d.ts +3 -2
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +3 -3
- package/dist/client/wsMutationExecutor.js +3 -3
- package/dist/context.d.ts +22 -9
- package/dist/context.js +33 -9
- package/dist/coordination/ClaimLog.d.ts +26 -0
- package/dist/coordination/ClaimLog.js +32 -0
- package/dist/coordination/index.d.ts +1 -15
- package/dist/coordination/index.js +8 -31
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +2 -2
- package/dist/docs/catalog.d.ts +72 -0
- package/dist/docs/catalog.js +230 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +10 -0
- package/dist/environment.d.ts +1 -40
- package/dist/environment.js +8 -37
- package/dist/index.d.ts +44 -36
- package/dist/index.js +30 -22
- package/dist/interfaces/index.d.ts +44 -134
- package/dist/keys/index.d.ts +1 -77
- package/dist/keys/index.js +8 -190
- package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
- package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
- package/dist/mutators/Transaction.d.ts +1 -1
- package/dist/mutators/Transaction.js +1 -1
- package/dist/mutators/UndoManager.d.ts +6 -6
- package/dist/mutators/UndoManager.js +5 -5
- package/dist/mutators/defineMutators.d.ts +3 -3
- package/dist/mutators/defineMutators.js +1 -1
- package/dist/mutators/inverseOp.js +2 -2
- package/dist/mutators/mutateActions.d.ts +3 -3
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +1 -1
- package/dist/mutators/undoApply.js +1 -1
- package/dist/policy/index.d.ts +2 -2
- package/dist/policy/index.js +1 -1
- package/dist/query/client.d.ts +5 -2
- package/dist/query/client.js +10 -9
- package/dist/query/types.d.ts +6 -41
- package/dist/query/types.js +2 -2
- package/dist/react/AbloProvider.d.ts +18 -8
- package/dist/react/AbloProvider.js +10 -9
- package/dist/react/context.d.ts +3 -3
- package/dist/react/context.js +1 -1
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +6 -5
- package/dist/react/index.js +6 -3
- package/dist/react/internalContext.d.ts +1 -1
- package/dist/react/useAblo.d.ts +12 -5
- package/dist/react/useAblo.js +26 -8
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.d.ts +2 -2
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.d.ts +3 -3
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useUndoScope.d.ts +5 -5
- package/dist/react/useUndoScope.js +1 -1
- package/dist/schema/coordination.d.ts +69 -10
- package/dist/schema/coordination.js +90 -9
- package/dist/schema/ddl.js +2 -2
- package/dist/schema/diff.d.ts +1 -1
- package/dist/schema/generate.js +1 -1
- package/dist/schema/index.d.ts +11 -10
- package/dist/schema/index.js +22 -18
- package/dist/schema/queries.d.ts +27 -27
- package/dist/schema/queries.js +23 -23
- package/dist/schema/select.d.ts +3 -3
- package/dist/schema/select.js +6 -3
- package/dist/schema/serialize.d.ts +15 -6
- package/dist/schema/serialize.js +20 -3
- package/dist/schema/sugar.d.ts +6 -7
- package/dist/schema/sugar.js +9 -12
- package/dist/schema/syncDeltaRow.d.ts +4 -152
- package/dist/schema/syncDeltaRow.js +4 -105
- package/dist/server/adapter.d.ts +18 -1
- package/dist/server/commit.d.ts +10 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +1 -1
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/adapters/drizzle.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +2 -2
- package/dist/source/adapters/kysely.d.ts +1 -1
- package/dist/source/adapters/kysely.js +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +2 -2
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.d.ts +8 -3
- package/dist/source/adapters/prisma.js +1 -1
- package/dist/source/connector.js +1 -1
- package/dist/source/connectorProtocol.d.ts +2 -8
- package/dist/source/connectorProtocol.js +3 -2
- package/dist/source/contract.d.ts +29 -17
- package/dist/source/contract.js +27 -22
- package/dist/source/factory.d.ts +1 -1
- package/dist/source/idempotency.js +2 -2
- package/dist/source/index.d.ts +1 -0
- package/dist/source/index.js +3 -0
- package/dist/source/next.d.ts +1 -1
- package/dist/source/signing.d.ts +9 -2
- package/dist/source/signing.js +4 -1
- package/dist/source/types.d.ts +6 -4
- package/dist/source/types.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +6 -6
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +14 -13
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/{core → stores}/StoreManager.d.ts +9 -26
- package/dist/{core → stores}/StoreManager.js +29 -77
- package/dist/stores/SyncActionStore.d.ts +4 -2
- package/dist/stores/SyncActionStore.js +11 -17
- package/dist/stores/syncAction.d.ts +26 -0
- package/dist/stores/syncAction.js +16 -0
- package/dist/surface.d.ts +3 -3
- package/dist/surface.js +6 -4
- package/dist/sync/BootstrapFetcher.d.ts +127 -6
- package/dist/sync/BootstrapFetcher.js +511 -83
- package/dist/sync/ConnectionManager.d.ts +6 -198
- package/dist/sync/ConnectionManager.js +6 -677
- package/dist/sync/OnDemandLoader.d.ts +5 -2
- package/dist/sync/OnDemandLoader.js +61 -21
- package/dist/sync/SubscriptionManager.d.ts +13 -2
- package/dist/sync/SubscriptionManager.js +23 -5
- package/dist/sync/SyncWebSocket.d.ts +27 -510
- package/dist/sync/SyncWebSocket.js +76 -954
- package/dist/sync/awaitClaimGrant.d.ts +4 -44
- package/dist/sync/awaitClaimGrant.js +4 -109
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/commitFrames.d.ts +6 -40
- package/dist/sync/commitFrames.js +6 -97
- package/dist/sync/contextPorts.d.ts +18 -0
- package/dist/sync/contextPorts.js +31 -0
- package/dist/sync/createClaimStream.d.ts +5 -49
- package/dist/sync/createClaimStream.js +5 -469
- package/dist/sync/createPresenceStream.d.ts +26 -4
- package/dist/sync/createPresenceStream.js +28 -20
- package/dist/sync/createSnapshot.d.ts +2 -2
- package/dist/sync/createSnapshot.js +1 -1
- package/dist/sync/credentialLifecycle.d.ts +5 -173
- package/dist/sync/credentialLifecycle.js +5 -320
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +24 -6
- package/dist/sync/participants.js +32 -23
- package/dist/sync/schemaDrift.d.ts +55 -0
- package/dist/sync/schemaDrift.js +53 -0
- package/dist/sync/schemas.d.ts +23 -33
- package/dist/sync/schemas.js +29 -20
- package/dist/sync/syncPlan.d.ts +3 -3
- package/dist/sync/wsFrameHandlers.d.ts +6 -114
- package/dist/sync/wsFrameHandlers.js +6 -392
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/ablo.d.ts +88 -0
- package/dist/transaction/ablo.js +33 -0
- package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
- package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
- package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
- package/dist/transaction/auth/bootstrapScope.js +1 -0
- package/dist/transaction/auth/capability.d.ts +212 -0
- package/dist/transaction/auth/capability.js +224 -0
- package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
- package/dist/{client → transaction/auth}/identity.d.ts +8 -7
- package/dist/{client → transaction/auth}/identity.js +1 -1
- package/dist/transaction/auth/index.d.ts +162 -0
- package/dist/transaction/auth/index.js +304 -0
- package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
- package/dist/{auth → transaction/auth}/schemas.js +13 -13
- package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
- package/dist/{client → transaction/auth}/sessionMint.js +4 -7
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +56 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +124 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +34 -0
- package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/claimMeta.d.ts +49 -0
- package/dist/transaction/coordination/claimMeta.js +52 -0
- package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
- package/dist/transaction/coordination/createClaimStream.js +475 -0
- package/dist/transaction/coordination/events.d.ts +74 -0
- package/dist/transaction/coordination/events.js +7 -0
- package/dist/transaction/coordination/index.d.ts +19 -0
- package/dist/transaction/coordination/index.js +45 -0
- package/dist/transaction/coordination/locator.d.ts +104 -0
- package/dist/transaction/coordination/locator.js +102 -0
- package/dist/transaction/coordination/schema.d.ts +1536 -0
- package/dist/transaction/coordination/schema.js +1177 -0
- package/dist/transaction/coordination/targetConflict.d.ts +2 -0
- package/dist/transaction/coordination/targetConflict.js +107 -0
- package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
- package/dist/{coordination → transaction/coordination}/trace.js +18 -25
- package/dist/transaction/durableWrites.d.ts +62 -0
- package/dist/{client → transaction}/durableWrites.js +28 -3
- package/dist/transaction/environment.d.ts +105 -0
- package/dist/transaction/environment.js +108 -0
- package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +12 -12
- package/dist/{errorCodes.js → transaction/errorCodes.js} +45 -18
- package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
- package/dist/{errors.js → transaction/errors.js} +85 -16
- package/dist/transaction/footprint.d.ts +111 -0
- package/dist/transaction/footprint.js +0 -0
- package/dist/transaction/index.d.ts +20 -0
- package/dist/transaction/index.js +20 -0
- package/dist/transaction/keys/index.d.ts +87 -0
- package/dist/transaction/keys/index.js +207 -0
- package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
- package/dist/transaction/log/syncDeltaRow.js +95 -0
- package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
- package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
- package/dist/transaction/logger.d.ts +16 -0
- package/dist/transaction/logger.js +7 -0
- package/dist/transaction/observability.d.ts +53 -0
- package/dist/transaction/observability.js +19 -0
- package/dist/transaction/plugin.d.ts +285 -0
- package/dist/transaction/plugin.js +106 -0
- package/dist/{policy → transaction/policy}/types.d.ts +3 -3
- package/dist/{policy → transaction/policy}/types.js +2 -0
- package/dist/transaction/resources/httpResources.d.ts +321 -0
- package/dist/transaction/resources/httpResources.js +7 -0
- package/dist/transaction/resources/modelOperations.d.ts +427 -0
- package/dist/transaction/resources/modelOperations.js +12 -0
- package/dist/transaction/resources/mutationOptions.d.ts +66 -0
- package/dist/transaction/resources/mutationOptions.js +9 -0
- package/dist/transaction/resources/where.d.ts +101 -0
- package/dist/transaction/resources/where.js +115 -0
- package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
- package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
- package/dist/{schema → transaction/schema}/field.d.ts +17 -23
- package/dist/{schema → transaction/schema}/field.js +5 -5
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/loadStrategy.d.ts +45 -0
- package/dist/transaction/schema/loadStrategy.js +46 -0
- package/dist/{schema → transaction/schema}/model.d.ts +50 -35
- package/dist/{schema → transaction/schema}/model.js +30 -20
- package/dist/transaction/schema/openapi.d.ts +58 -0
- package/dist/transaction/schema/openapi.js +501 -0
- package/dist/{schema → transaction/schema}/relation.d.ts +21 -16
- package/dist/{schema → transaction/schema}/relation.js +7 -7
- package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
- package/dist/{schema → transaction/schema}/residency.js +0 -5
- package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
- package/dist/{schema → transaction/schema}/roles.js +5 -5
- package/dist/{schema → transaction/schema}/schema.d.ts +39 -10
- package/dist/{schema → transaction/schema}/schema.js +24 -3
- package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
- package/dist/{schema → transaction/schema}/tenancy.js +7 -4
- package/dist/transaction/transactionLayer.d.ts +82 -0
- package/dist/transaction/transactionLayer.js +24 -0
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +5 -6
- package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
- package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
- package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +11 -37
- package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
- package/dist/transaction/transport/commitFrames.d.ts +90 -0
- package/dist/transaction/transport/commitFrames.js +134 -0
- package/dist/transaction/transport/connectionManager.d.ts +215 -0
- package/dist/transaction/transport/connectionManager.js +673 -0
- package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
- package/dist/transaction/transport/credentialLifecycle.js +324 -0
- package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
- package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
- package/dist/transaction/transport/httpClient.d.ts +131 -0
- package/dist/{client → transaction/transport}/httpClient.js +6 -5
- package/dist/transaction/transport/httpOptions.d.ts +33 -0
- package/dist/transaction/transport/httpOptions.js +12 -0
- package/dist/{client → transaction/transport}/httpTransport.js +295 -97
- package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
- package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
- package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
- package/dist/transaction/transport/wsFrameHandlers.js +429 -0
- package/dist/transaction/transport/wsTransport.d.ts +574 -0
- package/dist/transaction/transport/wsTransport.js +1023 -0
- package/dist/transaction/types/assertExact.d.ts +17 -0
- package/dist/transaction/types/assertExact.js +1 -0
- package/dist/{types → transaction/types}/global.d.ts +17 -2
- package/dist/{types → transaction/types}/global.js +2 -1
- package/dist/{types → transaction/types}/index.d.ts +14 -46
- package/dist/{types → transaction/types}/index.js +7 -16
- package/dist/{types → transaction/types}/streams.d.ts +73 -45
- package/dist/transaction/utils/duration.d.ts +50 -0
- package/dist/{utils → transaction/utils}/duration.js +32 -0
- package/dist/{utils → transaction/utils}/json.d.ts +18 -0
- package/dist/transaction/utils/json.js +276 -0
- package/dist/transaction/wire/accountResponses.d.ts +420 -0
- package/dist/transaction/wire/accountResponses.js +290 -0
- package/dist/transaction/wire/auth.d.ts +56 -0
- package/dist/transaction/wire/auth.js +63 -0
- package/dist/transaction/wire/claimEvent.d.ts +76 -0
- package/dist/transaction/wire/claimEvent.js +73 -0
- package/dist/transaction/wire/claims.d.ts +530 -0
- package/dist/transaction/wire/claims.js +327 -0
- package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
- package/dist/{wire → transaction/wire}/commit.js +68 -47
- package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
- package/dist/{wire → transaction/wire}/delta.js +37 -13
- package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
- package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
- package/dist/transaction/wire/feedCursor.d.ts +60 -0
- package/dist/transaction/wire/feedCursor.js +82 -0
- package/dist/transaction/wire/feedEvent.d.ts +204 -0
- package/dist/transaction/wire/feedEvent.js +65 -0
- package/dist/transaction/wire/frames.d.ts +194 -0
- package/dist/transaction/wire/frames.js +50 -0
- package/dist/transaction/wire/inboundFrames.d.ts +562 -0
- package/dist/transaction/wire/inboundFrames.js +116 -0
- package/dist/transaction/wire/index.d.ts +54 -0
- package/dist/transaction/wire/index.js +83 -0
- package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
- package/dist/transaction/wire/listEnvelope.js +42 -0
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelResponses.d.ts +85 -0
- package/dist/transaction/wire/modelResponses.js +43 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +85 -38
- package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +141 -80
- package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
- package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
- package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
- package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
- package/dist/transactions/mutations/commitLatency.d.ts +52 -0
- package/dist/transactions/mutations/commitLatency.js +130 -0
- package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
- package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
- package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +18 -16
- package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +15 -15
- package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +15 -11
- package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +14 -12
- package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
- package/dist/transactions/mutations/durableWriteStore.js +12 -0
- package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
- package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +4 -3
- package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +7 -5
- package/dist/utils/mobxSetup.d.ts +1 -1
- package/dist/utils/mobxSetup.js +5 -2
- package/dist/{core → views}/QueryView.d.ts +2 -2
- package/dist/{core → views}/QueryView.js +2 -2
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/dist/webhooks/events.d.ts +2 -2
- package/dist/wire/index.d.ts +1 -34
- package/dist/wire/index.js +8 -49
- package/docs/agent-messaging.md +3 -3
- package/docs/agents.md +20 -13
- package/docs/api-keys.md +14 -10
- package/docs/api.md +27 -61
- package/docs/audit.md +6 -3
- package/docs/cli.md +41 -13
- package/docs/client-behavior.md +11 -9
- package/docs/concurrency-convention.md +49 -57
- package/docs/coordination.md +283 -121
- package/docs/data-sources.md +7 -5
- package/docs/debugging.md +39 -15
- package/docs/deployment.md +267 -0
- package/docs/examples/agent-human.md +49 -42
- package/docs/examples/ai-sdk-tool.md +69 -44
- package/docs/examples/existing-python-backend.md +8 -6
- package/docs/examples/nextjs.md +129 -47
- package/docs/examples/scoped-agent.md +46 -45
- package/docs/examples/server-agent.md +46 -26
- package/docs/groups.md +87 -30
- package/docs/guarantees.md +41 -12
- package/docs/how-it-works.md +38 -12
- package/docs/idempotency.md +126 -0
- package/docs/identity.md +77 -74
- package/docs/index.md +172 -86
- package/docs/integration-guide.md +31 -19
- package/docs/mcp.md +46 -21
- package/docs/migration.md +95 -18
- package/docs/operating-on-your-database.md +3 -1
- package/docs/projects.md +3 -1
- package/docs/quickstart.md +22 -5
- package/docs/react.md +31 -18
- package/docs/schema-contract.md +5 -3
- package/docs/session-settings.md +108 -0
- package/docs/sessions.md +4 -2
- package/docs/webhooks.md +12 -10
- package/llms.txt +48 -18
- package/package.json +21 -26
- package/dist/agent/Agent.d.ts +0 -366
- package/dist/agent/Agent.js +0 -514
- package/dist/agent/index.d.ts +0 -115
- package/dist/agent/index.js +0 -128
- package/dist/agent/session.d.ts +0 -93
- package/dist/agent/session.js +0 -149
- package/dist/agent/types.d.ts +0 -68
- package/dist/agent/types.js +0 -9
- package/dist/cli.cjs +0 -286329
- package/dist/client/durableWrites.d.ts +0 -21
- package/dist/client/httpClient.d.ts +0 -80
- package/dist/coordination/schema.d.ts +0 -722
- package/dist/coordination/schema.js +0 -578
- package/dist/schema/openapi.d.ts +0 -29
- package/dist/schema/openapi.js +0 -124
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -192
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -71
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/dist/transactions/durableWriteStore.js +0 -30
- package/dist/utils/duration.d.ts +0 -25
- package/dist/utils/json.js +0 -88
- package/dist/wire/errorEnvelope.d.ts +0 -55
- package/dist/wire/frames.d.ts +0 -197
- package/dist/wire/frames.js +0 -49
- package/dist/wire/listEnvelope.js +0 -18
- package/docs/interaction-model.md +0 -97
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
- /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
- /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
- /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
- /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
- /package/dist/{client → transaction}/persistence.d.ts +0 -0
- /package/dist/{client → transaction}/persistence.js +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
- /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
- /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
- /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
- /package/dist/{types → transaction/types}/modelData.js +0 -0
- /package/dist/{types → transaction/types}/participant.d.ts +0 -0
- /package/dist/{types → transaction/types}/participant.js +0 -0
- /package/dist/{types → transaction/types}/streams.js +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
- /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
- /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocol.js +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
- /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
- /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
- /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
- /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -1,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,23 +21,73 @@ 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
|
---
|
|
38
40
|
|
|
41
|
+
## How you hear about it
|
|
42
|
+
|
|
43
|
+
Four channels carry "something changed", and they answer four different
|
|
44
|
+
questions. Pick by the question you have.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// A screen that stays current.
|
|
48
|
+
ablo.documents.onChange((docs) => render(docs));
|
|
49
|
+
|
|
50
|
+
// Who else is in here, and what are they holding.
|
|
51
|
+
await using room = await ablo.documents.join(documentIds, { ttl: '5m' });
|
|
52
|
+
room.peers;
|
|
53
|
+
|
|
54
|
+
// Stop this write if the thing I read moved while I composed it.
|
|
55
|
+
await ablo.blocks.update({ id, data, reads: [{ group: 'workspace:abc', readAt, onStale: 'notify' }] });
|
|
56
|
+
|
|
57
|
+
// Tell me later if this moves, even though I am not writing now.
|
|
58
|
+
await ablo.documents.track({ id: 's-1' });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| Question | Channel | Arrives |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| What do the rows say right now? | `onChange` | As deltas land, on the socket |
|
|
64
|
+
| Who else is working here? | `join`, then `room.peers` and `room.claims` | As participants come and go, on the socket |
|
|
65
|
+
| Did the premise for **this** write move? | `reads` on the write | On that write's receipt, before it applies |
|
|
66
|
+
| Has anything I read moved since? | `track` | On your next commit's receipt |
|
|
67
|
+
|
|
68
|
+
Two distinctions do most of the work here.
|
|
69
|
+
|
|
70
|
+
**`join` is about people; `track` is about data.** Both open a subscription and
|
|
71
|
+
both are scoped by sync group, which is why they look alike. `join` reports
|
|
72
|
+
participants: who is present, what they are doing, which rows they hold. `track`
|
|
73
|
+
reports the rows themselves: something you said you cared about moved, here is
|
|
74
|
+
the watermark to re-read it at. A tool that wants to avoid duplicating a peer's
|
|
75
|
+
work needs `join`. A tool whose output goes stale when its inputs change needs
|
|
76
|
+
`track`.
|
|
77
|
+
|
|
78
|
+
**`reads` guards one write; `track` outlives it.** They speak the same
|
|
79
|
+
vocabulary and produce the same `StaleNotification`. A `reads` entry is checked
|
|
80
|
+
once, at the commit that carried it, and discarded. A `track` is persisted and
|
|
81
|
+
re-checked against every delta after it, so a long-running actor hears about a
|
|
82
|
+
change that landed while it was thinking, on the next commit it makes.
|
|
83
|
+
|
|
84
|
+
`onChange` and `join` need a live socket, so they are available on the default
|
|
85
|
+
WebSocket client. `reads` and `track` ride the commit, so they reach a socketless
|
|
86
|
+
actor over HTTP too, which is what makes them the notification path for agents
|
|
87
|
+
and workers.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
39
91
|
## Three ways a change reaches other rows
|
|
40
92
|
|
|
41
93
|
"A affects B and C" means three different things. The engine does the first two
|
|
@@ -43,13 +95,13 @@ for you and leaves the third to you — on purpose.
|
|
|
43
95
|
|
|
44
96
|
**Routing — who hears about a change.** Every row belongs to one or more sync
|
|
45
97
|
groups, and a write fans out to all of them. A row also inherits its ancestors'
|
|
46
|
-
groups: editing a
|
|
47
|
-
`
|
|
98
|
+
groups: editing a block stamps the delta with `block:…`, `document:…`, *and*
|
|
99
|
+
`workspace:…`, so everyone watching the workspace sees the block move. This is delivery,
|
|
48
100
|
resolved by walking the ownership tree at commit time. It routes the change; it
|
|
49
101
|
never recomputes a value.
|
|
50
102
|
|
|
51
|
-
**Structural cascade — what disappears with a change.** Deleting a
|
|
52
|
-
its
|
|
103
|
+
**Structural cascade — what disappears with a change.** Deleting a workspace removes
|
|
104
|
+
its documents and blocks. The database does that through `ON DELETE CASCADE`, but a
|
|
53
105
|
database-level cascade emits no delta, so open clients would quietly hold rows
|
|
54
106
|
that no longer exist. The engine closes that gap: before the delete it snapshots
|
|
55
107
|
the subtree and emits a tombstone for each descendant, routed to the right
|
|
@@ -85,8 +137,8 @@ reaches `C`.
|
|
|
85
137
|
|
|
86
138
|
The direction matters. The signal flows forward, A to B to C, and each hop is a
|
|
87
139
|
real write an actor chose to make. The engine supplies the edges (group
|
|
88
|
-
membership) and a stale signal on each edge (the
|
|
89
|
-
|
|
140
|
+
membership) and a stale signal on each edge (the premise check); the actors are
|
|
141
|
+
the runtime that walks them. It is closer to a spreadsheet an analyst
|
|
90
142
|
recalculates cell by cell than to a reactive engine that recomputes the whole
|
|
91
143
|
column for you.
|
|
92
144
|
|
|
@@ -102,16 +154,17 @@ Two consequences worth designing around:
|
|
|
102
154
|
|
|
103
155
|
---
|
|
104
156
|
|
|
105
|
-
## Declaring
|
|
157
|
+
## Declaring the batch premise
|
|
106
158
|
|
|
107
|
-
|
|
108
|
-
|
|
159
|
+
`reads[]` declares what the commit was based on. Each entry is a premise, and
|
|
160
|
+
each governs the *whole* commit: if one goes stale, its disposition applies to
|
|
161
|
+
every write in the batch, not just one operation. You choose the granularity per
|
|
109
162
|
entry.
|
|
110
163
|
|
|
111
164
|
```ts
|
|
112
165
|
reads: [
|
|
113
|
-
{ group: '
|
|
114
|
-
{ model: '
|
|
166
|
+
{ group: 'workspace:abc', readAt: N, onStale: 'notify' }, // did anything in the workspace move?
|
|
167
|
+
{ model: 'Document', id: 's-1', readAt: N, fields: ['title'] }, // did this row (this field) move?
|
|
115
168
|
]
|
|
116
169
|
```
|
|
117
170
|
|
|
@@ -137,7 +190,7 @@ the same row don't collide.
|
|
|
137
190
|
|
|
138
191
|
## Staying subscribed across commits: `track`
|
|
139
192
|
|
|
140
|
-
A
|
|
193
|
+
A batch premise guards a single commit: you state what you read, the engine
|
|
141
194
|
checks it, the premise is gone. That fits an actor that reads and writes in one
|
|
142
195
|
breath. It does not fit a long-running one — an agent that reads a row now,
|
|
143
196
|
works for a few minutes, and writes much later. By the time it commits, the
|
|
@@ -152,10 +205,10 @@ you, arriving on the write you were going to make anyway.
|
|
|
152
205
|
|
|
153
206
|
```ts
|
|
154
207
|
// Register interest and walk away — no write required.
|
|
155
|
-
await ablo.
|
|
208
|
+
await ablo.documents.track({ id: 's-1' });
|
|
156
209
|
|
|
157
210
|
// …minutes of other work later, on your next commit…
|
|
158
|
-
const res = await ablo.
|
|
211
|
+
const res = await ablo.blocks.update({ id: 'block-C', data: { text: revised } });
|
|
159
212
|
res.notifications; // populated if s-1 moved under you in the meantime
|
|
160
213
|
```
|
|
161
214
|
|
|
@@ -169,11 +222,11 @@ You can also register a track as part of a write you are already making, the
|
|
|
169
222
|
persisted companion to `reads`:
|
|
170
223
|
|
|
171
224
|
```ts
|
|
172
|
-
await ablo.
|
|
225
|
+
await ablo.documents.update({
|
|
173
226
|
id: 's-1',
|
|
174
227
|
data: { title: revised },
|
|
175
|
-
reads: [{ group: '
|
|
176
|
-
track: [{ group: '
|
|
228
|
+
reads: [{ group: 'workspace:abc', readAt: N, onStale: 'notify' }], // guards THIS commit
|
|
229
|
+
track: [{ group: 'workspace:abc' }], // and keeps watching after it
|
|
177
230
|
});
|
|
178
231
|
```
|
|
179
232
|
|
|
@@ -194,8 +247,8 @@ broad wakes actors for changes they don't care about, and one that is too narrow
|
|
|
194
247
|
misses the dependency you meant to track.
|
|
195
248
|
|
|
196
249
|
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
|
|
250
|
+
mutually consistent.** A workspace and its documents belong together because editing one
|
|
251
|
+
changes what the others mean; two unrelated workspaces do not. Reach for finer,
|
|
199
252
|
overlapping groups when you genuinely have a dependency chain to track, and keep
|
|
200
253
|
them coarse everywhere else.
|
|
201
254
|
|
|
@@ -203,8 +256,12 @@ them coarse everywhere else.
|
|
|
203
256
|
|
|
204
257
|
## Where this is defined
|
|
205
258
|
|
|
206
|
-
- **Access
|
|
207
|
-
|
|
259
|
+
- **Access**, meaning who may read or write a group, is
|
|
260
|
+
[`identity.md`](./identity.md).
|
|
261
|
+
- **The convention** behind non-coercion, the premise, and the notification is
|
|
208
262
|
[`concurrency-convention.md`](./concurrency-convention.md) (§4 and §5).
|
|
209
|
-
- **The mechanics
|
|
263
|
+
- **The mechanics**, the three coordination blocks underneath, are
|
|
210
264
|
[`coordination.md`](./coordination.md).
|
|
265
|
+
- **`join` and presence**, the participant half of the table above, are
|
|
266
|
+
[`coordination.md`](./coordination.md) for the claim stream and
|
|
267
|
+
[`react.md`](./react.md) for `useJoin`.
|
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.
|
|
@@ -63,11 +65,13 @@ await ablo.weatherReports.update({
|
|
|
63
65
|
`onStale: 'reject'` prevents lost updates. If the target changed after the
|
|
64
66
|
snapshot, the server rejects the write instead of applying stale reasoning.
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
Two other dispositions exist. `overwrite` applies the write with no stale check
|
|
69
|
+
at all. `notify` **holds** the write, so the row is left as it stands, and hands
|
|
70
|
+
back a `StaleNotification` carrying the current value for the actor to reconcile
|
|
71
|
+
and re-issue; the rest of the batch still commits.
|
|
67
72
|
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
- `notify` accepts the write and marks it for product review.
|
|
73
|
+
See [Concurrency Convention](./concurrency-convention.md) for the full taxonomy,
|
|
74
|
+
what each disposition is checked against, and where the convention stops.
|
|
71
75
|
|
|
72
76
|
## Claim Coordination
|
|
73
77
|
|
|
@@ -97,14 +101,39 @@ Agents should import the same schema as the app and write through
|
|
|
97
101
|
|
|
98
102
|
## Audit Trail
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
Attribution is not a separate log you opt into. It rides on the change itself.
|
|
105
|
+
Every broadcast delta names the actor, the authority it acted under, the
|
|
106
|
+
credential that authorized it, and the approval stage it was in:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
{
|
|
110
|
+
modelName: 'weatherReports',
|
|
111
|
+
modelId: 'report_stockholm',
|
|
112
|
+
actionType: 'U',
|
|
113
|
+
actor: { kind: 'agent', id: 'weather-agent-v3' },
|
|
114
|
+
onBehalfOf: { kind: 'user', id: 'user_8f2a' },
|
|
115
|
+
capabilityId: '…', // the key the write was authorized by
|
|
116
|
+
confirmationState: 'auto', // previewed | approved | required_human_approval
|
|
117
|
+
createdAt: '2026-05-14T14:22:01.034Z',
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`actor` and `onBehalfOf` are derived from the credential, not from the call site,
|
|
122
|
+
so an agent cannot name a different actor in its own write. `capabilityId` is
|
|
123
|
+
non-null for every agent and system commit, so a write can always be traced to
|
|
124
|
+
the key that made it, and from that key to the person it was issued to.
|
|
125
|
+
|
|
126
|
+
The stored history goes one step further than recording. Audit rows are chained
|
|
127
|
+
with a keyed hash, so the log is tamper-*evident*: `verify-chain` walks the chain
|
|
128
|
+
and, if it breaks, names the sequence number and the hashes that disagree. No
|
|
129
|
+
chain roots at an agent. The delegation root is always the person who set the
|
|
130
|
+
work in motion.
|
|
101
131
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- the model, operation, and state cursor.
|
|
132
|
+
For agent work this is what answers, after the fact: what changed, who authorized
|
|
133
|
+
it, which run did it, and whether a human was in the loop.
|
|
105
134
|
|
|
106
|
-
|
|
107
|
-
|
|
135
|
+
See [Audit Log](./audit.md) for the stored row shape, the filters, verification,
|
|
136
|
+
and export.
|
|
108
137
|
|
|
109
138
|
## Persistence
|
|
110
139
|
|
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,16 +10,16 @@ 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
|
-
## The mental model
|
|
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
|
-
- **Writes go through Ablo
|
|
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
|
|
22
24
|
Ablo applies the change to your Postgres through a scoped writer role. The commit
|
|
23
25
|
is accepted (`queued`) the moment Ablo takes it.
|
|
@@ -35,17 +37,41 @@ makes sure their writes don't clobber each other.
|
|
|
35
37
|
That's the shape: **you write through Ablo → it lands in your Postgres → the WAL
|
|
36
38
|
echo confirms it → everyone connected sees it live.**
|
|
37
39
|
|
|
40
|
+
## The primitives
|
|
41
|
+
|
|
42
|
+
| Primitive | Plane | Purpose |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `Schema` | State | Declares typed models the app and agents can read and write. |
|
|
45
|
+
| `Model` | State | The generated `ablo.<model>` model. Use `retrieve`/`list` (async reads), `local.retrieve`/`local.list`/`local.count` (the same verbs, synchronous and local-only), `create`, `update`, and `delete`. |
|
|
46
|
+
| `Claim` | Coordination | Who is working on a target. Taken via `ablo.<model>.claim({ id })` and read via `ablo.<model>.claim.state({ id })`. Ephemeral, never persisted. |
|
|
47
|
+
| `Commit` | Protocol | The durable write underneath model updates. Most users do not call it directly. |
|
|
48
|
+
| `Receipt` | Protocol | The lower-level durable result for custom runtimes. Schema writes use `wait: 'confirmed'`. |
|
|
49
|
+
|
|
50
|
+
### Why each primitive is separate
|
|
51
|
+
|
|
52
|
+
Why are `Claim`, `Commit`, and `Receipt` separate things instead of one? Each
|
|
53
|
+
does a job the others cannot. If you are coming from Replicache or Yjs you would
|
|
54
|
+
expect just `Commit`. Here is what the other two buy you over that minimum:
|
|
55
|
+
|
|
56
|
+
- **`Claim` is not a read lock.** Reads stay open. Claims serialize
|
|
57
|
+
acting-on-the-row, so slow work can wait in FIFO order, re-read, and write
|
|
58
|
+
from fresh state.
|
|
59
|
+
- **`Receipt` is not a `200 OK`.** It is the durable artifact a commit produced:
|
|
60
|
+
accepted commit id, server-assigned timestamps, stale-check outcome. It is
|
|
61
|
+
addressable after the fact and replayable into a different client. A status
|
|
62
|
+
code cannot be re-read by a sub-agent that was not on the original call.
|
|
63
|
+
|
|
38
64
|
## Where your data lives
|
|
39
65
|
|
|
40
66
|
You point Ablo at a Postgres database, and that's where its rows live. Only *which*
|
|
41
67
|
database differs by environment — the code is identical.
|
|
42
68
|
|
|
43
|
-
- **Production
|
|
69
|
+
- **Production:** your Postgres. `ablo connect` sets up a scoped writer role and
|
|
44
70
|
logical replication; your rows live in your database, and Ablo writes to them
|
|
45
71
|
through that role.
|
|
46
|
-
- **Sandbox and local dev
|
|
72
|
+
- **Sandbox and local dev:** a separate or local Postgres you can throw away. Same
|
|
47
73
|
models, same code, a different database behind them.
|
|
48
|
-
- **Before you connect one
|
|
74
|
+
- **Before you connect one.** Ablo keeps state in its own log, so you can build the
|
|
49
75
|
whole app today and point it at a real database when you're ready.
|
|
50
76
|
|
|
51
77
|
Registering the database is the whole switch. There is no tier or flag to choose.
|
|
@@ -57,7 +83,7 @@ Registering the database is the whole switch. There is no tier or flag to choose
|
|
|
57
83
|
npm install @abloatai/ablo
|
|
58
84
|
npx ablo init
|
|
59
85
|
|
|
60
|
-
# 2. Push your schema (the models
|
|
86
|
+
# 2. Push your schema (the models your agents edit together).
|
|
61
87
|
npx ablo push
|
|
62
88
|
|
|
63
89
|
# 3. Connect your database — one command, admin credential used once and discarded.
|
|
@@ -80,13 +106,13 @@ export const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
|
80
106
|
await ablo.tasks.update({ id: 'task_42', data: { status: 'done' }, wait: 'confirmed' });
|
|
81
107
|
|
|
82
108
|
// 6. Read — live, no fetch loop.
|
|
83
|
-
const task = ablo.tasks.
|
|
109
|
+
const task = ablo.tasks.local.retrieve('task_42');
|
|
84
110
|
|
|
85
111
|
// 7. Coordinate when more than one actor can touch a row. Hold a claim and Ablo
|
|
86
112
|
// serializes writes on that key against everyone else; read after claiming,
|
|
87
113
|
// then write. The lease releases automatically at the end of the scope.
|
|
88
114
|
await using _hold = await ablo.tasks.claim('task_42');
|
|
89
|
-
const latest = ablo.tasks.
|
|
115
|
+
const latest = ablo.tasks.local.retrieve('task_42'); // read after claiming, not from memory
|
|
90
116
|
await ablo.tasks.update({ id: 'task_42', data: { status: 'done' } });
|
|
91
117
|
```
|
|
92
118
|
|
|
@@ -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.
|