@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
|
@@ -3,29 +3,71 @@
|
|
|
3
3
|
* `ablo.<model>`.
|
|
4
4
|
*
|
|
5
5
|
* Each schema model gets one {@link ModelOperations}: the async server reads
|
|
6
|
-
* `retrieve` and `list`, the
|
|
7
|
-
*
|
|
6
|
+
* `retrieve` and `list`, the same verbs restricted to the local graph under
|
|
7
|
+
* `local`, the writes `create`, `update`, and `delete`, the coordination
|
|
8
8
|
* namespace `claim` (callable as `claim({ id })`, plus `claim.state`,
|
|
9
9
|
* `claim.queue`, `claim.release`, and `claim.reorder`), `join`, and `onChange`.
|
|
10
10
|
* The factory returns a plain object; the client assembles the `ablo.<model>`
|
|
11
11
|
* lookup table from one of these per model.
|
|
12
12
|
*/
|
|
13
13
|
import { autorun } from 'mobx';
|
|
14
|
-
import { AbloClaimedError,
|
|
15
|
-
import { reconcileFunctionalUpdate, } from '
|
|
14
|
+
import { AbloClaimedError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../transaction/errors.js';
|
|
15
|
+
import { reconcileFunctionalUpdate, } from '../transaction/resources/functionalUpdate.js';
|
|
16
|
+
import { claimDescription, } from '../transaction/coordination/schema.js';
|
|
16
17
|
import { Model, modelAsRow } from '../Model.js';
|
|
17
|
-
import { toMs } from '../utils/duration.js';
|
|
18
|
-
import { LEASE_TTL_MS } from '../wire/protocol.js';
|
|
19
|
-
import { heartbeatCadenceMs, resolveHeartbeatOptions, startClaimHeartbeatLoop, } from '
|
|
20
|
-
import { assertWriteOptions } from '
|
|
21
|
-
import {
|
|
18
|
+
import { toMs } from '../transaction/utils/duration.js';
|
|
19
|
+
import { LEASE_TTL_MS } from '../transaction/wire/protocol.js';
|
|
20
|
+
import { heartbeatCadenceMs, resolveHeartbeatOptions, resolveHeartbeatPlan, startClaimHeartbeatLoop, } from '../transaction/coordination/claimHeartbeatLoop.js';
|
|
21
|
+
import { assertWriteOptions } from '../transaction/resources/writeOptionsSchema.js';
|
|
22
|
+
import { modelTarget, subTarget } from '../transaction/coordination/index.js';
|
|
23
|
+
// A named claim-meta crossing (see `claim-meta-crossings-are-enumerated` in
|
|
24
|
+
// .dependency-cruiser.cjs): the reactive proxy's self-claim targets are
|
|
25
|
+
// decodes that build a public claim, so their `meta` converts wire→declared
|
|
26
|
+
// here like the other enumerated crossings.
|
|
27
|
+
import { declaredMeta } from '../transaction/coordination/claimMeta.js';
|
|
28
|
+
import { ModelScope } from '../transaction/types/index.js';
|
|
22
29
|
const modelClientMeta = new WeakMap();
|
|
23
30
|
export function getModelClientMeta(modelClient) {
|
|
24
31
|
if (typeof modelClient !== 'object' || modelClient === null)
|
|
25
32
|
return undefined;
|
|
26
33
|
return modelClientMeta.get(modelClient);
|
|
27
34
|
}
|
|
28
|
-
export function createModelProxy(schemaKey, registeredModelName, objectPool, syncClient, registry,
|
|
35
|
+
export function createModelProxy(schemaKey, registeredModelName, objectPool, syncClient, registry,
|
|
36
|
+
/**
|
|
37
|
+
* The one thing this factory asks of the loader: fetch rows for a model.
|
|
38
|
+
*
|
|
39
|
+
* Declared as the slice rather than the whole `OnDemandLoader` because the
|
|
40
|
+
* whole is a class, and a parameter typed as a class can only ever be
|
|
41
|
+
* satisfied by an instance of it — so every caller that has a narrower
|
|
42
|
+
* collaborator, a test most of all, is pushed into a cast through `unknown`
|
|
43
|
+
* to supply the one method that is actually read.
|
|
44
|
+
*/
|
|
45
|
+
hydration, collaboration,
|
|
46
|
+
/** The client-wide `wait` default; a per-call `wait` still wins over it. */
|
|
47
|
+
defaultWait) {
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a row **this** resource owns.
|
|
50
|
+
*
|
|
51
|
+
* The pool is one id space, so `objectPool.get(id)` happily returns another
|
|
52
|
+
* model's row. Every write path below addresses rows by bare id, so without
|
|
53
|
+
* this an id from a sibling model resolves and gets written — silently
|
|
54
|
+
* corrupting a row the caller never named.
|
|
55
|
+
*
|
|
56
|
+
* `undefined` means genuinely absent. A row belonging to another model throws:
|
|
57
|
+
* unlike a read, a cross-model *write* is never a legitimate outcome, and
|
|
58
|
+
* naming both models turns a silent corruption into a one-line diagnosis.
|
|
59
|
+
*/
|
|
60
|
+
const ownRowOrThrow = (id) => {
|
|
61
|
+
const own = objectPool.getOfType(id, registeredModelName);
|
|
62
|
+
if (own)
|
|
63
|
+
return own;
|
|
64
|
+
const foreign = objectPool.get(id);
|
|
65
|
+
if (!foreign)
|
|
66
|
+
return undefined;
|
|
67
|
+
const owner = foreign.getModelName();
|
|
68
|
+
throw new AbloValidationError(`No ${registeredModelName} with id ${id} — that id belongs to a ${owner}. ` +
|
|
69
|
+
`Read or write it through ${owner}.`, { code: 'entity_not_found' });
|
|
70
|
+
};
|
|
29
71
|
const ModelClass = registry.getModelByName(registeredModelName);
|
|
30
72
|
if (!ModelClass) {
|
|
31
73
|
throw new AbloValidationError(`Ablo: schema model "${schemaKey}" resolved to "${registeredModelName}", ` +
|
|
@@ -66,7 +108,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
66
108
|
return rows;
|
|
67
109
|
};
|
|
68
110
|
const waitForMutation = async (model, options) => {
|
|
69
|
-
|
|
111
|
+
// A per-call `wait` wins; otherwise the client-wide default decides. This
|
|
112
|
+
// is the single point that turns "confirmed" into actually waiting, so a
|
|
113
|
+
// client configured that way rejects on a refused write everywhere rather
|
|
114
|
+
// than in the one place a caller remembered to ask.
|
|
115
|
+
if ((options?.wait ?? defaultWait) !== 'confirmed')
|
|
70
116
|
return;
|
|
71
117
|
await syncClient.syncNow();
|
|
72
118
|
await syncClient.waitForConfirmation(model.getModelName(), model.id);
|
|
@@ -93,7 +139,6 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
93
139
|
value.object === 'claim' &&
|
|
94
140
|
typeof value.id === 'string' &&
|
|
95
141
|
typeof value.release === 'function';
|
|
96
|
-
const claimMeta = (options) => options?.meta;
|
|
97
142
|
const claimContextFromClaim = (claim) => {
|
|
98
143
|
return {
|
|
99
144
|
id: claim.id,
|
|
@@ -104,12 +149,8 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
104
149
|
status: claim.status,
|
|
105
150
|
expiresAt: claim.expiresAt,
|
|
106
151
|
target: {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
path: claim.target.path,
|
|
110
|
-
range: claim.target.range,
|
|
111
|
-
field: claim.target.field,
|
|
112
|
-
meta: claim.target.meta,
|
|
152
|
+
...modelTarget(claim.target),
|
|
153
|
+
...subTarget(claim.target),
|
|
113
154
|
},
|
|
114
155
|
};
|
|
115
156
|
};
|
|
@@ -139,26 +180,21 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
139
180
|
const held = collaboration.state({ model: wireModel, id });
|
|
140
181
|
const contended = !!held && held.heldBy !== collaboration.selfParticipantId;
|
|
141
182
|
const failFast = options.queue === false;
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
183
|
+
// The try-claim (`queue: false`): a held target is an expected outcome,
|
|
184
|
+
// not an error, so it resolves `null` — the caller reads `if (!claim)`
|
|
185
|
+
// and moves on; who holds it stays readable via `claim.state`. Best-effort
|
|
186
|
+
// at the client (a racing claim not yet synced into our snapshot slips
|
|
187
|
+
// through here) — the commit-time claim guard is the authoritative
|
|
188
|
+
// backstop that rejects the loser's first write. For work-distribution
|
|
189
|
+
// dedup that's exactly right: don't wait (that would double-process), skip.
|
|
148
190
|
if (failFast && contended) {
|
|
149
|
-
|
|
150
|
-
throw new AbloClaimedError(formatClaimedErrorMessage({
|
|
151
|
-
targetLabel: `${registeredModelName}/${id}`,
|
|
152
|
-
heldBy: held.heldBy,
|
|
153
|
-
claim,
|
|
154
|
-
fallback: `${registeredModelName}/${id} is held by ${held.heldBy ?? 'another participant'}.`,
|
|
155
|
-
}), { code: 'entity_claimed', claims: [claim] });
|
|
191
|
+
return null;
|
|
156
192
|
}
|
|
157
193
|
// Ensure the row exists locally before claiming.
|
|
158
|
-
let model =
|
|
194
|
+
let model = ownRowOrThrow(id);
|
|
159
195
|
if (!model) {
|
|
160
196
|
await load({ where: [['id', id]] });
|
|
161
|
-
model =
|
|
197
|
+
model = ownRowOrThrow(id);
|
|
162
198
|
}
|
|
163
199
|
if (!model) {
|
|
164
200
|
throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
|
|
@@ -180,15 +216,19 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
180
216
|
target: {
|
|
181
217
|
model: wireModel,
|
|
182
218
|
id,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
...(
|
|
219
|
+
// The whole sub-entity locator in one move — listing its members here
|
|
220
|
+
// is what let `fields` die between the caller and the lease, so the
|
|
221
|
+
// claim covered the whole row while the caller believed it named parts.
|
|
222
|
+
...subTarget(options),
|
|
187
223
|
},
|
|
188
|
-
description: options
|
|
224
|
+
description: claimDescription(options),
|
|
189
225
|
ttl: options.ttl,
|
|
190
226
|
queue: !failFast,
|
|
191
227
|
maxQueueDepth: options.maxQueueDepth,
|
|
228
|
+
// The one wait cap, declared once on ClaimTargetOptions — the socket
|
|
229
|
+
// wait and the HTTP poll-wait both honor it as `grant_timeout`.
|
|
230
|
+
waitTimeoutMs: options.waitTimeoutMs,
|
|
231
|
+
signal: options.signal,
|
|
192
232
|
});
|
|
193
233
|
// Only when the claim actually waited behind another holder can the row have
|
|
194
234
|
// changed underneath us — re-read so the claimed snapshot reflects what that
|
|
@@ -205,20 +245,22 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
205
245
|
// holder's final write may not have fanned out yet — the exact
|
|
206
246
|
// stale-snapshot race this re-read closes.
|
|
207
247
|
await load({ where: [['id', id]], type: 'complete' });
|
|
208
|
-
model =
|
|
248
|
+
model = ownRowOrThrow(id) ?? model;
|
|
209
249
|
}
|
|
210
250
|
const snapshot = collaboration.createSnapshot(schemaKey, id);
|
|
211
|
-
const description = options
|
|
251
|
+
const description = claimDescription(options);
|
|
212
252
|
// The self-claim's `ClaimTarget` mirrors what a peer's `claim.state` would
|
|
213
253
|
// report (`state` maps `held.target.model` to `type`), so a holder and a
|
|
214
254
|
// peer see the same `target.type` for one row — the wire model token.
|
|
255
|
+
// Its `meta` is the DECLARED shape (the handle is a public claim), so the
|
|
256
|
+
// wire-shaped projection converts back through `declaredMeta` — the same
|
|
257
|
+
// crossing the HTTP handle assembly makes.
|
|
258
|
+
const { meta: selfMeta, ...selfNarrowed } = subTarget(options);
|
|
215
259
|
const selfTarget = {
|
|
216
260
|
type: wireModel,
|
|
217
261
|
id,
|
|
218
|
-
...
|
|
219
|
-
...(
|
|
220
|
-
...(options.range ? { range: options.range } : {}),
|
|
221
|
-
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
262
|
+
...selfNarrowed,
|
|
263
|
+
...(selfMeta !== undefined ? { meta: declaredMeta(selfMeta) } : {}),
|
|
222
264
|
};
|
|
223
265
|
const ttlMs = options.ttl !== undefined ? toMs(options.ttl) : DEFAULT_LEASE_TTL_MS;
|
|
224
266
|
const expiresAt = Date.now() + ttlMs;
|
|
@@ -229,17 +271,20 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
229
271
|
description,
|
|
230
272
|
expiresAt,
|
|
231
273
|
});
|
|
274
|
+
const { meta: targetMeta, ...targetNarrowed } = subTarget(options);
|
|
232
275
|
const target = {
|
|
233
276
|
type: schemaKey,
|
|
234
277
|
id,
|
|
235
|
-
...
|
|
236
|
-
...(
|
|
237
|
-
...(options.range ? { range: options.range } : {}),
|
|
238
|
-
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
278
|
+
...targetNarrowed,
|
|
279
|
+
...(targetMeta !== undefined ? { meta: declaredMeta(targetMeta) } : {}),
|
|
239
280
|
};
|
|
281
|
+
// One reading of the heartbeat options — cadence and callbacks from
|
|
282
|
+
// whichever spelling the caller used (plan object, shorthand, or the
|
|
283
|
+
// deprecated flat callbacks).
|
|
284
|
+
const plan = resolveHeartbeatPlan(options);
|
|
240
285
|
// A beat resolves with the server's extended expiry; keep the local
|
|
241
286
|
// self-claim estimate in step so `claim.state` renders the real window,
|
|
242
|
-
// and surface every answer through `
|
|
287
|
+
// and surface every answer through the plan's `onBeat` (pressure signal).
|
|
243
288
|
const heartbeat = async (beatOptions) => {
|
|
244
289
|
if (!lease.heartbeat) {
|
|
245
290
|
throw new AbloValidationError('This claim handle has no heartbeat wiring, which the standard Ablo({ schema, apiKey }) client provides on every claim. This appears only when a claim is minted through an internal path that predates heartbeats.', { code: 'claim_not_wired' });
|
|
@@ -252,18 +297,16 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
252
297
|
const held = activeClaims.get(id);
|
|
253
298
|
if (held)
|
|
254
299
|
held.expiresAt = beat.expiresAt;
|
|
255
|
-
|
|
300
|
+
plan.onBeat?.(beat);
|
|
256
301
|
return beat;
|
|
257
302
|
};
|
|
258
303
|
// Opt-in auto-heartbeat: the loop beats until release, and a definitive
|
|
259
|
-
// loss stops it and surfaces through `
|
|
260
|
-
const stopHeartbeatLoop =
|
|
304
|
+
// loss stops it and surfaces through the plan's `onLost`.
|
|
305
|
+
const stopHeartbeatLoop = plan.loop
|
|
261
306
|
? startClaimHeartbeatLoop({
|
|
262
307
|
beat: () => heartbeat(),
|
|
263
|
-
intervalMs: heartbeatCadenceMs(ttlMs,
|
|
264
|
-
...(
|
|
265
|
-
? { onLost: options.onHeartbeatLost }
|
|
266
|
-
: {}),
|
|
308
|
+
intervalMs: heartbeatCadenceMs(ttlMs, plan.cadence),
|
|
309
|
+
...(plan.onLost ? { onLost: plan.onLost } : {}),
|
|
267
310
|
})
|
|
268
311
|
: undefined;
|
|
269
312
|
const release = () => {
|
|
@@ -305,19 +348,14 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
305
348
|
const held = collaboration.state({ model: wireModel, id });
|
|
306
349
|
const contended = !!held && held.heldBy !== collaboration.selfParticipantId;
|
|
307
350
|
const failFast = options.queue === false;
|
|
308
|
-
//
|
|
309
|
-
// Best-effort at the client —
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
351
|
+
// The try-claim (`queue: false`): resolve `null` if a holder is already
|
|
352
|
+
// visible — an expected outcome, not an error. Best-effort at the client —
|
|
353
|
+
// a row this participant never synced usually carries no local claim state
|
|
354
|
+
// either, so a peer gets the deterministic `null` only once it has
|
|
355
|
+
// observed the holder (entered the row's entity scope). The server's
|
|
356
|
+
// queue is the backstop for the queuing path.
|
|
313
357
|
if (failFast && contended) {
|
|
314
|
-
|
|
315
|
-
throw new AbloClaimedError(formatClaimedErrorMessage({
|
|
316
|
-
targetLabel: `${registeredModelName}/${id}`,
|
|
317
|
-
heldBy: held.heldBy,
|
|
318
|
-
claim,
|
|
319
|
-
fallback: `${registeredModelName}/${id} is held by ${held.heldBy ?? 'another participant'}.`,
|
|
320
|
-
}), { code: 'entity_claimed', claims: [claim] });
|
|
358
|
+
return null;
|
|
321
359
|
}
|
|
322
360
|
// Enter the entity scope before acquiring the lease so the holder's claim
|
|
323
361
|
// presence broadcasts to everyone in this entity group — the same ordering
|
|
@@ -329,29 +367,30 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
329
367
|
target: {
|
|
330
368
|
model: wireModel,
|
|
331
369
|
id,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
...(
|
|
370
|
+
// The whole sub-entity locator in one move — listing its members here
|
|
371
|
+
// is what let `fields` die between the caller and the lease, so the
|
|
372
|
+
// claim covered the whole row while the caller believed it named parts.
|
|
373
|
+
...subTarget(options),
|
|
336
374
|
},
|
|
337
|
-
description: options
|
|
375
|
+
description: claimDescription(options),
|
|
338
376
|
ttl: options.ttl,
|
|
339
377
|
queue: !failFast,
|
|
340
378
|
maxQueueDepth: options.maxQueueDepth,
|
|
379
|
+
// The one wait cap, declared once on ClaimTargetOptions — the socket
|
|
380
|
+
// wait and the HTTP poll-wait both honor it as `grant_timeout`.
|
|
381
|
+
waitTimeoutMs: options.waitTimeoutMs,
|
|
382
|
+
signal: options.signal,
|
|
341
383
|
});
|
|
342
384
|
// A watermark-only snapshot: `createSnapshot` still reads the engine's
|
|
343
385
|
// current `lastSyncId` even though the pool holds no row (the bucket is
|
|
344
386
|
// empty). It costs nothing extra and gives a write taken under this lease a
|
|
345
387
|
// real `readAt` to guard against changes since the lease was acquired.
|
|
346
388
|
const snapshot = collaboration.createSnapshot(schemaKey, id);
|
|
347
|
-
const description = options
|
|
389
|
+
const description = claimDescription(options);
|
|
348
390
|
const selfTarget = {
|
|
349
391
|
type: wireModel,
|
|
350
392
|
id,
|
|
351
|
-
...(options
|
|
352
|
-
...(options.path ? { path: options.path } : {}),
|
|
353
|
-
...(options.range ? { range: options.range } : {}),
|
|
354
|
-
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
393
|
+
...subTarget(options),
|
|
355
394
|
};
|
|
356
395
|
const ttlMs = options.ttl !== undefined ? toMs(options.ttl) : DEFAULT_LEASE_TTL_MS;
|
|
357
396
|
const expiresAt = Date.now() + ttlMs;
|
|
@@ -365,11 +404,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
365
404
|
const target = {
|
|
366
405
|
type: schemaKey,
|
|
367
406
|
id,
|
|
368
|
-
...(options
|
|
369
|
-
...(options.path ? { path: options.path } : {}),
|
|
370
|
-
...(options.range ? { range: options.range } : {}),
|
|
371
|
-
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
407
|
+
...subTarget(options),
|
|
372
408
|
};
|
|
409
|
+
// One reading of the heartbeat options — cadence and callbacks from
|
|
410
|
+
// whichever spelling the caller used.
|
|
411
|
+
const plan = resolveHeartbeatPlan(options);
|
|
373
412
|
// A beat resolves with the server's extended expiry; keep the local
|
|
374
413
|
// self-claim estimate in step so `claim.state` renders the real window.
|
|
375
414
|
const heartbeat = async (beatOptions) => {
|
|
@@ -384,16 +423,14 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
384
423
|
const held = activeClaims.get(id);
|
|
385
424
|
if (held)
|
|
386
425
|
held.expiresAt = beat.expiresAt;
|
|
387
|
-
|
|
426
|
+
plan.onBeat?.(beat);
|
|
388
427
|
return beat;
|
|
389
428
|
};
|
|
390
|
-
const stopHeartbeatLoop =
|
|
429
|
+
const stopHeartbeatLoop = plan.loop
|
|
391
430
|
? startClaimHeartbeatLoop({
|
|
392
431
|
beat: () => heartbeat(),
|
|
393
|
-
intervalMs: heartbeatCadenceMs(ttlMs,
|
|
394
|
-
...(
|
|
395
|
-
? { onLost: options.onHeartbeatLost }
|
|
396
|
-
: {}),
|
|
432
|
+
intervalMs: heartbeatCadenceMs(ttlMs, plan.cadence),
|
|
433
|
+
...(plan.onLost ? { onLost: plan.onLost } : {}),
|
|
397
434
|
})
|
|
398
435
|
: undefined;
|
|
399
436
|
const release = () => {
|
|
@@ -434,7 +471,41 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
434
471
|
// members to read/steer the coordination plane. Attach the readers to the
|
|
435
472
|
// callable so `ablo.<model>.claim(...)` and `ablo.<model>.claim.state(...)`
|
|
436
473
|
// are the same object.
|
|
437
|
-
|
|
474
|
+
// `state` and `queue` take a caller-named `meta` shape. The runtime cannot
|
|
475
|
+
// check it and is not meant to: `target.meta` is application data the
|
|
476
|
+
// protocol carries verbatim and never interprets, so naming its type is the
|
|
477
|
+
// caller asserting what it put there — the same bargain as parsing your own
|
|
478
|
+
// JSON into an interface. These read as `Claim` here, and the one assertion
|
|
479
|
+
// that applies the caller's parameter is on the assignment below, in one
|
|
480
|
+
// place rather than at every call site.
|
|
481
|
+
/**
|
|
482
|
+
* This client's own claim on a row, as a claim-state object.
|
|
483
|
+
*
|
|
484
|
+
* The server excludes a holder's own presence frames and the client skips
|
|
485
|
+
* them, so a row this client holds is absent from every peer-derived read.
|
|
486
|
+
* Both `state` and `list` therefore synthesize it from the stored lease, and
|
|
487
|
+
* they do it through here so the two answers cannot describe the same
|
|
488
|
+
* holding differently.
|
|
489
|
+
*/
|
|
490
|
+
const ownClaimState = (id) => {
|
|
491
|
+
const own = activeClaims.get(id);
|
|
492
|
+
if (!own)
|
|
493
|
+
return null;
|
|
494
|
+
return {
|
|
495
|
+
object: 'claim',
|
|
496
|
+
id: own.lease.id,
|
|
497
|
+
status: 'active',
|
|
498
|
+
target: own.target,
|
|
499
|
+
description: own.description,
|
|
500
|
+
heldBy: collaboration?.selfParticipantId ?? '',
|
|
501
|
+
participantKind: collaboration?.selfParticipantKind ?? 'user',
|
|
502
|
+
expiresAt: own.expiresAt,
|
|
503
|
+
// Symmetric with the peer projection: a holder reading its own claim
|
|
504
|
+
// sees the same `meta` an observer does.
|
|
505
|
+
...(own.target.meta !== undefined ? { meta: own.target.meta } : {}),
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
const claimReaders = {
|
|
438
509
|
state(params) {
|
|
439
510
|
// Read interest: a passive observer of a row's claim state must enter that
|
|
440
511
|
// row's entity scope, or it sits only on broader `org:`/`user:` groups and
|
|
@@ -445,20 +516,27 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
445
516
|
// the client skips them, so `state` would return null for a row this client
|
|
446
517
|
// holds. Synthesize the active claim from the stored lease so the holder
|
|
447
518
|
// sees its own claim, honoring the documented contract on `claim.state`.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
|
|
519
|
+
return (ownClaimState(params.id) ??
|
|
520
|
+
collaboration?.state({ model: wireModel, id: params.id }) ??
|
|
521
|
+
null);
|
|
522
|
+
},
|
|
523
|
+
/**
|
|
524
|
+
* Every claim on the row, holders first. Sub-row claims on disjoint parts
|
|
525
|
+
* are all granted, so a row can have several holders at once and
|
|
526
|
+
* {@link state} answers with one of them — this is the read that renders
|
|
527
|
+
* all of them. Same list envelope as {@link queue}, reactive on the same
|
|
528
|
+
* snapshot, so a render reads it inline.
|
|
529
|
+
*/
|
|
530
|
+
list(params) {
|
|
531
|
+
void collaboration?.enterScope?.({ [schemaKey]: params.id });
|
|
532
|
+
const own = ownClaimState(params.id);
|
|
533
|
+
const peers = collaboration?.holders({ model: wireModel, id: params.id }) ?? [];
|
|
534
|
+
return {
|
|
535
|
+
object: 'list',
|
|
536
|
+
// Own claim first: the server excludes a holder's own presence frames,
|
|
537
|
+
// so it is never among `peers` and the two never duplicate.
|
|
538
|
+
data: own ? [own, ...peers] : [...peers],
|
|
539
|
+
};
|
|
462
540
|
},
|
|
463
541
|
queue(params) {
|
|
464
542
|
return {
|
|
@@ -470,28 +548,17 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
470
548
|
collaboration?.reorder({ model: wireModel, id: params.id }, params.order);
|
|
471
549
|
},
|
|
472
550
|
release: guard((params) => releaseClaim(isClaimHandle(params) ? params.target.id : params.id)),
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
...params,
|
|
483
|
-
where: [['id', params.id]],
|
|
484
|
-
limit: 1,
|
|
485
|
-
});
|
|
486
|
-
return rows[0];
|
|
487
|
-
}),
|
|
488
|
-
// No automatic scope enrolment on bulk `list`/`getAll`: that would subscribe
|
|
489
|
-
// to an unbounded set of rows' entity groups.
|
|
490
|
-
list: guard(load),
|
|
491
|
-
get(id) {
|
|
492
|
-
return objectPool.get(id);
|
|
551
|
+
};
|
|
552
|
+
// The one place the caller's `meta` parameter is applied — see the note on
|
|
553
|
+
// `claimReaders`. Everything else about this object is checked structurally.
|
|
554
|
+
const claimApi = Object.assign(claim, claimReaders);
|
|
555
|
+
const local = {
|
|
556
|
+
retrieve(id) {
|
|
557
|
+
// Scoped to this model: an id belonging to a sibling model reads as
|
|
558
|
+
// absent rather than being handed back as if it were a `T`.
|
|
559
|
+
return objectPool.getOfType(id, registeredModelName);
|
|
493
560
|
},
|
|
494
|
-
|
|
561
|
+
list(options) {
|
|
495
562
|
const all = objectPool.getByType(ModelClass, (options?.state ?? ModelScope.live));
|
|
496
563
|
let result = all;
|
|
497
564
|
if (options?.where) {
|
|
@@ -525,9 +592,28 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
525
592
|
result = result.slice(0, options.limit);
|
|
526
593
|
return result;
|
|
527
594
|
},
|
|
528
|
-
|
|
529
|
-
return
|
|
595
|
+
count(options) {
|
|
596
|
+
return local.list(options).length;
|
|
530
597
|
},
|
|
598
|
+
};
|
|
599
|
+
const operations = {
|
|
600
|
+
local,
|
|
601
|
+
retrieve: guard(async (params) => {
|
|
602
|
+
// Read-interest enrolment: reading a row enters its entity scope, so a
|
|
603
|
+
// client lands in the same group the holder's claim presence fans out
|
|
604
|
+
// on and `claim.state`/`claim.queue` report peers. Best-effort and
|
|
605
|
+
// fire-and-forget — it never makes the read reject or run slower.
|
|
606
|
+
void collaboration?.enterScope?.({ [schemaKey]: params.id });
|
|
607
|
+
const rows = await load({
|
|
608
|
+
...params,
|
|
609
|
+
where: [['id', params.id]],
|
|
610
|
+
limit: 1,
|
|
611
|
+
});
|
|
612
|
+
return rows[0];
|
|
613
|
+
}),
|
|
614
|
+
// No automatic scope enrolment on bulk `list`: that would subscribe to an
|
|
615
|
+
// unbounded set of rows' entity groups.
|
|
616
|
+
list: guard(load),
|
|
531
617
|
create: guard(async (params) => {
|
|
532
618
|
const id = params.id ?? Model.generateId();
|
|
533
619
|
const opts = mutationOptions(params);
|
|
@@ -547,12 +633,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
547
633
|
target: {
|
|
548
634
|
model: wireModel,
|
|
549
635
|
id,
|
|
550
|
-
...(claim
|
|
551
|
-
...(claim.path ? { path: claim.path } : {}),
|
|
552
|
-
...(claim.range ? { range: claim.range } : {}),
|
|
553
|
-
...(claimMeta(claim) ? { meta: claimMeta(claim) } : {}),
|
|
636
|
+
...subTarget(claim),
|
|
554
637
|
},
|
|
555
|
-
description: claim
|
|
638
|
+
description: claimDescription(claim, 'creating'),
|
|
556
639
|
ttl: claim.ttl,
|
|
557
640
|
queue: claim.queue !== false,
|
|
558
641
|
maxQueueDepth: claim.maxQueueDepth,
|
|
@@ -615,7 +698,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
615
698
|
// `type: 'complete'` forces the round-trip — the hydration ledger
|
|
616
699
|
// would otherwise serve a possibly-stale local row for a hydrated id.
|
|
617
700
|
await load({ where: [['id', id]], type: 'complete' });
|
|
618
|
-
const fresh =
|
|
701
|
+
const fresh = ownRowOrThrow(id);
|
|
619
702
|
const snapshot = collaboration.createSnapshot(schemaKey, id);
|
|
620
703
|
return {
|
|
621
704
|
data: fresh ? modelAsRow(fresh) : undefined,
|
|
@@ -623,7 +706,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
623
706
|
};
|
|
624
707
|
},
|
|
625
708
|
writeNext: async (patch, readAt) => {
|
|
626
|
-
const model =
|
|
709
|
+
const model = ownRowOrThrow(id);
|
|
627
710
|
if (!model) {
|
|
628
711
|
throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
|
|
629
712
|
}
|
|
@@ -643,6 +726,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
643
726
|
const autoClaim = params.claim && !isClaimHandle(params.claim) ? params.claim : null;
|
|
644
727
|
if (autoClaim) {
|
|
645
728
|
const handle = await takeClaim({ ...autoClaim, id: params.id });
|
|
729
|
+
// A declined try-claim is `null` only on the standalone verb; a
|
|
730
|
+
// write that could not take its claim is a failed write.
|
|
731
|
+
if (!handle) {
|
|
732
|
+
throw new AbloClaimedError(`${registeredModelName}/${params.id} is held by another participant, so this update's claim could not be taken.`, { code: 'entity_claimed' });
|
|
733
|
+
}
|
|
646
734
|
try {
|
|
647
735
|
return await operations.update({ ...params, claim: handle });
|
|
648
736
|
}
|
|
@@ -651,7 +739,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
651
739
|
}
|
|
652
740
|
}
|
|
653
741
|
const { id } = params;
|
|
654
|
-
const model =
|
|
742
|
+
const model = ownRowOrThrow(id);
|
|
655
743
|
if (!model)
|
|
656
744
|
throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
|
|
657
745
|
// If we hold a claim on this row, guard the write with its snapshot
|
|
@@ -702,6 +790,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
702
790
|
const autoClaim = params.claim && !isClaimHandle(params.claim) ? params.claim : null;
|
|
703
791
|
if (autoClaim) {
|
|
704
792
|
const handle = await takeClaim({ ...autoClaim, id: params.id });
|
|
793
|
+
// Same rule as update: a write that could not take its claim fails.
|
|
794
|
+
if (!handle) {
|
|
795
|
+
throw new AbloClaimedError(`${registeredModelName}/${params.id} is held by another participant, so this delete's claim could not be taken.`, { code: 'entity_claimed' });
|
|
796
|
+
}
|
|
705
797
|
try {
|
|
706
798
|
await operations.delete({ ...params, claim: handle });
|
|
707
799
|
}
|
|
@@ -711,7 +803,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
711
803
|
return;
|
|
712
804
|
}
|
|
713
805
|
const { id } = params;
|
|
714
|
-
|
|
806
|
+
// Scoped: "ensure absent" stays idempotent for an id this model simply
|
|
807
|
+
// doesn't hold, but an id owned by a sibling model throws rather than
|
|
808
|
+
// deleting a row the caller never addressed.
|
|
809
|
+
const model = ownRowOrThrow(id);
|
|
715
810
|
// Idempotent delete: "ensure absent". A row that isn't in this client's
|
|
716
811
|
// replicated view is already gone from its perspective, so a delete is a
|
|
717
812
|
// no-op success rather than an `entity_not_found` error. This matches the
|
|
@@ -758,13 +853,13 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
758
853
|
};
|
|
759
854
|
// A track carries no write, so it rides the commit lane as a zero-operation
|
|
760
855
|
// commit: the queue tolerates disconnects and de-dupes replays, and the
|
|
761
|
-
// server's track-only path registers the
|
|
856
|
+
// server's track-only path registers the premise and reports anything
|
|
762
857
|
// that already fired. Reuse the same lane the batch `commits.create` door
|
|
763
858
|
// uses rather than opening a bespoke transport.
|
|
764
859
|
const clientTxId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
765
860
|
? crypto.randomUUID()
|
|
766
861
|
: `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
767
|
-
const queue = syncClient.
|
|
862
|
+
const queue = syncClient.getMutationQueue();
|
|
768
863
|
await queue.enqueueCommit(clientTxId, [], { track: [dep] });
|
|
769
864
|
const { notifications } = await queue.waitForCommitReceipt(clientTxId);
|
|
770
865
|
return notifications && notifications.length > 0 ? { notifications } : {};
|
|
@@ -777,8 +872,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
777
872
|
}),
|
|
778
873
|
onChange(callback, options) {
|
|
779
874
|
return autorun(() => {
|
|
780
|
-
|
|
781
|
-
callback(entities);
|
|
875
|
+
callback(local.list(options));
|
|
782
876
|
});
|
|
783
877
|
},
|
|
784
878
|
};
|