@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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared resource types of the client: {@link ModelRead}, {@link ModelClient},
|
|
3
|
+
* the commit and claim shapes, the session-mint params and resource, and the
|
|
4
|
+
* {@link HttpClaimApi} derivation. This module holds only types and has no runtime
|
|
5
|
+
* imports.
|
|
6
|
+
*/
|
|
7
|
+
import type { StaleNotification, ReadDependency } from '../coordination/schema.js';
|
|
8
|
+
import type { ModelTarget, ModelClaim } from '../coordination/schema.js';
|
|
9
|
+
export type { ModelTarget, ModelClaim };
|
|
10
|
+
import type { SchemaRecord } from '../schema/schema.js';
|
|
11
|
+
import type { SyncGroupInput } from '../schema/roles.js';
|
|
12
|
+
import type { Claim, ClaimStream, ClaimWaitOptions, Duration, HeldClaim } from '../types/streams.js';
|
|
13
|
+
import type { ModelUpdater, ContentionOptions } from './functionalUpdate.js';
|
|
14
|
+
import type { ClaimOptions, ClaimParams, ClaimReadApi, AwaitedClaimMethod, ServerReadOptions } from './createModelProxy.js';
|
|
15
|
+
/**
|
|
16
|
+
* The operations available on each model in the sync engine:
|
|
17
|
+
* `retrieve({ id })` — an async single-row server read
|
|
18
|
+
* `list({ where })` — an async collection server read
|
|
19
|
+
* `get(id)` / `getAll(...)` / `getCount(...)` — synchronous local-cache reads
|
|
20
|
+
* `create({ data })` / `update({ id, data })` / `delete({ id })` — writes
|
|
21
|
+
* `claim({ id })` — a durable claim handle for coordinated writes
|
|
22
|
+
*/
|
|
23
|
+
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, ModelOperations, } from './createModelProxy.js';
|
|
24
|
+
export type ModelOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
|
|
25
|
+
export type CommitWait = 'queued' | 'confirmed';
|
|
26
|
+
export interface ModelRead<T = Record<string, unknown>> {
|
|
27
|
+
/**
|
|
28
|
+
* The row, or `undefined` when no row matched the id (or it's outside the
|
|
29
|
+
* caller's scope). A miss is data-absence, not an error — `retrieve` never
|
|
30
|
+
* throws "not found", mirroring the WebSocket client's `T | undefined`.
|
|
31
|
+
* Branch on it: `const deal = (await ablo.deals.retrieve({ id })).data; if (!deal) …`.
|
|
32
|
+
*/
|
|
33
|
+
readonly data: T | undefined;
|
|
34
|
+
readonly stamp: number;
|
|
35
|
+
readonly claims: readonly ModelClaim[];
|
|
36
|
+
}
|
|
37
|
+
export type IfClaimedPolicy = 'return' | 'fail';
|
|
38
|
+
export interface ClaimedOptions {
|
|
39
|
+
/**
|
|
40
|
+
* What to do when another participant has claimed the target: `return`
|
|
41
|
+
* includes active claim metadata in the response; `fail` throws
|
|
42
|
+
* `AbloClaimedError`. Waiting for a claim to clear is a claim-side concern —
|
|
43
|
+
* take `ablo.<model>.claim({ id })` (it queues fairly); reads never block.
|
|
44
|
+
*/
|
|
45
|
+
readonly ifClaimed?: IfClaimedPolicy;
|
|
46
|
+
}
|
|
47
|
+
export type { ClaimWaitOptions } from '../types/streams.js';
|
|
48
|
+
export interface ModelReadOptions extends ClaimedOptions {
|
|
49
|
+
}
|
|
50
|
+
export interface ClaimCreateOptions {
|
|
51
|
+
readonly target: ModelTarget;
|
|
52
|
+
/** Human-readable phase shown to peers — `'editing'`, `'writing'`. The same
|
|
53
|
+
* word on every claim surface. */
|
|
54
|
+
readonly reason: string;
|
|
55
|
+
readonly ttl?: Duration;
|
|
56
|
+
/**
|
|
57
|
+
* Join the server's fair FIFO queue when the target is already claimed,
|
|
58
|
+
* rather than failing immediately. `create` then resolves only once the
|
|
59
|
+
* lease is actually ours (the server pushes `claim_acquired` if the target
|
|
60
|
+
* was free, or `claim_granted` when we reach the head of the line). Without
|
|
61
|
+
* this, a contended claim throws. Used by `ablo.<model>.claim` so writers
|
|
62
|
+
* serialize instead of racing.
|
|
63
|
+
*/
|
|
64
|
+
readonly queue?: boolean;
|
|
65
|
+
/** Cap on how long to wait for a queued grant before rejecting. */
|
|
66
|
+
readonly waitTimeoutMs?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Backpressure: reject with `AbloClaimedError('queue_too_deep')` instead of
|
|
69
|
+
* waiting if the queue is already `>= maxQueueDepth` when we join.
|
|
70
|
+
*/
|
|
71
|
+
readonly maxQueueDepth?: number;
|
|
72
|
+
}
|
|
73
|
+
export interface CommitOperationInput {
|
|
74
|
+
readonly action: ModelOperationAction;
|
|
75
|
+
/** The model name — matches `ablo.<model>` and the schema's `model()`. */
|
|
76
|
+
readonly model?: string;
|
|
77
|
+
readonly target?: ModelTarget;
|
|
78
|
+
readonly id?: string | null;
|
|
79
|
+
readonly data?: Record<string, unknown> | null;
|
|
80
|
+
readonly transactionId?: string | null;
|
|
81
|
+
readonly readAt?: number | null;
|
|
82
|
+
readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
|
|
83
|
+
}
|
|
84
|
+
export interface CommitCreateOptions {
|
|
85
|
+
readonly claimRef?: string | {
|
|
86
|
+
readonly id: string;
|
|
87
|
+
} | null;
|
|
88
|
+
readonly idempotencyKey?: string | null;
|
|
89
|
+
readonly readAt?: number | null;
|
|
90
|
+
readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
|
|
91
|
+
/**
|
|
92
|
+
* A claim handle from `ablo.<model>.claim({ id })` (or the HTTP claim
|
|
93
|
+
* surface). Same vocabulary as the per-model writes: the handle's
|
|
94
|
+
* snapshot watermark becomes the batch `readAt` default and `onStale`
|
|
95
|
+
* defaults to `'reject'`, so a commit that follows a claim is guarded
|
|
96
|
+
* against concurrent edits without re-stating the watermark by hand.
|
|
97
|
+
* Explicit `readAt`/`onStale` on the options win.
|
|
98
|
+
*/
|
|
99
|
+
readonly claim?: Claim | null;
|
|
100
|
+
readonly operation?: CommitOperationInput;
|
|
101
|
+
readonly operations?: readonly CommitOperationInput[];
|
|
102
|
+
readonly wait?: CommitWait;
|
|
103
|
+
/**
|
|
104
|
+
* Batch-level read dependencies — the "did anything I looked at change?" guard.
|
|
105
|
+
* Declare the rows (`{ model, id, readAt, fields? }`) or sync groups
|
|
106
|
+
* (`{ group, readAt }`, for example `deck:abc`) this batch was premised on; the
|
|
107
|
+
* server checks that none moved since `readAt` and fires the entry's `onStale`
|
|
108
|
+
* over the batch. This is distinct from the write-target `readAt`: it guards what
|
|
109
|
+
* you read, not what you write.
|
|
110
|
+
*/
|
|
111
|
+
readonly reads?: readonly ReadDependency[] | null;
|
|
112
|
+
}
|
|
113
|
+
export interface CommitReceipt {
|
|
114
|
+
readonly id: string;
|
|
115
|
+
readonly status: CommitWait;
|
|
116
|
+
readonly lastSyncId?: number;
|
|
117
|
+
/**
|
|
118
|
+
* Stale-context notifications: present only when this commit guarded a write with
|
|
119
|
+
* `onStale: 'notify'` and the premise moved concurrently. Each carries the
|
|
120
|
+
* conflicting field's current value, handed back as data rather than raising an
|
|
121
|
+
* `AbloStaleContextError`, so the caller — an agent or a human — decides how to
|
|
122
|
+
* resolve it.
|
|
123
|
+
*/
|
|
124
|
+
readonly notifications?: readonly StaleNotification[];
|
|
125
|
+
/**
|
|
126
|
+
* Ids of update or delete targets in this commit that matched no rows, because
|
|
127
|
+
* the row does not exist or is outside the caller's organization. Present and
|
|
128
|
+
* non-empty only when a write missed. The typed resource wrappers turn this into
|
|
129
|
+
* an `AbloNotFoundError`; a raw `commits.create` caller can inspect it directly.
|
|
130
|
+
*/
|
|
131
|
+
readonly missingIds?: readonly string[];
|
|
132
|
+
}
|
|
133
|
+
export interface CommitResource {
|
|
134
|
+
create(options: CommitCreateOptions): Promise<CommitReceipt>;
|
|
135
|
+
}
|
|
136
|
+
export interface ClaimResource extends ClaimStream {
|
|
137
|
+
create(options: ClaimCreateOptions): Promise<Claim>;
|
|
138
|
+
list(target?: Partial<ModelTarget>): readonly ModelClaim[];
|
|
139
|
+
waitFor(target: Partial<ModelTarget>, options?: ClaimWaitOptions): Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
export interface ModelMutationOptions extends ClaimedOptions {
|
|
142
|
+
readonly claimRef?: string | {
|
|
143
|
+
readonly id: string;
|
|
144
|
+
} | null;
|
|
145
|
+
readonly idempotencyKey?: string | null;
|
|
146
|
+
readonly readAt?: number | null;
|
|
147
|
+
readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
|
|
148
|
+
readonly wait?: CommitWait;
|
|
149
|
+
readonly claim?: Claim | ClaimOptions | null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The stateless HTTP claim surface. Most code puts a `claim` directly on the write
|
|
153
|
+
* (`update({ id, data, claim })`) and lets the SDK release it; reach for this
|
|
154
|
+
* namespace for multi-step handles and coordination screens.
|
|
155
|
+
*
|
|
156
|
+
* It is the same surface as the reactive claim API, but because every read is a
|
|
157
|
+
* server round-trip, `state`, `queue`, and `reorder` are awaited here. The
|
|
158
|
+
* WebSocket client resolves those synchronously from its local cache, which is what
|
|
159
|
+
* lets it read a claim's state inside a React render; a stateless client has no
|
|
160
|
+
* cache to read, so the promise is unavoidable.
|
|
161
|
+
*
|
|
162
|
+
* It is derived from `ClaimReadApi` through {@link AwaitedClaimMethod} so the two
|
|
163
|
+
* transports cannot drift: the only difference is the promise wrapper that
|
|
164
|
+
* statelessness forces. `claim({ id })` is identical on both (already async);
|
|
165
|
+
* `state`, `queue`, `reorder`, and `release` are the awaited form.
|
|
166
|
+
*/
|
|
167
|
+
export type HttpClaimApi<T = Record<string, unknown>> = ((params: ClaimParams<T>) => Promise<HeldClaim<T>>) & {
|
|
168
|
+
[K in keyof ClaimReadApi<T>]: AwaitedClaimMethod<ClaimReadApi<T>[K]>;
|
|
169
|
+
};
|
|
170
|
+
export interface ModelClient<T = Record<string, unknown>> {
|
|
171
|
+
/**
|
|
172
|
+
* Single-row read over HTTP. **Returns an envelope, not the bare row** — the
|
|
173
|
+
* row is on `.data`, alongside the `.stamp` watermark (for stale-context
|
|
174
|
+
* guards on the following write) and any active `.claims`. A stateless HTTP
|
|
175
|
+
* client can't synthesize the watermark from a local snapshot, so the
|
|
176
|
+
* envelope is load-bearing here (the WebSocket client's `retrieve` returns
|
|
177
|
+
* `T | undefined` because it reads from its local cache).
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* const deal = await ablo.deals.retrieve({ id });
|
|
181
|
+
* deal.data?.recommendation; // ← the row is on .data
|
|
182
|
+
* deal.stamp; // watermark — pass to the next write's readAt
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
retrieve(params: ModelReadOptions & {
|
|
186
|
+
readonly id: string;
|
|
187
|
+
}): Promise<ModelRead<T>>;
|
|
188
|
+
/**
|
|
189
|
+
* Collection read over HTTP (server round-trip). Equality `where`, `orderBy`,
|
|
190
|
+
* `limit`. Present on the stateless protocol client; the store-backed
|
|
191
|
+
* `.model(name)` accessor omits it (use the typed `ablo.<model>.list` there).
|
|
192
|
+
*/
|
|
193
|
+
list?(options?: ServerReadOptions<T>): Promise<T[]>;
|
|
194
|
+
/**
|
|
195
|
+
* Creates a row and returns the confirmed server row, including framework
|
|
196
|
+
* defaults such as `createdAt` and `createdBy`. Matches the stateful client's
|
|
197
|
+
* `create`. Passing an id that already exists is idempotent: the existing row is
|
|
198
|
+
* returned, not the input.
|
|
199
|
+
*/
|
|
200
|
+
create(params: ModelMutationOptions & {
|
|
201
|
+
readonly data: Record<string, unknown>;
|
|
202
|
+
readonly id?: string | null;
|
|
203
|
+
}): Promise<T>;
|
|
204
|
+
update(params: ModelMutationOptions & {
|
|
205
|
+
readonly id: string;
|
|
206
|
+
readonly data: Record<string, unknown>;
|
|
207
|
+
}): Promise<CommitReceipt>;
|
|
208
|
+
/**
|
|
209
|
+
* Update under contention with a function of the latest state —
|
|
210
|
+
* `update(id, current => next)`, the `setState(prev => next)` of the data
|
|
211
|
+
* layer. The SDK reads the freshest row, runs your updater, writes it as a
|
|
212
|
+
* compare-and-swap against the row's watermark, and re-reads + re-runs on any
|
|
213
|
+
* concurrent write. No claim, no identity, no conflict codes surface: the
|
|
214
|
+
* write either lands or throws {@link AbloContentionError} once its reconcile
|
|
215
|
+
* budget is spent. Return `null`/`undefined` from the updater to skip the
|
|
216
|
+
* write (resolves to `undefined`).
|
|
217
|
+
*/
|
|
218
|
+
update(id: string, updater: ModelUpdater<T>, options?: ContentionOptions): Promise<CommitReceipt | undefined>;
|
|
219
|
+
delete(params: ModelMutationOptions & {
|
|
220
|
+
readonly id: string;
|
|
221
|
+
}): Promise<CommitReceipt>;
|
|
222
|
+
/**
|
|
223
|
+
* Durable lease + FIFO wait-line over HTTP — coordination without a socket.
|
|
224
|
+
* Present on the stateless protocol client (`Ablo({ schema: null })` /
|
|
225
|
+
* `createAbloHttpClient`); the store-backed `.model(name)` accessor omits it
|
|
226
|
+
* (the typed `ablo.<model>.claim` proxy is the full reactive namespace there).
|
|
227
|
+
*/
|
|
228
|
+
claim?: HttpClaimApi<T>;
|
|
229
|
+
}
|
|
230
|
+
/** A single data operation a scoped **agent** session may perform on a model. */
|
|
231
|
+
export type SessionOperation = 'read' | 'create' | 'update' | 'delete';
|
|
232
|
+
/** Parameters for minting an end-user session — full data authority within the
|
|
233
|
+
* organization. Mints an `ek_` token. `user.id` is your end user's id from your
|
|
234
|
+
* own identity provider and becomes the session's `participantId`; Ablo does not
|
|
235
|
+
* model your users, so it is treated as an opaque string at the trust boundary. */
|
|
236
|
+
export interface CreateUserSessionParams {
|
|
237
|
+
/** Your end user. `id` becomes the token's `participantId`. */
|
|
238
|
+
user: {
|
|
239
|
+
id: string;
|
|
240
|
+
};
|
|
241
|
+
/** Mint the session into this organization instead of the key's own — for a
|
|
242
|
+
* platform that serves many tenants from one backend. Requires the `sk_` key to
|
|
243
|
+
* carry the `ephemeral:mint-any-org` scope; omit it for the normal
|
|
244
|
+
* single-tenant case. */
|
|
245
|
+
organizationId?: string;
|
|
246
|
+
/** Sync groups this session may subscribe to — typed (`'default'` or
|
|
247
|
+
* `<namespace>:<id>`; build with `syncGroup(kind, id)` from
|
|
248
|
+
* `@abloatai/ablo/schema`). Omit for the server default:
|
|
249
|
+
* `[org:<your org>, user:<user.id>]`. */
|
|
250
|
+
syncGroups?: readonly SyncGroupInput[];
|
|
251
|
+
/** Token lifetime in seconds. Defaults to 900 (15 minutes). */
|
|
252
|
+
ttlSeconds?: number;
|
|
253
|
+
/** Opaque identity blob echoed back to the client as `ablo.user`. */
|
|
254
|
+
userMeta?: Record<string, unknown>;
|
|
255
|
+
agent?: never;
|
|
256
|
+
can?: never;
|
|
257
|
+
}
|
|
258
|
+
/** Mint params for a scoped **agent** session — mints a restricted `rk_` token
|
|
259
|
+
* gated to exactly the operations named in `can`. `can` is typed off your
|
|
260
|
+
* schema (no magic `'task.update'` strings): `{ Task: ['update'], Deck: ['read'] }`
|
|
261
|
+
* — the SDK serializes each entry to the wire allowlist (`task.update`). */
|
|
262
|
+
export interface CreateAgentSessionParams<S extends SchemaRecord> {
|
|
263
|
+
/** Your agent. `id` becomes the token's `participantId`. */
|
|
264
|
+
agent: {
|
|
265
|
+
id: string;
|
|
266
|
+
};
|
|
267
|
+
/** Per-model operation allowlist, typed against the schema's model names. */
|
|
268
|
+
can: Partial<Record<keyof S & string, readonly SessionOperation[]>>;
|
|
269
|
+
/** Sync groups this session may subscribe to — typed (`'default'` or
|
|
270
|
+
* `<namespace>:<id>`; build with `syncGroup(kind, id)` from
|
|
271
|
+
* `@abloatai/ablo/schema`). Omit for the server default: the org
|
|
272
|
+
* anchor (`org:<your org>`) + the agent's own anchor. */
|
|
273
|
+
syncGroups?: readonly SyncGroupInput[];
|
|
274
|
+
/** Token lifetime in seconds. Defaults to 900 (15 minutes). */
|
|
275
|
+
ttlSeconds?: number;
|
|
276
|
+
/** Opaque identity blob echoed back to the client as `ablo.agent`. */
|
|
277
|
+
userMeta?: Record<string, unknown>;
|
|
278
|
+
user?: never;
|
|
279
|
+
}
|
|
280
|
+
/** Params for {@link Ablo.sessions}.create — a discriminated union: pass
|
|
281
|
+
* `{ user }` for a full-authority end-user session (`ek_`) or `{ agent, can }`
|
|
282
|
+
* for a scoped agent session (`rk_`). */
|
|
283
|
+
export type CreateSessionParams<S extends SchemaRecord> = CreateUserSessionParams | CreateAgentSessionParams<S>;
|
|
284
|
+
/** Params for {@link Ablo.agents}.create — a flattened agent descriptor (no
|
|
285
|
+
* `{ agent }` discriminator: `agents.create` only ever mints an agent). Unlike
|
|
286
|
+
* {@link CreateSessionParams} it resolves to a connected, scoped {@link Ablo}
|
|
287
|
+
* client rather than a raw token. */
|
|
288
|
+
export interface CreateAgentClientParams<S extends SchemaRecord> {
|
|
289
|
+
/** The wire participant identity (`agent:<id>`) that claim exclusion and the
|
|
290
|
+
* FIFO queue gate on. Omit it to get a fresh random id — a distinct, independent
|
|
291
|
+
* participant, which is the default and what you want for concurrent agents.
|
|
292
|
+
* Pass a stable string only when one logical agent must re-attach to its own
|
|
293
|
+
* held claims across reconnects or restarts. */
|
|
294
|
+
id?: string;
|
|
295
|
+
/** A human-readable label for logs and attribution (carried in `userMeta.name`).
|
|
296
|
+
* It is independent of `id`: two agents that share a `name` still receive
|
|
297
|
+
* distinct ids and coordinate as separate participants — `name` never derives or
|
|
298
|
+
* collapses identity. */
|
|
299
|
+
name?: string;
|
|
300
|
+
/** Per-model operation allowlist, typed against the schema's model names. */
|
|
301
|
+
can: Partial<Record<keyof S & string, readonly SessionOperation[]>>;
|
|
302
|
+
/** Sync groups this agent may subscribe to — typed (`'default'` or
|
|
303
|
+
* `<namespace>:<id>`). Omit for the server default (org anchor + the
|
|
304
|
+
* agent's own anchor). */
|
|
305
|
+
syncGroups?: readonly SyncGroupInput[];
|
|
306
|
+
/** Token lifetime in seconds. Defaults to 900 (15 minutes); the returned client
|
|
307
|
+
* re-mints before expiry, so a long-running agent never handles rotation
|
|
308
|
+
* itself. */
|
|
309
|
+
ttlSeconds?: number;
|
|
310
|
+
/** Extra opaque identity blob echoed on the session scope. Merged with
|
|
311
|
+
* `name` (the `name` param wins if you also set `userMeta.name`). */
|
|
312
|
+
userMeta?: Record<string, unknown>;
|
|
313
|
+
}
|
|
314
|
+
/** A minted session. `token` is the secret the holder presents as its bearer. */
|
|
315
|
+
export interface AbloSession {
|
|
316
|
+
object: 'session';
|
|
317
|
+
/** Stable id of the minted credential (for revocation). */
|
|
318
|
+
id: string;
|
|
319
|
+
/** The short-lived session token — `ek_` for a `{ user }` session, `rk_`
|
|
320
|
+
* for an `{ agent }` session. Hand this to the participant's runtime. */
|
|
321
|
+
token: string;
|
|
322
|
+
/** ISO-8601 expiry. */
|
|
323
|
+
expiresAt: string;
|
|
324
|
+
organizationId: string;
|
|
325
|
+
scope: {
|
|
326
|
+
organizationId: string;
|
|
327
|
+
syncGroups: readonly string[];
|
|
328
|
+
operations: readonly string[];
|
|
329
|
+
participantKind: 'user' | 'agent' | 'system';
|
|
330
|
+
participantId: string;
|
|
331
|
+
};
|
|
332
|
+
userMeta: Record<string, unknown>;
|
|
333
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared resource types of the client: {@link ModelRead}, {@link ModelClient},
|
|
3
|
+
* the commit and claim shapes, the session-mint params and resource, and the
|
|
4
|
+
* {@link HttpClaimApi} derivation. This module holds only types and has no runtime
|
|
5
|
+
* imports.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives engine configuration from a schema. This module holds two pure
|
|
3
|
+
* functions: {@link computeFKDepthPriority} works out a safe row-insertion
|
|
4
|
+
* order from the schema's foreign-key relations, and
|
|
5
|
+
* {@link deriveConfigFromSchema} packages that ordering, together with a few
|
|
6
|
+
* defaults, into the {@link SyncEngineConfig} a client uses at startup. Both
|
|
7
|
+
* are deterministic transforms of the schema and hold no engine state.
|
|
8
|
+
*/
|
|
9
|
+
import type { Schema } from '../schema/schema.js';
|
|
10
|
+
import type { SyncEngineConfig } from '../interfaces/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Computes a create-priority map that gives the engine a safe order for
|
|
13
|
+
* inserting rows, so a child row is never written before the parent its
|
|
14
|
+
* foreign key references.
|
|
15
|
+
*
|
|
16
|
+
* Every `belongsTo` relation is an edge from a child model to its parent. This
|
|
17
|
+
* function runs Tarjan's strongly-connected-components algorithm over that
|
|
18
|
+
* graph, which does two things at once: it groups any models that reference
|
|
19
|
+
* each other in a cycle into a single component, and it produces those
|
|
20
|
+
* components in an order where parents come before children. Each model then
|
|
21
|
+
* gets a numeric priority from that order, where a lower number means "insert
|
|
22
|
+
* earlier". Top-level models with no parent — an organization or a theme, say —
|
|
23
|
+
* come first, and the deepest descendants come last.
|
|
24
|
+
*
|
|
25
|
+
* Models in the same cycle share a priority, so within a cycle the order rows
|
|
26
|
+
* were queued in breaks the tie. To break a cycle deterministically instead,
|
|
27
|
+
* mark one side of it with `belongsTo(target, fk, { defer: true })`. A deferred
|
|
28
|
+
* edge is left out of the graph, which turns the cycle into a chain and gives
|
|
29
|
+
* the deferred child a strictly higher priority than its parent. Pair it with a
|
|
30
|
+
* Postgres `DEFERRABLE INITIALLY DEFERRED` constraint if you also want the
|
|
31
|
+
* database to relax its check. See {@link BelongsToOptions.defer}.
|
|
32
|
+
*
|
|
33
|
+
* The returned map is keyed by each model's wire type name
|
|
34
|
+
* ({@link ModelDef.typename}, falling back to the schema key), because that is
|
|
35
|
+
* the name the engine looks up at commit time. Keying by the schema key would
|
|
36
|
+
* miss that lookup, and every model would fall back to the default priority.
|
|
37
|
+
*
|
|
38
|
+
* The result does not depend on which model the walk starts from, and the
|
|
39
|
+
* algorithm runs in time linear in the number of models plus relations.
|
|
40
|
+
* Reference: Tarjan, R. (1972), "Depth-first search and linear graph
|
|
41
|
+
* algorithms."
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeFKDepthPriority(schema: Schema): ReadonlyMap<string, number>;
|
|
44
|
+
export declare function deriveConfigFromSchema(schema: Schema): SyncEngineConfig;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives engine configuration from a schema. This module holds two pure
|
|
3
|
+
* functions: {@link computeFKDepthPriority} works out a safe row-insertion
|
|
4
|
+
* order from the schema's foreign-key relations, and
|
|
5
|
+
* {@link deriveConfigFromSchema} packages that ordering, together with a few
|
|
6
|
+
* defaults, into the {@link SyncEngineConfig} a client uses at startup. Both
|
|
7
|
+
* are deterministic transforms of the schema and hold no engine state.
|
|
8
|
+
*/
|
|
9
|
+
import { schemaHash } from '../schema/serialize.js';
|
|
10
|
+
// ── Config derivation from schema ─────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Computes a create-priority map that gives the engine a safe order for
|
|
13
|
+
* inserting rows, so a child row is never written before the parent its
|
|
14
|
+
* foreign key references.
|
|
15
|
+
*
|
|
16
|
+
* Every `belongsTo` relation is an edge from a child model to its parent. This
|
|
17
|
+
* function runs Tarjan's strongly-connected-components algorithm over that
|
|
18
|
+
* graph, which does two things at once: it groups any models that reference
|
|
19
|
+
* each other in a cycle into a single component, and it produces those
|
|
20
|
+
* components in an order where parents come before children. Each model then
|
|
21
|
+
* gets a numeric priority from that order, where a lower number means "insert
|
|
22
|
+
* earlier". Top-level models with no parent — an organization or a theme, say —
|
|
23
|
+
* come first, and the deepest descendants come last.
|
|
24
|
+
*
|
|
25
|
+
* Models in the same cycle share a priority, so within a cycle the order rows
|
|
26
|
+
* were queued in breaks the tie. To break a cycle deterministically instead,
|
|
27
|
+
* mark one side of it with `belongsTo(target, fk, { defer: true })`. A deferred
|
|
28
|
+
* edge is left out of the graph, which turns the cycle into a chain and gives
|
|
29
|
+
* the deferred child a strictly higher priority than its parent. Pair it with a
|
|
30
|
+
* Postgres `DEFERRABLE INITIALLY DEFERRED` constraint if you also want the
|
|
31
|
+
* database to relax its check. See {@link BelongsToOptions.defer}.
|
|
32
|
+
*
|
|
33
|
+
* The returned map is keyed by each model's wire type name
|
|
34
|
+
* ({@link ModelDef.typename}, falling back to the schema key), because that is
|
|
35
|
+
* the name the engine looks up at commit time. Keying by the schema key would
|
|
36
|
+
* miss that lookup, and every model would fall back to the default priority.
|
|
37
|
+
*
|
|
38
|
+
* The result does not depend on which model the walk starts from, and the
|
|
39
|
+
* algorithm runs in time linear in the number of models plus relations.
|
|
40
|
+
* Reference: Tarjan, R. (1972), "Depth-first search and linear graph
|
|
41
|
+
* algorithms."
|
|
42
|
+
*/
|
|
43
|
+
export function computeFKDepthPriority(schema) {
|
|
44
|
+
// schemaKey → typename (wire name used at transaction time)
|
|
45
|
+
const keyToTypename = new Map();
|
|
46
|
+
for (const [key, def] of Object.entries(schema.models)) {
|
|
47
|
+
keyToTypename.set(key, def.typename ?? key);
|
|
48
|
+
}
|
|
49
|
+
// Adjacency: schemaKey → parent schema keys pulled from `belongsTo`.
|
|
50
|
+
// Parents not in the schema (e.g. external types) are dropped so the
|
|
51
|
+
// graph stays closed. Edges marked `{ defer: true }` are also
|
|
52
|
+
// dropped — the schema author has declared this side of a cycle to
|
|
53
|
+
// be the "soft" one (insert with null FK, patch later), so the
|
|
54
|
+
// dependency-graph walker treats it as if the edge weren't there.
|
|
55
|
+
// That breaks the cycle deterministically and lets the other side
|
|
56
|
+
// become a strict topological predecessor.
|
|
57
|
+
const parentsOf = new Map();
|
|
58
|
+
for (const [key, def] of Object.entries(schema.models)) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const rel of Object.values(def.relations)) {
|
|
61
|
+
if (rel.type !== 'belongsTo')
|
|
62
|
+
continue;
|
|
63
|
+
if (!keyToTypename.has(rel.target))
|
|
64
|
+
continue;
|
|
65
|
+
if (rel.options?.defer === true)
|
|
66
|
+
continue;
|
|
67
|
+
out.push(rel.target);
|
|
68
|
+
}
|
|
69
|
+
parentsOf.set(key, out);
|
|
70
|
+
}
|
|
71
|
+
// Tarjan SCC bookkeeping
|
|
72
|
+
const dfsIndex = new Map();
|
|
73
|
+
const lowlink = new Map();
|
|
74
|
+
const onStack = new Set();
|
|
75
|
+
const stack = [];
|
|
76
|
+
const sccs = [];
|
|
77
|
+
let counter = 0;
|
|
78
|
+
function strongconnect(v) {
|
|
79
|
+
dfsIndex.set(v, counter);
|
|
80
|
+
lowlink.set(v, counter);
|
|
81
|
+
counter++;
|
|
82
|
+
stack.push(v);
|
|
83
|
+
onStack.add(v);
|
|
84
|
+
for (const w of parentsOf.get(v) ?? []) {
|
|
85
|
+
if (!dfsIndex.has(w)) {
|
|
86
|
+
strongconnect(w);
|
|
87
|
+
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
|
|
88
|
+
}
|
|
89
|
+
else if (onStack.has(w)) {
|
|
90
|
+
// Back-edge into the active DFS path — w is in the same SCC as v.
|
|
91
|
+
lowlink.set(v, Math.min(lowlink.get(v), dfsIndex.get(w)));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// v is the root of an SCC: pop everything down to v inclusive.
|
|
95
|
+
if (lowlink.get(v) === dfsIndex.get(v)) {
|
|
96
|
+
const component = [];
|
|
97
|
+
let w;
|
|
98
|
+
do {
|
|
99
|
+
w = stack.pop();
|
|
100
|
+
onStack.delete(w);
|
|
101
|
+
component.push(w);
|
|
102
|
+
} while (w !== v);
|
|
103
|
+
sccs.push(component);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const key of keyToTypename.keys()) {
|
|
107
|
+
if (!dfsIndex.has(key))
|
|
108
|
+
strongconnect(key);
|
|
109
|
+
}
|
|
110
|
+
// Tarjan emits SCCs in reverse topological order of the condensation.
|
|
111
|
+
// In our edge convention (child→parent), reverse-topo of the
|
|
112
|
+
// condensation means root-SCCs (no outgoing edges = no parents)
|
|
113
|
+
// first, leaf-SCCs (deepest descendants) last. We could just use
|
|
114
|
+
// emit-order as the priority — but that gives independent sibling
|
|
115
|
+
// SCCs different priorities, which is semantically wrong: siblings
|
|
116
|
+
// don't depend on each other and shouldn't be ordered relative to
|
|
117
|
+
// each other.
|
|
118
|
+
//
|
|
119
|
+
// Instead, do one more pass to compute *longest-path depth* on the
|
|
120
|
+
// condensation DAG: depth(SCC) = max(depth(parent SCC)) + 1, or 0
|
|
121
|
+
// for SCCs with no in-schema parents. SCCs at the same depth get
|
|
122
|
+
// the same priority — siblings stay tied, insertion order in the
|
|
123
|
+
// queue breaks the tie. Priority = (depth + 1) * 10.
|
|
124
|
+
//
|
|
125
|
+
// We can compute this in a single pass over the SCCs because
|
|
126
|
+
// Tarjan's emit-order *is* a valid topological order of the
|
|
127
|
+
// condensation: when we process sccs[i], every parent SCC has
|
|
128
|
+
// already been assigned a depth.
|
|
129
|
+
const nodeToSccIdx = new Map();
|
|
130
|
+
sccs.forEach((scc, i) => {
|
|
131
|
+
for (const node of scc)
|
|
132
|
+
nodeToSccIdx.set(node, i);
|
|
133
|
+
});
|
|
134
|
+
const sccDepth = new Map();
|
|
135
|
+
sccs.forEach((scc, i) => {
|
|
136
|
+
let maxParentDepth = -1;
|
|
137
|
+
for (const node of scc) {
|
|
138
|
+
for (const parent of parentsOf.get(node) ?? []) {
|
|
139
|
+
const parentSccIdx = nodeToSccIdx.get(parent);
|
|
140
|
+
if (parentSccIdx === undefined)
|
|
141
|
+
continue;
|
|
142
|
+
if (parentSccIdx === i)
|
|
143
|
+
continue; // intra-SCC edge — not a dep
|
|
144
|
+
const d = sccDepth.get(parentSccIdx);
|
|
145
|
+
if (d !== undefined && d > maxParentDepth)
|
|
146
|
+
maxParentDepth = d;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
sccDepth.set(i, maxParentDepth + 1);
|
|
150
|
+
});
|
|
151
|
+
const out = new Map();
|
|
152
|
+
sccs.forEach((scc, i) => {
|
|
153
|
+
const priority = (sccDepth.get(i) + 1) * 10;
|
|
154
|
+
for (const key of scc) {
|
|
155
|
+
out.set(keyToTypename.get(key), priority);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
export function deriveConfigFromSchema(schema) {
|
|
161
|
+
// Field-level serialization for commits happens in the transaction queue,
|
|
162
|
+
// which reads each model's declared fields from the model registry at commit
|
|
163
|
+
// time. There is no per-field metadata to configure here, so these maps stay
|
|
164
|
+
// empty.
|
|
165
|
+
return {
|
|
166
|
+
modelCreatePriority: computeFKDepthPriority(schema),
|
|
167
|
+
defaultCreatePriority: 40,
|
|
168
|
+
defaultNonCreatePriority: 50,
|
|
169
|
+
essentialFields: {},
|
|
170
|
+
classNameFallbackMap: {},
|
|
171
|
+
// Hash this schema once, so startup can detect when it has drifted from the
|
|
172
|
+
// schema the server currently has active. The server and the `ablo push`
|
|
173
|
+
// command compute this same hash.
|
|
174
|
+
expectedSchemaHash: schemaHash(schema),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import type { SchemaRecord } from '../schema/schema.js';
|
|
2
|
-
import type { AbloSession, CreateSessionParams } from './
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
2
|
+
import type { AbloSession, CreateSessionParams } from './resourceTypes.js';
|
|
3
|
+
/**
|
|
4
|
+
* The resolved control-plane details a mint needs: a secret key, a base URL,
|
|
5
|
+
* and an optional `fetch`. When `fetch` is omitted, the auth helpers fall back
|
|
6
|
+
* to the runtime's global `fetch`.
|
|
7
|
+
*/
|
|
5
8
|
export interface MintSessionContext {
|
|
6
9
|
readonly apiKey: string;
|
|
7
10
|
readonly baseUrl: string;
|
|
8
11
|
readonly fetch?: typeof fetch;
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* checks, but `can` is keyed by schema key — so
|
|
13
|
-
* on a model whose
|
|
14
|
-
* `document.update`, not `documents.update
|
|
15
|
-
* `capability_scope_denied
|
|
16
|
-
* there the `can` key already
|
|
13
|
+
* Maps each schema key to its wire type name. Only the schema-aware client
|
|
14
|
+
* supplies this. A capability is scoped by the lowercased type name the
|
|
15
|
+
* server checks, but `can` is keyed by schema key — so
|
|
16
|
+
* `can: { documents: ['update'] }` on a model whose type name is overridden
|
|
17
|
+
* to `Document` must mint `document.update`, not `documents.update`, or the
|
|
18
|
+
* server denies the write with `capability_scope_denied`. The schemaless
|
|
19
|
+
* client omits this map, because there the `can` key is already the wire
|
|
20
|
+
* token and needs no translation.
|
|
17
21
|
*/
|
|
18
22
|
readonly modelTypenames?: Readonly<Record<string, string>>;
|
|
19
23
|
}
|
|
20
24
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* Mints a session token from an already-resolved secret key and base URL.
|
|
26
|
+
* Routes the `{ user }` or `{ agent }` request to the matching mint endpoint
|
|
27
|
+
* and reshapes the response into an {@link AbloSession}.
|
|
24
28
|
*/
|
|
25
29
|
export declare function mintSession<S extends SchemaRecord>(params: CreateSessionParams<S>, ctx: MintSessionContext): Promise<AbloSession>;
|