@abloatai/ablo 0.26.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/CHANGELOG.md +14 -0
- package/README.md +101 -85
- package/dist/BaseSyncedStore.d.ts +85 -88
- package/dist/BaseSyncedStore.js +131 -147
- package/dist/Database.d.ts +54 -68
- package/dist/Database.js +97 -113
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +85 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +12 -16
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +46 -61
- package/dist/ModelRegistry.d.ts +21 -19
- package/dist/ModelRegistry.js +23 -27
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +5 -6
- package/dist/SyncClient.d.ts +112 -112
- package/dist/SyncClient.js +165 -172
- 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 +27 -32
- package/dist/agent/Agent.js +18 -19
- 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} +26 -33
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +6 -7
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +69 -74
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +6 -5
- package/dist/auth/credentialSource.js +9 -10
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +31 -37
- 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 +167 -119
- package/dist/client/Ablo.d.ts +73 -73
- package/dist/client/Ablo.js +125 -160
- package/dist/client/ApiClient.d.ts +30 -19
- package/dist/client/ApiClient.js +133 -38
- package/dist/client/auth.d.ts +47 -47
- package/dist/client/auth.js +108 -117
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +5 -6
- package/dist/client/consoleLogger.js +5 -6
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +25 -30
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +152 -122
- package/dist/client/credentialEndpoint.d.ts +40 -42
- package/dist/client/credentialEndpoint.js +35 -36
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +9 -12
- package/dist/client/hostedEndpoints.js +9 -12
- package/dist/client/httpClient.d.ts +57 -53
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +47 -58
- package/dist/client/modelRegistration.d.ts +5 -9
- package/dist/client/modelRegistration.js +67 -87
- package/dist/client/options.d.ts +134 -157
- package/dist/client/options.js +3 -7
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +64 -75
- package/dist/client/resourceTypes.js +4 -10
- package/dist/client/schemaConfig.d.ts +31 -43
- package/dist/client/schemaConfig.js +38 -50
- package/dist/client/sessionMint.d.ts +16 -12
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +8 -9
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +15 -20
- package/dist/client/wsMutationExecutor.js +17 -23
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -4
- package/dist/coordination/index.d.ts +10 -8
- package/dist/coordination/index.js +14 -12
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -10
- package/dist/coordination/trace.js +13 -14
- package/dist/core/DatabaseManager.d.ts +5 -7
- package/dist/core/DatabaseManager.js +15 -19
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +22 -28
- package/dist/core/QueryView.d.ts +8 -8
- package/dist/core/QueryView.js +2 -2
- package/dist/core/StoreManager.d.ts +12 -14
- package/dist/core/StoreManager.js +21 -24
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +17 -12
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +42 -43
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +63 -61
- package/dist/core/storeContract.js +8 -12
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +107 -99
- package/dist/errorCodes.js +131 -132
- package/dist/errors.d.ts +160 -166
- package/dist/errors.js +155 -158
- package/dist/index.d.ts +30 -27
- package/dist/index.js +89 -86
- package/dist/interfaces/index.d.ts +102 -113
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +41 -40
- 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 +145 -156
- package/dist/mutators/defineMutators.d.ts +23 -34
- 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 +104 -100
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +28 -23
- package/dist/query/client.js +45 -43
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +1 -1
- package/dist/react/AbloProvider.js +2 -2
- package/dist/react/context.d.ts +25 -28
- 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 +16 -14
- 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 +10 -11
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +8 -8
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +3 -3
- package/dist/react/useReactive.js +2 -2
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +1 -1
- 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 +20 -24
- package/dist/schema/ddlLock.js +18 -23
- 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 +35 -48
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +21 -23
- package/dist/schema/model.d.ts +118 -143
- package/dist/schema/model.js +22 -33
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +5 -3
- package/dist/schema/queries.d.ts +29 -31
- package/dist/schema/queries.js +23 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +16 -13
- package/dist/schema/residency.js +16 -13
- package/dist/schema/roles.d.ts +36 -43
- package/dist/schema/roles.js +31 -37
- package/dist/schema/schema.d.ts +33 -42
- package/dist/schema/schema.js +31 -32
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +28 -31
- 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} +26 -49
- 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 +30 -25
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +30 -25
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +24 -23
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +9 -8
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +22 -25
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +17 -11
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +28 -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 +46 -58
- package/dist/source/factory.js +22 -27
- package/dist/source/index.d.ts +7 -9
- package/dist/source/index.js +12 -14
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +9 -10
- package/dist/source/next.js +6 -7
- package/dist/source/pushQueue.d.ts +69 -47
- package/dist/source/pushQueue.js +32 -28
- package/dist/source/signing.d.ts +46 -17
- package/dist/source/signing.js +28 -11
- package/dist/source/types.d.ts +121 -104
- package/dist/source/types.js +13 -14
- package/dist/stores/ObjectStore.d.ts +10 -11
- package/dist/stores/ObjectStore.js +11 -12
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +7 -11
- package/dist/stores/SyncActionStore.js +13 -17
- package/dist/surface.d.ts +27 -20
- package/dist/surface.js +27 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +36 -42
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +76 -76
- package/dist/sync/ConnectionManager.d.ts +39 -50
- package/dist/sync/ConnectionManager.js +55 -66
- package/dist/sync/NetworkProbe.d.ts +24 -29
- package/dist/sync/NetworkProbe.js +63 -69
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +42 -41
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +59 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +43 -57
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +43 -51
- package/dist/sync/SyncWebSocket.d.ts +139 -165
- package/dist/sync/SyncWebSocket.js +191 -223
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +11 -11
- package/dist/sync/bootstrapApply.d.ts +34 -24
- package/dist/sync/bootstrapApply.js +27 -19
- package/dist/sync/commitFrames.d.ts +21 -20
- package/dist/sync/commitFrames.js +18 -18
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +105 -23
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +12 -14
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +104 -104
- package/dist/sync/credentialLifecycle.js +140 -147
- package/dist/sync/deltaPipeline.d.ts +36 -34
- package/dist/sync/deltaPipeline.js +64 -65
- package/dist/sync/groupChange.d.ts +63 -61
- package/dist/sync/groupChange.js +74 -78
- package/dist/sync/heartbeat.d.ts +34 -33
- package/dist/sync/heartbeat.js +31 -31
- package/dist/sync/participants.d.ts +19 -19
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +17 -21
- package/dist/sync/syncCursor.js +17 -21
- package/dist/sync/syncPlan.d.ts +28 -36
- package/dist/sync/syncPlan.js +18 -19
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +35 -36
- package/dist/sync/wsFrameHandlers.js +63 -67
- package/dist/testing/fixtures/bootstrap.d.ts +12 -6
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +30 -33
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +11 -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 -15
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +12 -10
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +2 -2
- 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 +20 -17
- package/dist/testing/mocks/MockSyncContext.js +15 -13
- package/dist/testing/mocks/MockSyncStore.d.ts +10 -10
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +26 -22
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +181 -176
- package/dist/transactions/TransactionQueue.js +338 -350
- package/dist/transactions/TransactionStore.d.ts +6 -4
- package/dist/transactions/TransactionStore.js +6 -4
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +41 -17
- package/dist/transactions/coalesceRules.js +40 -17
- package/dist/transactions/commitPayload.d.ts +48 -52
- package/dist/transactions/commitPayload.js +48 -57
- package/dist/transactions/deltaConfirmation.d.ts +20 -22
- package/dist/transactions/deltaConfirmation.js +37 -45
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/{persistedReplay.d.ts → replayValidation.d.ts} +28 -22
- package/dist/transactions/{persistedReplay.js → replayValidation.js} +33 -27
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +71 -77
- package/dist/types/index.js +22 -22
- package/dist/types/modelData.d.ts +6 -8
- package/dist/types/modelData.js +5 -7
- package/dist/types/participant.d.ts +10 -11
- package/dist/types/participant.js +6 -8
- package/dist/types/streams.d.ts +208 -195
- 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} +42 -98
- 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 +30 -31
- package/dist/wire/errorEnvelope.js +34 -40
- package/dist/wire/frames.d.ts +79 -86
- package/dist/wire/frames.js +26 -33
- package/dist/wire/index.d.ts +14 -12
- package/dist/wire/index.js +30 -26
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +25 -32
- package/dist/wire/protocol.js +25 -32
- package/dist/wire/protocolVersion.d.ts +44 -40
- package/dist/wire/protocolVersion.js +44 -40
- package/docs/coordination.md +59 -0
- package/package.json +11 -10
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- 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 -8
- package/dist/server/storage-mode.js +0 -28
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/transactions/optimistic.d.ts +0 -24
- package/dist/transactions/optimistic.js +0 -45
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Builds the typed client for a single schema model — the object reached as
|
|
3
|
+
* `ablo.<model>`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* local-graph snapshots `get` / `getAll` / `getCount`, the writes
|
|
12
|
-
* `create` / `update` / `delete`, the coordination namespace `claim`
|
|
13
|
-
* (`claim({ id })` plus `claim.state` / `claim.queue` / `claim.release` /
|
|
14
|
-
* `claim.reorder`), and `onChange`. The factory returns a plain object; the
|
|
15
|
-
* client assembles the `ablo.<model>` lookup table from these.
|
|
5
|
+
* Each schema model gets one {@link ModelOperations}: the async server reads
|
|
6
|
+
* `retrieve` and `list`, the synchronous local-graph snapshots `get`, `getAll`,
|
|
7
|
+
* and `getCount`, the writes `create`, `update`, and `delete`, the coordination
|
|
8
|
+
* namespace `claim` (callable as `claim({ id })`, plus `claim.state`,
|
|
9
|
+
* `claim.queue`, `claim.release`, and `claim.reorder`), `watch`, and `onChange`.
|
|
10
|
+
* The factory returns a plain object; the client assembles the `ablo.<model>`
|
|
11
|
+
* lookup table from one of these per model.
|
|
16
12
|
*/
|
|
17
13
|
import { autorun } from 'mobx';
|
|
18
14
|
import { AbloClaimedError, AbloStaleContextError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../errors.js';
|
|
@@ -21,6 +17,7 @@ import { reconcileFunctionalUpdate, } from './functionalUpdate.js';
|
|
|
21
17
|
import { Model, modelAsRow } from '../Model.js';
|
|
22
18
|
import { toMs } from '../utils/duration.js';
|
|
23
19
|
import { LEASE_TTL_MS } from '../wire/protocol.js';
|
|
20
|
+
import { heartbeatCadenceMs, resolveHeartbeatOptions, startClaimHeartbeatLoop, } from './claimHeartbeatLoop.js';
|
|
24
21
|
import { assertWriteOptions } from './writeOptionsSchema.js';
|
|
25
22
|
import { ModelScope } from '../types/index.js';
|
|
26
23
|
const modelClientMeta = new WeakMap();
|
|
@@ -35,15 +32,15 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
35
32
|
throw new AbloValidationError(`Ablo: schema model "${schemaKey}" resolved to "${registeredModelName}", ` +
|
|
36
33
|
'but no matching constructor was registered.', { code: 'model_not_registered' });
|
|
37
34
|
}
|
|
38
|
-
// The coordination plane
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// target.model
|
|
46
|
-
// use this.
|
|
35
|
+
// The coordination plane must speak the same wire dialect as the commit
|
|
36
|
+
// plane: the lowercased typename (`task`), not the schema key (`tasks`). The
|
|
37
|
+
// server's commit-time claim guard probes the lease store with the commit
|
|
38
|
+
// operation's model name, so a lease recorded under the schema key never
|
|
39
|
+
// matches — which would silently disarm the guard for every model whose
|
|
40
|
+
// schema key differs from its typename (a plural key against a singular
|
|
41
|
+
// typename, i.e. nearly all of them). Public surfaces such as
|
|
42
|
+
// `Claim.target.model` keep the schema key; only the wire and coordination
|
|
43
|
+
// targets use this.
|
|
47
44
|
const wireModel = registeredModelName.toLowerCase();
|
|
48
45
|
// Last-line guarantee for the public surface: any rejection from a lower
|
|
49
46
|
// layer (transport timeout, IndexedDB failure, a third-party throw) is
|
|
@@ -76,20 +73,21 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
76
73
|
await syncClient.waitForConfirmation(model.getModelName(), model.id);
|
|
77
74
|
};
|
|
78
75
|
// Claims this proxy currently holds, keyed by entity id. Lets the flat
|
|
79
|
-
// `release({ id })` and `update({ id, data })` find the lease
|
|
80
|
-
// took
|
|
76
|
+
// `release({ id })` and `update({ id, data })` find the lease and snapshot a
|
|
77
|
+
// `claim({ id })` took, without a per-call handle. Released on dispose,
|
|
78
|
+
// explicit release, or TTL expiry.
|
|
81
79
|
//
|
|
82
|
-
// `target
|
|
80
|
+
// `target`, `reason`, and `expiresAt` are kept alongside the lease so
|
|
83
81
|
// `claim.state` can synthesize a self-claim: the server excludes a holder's
|
|
84
|
-
// own presence frames, so
|
|
85
|
-
//
|
|
86
|
-
// TTL (a
|
|
87
|
-
// the server's keepalive lease window when no TTL was requested.
|
|
82
|
+
// own presence frames, so this proxy is the only place that knows the client
|
|
83
|
+
// holds the row. `expiresAt` is the client's best estimate from the requested
|
|
84
|
+
// TTL (a real epoch-millisecond expiry, not a fabricated watermark), defaulting
|
|
85
|
+
// to the server's keepalive lease window when no TTL was requested.
|
|
88
86
|
const activeClaims = new Map();
|
|
89
|
-
// Server keepalive lease window
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
87
|
+
// Server keepalive lease window — the same `LEASE_TTL_MS` the wire protocol
|
|
88
|
+
// declares, so the client's estimate and the server's lease cannot drift.
|
|
89
|
+
// This is the fallback expiry estimate when a claim is taken without an
|
|
90
|
+
// explicit TTL.
|
|
93
91
|
const DEFAULT_LEASE_TTL_MS = LEASE_TTL_MS;
|
|
94
92
|
const isClaimHandle = (value) => typeof value === 'object' &&
|
|
95
93
|
value !== null &&
|
|
@@ -124,9 +122,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
124
122
|
};
|
|
125
123
|
const mutationOptions = (params) => {
|
|
126
124
|
const { id: _id, data: _data, claim: _claim, ...rest } = params;
|
|
127
|
-
//
|
|
128
|
-
// Catches plain-
|
|
129
|
-
// a typed error instead of a silent no-op or a server 400.
|
|
125
|
+
// The write-options schema — the runtime twin of the compile-time params.
|
|
126
|
+
// Catches plain-JavaScript callers (for example `onStale: 'rejct'`) at the
|
|
127
|
+
// call site with a typed error instead of a silent no-op or a server 400.
|
|
130
128
|
assertWriteOptions(rest, `${schemaKey} write`);
|
|
131
129
|
return rest;
|
|
132
130
|
};
|
|
@@ -142,12 +140,12 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
142
140
|
throw new AbloValidationError(`Model "${schemaKey}" was built without the collaboration runtime, so claim() is unavailable here. Claiming needs no per-model config — use the standard Ablo({ schema, apiKey }) client and every model is claimable.`, { code: 'model_claim_not_configured' });
|
|
143
141
|
}
|
|
144
142
|
const { id, ...options } = params;
|
|
145
|
-
// Is someone
|
|
146
|
-
// snapshot up front — it decides whether
|
|
147
|
-
// claim (a free
|
|
143
|
+
// Is someone else already on this target? Read the local coordination
|
|
144
|
+
// snapshot up front — it decides whether a re-read is needed after the
|
|
145
|
+
// claim (a free or already-held target cannot have changed underneath us).
|
|
148
146
|
const held = collaboration.state({ model: wireModel, id });
|
|
149
147
|
const contended = !!held && held.heldBy !== collaboration.selfParticipantId;
|
|
150
|
-
const failFast = options
|
|
148
|
+
const failFast = options.queue === false;
|
|
151
149
|
// Fail-fast (`queue: false`): if another participant already holds it,
|
|
152
150
|
// reject now instead of queuing. Best-effort at the client (a racing
|
|
153
151
|
// claim not yet synced into our snapshot slips through here) — the
|
|
@@ -155,13 +153,13 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
155
153
|
// the loser's first write. For work-distribution dedup that's exactly
|
|
156
154
|
// right: don't wait (that would double-process), skip.
|
|
157
155
|
if (failFast && contended) {
|
|
158
|
-
const claim =
|
|
156
|
+
const claim = claimContextFromClaim(held);
|
|
159
157
|
throw new AbloClaimedError(formatClaimedErrorMessage({
|
|
160
158
|
targetLabel: `${registeredModelName}/${id}`,
|
|
161
|
-
heldBy: held
|
|
159
|
+
heldBy: held.heldBy,
|
|
162
160
|
claim,
|
|
163
|
-
fallback: `${registeredModelName}/${id} is held by ${held
|
|
164
|
-
}), { code: 'entity_claimed', claims:
|
|
161
|
+
fallback: `${registeredModelName}/${id} is held by ${held.heldBy ?? 'another participant'}.`,
|
|
162
|
+
}), { code: 'entity_claimed', claims: [claim] });
|
|
165
163
|
}
|
|
166
164
|
// Ensure the row exists locally before claiming.
|
|
167
165
|
let model = objectPool.get(id);
|
|
@@ -172,65 +170,65 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
172
170
|
if (!model) {
|
|
173
171
|
throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
|
|
174
172
|
}
|
|
175
|
-
// Write
|
|
176
|
-
// holder's claim presence broadcasts to
|
|
177
|
-
// including a peer that subscribed just before us. Pinning before the
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
173
|
+
// Write intent: enter the entity scope before acquiring the lease so the
|
|
174
|
+
// holder's claim presence broadcasts to everyone in this entity group,
|
|
175
|
+
// including a peer that subscribed just before us. Pinning before the lease
|
|
176
|
+
// rather than after closes the subscribe-versus-broadcast race: the server
|
|
177
|
+
// fans presence out at claim time, so this client must be in the group when
|
|
178
|
+
// the claim lands. Awaited because the broadcast ordering depends on it;
|
|
179
|
+
// still best-effort (the store swallows reconcile errors).
|
|
182
180
|
await collaboration.pinScope?.({ [schemaKey]: id });
|
|
183
|
-
// Acquire the lease.
|
|
184
|
-
// fair FIFO queue
|
|
185
|
-
// ours, blocking behind any current holder, with no
|
|
186
|
-
// orders contenders
|
|
187
|
-
// observed conflict above, so this just records
|
|
181
|
+
// Acquire the lease. By default (`queue` is not false) this goes through the
|
|
182
|
+
// server's fair FIFO queue: `queue: true` resolves only once the lease is
|
|
183
|
+
// genuinely ours, blocking behind any current holder, with no check-then-act
|
|
184
|
+
// gap because the server orders contenders. Fail-fast skips the queue: an
|
|
185
|
+
// observed conflict was already rejected above, so this just records the lease.
|
|
188
186
|
const lease = await collaboration.createClaim({
|
|
189
187
|
target: {
|
|
190
188
|
model: wireModel,
|
|
191
189
|
id,
|
|
192
|
-
...(options
|
|
193
|
-
...(options
|
|
194
|
-
...(options
|
|
190
|
+
...(options.field ? { field: options.field } : {}),
|
|
191
|
+
...(options.path ? { path: options.path } : {}),
|
|
192
|
+
...(options.range ? { range: options.range } : {}),
|
|
195
193
|
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
196
194
|
},
|
|
197
|
-
reason: options
|
|
198
|
-
ttl: options
|
|
195
|
+
reason: options.reason ?? 'editing',
|
|
196
|
+
ttl: options.ttl,
|
|
199
197
|
queue: !failFast,
|
|
200
|
-
maxQueueDepth: options
|
|
198
|
+
maxQueueDepth: options.maxQueueDepth,
|
|
201
199
|
});
|
|
202
|
-
// Only when
|
|
203
|
-
// changed underneath us — re-read so the claimed snapshot reflects what
|
|
204
|
-
//
|
|
205
|
-
// - `lease.waited` — the server granted
|
|
206
|
-
// provably queued behind a holder. Authoritative; works even when
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// - `contended` — the local snapshot saw a holder up front. Kept for
|
|
210
|
-
//
|
|
200
|
+
// Only when the claim actually waited behind another holder can the row have
|
|
201
|
+
// changed underneath us — re-read so the claimed snapshot reflects what that
|
|
202
|
+
// holder committed before releasing. Either of two signals suffices:
|
|
203
|
+
// - `lease.waited` — the server granted the claim after the client
|
|
204
|
+
// provably queued behind a holder. Authoritative; it works even when the
|
|
205
|
+
// local snapshot is blind, since claim fan-out is entity-scoped and a
|
|
206
|
+
// broadly-subscribed client never observes peers' claims.
|
|
207
|
+
// - `contended` — the local snapshot saw a holder up front. Kept for the
|
|
208
|
+
// no-queue paths, where no grant frame exists.
|
|
211
209
|
if ((contended || lease.waited === true) && !failFast) {
|
|
212
|
-
// `type: 'complete'` forces the round-trip: the hydration ledger
|
|
213
|
-
// otherwise
|
|
214
|
-
// holder's final write may not have fanned out
|
|
215
|
-
// stale-snapshot race this re-read
|
|
210
|
+
// `type: 'complete'` forces the round-trip: the hydration ledger would
|
|
211
|
+
// otherwise serve the local row for an already-hydrated id, and the
|
|
212
|
+
// holder's final write may not have fanned out yet — the exact
|
|
213
|
+
// stale-snapshot race this re-read closes.
|
|
216
214
|
await load({ where: [['id', id]], type: 'complete' });
|
|
217
215
|
model = objectPool.get(id) ?? model;
|
|
218
216
|
}
|
|
219
217
|
const snapshot = collaboration.createSnapshot(schemaKey, id);
|
|
220
|
-
const reason = options
|
|
218
|
+
const reason = options.reason ?? 'editing';
|
|
221
219
|
// The self-claim's `ClaimTarget` mirrors what a peer's `claim.state` would
|
|
222
|
-
// report (`state` maps `held.target.model`
|
|
223
|
-
// peer see the
|
|
220
|
+
// report (`state` maps `held.target.model` to `type`), so a holder and a
|
|
221
|
+
// peer see the same `target.type` for one row — the wire model token.
|
|
224
222
|
const selfTarget = {
|
|
225
223
|
type: wireModel,
|
|
226
224
|
id,
|
|
227
|
-
...(options
|
|
228
|
-
...(options
|
|
229
|
-
...(options
|
|
225
|
+
...(options.field ? { field: options.field } : {}),
|
|
226
|
+
...(options.path ? { path: options.path } : {}),
|
|
227
|
+
...(options.range ? { range: options.range } : {}),
|
|
230
228
|
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
231
229
|
};
|
|
232
|
-
const
|
|
233
|
-
|
|
230
|
+
const ttlMs = options.ttl !== undefined ? toMs(options.ttl) : DEFAULT_LEASE_TTL_MS;
|
|
231
|
+
const expiresAt = Date.now() + ttlMs;
|
|
234
232
|
activeClaims.set(id, {
|
|
235
233
|
lease,
|
|
236
234
|
snapshot,
|
|
@@ -241,24 +239,57 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
241
239
|
const target = {
|
|
242
240
|
type: schemaKey,
|
|
243
241
|
id,
|
|
244
|
-
...(options
|
|
245
|
-
...(options
|
|
246
|
-
...(options
|
|
242
|
+
...(options.field ? { field: options.field } : {}),
|
|
243
|
+
...(options.path ? { path: options.path } : {}),
|
|
244
|
+
...(options.range ? { range: options.range } : {}),
|
|
247
245
|
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
248
246
|
};
|
|
249
|
-
|
|
247
|
+
// A beat resolves with the server's extended expiry; keep the local
|
|
248
|
+
// self-claim estimate in step so `claim.state` renders the real window,
|
|
249
|
+
// and surface every answer through `onHeartbeat` (pressure signal).
|
|
250
|
+
const heartbeat = async (beatOptions) => {
|
|
251
|
+
if (!lease.heartbeat) {
|
|
252
|
+
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' });
|
|
253
|
+
}
|
|
254
|
+
const resolved = resolveHeartbeatOptions(beatOptions);
|
|
255
|
+
const beat = await lease.heartbeat({
|
|
256
|
+
ttl: resolved.ttl ?? options.ttl,
|
|
257
|
+
...(resolved.details !== undefined ? { details: resolved.details } : {}),
|
|
258
|
+
});
|
|
259
|
+
const held = activeClaims.get(id);
|
|
260
|
+
if (held)
|
|
261
|
+
held.expiresAt = beat.expiresAt;
|
|
262
|
+
options.onHeartbeat?.(beat);
|
|
263
|
+
return beat;
|
|
264
|
+
};
|
|
265
|
+
// Opt-in auto-heartbeat: the loop beats until release, and a definitive
|
|
266
|
+
// loss stops it and surfaces through `onHeartbeatLost`.
|
|
267
|
+
const stopHeartbeatLoop = options.heartbeat
|
|
268
|
+
? startClaimHeartbeatLoop({
|
|
269
|
+
beat: () => heartbeat(),
|
|
270
|
+
intervalMs: heartbeatCadenceMs(ttlMs, options.heartbeat),
|
|
271
|
+
...(options.onHeartbeatLost
|
|
272
|
+
? { onLost: options.onHeartbeatLost }
|
|
273
|
+
: {}),
|
|
274
|
+
})
|
|
275
|
+
: undefined;
|
|
276
|
+
const release = () => {
|
|
277
|
+
stopHeartbeatLoop?.();
|
|
278
|
+
return releaseClaim(id);
|
|
279
|
+
};
|
|
250
280
|
return {
|
|
251
281
|
object: 'claim',
|
|
252
282
|
id: lease.id,
|
|
253
283
|
readAt: snapshot.stamp,
|
|
254
284
|
target,
|
|
255
285
|
reason,
|
|
256
|
-
...(options
|
|
286
|
+
...(options.description ? { description: options.description } : {}),
|
|
257
287
|
data: modelAsRow(model),
|
|
258
288
|
release,
|
|
259
289
|
revoke: () => {
|
|
260
290
|
void release();
|
|
261
291
|
},
|
|
292
|
+
heartbeat,
|
|
262
293
|
[Symbol.asyncDispose]: release,
|
|
263
294
|
};
|
|
264
295
|
};
|
|
@@ -269,15 +300,15 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
269
300
|
// are the same object.
|
|
270
301
|
const claimApi = Object.assign(guard(claim), {
|
|
271
302
|
state(params) {
|
|
272
|
-
// Read
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
303
|
+
// Read interest: a passive observer of a row's claim state must enter that
|
|
304
|
+
// row's entity scope, or it sits only on broader `org:`/`user:` groups and
|
|
305
|
+
// never receives the holder's entity-scoped claim presence. Best-effort
|
|
306
|
+
// and fire-and-forget — it never blocks or rejects the read.
|
|
276
307
|
void collaboration?.enterScope?.({ [schemaKey]: params.id });
|
|
277
|
-
// Self-awareness: the server excludes a holder's
|
|
278
|
-
// the client skips them, so `state`
|
|
279
|
-
// Synthesize the active claim
|
|
280
|
-
//
|
|
308
|
+
// Self-awareness: the server excludes a holder's own presence frames and
|
|
309
|
+
// the client skips them, so `state` would return null for a row this client
|
|
310
|
+
// holds. Synthesize the active claim from the stored lease so the holder
|
|
311
|
+
// sees its own claim, honoring the documented contract on `claim.state`.
|
|
281
312
|
const own = activeClaims.get(params.id);
|
|
282
313
|
if (own) {
|
|
283
314
|
return {
|
|
@@ -306,10 +337,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
306
337
|
});
|
|
307
338
|
const operations = {
|
|
308
339
|
retrieve: guard(async (params) => {
|
|
309
|
-
// Read-interest enrolment:
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
// fire-and-forget — never
|
|
340
|
+
// Read-interest enrolment: reading a row enters its entity scope, so a
|
|
341
|
+
// client lands in the same group the holder's claim presence fans out
|
|
342
|
+
// on and `claim.state`/`claim.queue` report peers. Best-effort and
|
|
343
|
+
// fire-and-forget — it never makes the read reject or run slower.
|
|
313
344
|
void collaboration?.enterScope?.({ [schemaKey]: params.id });
|
|
314
345
|
const rows = await load({
|
|
315
346
|
...params,
|
|
@@ -318,9 +349,8 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
318
349
|
});
|
|
319
350
|
return rows[0];
|
|
320
351
|
}),
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
// enrolment is a deliberate follow-up (a bounded, opt-in policy).
|
|
352
|
+
// No automatic scope enrolment on bulk `list`/`getAll`: that would subscribe
|
|
353
|
+
// to an unbounded set of rows' entity groups.
|
|
324
354
|
list: guard(load),
|
|
325
355
|
get(id) {
|
|
326
356
|
return objectPool.get(id);
|
|
@@ -371,11 +401,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
371
401
|
if (!collaboration) {
|
|
372
402
|
throw new AbloValidationError(`Model "${schemaKey}" was built without the collaboration runtime, so claim() is unavailable here. Claiming needs no per-model config — use the standard Ablo({ schema, apiKey }) client and every model is claimable.`, { code: 'model_claim_not_configured' });
|
|
373
403
|
}
|
|
374
|
-
// Write
|
|
375
|
-
// create-claim so the holder's claim presence broadcasts to
|
|
376
|
-
// already in this entity group (closing the subscribe-
|
|
404
|
+
// Write intent: enter the new row's entity scope before acquiring the
|
|
405
|
+
// create-claim so the holder's claim presence broadcasts to everyone
|
|
406
|
+
// already in this entity group (closing the subscribe-versus-broadcast
|
|
377
407
|
// race — see `takeClaim`). Released with the lease in the `finally`
|
|
378
|
-
// below. Awaited for broadcast ordering; still
|
|
408
|
+
// below. Awaited for broadcast ordering; still best-effort.
|
|
379
409
|
await collaboration.pinScope?.({ [schemaKey]: id });
|
|
380
410
|
autoLease = await collaboration.createClaim({
|
|
381
411
|
target: {
|
|
@@ -392,10 +422,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
392
422
|
maxQueueDepth: claim.maxQueueDepth,
|
|
393
423
|
});
|
|
394
424
|
}
|
|
395
|
-
// Default `organizationId` from the client's identity
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
425
|
+
// Default `organizationId` from the client's identity, matching the other
|
|
426
|
+
// write path — without this, a caller that omits it would create an
|
|
427
|
+
// org-unscoped row on one write path but not the other. An explicit value
|
|
428
|
+
// in `data` still wins via the spread.
|
|
399
429
|
const orgDefault = params.data.organizationId ??
|
|
400
430
|
syncClient.getOrganizationId();
|
|
401
431
|
const model = new ModelClass({
|
|
@@ -481,7 +511,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
481
511
|
return await operations.update({ ...params, claim: handle });
|
|
482
512
|
}
|
|
483
513
|
finally {
|
|
484
|
-
await handle.release
|
|
514
|
+
await handle.release();
|
|
485
515
|
}
|
|
486
516
|
}
|
|
487
517
|
const { id } = params;
|
|
@@ -515,10 +545,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
515
545
|
...opts,
|
|
516
546
|
...(handle ? { claim: { id: handle.id } } : {}),
|
|
517
547
|
};
|
|
518
|
-
// Local user update: `applyChanges` keeps change tracking
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
548
|
+
// Local user update: `applyChanges` keeps change tracking on so the
|
|
549
|
+
// edited fields land in `modifiedProperties` and are actually sent to
|
|
550
|
+
// the server. (`updateFromData` is the hydration path and would discard
|
|
551
|
+
// the tracking, producing an empty `input: {}` no-op mutation.)
|
|
522
552
|
model.applyChanges(params.data);
|
|
523
553
|
syncClient.update(model, effective);
|
|
524
554
|
await waitForMutation(model, effective);
|
|
@@ -537,17 +567,17 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
537
567
|
await operations.delete({ ...params, claim: handle });
|
|
538
568
|
}
|
|
539
569
|
finally {
|
|
540
|
-
await handle.release
|
|
570
|
+
await handle.release();
|
|
541
571
|
}
|
|
542
572
|
return;
|
|
543
573
|
}
|
|
544
574
|
const { id } = params;
|
|
545
575
|
const model = objectPool.get(id);
|
|
546
|
-
// Idempotent delete
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
//
|
|
576
|
+
// Idempotent delete: "ensure absent". A row that isn't in this client's
|
|
577
|
+
// replicated view is already gone from its perspective, so a delete is a
|
|
578
|
+
// no-op success rather than an `entity_not_found` error. This matches the
|
|
579
|
+
// HTTP client and makes delete safe to retry or race (two actors deleting
|
|
580
|
+
// the same row).
|
|
551
581
|
if (!model)
|
|
552
582
|
return;
|
|
553
583
|
const claimed = activeClaims.get(id);
|
|
@@ -1,45 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Support for the endpoint-string form of `apiKey`.
|
|
3
3
|
*
|
|
4
|
-
* `Ablo({ schema, apiKey: '/api/ablo-session' })
|
|
5
|
-
* session-mint route and the
|
|
6
|
-
* parses the minted token, keeps it fresh
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* function form of `apiKey` remains the escape hatch for custom headers,
|
|
4
|
+
* With `Ablo({ schema, apiKey: '/api/ablo-session' })`, you point the client at
|
|
5
|
+
* your own session-mint route and the client owns the exchange: it POSTs to the
|
|
6
|
+
* endpoint, parses the minted token, keeps it fresh, and classifies failures
|
|
7
|
+
* onto the resolver's three outcomes. The string form covers the common case;
|
|
8
|
+
* the function form of `apiKey` remains the escape hatch for custom headers,
|
|
10
9
|
* bodies, or non-HTTP mints.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
* `https://` is an endpoint
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* always a literal key
|
|
11
|
+
* The form is detected by prefix: a string starting with `/`, `http://`, or
|
|
12
|
+
* `https://` is an endpoint, and anything else is a literal key. Ablo keys are
|
|
13
|
+
* prefixed (`sk_`/`pk_`/`ek_`/`rk_`), so the two shapes cannot collide. An
|
|
14
|
+
* `ABLO_API_KEY` environment value is never treated as an endpoint — it is
|
|
15
|
+
* always a literal key (see `resolveApiKey`).
|
|
17
16
|
*
|
|
18
|
-
* Wire contract
|
|
17
|
+
* Wire contract:
|
|
19
18
|
* POST <endpoint> (same-origin, `credentials: 'include'` so cookies flow)
|
|
20
|
-
* → 200 `{ token, expiresAt? }`
|
|
21
|
-
* → 200 `{ token: null }` or 401/403
|
|
22
|
-
* → anything else
|
|
19
|
+
* → 200 `{ token, expiresAt? }` a fresh short-lived `ek_`/`rk_`
|
|
20
|
+
* → 200 `{ token: null }` or 401/403 the login itself is gone (sign out)
|
|
21
|
+
* → anything else transient — retry, do not sign out
|
|
23
22
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
* user out on a 500
|
|
27
|
-
*
|
|
28
|
-
* retry contract, translated to HTTP statuses).
|
|
23
|
+
* The three-way mapping is the reason to build this in. Hand-written token
|
|
24
|
+
* fetchers routinely get it wrong — mapping any non-OK response to `null` signs
|
|
25
|
+
* the user out on a 500. Encoded here once, every consumer inherits the correct
|
|
26
|
+
* split between a terminal sign-out and a transient retry.
|
|
29
27
|
*/
|
|
30
28
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* in this leaf so the credential resolver it types has no import cycle.
|
|
29
|
+
* An async callable that resolves the current credential. It serves two uses:
|
|
30
|
+
* credential rotation (for example against AWS STS, GCP IAM, or Vault) and the
|
|
31
|
+
* short-lived per-user browser path (minting a fresh `ek_`/`rk_` from the
|
|
32
|
+
* signed-in session). It is re-exported from `./auth` so existing import paths
|
|
33
|
+
* keep working, and defined here so the resolver it types has no import cycle.
|
|
37
34
|
*
|
|
38
|
-
*
|
|
39
|
-
* (terminal
|
|
40
|
-
* signs out); or
|
|
41
|
-
*
|
|
42
|
-
*
|
|
35
|
+
* The contract has three outcomes: resolve a token; resolve `null` when the
|
|
36
|
+
* login itself is gone (terminal — the credential lifecycle treats this as
|
|
37
|
+
* `session_expired` and signs out); or throw on a transient failure (back off
|
|
38
|
+
* and retry, without signing out). A long-lived static `apiKey` string needs
|
|
39
|
+
* none of this and is used as-is.
|
|
43
40
|
*/
|
|
44
41
|
export type ApiKeySetter = () => Promise<string | null>;
|
|
45
42
|
/**
|
|
@@ -48,16 +45,17 @@ export type ApiKeySetter = () => Promise<string | null>;
|
|
|
48
45
|
*/
|
|
49
46
|
export declare function isCredentialEndpoint(value: string): boolean;
|
|
50
47
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* - resolves the minted token string on success
|
|
54
|
-
* - resolves `null` when the login is gone (401
|
|
55
|
-
* `{ token: null }`)
|
|
56
|
-
* -
|
|
57
|
-
* response)
|
|
48
|
+
* Builds the resolver behind an endpoint-string `apiKey`. It follows the
|
|
49
|
+
* {@link ApiKeySetter} contract end to end:
|
|
50
|
+
* - resolves the minted token string on success;
|
|
51
|
+
* - resolves `null` when the login is gone (a 401 or 403, or an explicit
|
|
52
|
+
* `{ token: null }`) — terminal, so the client signs out;
|
|
53
|
+
* - throws on anything transient (a network failure, a 5xx or 429, or a
|
|
54
|
+
* malformed response) — so the lifecycle backs off and retries without
|
|
55
|
+
* signing out.
|
|
58
56
|
*
|
|
59
|
-
* A relative endpoint invoked server
|
|
60
|
-
* transient by contract
|
|
61
|
-
*
|
|
57
|
+
* A relative endpoint invoked on a server (where `fetch` has no origin) throws,
|
|
58
|
+
* which is transient by contract; the credential lifecycle translates that exact
|
|
59
|
+
* failure into an actionable "use an absolute URL server-side" warning.
|
|
62
60
|
*/
|
|
63
61
|
export declare function createEndpointCredentialResolver(endpoint: string): ApiKeySetter;
|