@abloatai/ablo 0.25.0 → 0.27.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 +5 -3
- package/CHANGELOG.md +34 -0
- package/README.md +104 -88
- package/dist/BaseSyncedStore.d.ts +140 -266
- package/dist/BaseSyncedStore.js +338 -739
- package/dist/Database.d.ts +62 -77
- package/dist/Database.js +106 -127
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +91 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +16 -15
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +52 -69
- package/dist/ModelRegistry.d.ts +46 -25
- package/dist/ModelRegistry.js +32 -30
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +6 -7
- package/dist/SyncClient.d.ts +119 -109
- package/dist/SyncClient.js +303 -224
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/adapters/alwaysOnline.d.ts +6 -8
- package/dist/adapters/alwaysOnline.js +6 -8
- package/dist/adapters/inMemoryStorage.d.ts +9 -9
- package/dist/adapters/inMemoryStorage.js +9 -9
- package/dist/agent/Agent.d.ts +39 -31
- package/dist/agent/Agent.js +35 -23
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +5 -5
- package/dist/agent/session.d.ts +47 -44
- package/dist/agent/session.js +37 -48
- package/dist/agent/types.d.ts +26 -31
- package/dist/agent/types.js +6 -7
- package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
- package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
- package/dist/ai-sdk/coordinationContext.d.ts +46 -0
- package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +30 -31
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +7 -8
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/credentialPolicy.d.ts +74 -71
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +7 -18
- package/dist/auth/credentialSource.js +10 -18
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +34 -40
- package/dist/auth/schemas.d.ts +5 -4
- package/dist/auth/schemas.js +5 -4
- package/dist/batching/index.d.ts +19 -21
- package/dist/batching/index.js +14 -17
- package/dist/cli.cjs +483 -369
- package/dist/client/Ablo.d.ts +107 -836
- package/dist/client/Ablo.js +174 -833
- package/dist/client/ApiClient.d.ts +44 -20
- package/dist/client/ApiClient.js +193 -44
- package/dist/client/auth.d.ts +51 -60
- package/dist/client/auth.js +137 -110
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +35 -0
- package/dist/client/consoleLogger.js +44 -0
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +26 -31
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +158 -124
- package/dist/client/credentialEndpoint.d.ts +61 -0
- package/dist/client/credentialEndpoint.js +86 -0
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +21 -0
- package/dist/client/hostedEndpoints.js +21 -0
- package/dist/client/httpClient.d.ts +58 -54
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +49 -59
- package/dist/client/modelRegistration.d.ts +10 -0
- package/dist/client/modelRegistration.js +301 -0
- package/dist/client/options.d.ts +373 -0
- package/dist/client/options.js +6 -0
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +333 -0
- package/dist/client/resourceTypes.js +7 -0
- package/dist/client/schemaConfig.d.ts +44 -0
- package/dist/client/schemaConfig.js +176 -0
- package/dist/client/sessionMint.d.ts +17 -13
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +9 -10
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +28 -0
- package/dist/client/wsMutationExecutor.js +71 -0
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -7
- package/dist/coordination/index.d.ts +13 -4
- package/dist/coordination/index.js +29 -4
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -11
- package/dist/coordination/trace.js +13 -15
- package/dist/core/DatabaseManager.d.ts +5 -8
- package/dist/core/DatabaseManager.js +38 -40
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +27 -34
- package/dist/core/QueryView.d.ts +17 -5
- package/dist/core/QueryView.js +6 -7
- package/dist/core/StoreManager.d.ts +14 -16
- package/dist/core/StoreManager.js +26 -25
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +18 -13
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +57 -54
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +145 -0
- package/dist/core/storeContract.js +12 -0
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +118 -101
- package/dist/errorCodes.js +277 -260
- package/dist/errors.d.ts +170 -165
- package/dist/errors.js +161 -151
- package/dist/index.d.ts +30 -27
- package/dist/index.js +90 -82
- package/dist/interfaces/index.d.ts +108 -133
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +59 -49
- package/dist/mutators/RecordingTransaction.d.ts +16 -16
- package/dist/mutators/RecordingTransaction.js +31 -37
- package/dist/mutators/Transaction.d.ts +18 -26
- package/dist/mutators/Transaction.js +14 -20
- package/dist/mutators/UndoManager.d.ts +122 -131
- package/dist/mutators/UndoManager.js +149 -155
- package/dist/mutators/defineMutators.d.ts +24 -37
- package/dist/mutators/defineMutators.js +14 -20
- package/dist/mutators/inverseOp.d.ts +12 -15
- package/dist/mutators/inverseOp.js +12 -15
- package/dist/mutators/mutateActions.d.ts +10 -9
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +9 -8
- package/dist/mutators/readerActions.js +2 -2
- package/dist/mutators/undoApply.d.ts +31 -27
- package/dist/mutators/undoApply.js +26 -24
- package/dist/policy/index.d.ts +5 -3
- package/dist/policy/index.js +5 -3
- package/dist/policy/types.d.ts +105 -101
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +32 -16
- package/dist/query/client.js +103 -72
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +7 -11
- package/dist/react/AbloProvider.js +24 -17
- package/dist/react/context.d.ts +27 -146
- package/dist/react/context.js +9 -10
- package/dist/react/index.d.ts +41 -42
- package/dist/react/index.js +37 -38
- package/dist/react/internalContext.d.ts +17 -19
- package/dist/react/useAblo.d.ts +23 -22
- package/dist/react/useAblo.js +17 -15
- package/dist/react/useCurrentUserId.d.ts +8 -7
- package/dist/react/useCurrentUserId.js +8 -7
- package/dist/react/useErrorListener.d.ts +7 -7
- package/dist/react/useErrorListener.js +11 -12
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +9 -9
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +10 -4
- package/dist/react/useReactive.js +2 -3
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +3 -3
- package/dist/schema/coordination.d.ts +21 -25
- package/dist/schema/coordination.js +21 -25
- package/dist/schema/ddl.d.ts +43 -39
- package/dist/schema/ddl.js +75 -68
- package/dist/schema/ddlLock.d.ts +35 -0
- package/dist/schema/ddlLock.js +46 -0
- package/dist/schema/diff.d.ts +99 -61
- package/dist/schema/diff.js +43 -34
- package/dist/schema/field.d.ts +37 -42
- package/dist/schema/field.js +36 -49
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +5 -4
- package/dist/schema/index.js +29 -21
- package/dist/schema/model.d.ts +121 -146
- package/dist/schema/model.js +24 -35
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +7 -1
- package/dist/schema/queries.d.ts +30 -32
- package/dist/schema/queries.js +24 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +38 -0
- package/dist/schema/residency.js +30 -0
- package/dist/schema/roles.d.ts +45 -27
- package/dist/schema/roles.js +52 -21
- package/dist/schema/schema.d.ts +36 -45
- package/dist/schema/schema.js +42 -39
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +36 -39
- package/dist/schema/serialize.js +27 -31
- package/dist/schema/sugar.d.ts +17 -32
- package/dist/schema/sugar.js +14 -29
- package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +27 -50
- package/dist/schema/syncDeltaRow.js +89 -0
- package/dist/schema/tenancy.d.ts +44 -46
- package/dist/schema/tenancy.js +46 -48
- package/dist/server/adapter.d.ts +58 -58
- package/dist/server/adapter.js +13 -14
- package/dist/server/commit.d.ts +60 -64
- package/dist/server/index.d.ts +9 -10
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +70 -0
- package/dist/server/readConfig.js +8 -0
- package/dist/server/storageMode.d.ts +23 -0
- package/dist/server/storageMode.js +17 -0
- package/dist/source/adapter.d.ts +31 -26
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +34 -28
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +28 -26
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +10 -9
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +27 -29
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +27 -19
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +30 -28
- package/dist/source/connectorProtocol.d.ts +160 -0
- package/dist/source/connectorProtocol.js +162 -0
- package/dist/source/contract.d.ts +26 -27
- package/dist/source/contract.js +28 -29
- package/dist/source/factory.d.ts +94 -0
- package/dist/source/factory.js +268 -0
- package/dist/source/index.d.ts +10 -462
- package/dist/source/index.js +17 -421
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +10 -11
- package/dist/source/next.js +7 -8
- package/dist/source/pushQueue.d.ts +70 -48
- package/dist/source/pushQueue.js +36 -29
- package/dist/source/signing.d.ts +88 -0
- package/dist/source/signing.js +159 -0
- package/dist/source/types.d.ts +351 -0
- package/dist/source/types.js +43 -0
- package/dist/stores/ObjectStore.d.ts +11 -12
- package/dist/stores/ObjectStore.js +34 -35
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +8 -12
- package/dist/stores/SyncActionStore.js +77 -46
- package/dist/surface.d.ts +28 -21
- package/dist/surface.js +28 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +37 -45
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +101 -80
- package/dist/sync/ConnectionManager.d.ts +47 -50
- package/dist/sync/ConnectionManager.js +74 -70
- package/dist/sync/NetworkProbe.d.ts +27 -31
- package/dist/sync/NetworkProbe.js +67 -72
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +49 -36
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +79 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +45 -59
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +44 -52
- package/dist/sync/SyncWebSocket.d.ts +175 -250
- package/dist/sync/SyncWebSocket.js +431 -769
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +38 -30
- package/dist/sync/bootstrapApply.d.ts +70 -0
- package/dist/sync/bootstrapApply.js +73 -0
- package/dist/sync/commitFrames.d.ts +44 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +108 -25
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +13 -17
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +322 -0
- package/dist/sync/deltaPipeline.d.ts +113 -0
- package/dist/sync/deltaPipeline.js +261 -0
- package/dist/sync/groupChange.d.ts +113 -0
- package/dist/sync/groupChange.js +242 -0
- package/dist/sync/heartbeat.d.ts +63 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +27 -27
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +40 -0
- package/dist/sync/syncCursor.js +55 -0
- package/dist/sync/syncPlan.d.ts +54 -0
- package/dist/sync/syncPlan.js +50 -0
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +116 -0
- package/dist/sync/wsFrameHandlers.js +374 -0
- package/dist/testing/fixtures/bootstrap.d.ts +21 -17
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +31 -34
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +12 -10
- package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
- package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
- package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -18
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +14 -11
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.js +3 -3
- package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
- package/dist/testing/mocks/MockMutationExecutor.js +15 -14
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
- package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
- package/dist/testing/mocks/MockSyncContext.d.ts +21 -34
- package/dist/testing/mocks/MockSyncContext.js +16 -45
- package/dist/testing/mocks/MockSyncStore.d.ts +14 -14
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +28 -24
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +190 -221
- package/dist/transactions/TransactionQueue.js +424 -822
- package/dist/transactions/TransactionStore.d.ts +20 -0
- package/dist/transactions/TransactionStore.js +53 -0
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +58 -0
- package/dist/transactions/coalesceRules.js +140 -0
- package/dist/transactions/commitPayload.d.ts +130 -0
- package/dist/transactions/commitPayload.js +143 -0
- package/dist/transactions/deltaConfirmation.d.ts +58 -0
- package/dist/transactions/deltaConfirmation.js +215 -0
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/replayValidation.d.ts +99 -0
- package/dist/transactions/replayValidation.js +111 -0
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +74 -80
- package/dist/types/index.js +22 -27
- package/dist/types/modelData.d.ts +10 -0
- package/dist/types/modelData.js +9 -0
- package/dist/types/participant.d.ts +20 -0
- package/dist/types/participant.js +10 -0
- package/dist/types/streams.d.ts +216 -209
- package/dist/types/streams.js +7 -7
- package/dist/utils/asyncIterator.d.ts +25 -32
- package/dist/utils/asyncIterator.js +25 -32
- package/dist/utils/duration.d.ts +12 -15
- package/dist/utils/duration.js +12 -15
- package/dist/utils/mobxSetup.d.ts +53 -0
- package/dist/utils/{mobx-setup.js → mobxSetup.js} +44 -100
- package/dist/webhooks/events.d.ts +21 -16
- package/dist/webhooks/events.js +10 -8
- package/dist/webhooks/index.d.ts +5 -7
- package/dist/webhooks/index.js +5 -7
- package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
- package/dist/wire/delta.js +114 -0
- package/dist/wire/errorEnvelope.d.ts +35 -27
- package/dist/wire/errorEnvelope.js +38 -32
- package/dist/wire/frames.d.ts +150 -67
- package/dist/wire/frames.js +48 -1
- package/dist/wire/index.d.ts +18 -13
- package/dist/wire/index.js +36 -13
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +38 -0
- package/dist/wire/protocol.js +38 -0
- package/dist/wire/protocolVersion.d.ts +60 -0
- package/dist/wire/protocolVersion.js +67 -0
- package/docs/api-keys.md +4 -3
- package/docs/coordination.md +59 -0
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/package.json +24 -21
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- package/dist/server/read-config.d.ts +0 -67
- package/dist/server/read-config.js +0 -8
- package/dist/server/storage-mode.d.ts +0 -1
- package/dist/server/storage-mode.js +0 -18
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Waits for a queued claim to reach the head of the line — the client side of
|
|
3
|
+
* the fair-queue handover. When a claim is contended, the server puts it in a
|
|
4
|
+
* queue and replies that it is queued (HTTP 202 on `/v1/claims`, or a
|
|
5
|
+
* `claim_queued` frame over the WebSocket). The grant is delivered later, when
|
|
6
|
+
* the claim reaches the head, as a `claim_granted` frame. This resolves once
|
|
7
|
+
* that frame arrives for the given `claimId`, so the caller's `claim` promise
|
|
8
|
+
* stays pending — event-driven, with no polling — until it is actually the
|
|
9
|
+
* caller's turn. It rejects if the claim is lost (`claim_lost`: taken away by a
|
|
10
|
+
* TTL lapse on disconnect, or revoked) or if an optional timeout elapses.
|
|
3
11
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* PUSHED later over the WS as `claim_granted` when the claim reaches the head.
|
|
7
|
-
* This resolves once that frame arrives for our `claimId` — so the caller's
|
|
8
|
-
* `claim` promise stays pending (event-driven; no poll, no race) until it's
|
|
9
|
-
* actually our turn. Rejects on `claim_lost` (surfaced as `claim_lost`: the claim was taken away — TTL
|
|
10
|
-
* lapse on disconnect, revoke) or an optional timeout.
|
|
11
|
-
*
|
|
12
|
-
* Takes only a minimal `{ subscribe }` transport so it unit-tests against a
|
|
13
|
-
* fake; `SyncWebSocket` satisfies it structurally.
|
|
12
|
+
* It needs only a minimal `{ subscribe }` transport, so it can be tested
|
|
13
|
+
* against a fake; {@link SyncWebSocket} satisfies it.
|
|
14
14
|
*/
|
|
15
15
|
export interface GrantTransport {
|
|
16
16
|
subscribe(event: 'claim_acquired' | 'claim_granted' | 'claim_lost' | 'claim_queued' | 'claim_rejected', handler: (payload: Record<string, unknown>) => void): () => void;
|
|
17
17
|
}
|
|
18
18
|
export interface ClaimGrantInfo {
|
|
19
19
|
/**
|
|
20
|
-
* True when the grant arrived as `claim_granted` —
|
|
21
|
-
*
|
|
22
|
-
* False for the immediate `claim_acquired
|
|
20
|
+
* True when the grant arrived as `claim_granted` — the target was held when
|
|
21
|
+
* the caller asked, and the caller waited in the FIFO line behind the holder.
|
|
22
|
+
* False for the immediate `claim_acquired`, where the target was free.
|
|
23
23
|
*
|
|
24
|
-
* Callers
|
|
25
|
-
*
|
|
26
|
-
* presence
|
|
27
|
-
*
|
|
24
|
+
* Callers read this to know the row may have changed while they queued. Claim
|
|
25
|
+
* visibility is scoped to the entity, so a broad, organization-wide
|
|
26
|
+
* subscription receives no presence or claim fan-out, and the local
|
|
27
|
+
* coordination snapshot cannot be trusted to tell whether the caller waited.
|
|
28
28
|
* The grant frame itself is the authoritative signal.
|
|
29
29
|
*/
|
|
30
30
|
readonly waited: boolean;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Waits for a queued claim to reach the head of the line — the client side of
|
|
3
|
+
* the fair-queue handover. When a claim is contended, the server puts it in a
|
|
4
|
+
* queue and replies that it is queued (HTTP 202 on `/v1/claims`, or a
|
|
5
|
+
* `claim_queued` frame over the WebSocket). The grant is delivered later, when
|
|
6
|
+
* the claim reaches the head, as a `claim_granted` frame. This resolves once
|
|
7
|
+
* that frame arrives for the given `claimId`, so the caller's `claim` promise
|
|
8
|
+
* stays pending — event-driven, with no polling — until it is actually the
|
|
9
|
+
* caller's turn. It rejects if the claim is lost (`claim_lost`: taken away by a
|
|
10
|
+
* TTL lapse on disconnect, or revoked) or if an optional timeout elapses.
|
|
3
11
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* PUSHED later over the WS as `claim_granted` when the claim reaches the head.
|
|
7
|
-
* This resolves once that frame arrives for our `claimId` — so the caller's
|
|
8
|
-
* `claim` promise stays pending (event-driven; no poll, no race) until it's
|
|
9
|
-
* actually our turn. Rejects on `claim_lost` (surfaced as `claim_lost`: the claim was taken away — TTL
|
|
10
|
-
* lapse on disconnect, revoke) or an optional timeout.
|
|
11
|
-
*
|
|
12
|
-
* Takes only a minimal `{ subscribe }` transport so it unit-tests against a
|
|
13
|
-
* fake; `SyncWebSocket` satisfies it structurally.
|
|
12
|
+
* It needs only a minimal `{ subscribe }` transport, so it can be tested
|
|
13
|
+
* against a fake; {@link SyncWebSocket} satisfies it.
|
|
14
14
|
*/
|
|
15
15
|
import { AbloClaimedError, formatClaimedErrorMessage, claimTargetLabel, } from '../errors.js';
|
|
16
16
|
import { getContext } from '../context.js';
|
|
@@ -31,7 +31,7 @@ export function awaitClaimGrant(transport, claimId, options) {
|
|
|
31
31
|
unsubs.push(transport.subscribe('claim_acquired', (p) => {
|
|
32
32
|
if (p?.claimId === claimId) {
|
|
33
33
|
getContext().logger.debug(`claim: acquired ${claimId} (target was free)`);
|
|
34
|
-
settle(() => resolve({ waited: false }));
|
|
34
|
+
settle(() => { resolve({ waited: false }); });
|
|
35
35
|
}
|
|
36
36
|
}));
|
|
37
37
|
unsubs.push(transport.subscribe('claim_granted', (p) => {
|
|
@@ -39,7 +39,7 @@ export function awaitClaimGrant(transport, claimId, options) {
|
|
|
39
39
|
// Promoted to the head of the line — the creator's "it's the agent's
|
|
40
40
|
// turn now" moment after waiting behind a holder.
|
|
41
41
|
getContext().logger.info(`claim: granted ${claimId} — your turn (waited in queue)`);
|
|
42
|
-
settle(() => resolve({ waited: true }));
|
|
42
|
+
settle(() => { resolve({ waited: true }); });
|
|
43
43
|
}
|
|
44
44
|
}));
|
|
45
45
|
if (options?.maxQueueDepth !== undefined) {
|
|
@@ -49,7 +49,9 @@ export function awaitClaimGrant(transport, claimId, options) {
|
|
|
49
49
|
return;
|
|
50
50
|
const position = typeof p.position === 'number' ? p.position : 0;
|
|
51
51
|
if (position >= max) {
|
|
52
|
-
settle(() =>
|
|
52
|
+
settle(() => {
|
|
53
|
+
reject(new AbloClaimedError(`Claim queue for ${claimId} is ${position} deep (max ${max}).`, { code: 'queue_too_deep' }));
|
|
54
|
+
});
|
|
53
55
|
}
|
|
54
56
|
}));
|
|
55
57
|
}
|
|
@@ -64,29 +66,35 @@ export function awaitClaimGrant(transport, claimId, options) {
|
|
|
64
66
|
field: rejection.target.field,
|
|
65
67
|
})
|
|
66
68
|
: claimId;
|
|
67
|
-
settle(() =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
settle(() => {
|
|
70
|
+
reject(new AbloClaimedError(formatClaimedErrorMessage({
|
|
71
|
+
targetLabel: target,
|
|
72
|
+
heldBy: rejection.heldBy,
|
|
73
|
+
claim: rejection.heldByClaim,
|
|
74
|
+
policyReason: rejection.policyReason,
|
|
75
|
+
fallback: `Claim rejected for ${target}.`,
|
|
76
|
+
}), {
|
|
77
|
+
code: rejection.reason === 'conflict'
|
|
78
|
+
? 'claim_conflict'
|
|
79
|
+
: 'claim_lease_unavailable',
|
|
80
|
+
claims: rejection.heldByClaim ? [rejection.heldByClaim] : undefined,
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
79
83
|
}));
|
|
80
84
|
unsubs.push(transport.subscribe('claim_lost', (p) => {
|
|
81
85
|
if (p?.claimId === claimId) {
|
|
82
|
-
settle(() =>
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
settle(() => {
|
|
87
|
+
reject(new AbloClaimedError(`Claim lost while queued for ${claimId}.`, {
|
|
88
|
+
code: 'claim_lost',
|
|
89
|
+
}));
|
|
90
|
+
});
|
|
85
91
|
}
|
|
86
92
|
}));
|
|
87
93
|
if (options?.timeoutMs && options.timeoutMs > 0) {
|
|
88
94
|
timer = setTimeout(() => {
|
|
89
|
-
settle(() =>
|
|
95
|
+
settle(() => {
|
|
96
|
+
reject(new AbloClaimedError(`Timed out waiting for the queue grant on claim ${claimId}.`, { code: 'grant_timeout' }));
|
|
97
|
+
});
|
|
90
98
|
}, options.timeoutMs);
|
|
91
99
|
}
|
|
92
100
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies a bootstrap result to the in-memory object pool. When a client
|
|
3
|
+
* connects, the server sends either a full snapshot of the models it can see or
|
|
4
|
+
* a partial catch-up of the deltas it missed. These functions route that result
|
|
5
|
+
* into the pool, protect entities that arrived mid-bootstrap from being swept
|
|
6
|
+
* away as stale, and replay any deltas that queued while the bootstrap was in
|
|
7
|
+
* flight.
|
|
8
|
+
*
|
|
9
|
+
* The functions here reach their host store only through the small
|
|
10
|
+
* {@link PoolContext} interface, not the store's concrete class, so the two can
|
|
11
|
+
* reference each other without forming an import cycle. The pool writes
|
|
12
|
+
* themselves — creating models, healing partial rows, upserting, and removing
|
|
13
|
+
* stale local copies the server no longer reports — are performed by the sync
|
|
14
|
+
* client behind that interface.
|
|
15
|
+
*/
|
|
16
|
+
import type { BootstrapResult } from '../Database.js';
|
|
17
|
+
import type { SyncDelta } from './SyncWebSocket.js';
|
|
18
|
+
/** Counts describing what applying a bootstrap changed in the pool: entities
|
|
19
|
+
* added, updated, removed, skipped, and healed, plus the elapsed time. */
|
|
20
|
+
export interface RehydrationStats {
|
|
21
|
+
added: number;
|
|
22
|
+
updated: number;
|
|
23
|
+
removed: number;
|
|
24
|
+
skipped: number;
|
|
25
|
+
healed: number;
|
|
26
|
+
elapsedMs: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The methods the bootstrap-apply functions call back into on the host store.
|
|
30
|
+
* The two data-application methods arrive with relation enrichment already
|
|
31
|
+
* bound by the host, so a subclass override of how relations are enriched still
|
|
32
|
+
* takes effect through this interface.
|
|
33
|
+
*/
|
|
34
|
+
export interface PoolContext {
|
|
35
|
+
/** Applies persisted delta results to the in-memory pool, with the host's relation enrichment bound. */
|
|
36
|
+
applyDeltaBatchToPool(results: NonNullable<BootstrapResult['deltaResults']>): void;
|
|
37
|
+
/** Writes bootstrap data into the pool: creates models, heals partial rows, upserts, and removes stale local copies the server no longer reports. */
|
|
38
|
+
applyBootstrapDataToPool(bootstrapData: {
|
|
39
|
+
models?: Record<string, unknown[]>;
|
|
40
|
+
failedModels?: string[];
|
|
41
|
+
}, protectedIds?: ReadonlySet<string>): {
|
|
42
|
+
added: number;
|
|
43
|
+
updated: number;
|
|
44
|
+
removed: number;
|
|
45
|
+
skipped: number;
|
|
46
|
+
healed: number;
|
|
47
|
+
};
|
|
48
|
+
/** Pool size — for the completion log line. */
|
|
49
|
+
getPoolSize(): number;
|
|
50
|
+
/** Every id currently in the pool, used to work out which entities must survive the stale-sweep (see {@link collectDeltaProtectedIds}). */
|
|
51
|
+
getAllPoolIds(): string[];
|
|
52
|
+
/** Deltas that queued while a bootstrap was in flight; null when no bootstrap
|
|
53
|
+
* is running. The host backs this with a field exposed through accessors. */
|
|
54
|
+
bootstrapDeltaQueue: SyncDelta[] | null;
|
|
55
|
+
/** Applies a complete set of deltas to the pool atomically — one write, one
|
|
56
|
+
* re-render. This entry point lives on the host, not in this module. */
|
|
57
|
+
applyDeltaFrame(deltas: SyncDelta[]): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Applies a bootstrap result to the in-memory pool and returns what changed.
|
|
61
|
+
* A full bootstrap creates, heals, and upserts models and removes stale local
|
|
62
|
+
* copies the server no longer reports; a partial bootstrap routes the missed
|
|
63
|
+
* deltas through the delta-apply path so deletions evict their entities. See
|
|
64
|
+
* {@link RehydrationStats} for the returned counts.
|
|
65
|
+
*/
|
|
66
|
+
export declare function applyBootstrapToPool(ctx: PoolContext, bootstrapResult: BootstrapResult, protectedIds?: ReadonlySet<string>): RehydrationStats;
|
|
67
|
+
/** Collects the ids that must survive the post-bootstrap stale-sweep: entities added by deltas that arrived while the bootstrap was in flight. */
|
|
68
|
+
export declare function collectDeltaProtectedIds(ctx: PoolContext, preBootstrapIds: ReadonlySet<string>): Set<string>;
|
|
69
|
+
/** Replays the deltas that queued while a bootstrap was in flight, applying them as one atomic frame. */
|
|
70
|
+
export declare function replayQueuedDeltas(ctx: PoolContext): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies a bootstrap result to the in-memory object pool. When a client
|
|
3
|
+
* connects, the server sends either a full snapshot of the models it can see or
|
|
4
|
+
* a partial catch-up of the deltas it missed. These functions route that result
|
|
5
|
+
* into the pool, protect entities that arrived mid-bootstrap from being swept
|
|
6
|
+
* away as stale, and replay any deltas that queued while the bootstrap was in
|
|
7
|
+
* flight.
|
|
8
|
+
*
|
|
9
|
+
* The functions here reach their host store only through the small
|
|
10
|
+
* {@link PoolContext} interface, not the store's concrete class, so the two can
|
|
11
|
+
* reference each other without forming an import cycle. The pool writes
|
|
12
|
+
* themselves — creating models, healing partial rows, upserting, and removing
|
|
13
|
+
* stale local copies the server no longer reports — are performed by the sync
|
|
14
|
+
* client behind that interface.
|
|
15
|
+
*/
|
|
16
|
+
import { getContext } from '../context.js';
|
|
17
|
+
/**
|
|
18
|
+
* Applies a bootstrap result to the in-memory pool and returns what changed.
|
|
19
|
+
* A full bootstrap creates, heals, and upserts models and removes stale local
|
|
20
|
+
* copies the server no longer reports; a partial bootstrap routes the missed
|
|
21
|
+
* deltas through the delta-apply path so deletions evict their entities. See
|
|
22
|
+
* {@link RehydrationStats} for the returned counts.
|
|
23
|
+
*/
|
|
24
|
+
export function applyBootstrapToPool(ctx, bootstrapResult, protectedIds) {
|
|
25
|
+
const { bootstrapData } = bootstrapResult;
|
|
26
|
+
// Partial bootstrap: the missed deltas are already written to the local
|
|
27
|
+
// store. Route the same results through the delta-apply path so the
|
|
28
|
+
// in-memory pool also evicts deleted entities and updates modified ones.
|
|
29
|
+
// Without this, a reconnect delete persists locally but its stale copy
|
|
30
|
+
// lingers in the pool until a full reload.
|
|
31
|
+
if (bootstrapData.type === 'partial') {
|
|
32
|
+
const deltaResults = bootstrapResult.deltaResults;
|
|
33
|
+
if (deltaResults && deltaResults.length > 0) {
|
|
34
|
+
ctx.applyDeltaBatchToPool(deltaResults);
|
|
35
|
+
}
|
|
36
|
+
return { added: 0, updated: 0, removed: 0, skipped: 0, healed: 0, elapsedMs: 0 };
|
|
37
|
+
}
|
|
38
|
+
if (!bootstrapData.models) {
|
|
39
|
+
return { added: 0, updated: 0, removed: 0, skipped: 0, healed: 0, elapsedMs: 0 };
|
|
40
|
+
}
|
|
41
|
+
const start = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
42
|
+
// Creates models, heals partial rows, upserts, and removes stale local copies.
|
|
43
|
+
const stats = ctx.applyBootstrapDataToPool(bootstrapData, protectedIds);
|
|
44
|
+
const elapsedMs = Math.round((typeof performance !== 'undefined' ? performance.now() : Date.now()) - start);
|
|
45
|
+
getContext().logger.info('[BaseSyncedStore] Bootstrap applied', {
|
|
46
|
+
...stats, elapsedMs, poolSize: ctx.getPoolSize(),
|
|
47
|
+
});
|
|
48
|
+
return { ...stats, elapsedMs };
|
|
49
|
+
}
|
|
50
|
+
/** Collects the ids that must survive the post-bootstrap stale-sweep: entities added by deltas that arrived while the bootstrap was in flight. */
|
|
51
|
+
export function collectDeltaProtectedIds(ctx, preBootstrapIds) {
|
|
52
|
+
const protectedIds = new Set();
|
|
53
|
+
for (const id of ctx.getAllPoolIds()) {
|
|
54
|
+
if (!preBootstrapIds.has(id))
|
|
55
|
+
protectedIds.add(id);
|
|
56
|
+
}
|
|
57
|
+
for (const delta of ctx.bootstrapDeltaQueue ?? []) {
|
|
58
|
+
if (delta.actionType !== 'D' && delta.modelId)
|
|
59
|
+
protectedIds.add(delta.modelId);
|
|
60
|
+
}
|
|
61
|
+
return protectedIds;
|
|
62
|
+
}
|
|
63
|
+
/** Replays the deltas that queued while a bootstrap was in flight, applying them as one atomic frame. */
|
|
64
|
+
export function replayQueuedDeltas(ctx) {
|
|
65
|
+
const queue = ctx.bootstrapDeltaQueue;
|
|
66
|
+
ctx.bootstrapDeltaQueue = null;
|
|
67
|
+
if (!queue || queue.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
// Deltas that landed during bootstrap are a complete frame — apply
|
|
70
|
+
// them atomically (one flush, one re-render) rather than dribbling
|
|
71
|
+
// each back through the live debounce path.
|
|
72
|
+
ctx.applyDeltaFrame(queue);
|
|
73
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the outgoing frames the sync WebSocket sends on the commit path, and
|
|
3
|
+
* provides the single place claim events are traced. These are stateless
|
|
4
|
+
* helpers that hold no socket state, so both the transport and its frame
|
|
5
|
+
* dispatch can share them.
|
|
6
|
+
*/
|
|
7
|
+
import type { CommitMessage } from '../wire/index.js';
|
|
8
|
+
import type { MutationOperation, ClaimEvent } from '../interfaces/index.js';
|
|
9
|
+
import type { StaleNotification, ReadDependency } from '../coordination/schema.js';
|
|
10
|
+
/**
|
|
11
|
+
* The value a commit acknowledgement resolves to. `notifications` is present
|
|
12
|
+
* only when a guarded write (`onStale: 'notify'`) met a concurrent change; it
|
|
13
|
+
* carries the advisory signal that lets the writer self-heal, and the same
|
|
14
|
+
* signal also arrives on the `conflict:notified` event.
|
|
15
|
+
*/
|
|
16
|
+
export interface CommitAck {
|
|
17
|
+
lastSyncId: number;
|
|
18
|
+
notifications?: StaleNotification[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Converts the client's list of {@link MutationOperation} values into the wire
|
|
22
|
+
* {@link CommitMessage} the server accepts. This is the one place the loosely
|
|
23
|
+
* typed operation — its `type` is a string, and it carries client-only
|
|
24
|
+
* `options` the server never reads — becomes the strict wire contract. Mapping
|
|
25
|
+
* each field by hand means a change to {@link CommitOperation} fails to compile
|
|
26
|
+
* here; the single `as` cast narrows the validated `type` to the wire union and
|
|
27
|
+
* is the only place that loosening happens.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildCommitFrame(operations: readonly MutationOperation[], clientTxId: string, causedByTaskId?: string | null, reads?: readonly ReadDependency[] | null): CommitMessage;
|
|
30
|
+
/**
|
|
31
|
+
* Defensively validate the optional `notifications` array off a commit ack.
|
|
32
|
+
* Untrusted wire data — a malformed entry is dropped rather than throwing,
|
|
33
|
+
* so a bad notification never sinks an otherwise-successful commit.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseNotifications(raw: unknown): StaleNotification[] | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The single place claim events are traced. Every `claim_*` frame passes
|
|
38
|
+
* through here, so a developer debugging a collision gets one consistent record
|
|
39
|
+
* — a console line and a structured capture — without each frame case
|
|
40
|
+
* re-deriving the row and holder shape. The wire payload is loosely typed
|
|
41
|
+
* (`Record<string, unknown>`), so this is the one place that narrows it into a
|
|
42
|
+
* {@link ClaimEvent}.
|
|
43
|
+
*/
|
|
44
|
+
export declare function recordClaim(phase: ClaimEvent['phase'], payload: Record<string, unknown>): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the outgoing frames the sync WebSocket sends on the commit path, and
|
|
3
|
+
* provides the single place claim events are traced. These are stateless
|
|
4
|
+
* helpers that hold no socket state, so both the transport and its frame
|
|
5
|
+
* dispatch can share them.
|
|
6
|
+
*/
|
|
7
|
+
import { getContext } from '../context.js';
|
|
8
|
+
import { staleNotificationSchema, wireParticipantKindSchema, } from '../coordination/schema.js';
|
|
9
|
+
import { formatClaim } from '../coordination/trace.js';
|
|
10
|
+
/**
|
|
11
|
+
* Converts the client's list of {@link MutationOperation} values into the wire
|
|
12
|
+
* {@link CommitMessage} the server accepts. This is the one place the loosely
|
|
13
|
+
* typed operation — its `type` is a string, and it carries client-only
|
|
14
|
+
* `options` the server never reads — becomes the strict wire contract. Mapping
|
|
15
|
+
* each field by hand means a change to {@link CommitOperation} fails to compile
|
|
16
|
+
* here; the single `as` cast narrows the validated `type` to the wire union and
|
|
17
|
+
* is the only place that loosening happens.
|
|
18
|
+
*/
|
|
19
|
+
export function buildCommitFrame(operations, clientTxId, causedByTaskId, reads) {
|
|
20
|
+
const payload = {
|
|
21
|
+
operations: operations.map((op) => ({
|
|
22
|
+
type: op.type,
|
|
23
|
+
model: op.model,
|
|
24
|
+
id: op.id,
|
|
25
|
+
input: op.input,
|
|
26
|
+
transactionId: op.transactionId,
|
|
27
|
+
readAt: op.readAt,
|
|
28
|
+
onStale: op.onStale,
|
|
29
|
+
})),
|
|
30
|
+
clientTxId,
|
|
31
|
+
};
|
|
32
|
+
if (causedByTaskId)
|
|
33
|
+
payload.causedByTaskId = causedByTaskId;
|
|
34
|
+
// The read set the batch was premised on: the rows or groups the writer read before committing.
|
|
35
|
+
if (reads && reads.length > 0)
|
|
36
|
+
payload.reads = [...reads];
|
|
37
|
+
return { type: 'commit', payload };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Defensively validate the optional `notifications` array off a commit ack.
|
|
41
|
+
* Untrusted wire data — a malformed entry is dropped rather than throwing,
|
|
42
|
+
* so a bad notification never sinks an otherwise-successful commit.
|
|
43
|
+
*/
|
|
44
|
+
export function parseNotifications(raw) {
|
|
45
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const entry of raw) {
|
|
49
|
+
const parsed = staleNotificationSchema.safeParse(entry);
|
|
50
|
+
if (parsed.success)
|
|
51
|
+
out.push(parsed.data);
|
|
52
|
+
}
|
|
53
|
+
return out.length > 0 ? out : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The single place claim events are traced. Every `claim_*` frame passes
|
|
57
|
+
* through here, so a developer debugging a collision gets one consistent record
|
|
58
|
+
* — a console line and a structured capture — without each frame case
|
|
59
|
+
* re-deriving the row and holder shape. The wire payload is loosely typed
|
|
60
|
+
* (`Record<string, unknown>`), so this is the one place that narrows it into a
|
|
61
|
+
* {@link ClaimEvent}.
|
|
62
|
+
*/
|
|
63
|
+
export function recordClaim(phase, payload) {
|
|
64
|
+
const str = (v) => typeof v === 'string' ? v : undefined;
|
|
65
|
+
// Targets arrive flat ({ entityType, entityId }) or nested under `target`.
|
|
66
|
+
const target = payload.target && typeof payload.target === 'object'
|
|
67
|
+
? payload.target
|
|
68
|
+
: payload;
|
|
69
|
+
const kind = wireParticipantKindSchema.safeParse(payload.participantKind);
|
|
70
|
+
const event = {
|
|
71
|
+
phase,
|
|
72
|
+
claimId: str(payload.claimId),
|
|
73
|
+
model: str(target.entityType) ?? str(target.model),
|
|
74
|
+
id: str(target.entityId) ?? str(target.id),
|
|
75
|
+
field: str(target.field),
|
|
76
|
+
actor: str(payload.actor) ?? str(payload.heldBy),
|
|
77
|
+
participantKind: kind.success ? kind.data : undefined,
|
|
78
|
+
position: typeof payload.position === 'number' ? payload.position : undefined,
|
|
79
|
+
reason: str(payload.policyReason) ?? str(payload.reason),
|
|
80
|
+
};
|
|
81
|
+
const message = formatClaim(event);
|
|
82
|
+
// A rejection or lost lease is the collision a developer is actively
|
|
83
|
+
// debugging → warn (shows at the default log level). The routine events
|
|
84
|
+
// (acquired/queued/granted/expired) are debug-only so they never drown the
|
|
85
|
+
// console until you opt in with `new Ablo({ debug: true })`.
|
|
86
|
+
const isCollision = phase === 'rejected' || phase === 'lost';
|
|
87
|
+
const ctx = getContext();
|
|
88
|
+
if (isCollision)
|
|
89
|
+
ctx.logger.warn(message);
|
|
90
|
+
else
|
|
91
|
+
ctx.logger.debug(message);
|
|
92
|
+
ctx.observability.breadcrumb(message, 'sync.coordination', isCollision ? 'warning' : 'info');
|
|
93
|
+
ctx.observability.captureClaim(event);
|
|
94
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Creates a {@link ClaimStream} over a live sync connection. A claim is a
|
|
3
|
+
* short-lived, advisory lease a participant takes on an entity (or a field of
|
|
4
|
+
* one) to signal "I'm working on this"; the stream lets you take claims, see
|
|
5
|
+
* everyone else's, and watch the wait queue when a claim is contended.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The stream is built directly on the sync WebSocket and shares that one
|
|
8
|
+
* connection. It learns about other participants' claims from the same
|
|
9
|
+
* `presence_update` frames the {@link createPresenceStream} presence stream
|
|
10
|
+
* consumes — the server piggybacks each participant's `activeClaims` on every
|
|
11
|
+
* presence frame — and sends its own claims as `claim_begin` and
|
|
9
12
|
* `claim_abandon` frames.
|
|
10
13
|
*
|
|
11
|
-
* Wire
|
|
12
|
-
* • Outbound
|
|
13
|
-
*
|
|
14
|
-
* • Outbound
|
|
15
|
-
*
|
|
16
|
-
* • Inbound
|
|
17
|
-
*
|
|
18
|
-
* • Inbound
|
|
19
|
-
*
|
|
20
|
-
* After the dual-engine collapse (step #36), this is the only
|
|
21
|
-
* ClaimStream factory in the SDK; the older compatibility path
|
|
22
|
-
* deletes.
|
|
14
|
+
* Wire frames:
|
|
15
|
+
* • Outbound `claim_begin` — announce a claim: `{ claimId, entityType,
|
|
16
|
+
* entityId, reason, field?, estimatedMs? }`.
|
|
17
|
+
* • Outbound `claim_abandon` — release it: `{ claimId, entityType?,
|
|
18
|
+
* entityId? }`.
|
|
19
|
+
* • Inbound, via presence — `event.activeClaims`, each stamped with
|
|
20
|
+
* `declaredAt` and `expiresAt`.
|
|
21
|
+
* • Inbound `claim_rejected` — the server refused the claim, with conflict
|
|
22
|
+
* metadata.
|
|
23
23
|
*/
|
|
24
24
|
import type { SyncWebSocket } from './SyncWebSocket.js';
|
|
25
25
|
import type { ClaimOptions, Claim, ClaimStream, PresenceTarget } from '../types/streams.js';
|
|
@@ -29,10 +29,11 @@ export interface ClaimStreamConfig {
|
|
|
29
29
|
}
|
|
30
30
|
export interface AttachableClaimStream extends ClaimStream {
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
* {@link Claim}
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* Mints a lease directly: sends the `claim_begin` frame and returns a held
|
|
33
|
+
* {@link Claim} that carries no row `data` (the resource layer reads the row
|
|
34
|
+
* and stamps it). This is an internal entry point, not part of the public
|
|
35
|
+
* {@link ClaimStream}; application code takes a claim through
|
|
36
|
+
* `ablo.<model>.claim({ id })`, which is built on this.
|
|
36
37
|
*/
|
|
37
38
|
claim(target: PresenceTarget, opts?: ClaimOptions): Claim;
|
|
38
39
|
attach(transport: SyncWebSocket): void;
|