@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
package/dist/errorCodes.js
CHANGED
|
@@ -1,68 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
2
|
+
* The registry of every stable error code Ablo can produce. Error handling has
|
|
3
|
+
* two levels, and this file defines the finer one.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* - The `type` is the coarse category, and each one corresponds to an
|
|
6
|
+
* {@link AbloError} subclass such as `AbloPermissionError` or
|
|
7
|
+
* `AbloValidationError`. Catching by `instanceof` is equivalent to switching
|
|
8
|
+
* on `error.type`.
|
|
9
|
+
* - The `code` is the fine-grained, machine-readable identifier defined here,
|
|
10
|
+
* written in `snake_case` (for example `entity_claimed` or `queue_too_deep`).
|
|
11
|
+
* This is what you switch on to handle a specific situation, and what the
|
|
12
|
+
* documentation link for an error is built from.
|
|
6
13
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - **`code`** — the fine-grained, machine-readable identifier in this
|
|
11
|
-
* file. `snake_case`, ordered noun→state (`entity_claimed`) or
|
|
12
|
-
* condition→constraint (`queue_too_deep`). This is what callers
|
|
13
|
-
* `switch` on for specific handling, and what `doc_url` is derived from.
|
|
14
|
+
* The client, the server, and the tool-calling boundary all speak this same
|
|
15
|
+
* vocabulary, which makes the registry part of the API contract. Two things
|
|
16
|
+
* follow from that:
|
|
14
17
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* 'client'` codes are local SDK invariants (you forgot to open the DB,
|
|
28
|
-
* a model isn't registered) — never sent over the network, so they
|
|
29
|
-
* carry no `httpStatus`, exactly as Stripe omits client-side
|
|
30
|
-
* programmer errors from its published code list.
|
|
18
|
+
* 1. {@link ErrorCode} is a closed set — plus the dynamic `policy:${string}`
|
|
19
|
+
* family — so producing a code that is not registered here is a
|
|
20
|
+
* compile-time error. The {@link AbloError} constructor accepts only a
|
|
21
|
+
* registered code. The one place an arbitrary string is accepted as a code
|
|
22
|
+
* is where an incoming response is parsed, which lets an older client
|
|
23
|
+
* tolerate a code from a newer server it does not yet recognize.
|
|
24
|
+
* 2. The codes marked `surface: 'wire'` are the ones that cross the network
|
|
25
|
+
* and are mapped at the HTTP and tool-calling boundaries; the public error
|
|
26
|
+
* documentation is generated from them. Codes marked `surface: 'client'`
|
|
27
|
+
* describe local mistakes — accessing the database before opening it, or
|
|
28
|
+
* writing to a model that was never registered — and are never sent over
|
|
29
|
+
* the network, so they carry no HTTP status.
|
|
31
30
|
*/
|
|
32
31
|
import { z } from 'zod';
|
|
33
32
|
/**
|
|
34
|
-
*
|
|
35
|
-
* their
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* and on the `Ablo-Version` response header so a
|
|
33
|
+
* The version of the error contract: the envelope shape together with the set of
|
|
34
|
+
* codes and their meanings. It is date-based, and changes only when the contract
|
|
35
|
+
* changes in a way a consumer can observe — a code added or removed, an HTTP
|
|
36
|
+
* status changed, or an envelope field changed. It is emitted in the generated
|
|
37
|
+
* error documentation and returned on the `Ablo-Version` response header, so a
|
|
38
|
+
* consumer can detect when its expected contract has drifted from the server's.
|
|
39
39
|
*/
|
|
40
|
-
export const ERROR_CONTRACT_VERSION = '2026-
|
|
40
|
+
export const ERROR_CONTRACT_VERSION = '2026-07-03';
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* compile-time completeness instead of ad-hoc `if (!isRetryableCode(...))`
|
|
49
|
-
* chains.
|
|
42
|
+
* A closed classification of how a failure can be recovered from — a level above
|
|
43
|
+
* the raw {@link ErrorCode}. Where a code says what went wrong, a recovery class
|
|
44
|
+
* says what a client should do about it, which is exactly the distinction the
|
|
45
|
+
* connection layer needs to decide between retrying, re-minting a credential, and
|
|
46
|
+
* signing the user out. Every code maps to one of these, and the set is
|
|
47
|
+
* validated at runtime.
|
|
50
48
|
*
|
|
51
|
-
* - `access_credential_expiry` — the
|
|
52
|
-
*
|
|
53
|
-
* is
|
|
54
|
-
* and retry the same request. This
|
|
55
|
-
*
|
|
56
|
-
* is routine
|
|
57
|
-
* - `session_expiry` — the
|
|
49
|
+
* - `access_credential_expiry` — the short-lived access credential the client
|
|
50
|
+
* presents (its ephemeral `ek_` or `rk_` key) has expired, while the
|
|
51
|
+
* underlying login is still valid. The remedy is to mint a fresh key from the
|
|
52
|
+
* session and retry the same request. This does not sign the user out; a
|
|
53
|
+
* short-lived key expiring — for example after a laptop resumes from sleep —
|
|
54
|
+
* is routine.
|
|
55
|
+
* - `session_expiry` — the long-lived login itself is gone. This is terminal:
|
|
58
56
|
* sign out and route to re-authentication.
|
|
59
|
-
* - `auth_blocked` — reachable
|
|
60
|
-
* (wrong key
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* - `
|
|
64
|
-
*
|
|
65
|
-
*
|
|
57
|
+
* - `auth_blocked` — the server was reachable but rejected the kind or
|
|
58
|
+
* configuration of the credential (wrong key type, untrusted issuer, no
|
|
59
|
+
* organization). Re-authenticating would present the same rejected credential
|
|
60
|
+
* and loop, so the client should stop rather than reconnect or sign out.
|
|
61
|
+
* - `permission` — an authorization denial (403) based on scope, role, or
|
|
62
|
+
* membership.
|
|
63
|
+
* - `transient` — a temporary failure, such as a server error or lease
|
|
64
|
+
* contention, that may succeed if the same request is retried unchanged.
|
|
65
|
+
* - `none` — not a recoverable authentication condition: validation errors,
|
|
66
|
+
* not-found, local invariants, and any code an older client does not
|
|
67
|
+
* recognize.
|
|
66
68
|
*/
|
|
67
69
|
export const RECOVERY_CLASSES = [
|
|
68
70
|
'access_credential_expiry',
|
|
@@ -72,214 +74,220 @@ export const RECOVERY_CLASSES = [
|
|
|
72
74
|
'transient',
|
|
73
75
|
'none',
|
|
74
76
|
];
|
|
75
|
-
/** Zod enum
|
|
76
|
-
*
|
|
77
|
+
/** A Zod enum over {@link RECOVERY_CLASSES}, for validating a recovery class at
|
|
78
|
+
* runtime. */
|
|
77
79
|
export const recoveryClassSchema = z.enum(RECOVERY_CLASSES);
|
|
78
80
|
const wire = (category, httpStatus, retryable, message, recovery) => ({ category, surface: 'wire', httpStatus, retryable, message, recovery });
|
|
79
81
|
const client = (category, message) => ({ category, surface: 'client', retryable: false, message });
|
|
80
82
|
/**
|
|
81
|
-
* The
|
|
82
|
-
*
|
|
83
|
+
* The complete set of stable error codes, keyed by code. A code must be added
|
|
84
|
+
* here before it can be thrown, since the {@link AbloError} constructor accepts
|
|
85
|
+
* only codes from this set.
|
|
83
86
|
*/
|
|
84
87
|
export const ERROR_CODES = {
|
|
85
88
|
// ── auth (401) ─────────────────────────────────────────────────────
|
|
86
|
-
apikey_invalid: wire('auth', 401, false,
|
|
87
|
-
apikey_revoked: wire('auth', 401, false, 'API key has been revoked.'),
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
apikey_expired: wire('auth', 401, false, 'API key has expired.', 'access_credential_expiry'),
|
|
95
|
-
apikey_missing: wire('auth', 401, false, '
|
|
96
|
-
api_key_required: wire('auth', 401, false, 'This operation requires an API key.'),
|
|
97
|
-
capability_id_missing: wire('auth', 401, false, '
|
|
98
|
-
exchange_failed: wire('auth', 401, false, 'The API
|
|
99
|
-
identity_resolve_failed: wire('auth', 401, false, '
|
|
89
|
+
apikey_invalid: wire('auth', 401, false, "This API key isn't one Ablo recognizes — it may be mistyped, truncated, or belong to a different environment. Check the key and try again."),
|
|
90
|
+
apikey_revoked: wire('auth', 401, false, 'This API key has been revoked and can no longer be used. Mint a new key from the dashboard.'),
|
|
91
|
+
// The short-lived access credential — the ephemeral key (`ek_` for users,
|
|
92
|
+
// `rk_` for agents) minted from the login and presented as a bearer token.
|
|
93
|
+
// Its expiry is routine and re-mintable: get a fresh key from the still-valid
|
|
94
|
+
// session and retry, rather than signing out. An agent's expired `rk_` must
|
|
95
|
+
// not sign a human out either. This is the one code on the silent re-mint
|
|
96
|
+
// path; see the `access_credential_expiry` recovery class.
|
|
97
|
+
apikey_expired: wire('auth', 401, false, 'This ephemeral API key has expired. Mint a fresh key from your still-valid session and retry the request.', 'access_credential_expiry'),
|
|
98
|
+
apikey_missing: wire('auth', 401, false, 'The request arrived without an API key. Send one as `Authorization: Bearer <key>`.'),
|
|
99
|
+
api_key_required: wire('auth', 401, false, 'This operation requires an API key, and none was presented. Send one as `Authorization: Bearer <key>`.'),
|
|
100
|
+
capability_id_missing: wire('auth', 401, false, 'This request must name a capability id, but none was provided.'),
|
|
101
|
+
exchange_failed: wire('auth', 401, false, 'The API key could not be exchanged for a working credential — the exchange was rejected. Check that the key is still valid.'),
|
|
102
|
+
identity_resolve_failed: wire('auth', 401, false, 'The server could not resolve an identity for this credential — the identity lookup was rejected. Check that the credential is still valid.'),
|
|
100
103
|
auth_no_credentials: wire('auth', 401, false, 'No recognized authentication credential was presented — no API key and no bearer JWT. Send `Authorization: Bearer <token>`.'),
|
|
101
|
-
identity_missing_organization: wire('auth', 401, false, 'Authentication succeeded but
|
|
102
|
-
// The long-lived login is gone
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
identity_missing_organization: wire('auth', 401, false, 'Authentication succeeded, but the credential resolves to no organization, so requests cannot be scoped. Check that the key or token carries an organization.'),
|
|
105
|
+
// The long-lived login is gone; this is terminal and drives sign-out and
|
|
106
|
+
// re-authentication.
|
|
107
|
+
session_expired: wire('auth', 401, false, 'Your session has expired or is no longer valid. Sign in again to continue.', 'session_expiry'),
|
|
108
|
+
// `jwt_invalid` is the general fallback; the codes below it split out specific
|
|
109
|
+
// failure modes, so an integrator can tell a wrong JWKS registration from a
|
|
110
|
+
// token with no organization claim from a wrong audience, instead of getting
|
|
111
|
+
// one opaque code for all of them.
|
|
112
|
+
jwt_invalid: wire('auth', 401, false, "The bearer JWT failed validation for a reason the server could not classify further. Check the token's issuer, signature, audience, and expiry."),
|
|
113
|
+
jwt_malformed: wire('auth', 401, false, 'The bearer token is not a well-formed JWT and could not be decoded. Check that the full, unmodified token was sent.'),
|
|
110
114
|
jwt_missing_issuer: wire('auth', 401, false, 'The bearer JWT has no `iss` (issuer) claim, so it cannot be routed to a trusted issuer.'),
|
|
111
115
|
jwt_issuer_untrusted: wire('auth', 401, false, "The bearer JWT's `iss` is not a registered trusted issuer. Register it via POST /v1/trusted-issuers, or check the token's issuer claim."),
|
|
112
116
|
jwt_signature_invalid: wire('auth', 401, false, "The bearer JWT's signature could not be verified against the issuer's JWKS (wrong key, rotated key, or forged token)."),
|
|
113
117
|
jwt_audience_mismatch: wire('auth', 401, false, "The bearer JWT's `aud` (audience) claim does not match the audience this issuer is registered with."),
|
|
114
118
|
jwt_missing_subject: wire('auth', 401, false, 'The bearer JWT has no `sub` (subject) claim to identify the user.'),
|
|
115
119
|
jwt_missing_organization: wire('auth', 401, false, 'The bearer JWT carries no organization context — neither a fixed org for the issuer nor the configured organization claim.'),
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
jwt_expired: wire('auth', 401, false, 'The bearer JWT has expired; obtain a fresh token.', 'session_expiry'),
|
|
120
|
+
// Applies only to the trusted-issuer path, where a customer authenticates with
|
|
121
|
+
// a JWT from their own identity provider. When such a token expires, the
|
|
122
|
+
// remedy is to re-authenticate against that provider, so it classifies as a
|
|
123
|
+
// session expiry.
|
|
124
|
+
jwt_expired: wire('auth', 401, false, 'The bearer JWT has expired. Obtain a fresh token from your identity provider and retry.', 'session_expiry'),
|
|
122
125
|
jwt_org_membership_denied: wire('auth', 403, false, "The bearer JWT's subject is not an active member of the organization in its `org_id` claim (removed, suspended, or the claim does not match a membership)."),
|
|
123
|
-
file_upload_auth_required: wire('auth', 401, false, 'File
|
|
124
|
-
browser_apikey_blocked: client('auth', '
|
|
125
|
-
browser_database_url_blocked: client('auth', 'A database connection string
|
|
126
|
-
datasource_registration_failed: client('auth', '
|
|
127
|
-
datasource_connection_unsupported: wire('validation', 400, false, 'This deployment
|
|
126
|
+
file_upload_auth_required: wire('auth', 401, false, 'File uploads require an authenticated session. Sign in and retry.'),
|
|
127
|
+
browser_apikey_blocked: client('auth', 'A raw API key was used from a browser, where anyone can read it. Keep secret keys server-side and hand the browser a short-lived ephemeral key instead.'),
|
|
128
|
+
browser_database_url_blocked: client('auth', 'A database connection string was used from a browser context. It carries database credentials, so it must stay server-side.'),
|
|
129
|
+
datasource_registration_failed: client('auth', 'The provided `databaseUrl` could not be registered as a data source. Check that the connection string is valid and the database is reachable.'),
|
|
130
|
+
datasource_connection_unsupported: wire('validation', 400, false, 'This deployment does not accept direct connection-string data sources. Register a signed Data Source endpoint instead.'),
|
|
128
131
|
datasource_direct_deprecated: wire('validation', 410, false, 'The direct (connection string) datasource is deprecated. Register a signed Data Source endpoint instead — your app owns the write and your credentials never leave it.'),
|
|
129
132
|
// ── permission / capability (403) ──────────────────────────────────
|
|
130
|
-
capability_scope_denied: wire('capability', 403, false,
|
|
131
|
-
issuer_register_forbidden: wire('permission', 403, false, 'Registering a trusted issuer requires a secret (sk_) API key.'),
|
|
132
|
-
capability_invalid: wire('capability', 403, false, '
|
|
133
|
+
capability_scope_denied: wire('capability', 403, false, 'This action falls outside the scope granted to the connection, so it was denied.'),
|
|
134
|
+
issuer_register_forbidden: wire('permission', 403, false, 'Registering a trusted issuer requires a secret (`sk_`) API key. The key presented is not a secret key.'),
|
|
135
|
+
capability_invalid: wire('capability', 403, false, 'This capability cannot be used — it is unknown, revoked, or expired. Request a fresh grant.'),
|
|
133
136
|
test_database_not_registered: wire('permission', 403, false, 'Test mode requires a registered dev database for this org — run `npx ablo init`, or construct the client with `databaseUrl` using your test key.'),
|
|
134
137
|
tenant_routing_failed: wire('server', 500, true, "The org's registered database could not be resolved or dialed. Ablo never falls back to shared storage for a dedicated tenant — retry, and check the datasource status if it persists."),
|
|
135
|
-
database_role_cannot_enforce_rls: wire('permission', 403, false, 'The
|
|
136
|
-
database_role_unreadable: wire('permission', 403, false, '
|
|
137
|
-
database_tables_unforced_rls: wire('permission', 403, false, '
|
|
138
|
-
database_host_not_allowed: wire('permission', 403, false,
|
|
139
|
-
//
|
|
140
|
-
//
|
|
138
|
+
database_role_cannot_enforce_rls: wire('permission', 403, false, 'The database role Ablo connects with is a superuser or has `BYPASSRLS`, so Postgres will not enforce row-level security for it. Connect with a role that is subject to RLS.'),
|
|
139
|
+
database_role_unreadable: wire('permission', 403, false, 'Ablo could not introspect the database role it connects with, so it cannot verify that row-level security is enforced.'),
|
|
140
|
+
database_tables_unforced_rls: wire('permission', 403, false, 'Some synced tables do not have `FORCE ROW LEVEL SECURITY` applied, so the table owner can bypass row isolation. Run `ALTER TABLE ... FORCE ROW LEVEL SECURITY` on each synced table.'),
|
|
141
|
+
database_host_not_allowed: wire('permission', 403, false, "The database host resolves to a private, loopback, or link-local address, which Ablo's servers will not connect to. Use a publicly resolvable host."),
|
|
142
|
+
// Older spellings of the `database_*` codes above, still sent by some servers
|
|
143
|
+
// and kept so they classify identically. Prefer the `database_*` codes.
|
|
141
144
|
byo_role_cannot_enforce_rls: wire('permission', 403, false, 'The direct Postgres connector role cannot enforce row-level security.'),
|
|
142
145
|
byo_role_unreadable: wire('permission', 403, false, 'The direct Postgres connector role could not be introspected.'),
|
|
143
146
|
byo_tenant_tables_unforced_rls: wire('permission', 403, false, 'Tenant tables do not have RLS forced under the direct Postgres connector role.'),
|
|
144
147
|
byo_host_not_allowed: wire('permission', 403, false, 'The direct Postgres connector host resolves to a private, loopback, or link-local address and cannot be used.'),
|
|
145
148
|
// ── claim / claim conflict (409) ──────────────────────────────────
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
malformed_claim: wire('claim', 400, false, 'The claim payload was malformed.'),
|
|
159
|
-
malformed_subscription: wire('validation', 400, false, 'The update_subscription payload was malformed; expected { syncGroups: string[] }.'),
|
|
160
|
-
model_claimed: wire('claim', 409, false, 'The model instance is claimed by another participant.'),
|
|
161
|
-
model_claimed_timeout: wire('claim', 409, false, 'Timed out waiting for a model claim to clear.'),
|
|
149
|
+
// A rejection because another participant holds a claim is not retryable.
|
|
150
|
+
// Re-sending the same write cannot succeed while the claim is held, and a
|
|
151
|
+
// claim can outlive any reasonable retry budget, so an automatic retry would
|
|
152
|
+
// only loop. Recovery belongs to the caller: take a claim, which queues fairly
|
|
153
|
+
// behind the holder (`ablo.<model>.claim`), or re-read and rebase.
|
|
154
|
+
claim_conflict: wire('claim', 409, false, 'Another participant holds a claim on this row, so the write was rejected. Take a claim with `ablo.<model>.claim` to queue fairly behind the holder, or re-read and rebase.'),
|
|
155
|
+
claim_lost: wire('claim', 409, false, 'The claim held on this row was lost before the write could apply. Re-acquire the claim and retry.'),
|
|
156
|
+
entity_claimed: wire('claim', 409, false, 'This row is currently claimed by another participant, so the write was blocked. Queue behind the holder with `ablo.<model>.claim`, or wait for the claim to clear.'),
|
|
157
|
+
malformed_claim: wire('claim', 400, false, 'The claim payload could not be parsed. A claim must name the model and the entity it targets; check the payload shape and resend.'),
|
|
158
|
+
malformed_subscription: wire('validation', 400, false, 'The `update_subscription` payload was malformed; expected `{ syncGroups: string[] }`.'),
|
|
159
|
+
model_claimed: wire('claim', 409, false, 'Another participant holds a claim on this row. Read `claim.state` to see who holds it, or queue behind them with a claim of your own.'),
|
|
160
|
+
model_claimed_timeout: wire('claim', 409, false, 'Another participant held a claim on this row and did not release it in time. Retry, or read `claim.state` to see who holds it.'),
|
|
162
161
|
model_claim_not_configured: client('claim', 'Claiming requires the collaboration runtime, which the standard Ablo({ schema, apiKey }) client wires up for every model automatically — there is no per-model claim configuration to add. This appears only when a model proxy is constructed directly without that runtime (an internal/advanced path).'),
|
|
163
162
|
model_watch_not_configured: client('claim', 'watch() opens a presence/claim subscription and needs a live WebSocket, so it is unavailable on the HTTP transport and on model proxies built without a socket. Use the standard Ablo({ schema, apiKey }) client (default WebSocket transport).'),
|
|
164
163
|
// ── stale context / idempotency (409) ──────────────────────────────
|
|
165
|
-
stale_context: wire('conflict', 409, true,
|
|
164
|
+
stale_context: wire('conflict', 409, true, "The row changed after you read it — the write's `readAt` watermark is older than the current row version. Re-read the row and retry."),
|
|
166
165
|
// Raised by the functional `update(id, current => next)` form once its
|
|
167
|
-
// internal reconcile budget is exhausted
|
|
168
|
-
// contended.
|
|
169
|
-
//
|
|
170
|
-
contention_exhausted: client('conflict', 'A functional update
|
|
171
|
-
update_aborted: client('conflict', 'The functional update
|
|
172
|
-
idempotency_conflict: wire('conflict', 409, false, '
|
|
173
|
-
idempotency_key_too_long: wire('validation', 400, false, 'The supplied Idempotency-Key exceeds the maximum length.'),
|
|
166
|
+
// internal reconcile budget is exhausted, because the row stayed continuously
|
|
167
|
+
// contended. The SDK has already retried; the caller decides whether to back
|
|
168
|
+
// off, raise `retries`, or move the row to the WebSocket transport.
|
|
169
|
+
contention_exhausted: client('conflict', 'A functional update kept losing to concurrent writes and exhausted its reconcile budget. Back off and retry, raise `retries`, or move the row to the WebSocket transport.'),
|
|
170
|
+
update_aborted: client('conflict', 'The functional update was aborted via its `AbortSignal` before the write landed; nothing was written.'),
|
|
171
|
+
idempotency_conflict: wire('conflict', 409, false, 'This `Idempotency-Key` was already used with a different request body. Reuse a key only to retry an identical request; otherwise generate a new one.'),
|
|
172
|
+
idempotency_key_too_long: wire('validation', 400, false, 'The supplied `Idempotency-Key` exceeds the maximum length. Use a shorter key — a UUID works well.'),
|
|
174
173
|
// ── validation (400 / 422) ─────────────────────────────────────────
|
|
175
174
|
write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `claim`) failed validation against the write-options schema.'),
|
|
176
175
|
source_operation_id_required: client('validation', 'A data-source operation arrived without the entity `id` it targets.'),
|
|
177
|
-
source_adapter_misconfigured: client('validation', 'The data-source ORM adapter could not map a schema model onto the backing client
|
|
178
|
-
|
|
176
|
+
source_adapter_misconfigured: client('validation', 'The data-source ORM adapter could not map a schema model onto the backing client — the client exposes no matching delegate or model. Check that the adapter and schema agree on model names.'),
|
|
177
|
+
// The server validates every incoming data-source event before appending it
|
|
178
|
+
// to the log and rejects the whole batch with this code; `param` names the
|
|
179
|
+
// offending index and field path, such as `events[3].entityId`. It is also
|
|
180
|
+
// raised on the client when an outbound source event cannot be built.
|
|
181
|
+
source_event_invalid: wire('validation', 400, false, 'A data-source event was malformed — missing or invalid id, model, entityId, type, or field value. The whole event batch was rejected and nothing was ingested; fix the offending outbox row and re-send.'),
|
|
179
182
|
duration_invalid: client('validation', 'A duration value was not a number of seconds or a "500ms" | "30s" | "3m" | "24h" string.'),
|
|
180
183
|
schema_definition_invalid: client('validation', 'A schema definition value was invalid (bad column identifier, non-finite backfill, or unsupported schema-JSON version).'),
|
|
181
184
|
cli_invalid_arguments: client('validation', 'The CLI was invoked with an unknown flag or a malformed flag value.'),
|
|
182
|
-
turn_validation_failed: wire('validation', 422, false, 'The agent turn failed server-side validation.'),
|
|
185
|
+
turn_validation_failed: wire('validation', 422, false, 'The agent turn payload failed server-side validation and was not applied.'),
|
|
183
186
|
commit_operation_required: wire('validation', 400, false, 'A commit must carry `operation` or `operations`.'),
|
|
187
|
+
// Both commit transports — the WebSocket `commit` frame and the HTTP
|
|
188
|
+
// `/v1/commits` endpoint — validate every operation and reject the whole batch
|
|
189
|
+
// with this code. `param` names the offending index and field path, such as
|
|
190
|
+
// `operations[3].readAt`.
|
|
191
|
+
commit_operation_invalid: wire('validation', 400, false, 'A commit operation failed validation against the wire commit-operation schema — wrong field type (e.g. a string `readAt`), unknown `type`, or missing `model`. The whole batch was rejected; the error names the offending operation index and field path.'),
|
|
184
192
|
commit_operation_model_required: wire('validation', 400, false, 'A commit operation is missing its `model`.'),
|
|
185
|
-
commit_operations_ambiguous: wire('validation', 400, false, 'A commit supplied both `operation` and `operations`.'),
|
|
193
|
+
commit_operations_ambiguous: wire('validation', 400, false, 'A commit supplied both `operation` and `operations`. Send one or the other, not both.'),
|
|
186
194
|
commit_too_many_operations: wire('validation', 400, false, 'A commit exceeded the per-commit operation limit; split it into smaller batches.'),
|
|
187
|
-
model_required_field_missing: wire('validation', 400, false, '
|
|
188
|
-
model_identifier_missing: wire('validation', 400, false,
|
|
189
|
-
snapshot_reserved_key: wire('validation', 400, false, '
|
|
190
|
-
mesh_message_invalid_input: wire('validation', 400, false, 'The mesh message failed input validation.'),
|
|
191
|
-
mesh_message_from_id_spoof: wire('validation', 403, false,
|
|
192
|
-
mesh_message_from_kind_mismatch: wire('validation', 403, false,
|
|
193
|
-
agent_perception_missing_context: wire('validation', 422, false, 'The agent perception request
|
|
195
|
+
model_required_field_missing: wire('validation', 400, false, 'The write is missing a field the model marks as required. Include the field and retry.'),
|
|
196
|
+
model_identifier_missing: wire('validation', 400, false, "The payload is missing the model's identifier, so the target row cannot be determined. Include the `id` field."),
|
|
197
|
+
snapshot_reserved_key: wire('validation', 400, false, 'The snapshot uses a key name that is reserved by the runtime. Rename the key and retry.'),
|
|
198
|
+
mesh_message_invalid_input: wire('validation', 400, false, 'The mesh message payload failed input validation and was not delivered.'),
|
|
199
|
+
mesh_message_from_id_spoof: wire('validation', 403, false, "The mesh message's `from` id does not match the authenticated sender, so it was rejected — participants may only send as themselves."),
|
|
200
|
+
mesh_message_from_kind_mismatch: wire('validation', 403, false, "The mesh message's `from` kind does not match the kind of the authenticated sender, so it was rejected."),
|
|
201
|
+
agent_perception_missing_context: wire('validation', 422, false, 'The agent perception request is missing context it needs to run. Include the required context fields and retry.'),
|
|
194
202
|
// ── not found (404) ────────────────────────────────────────────────
|
|
195
|
-
entity_not_found: wire('not_found', 404, false, '
|
|
196
|
-
model_not_found: wire('not_found', 404, false, '
|
|
197
|
-
mutate_update_entity_not_found: wire('not_found', 404, false, 'The
|
|
198
|
-
task_id_missing: wire('server', 502, true, 'The task-create response
|
|
199
|
-
// ── data integrity /
|
|
200
|
-
// Emitted when a
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
constraint_violation: wire('validation', 400, false, 'A database integrity constraint was violated.'),
|
|
203
|
+
entity_not_found: wire('not_found', 404, false, 'No row exists with the requested id. It may have been deleted, or the id may belong to a different environment.'),
|
|
204
|
+
model_not_found: wire('not_found', 404, false, 'No row of this model exists with the requested id. It may have been deleted, or the id may belong to a different environment.'),
|
|
205
|
+
mutate_update_entity_not_found: wire('not_found', 404, false, 'The row targeted by this update does not exist — it may have been deleted since you read it. Re-read before retrying.'),
|
|
206
|
+
task_id_missing: wire('server', 502, true, 'The task-create response arrived without a task id, so the result cannot be used. Retry the request.'),
|
|
207
|
+
// ── data integrity / database constraints ──────────────────────────
|
|
208
|
+
// Emitted when a database integrity constraint rejects a write. None are
|
|
209
|
+
// retryable: the same payload re-sent unchanged fails identically, so the
|
|
210
|
+
// client must roll back rather than retry. The server maps the underlying SQL
|
|
211
|
+
// constraint to one of these codes and places the raw constraint, column, and
|
|
212
|
+
// table detail in `details` instead of exposing the driver's message text.
|
|
213
|
+
not_null_violation: wire('validation', 400, false, 'The database rejected the write because a required column was left empty — a not-null constraint. The error details name the column; supply a value and retry.'),
|
|
214
|
+
foreign_key_violation: wire('conflict', 409, false, 'The database rejected the write on a foreign-key constraint: a referenced row does not exist, or the row being deleted is still referenced by others. The error details name the constraint.'),
|
|
215
|
+
unique_violation: wire('conflict', 409, false, 'The write duplicates a value that must be unique — another row already holds it. Choose a different value, or update the existing row.'),
|
|
216
|
+
check_violation: wire('validation', 400, false, 'The database rejected a value that fails one of its check constraints. The error details name the constraint; adjust the value and retry.'),
|
|
217
|
+
constraint_violation: wire('validation', 400, false, 'The database rejected the write on an integrity constraint. The error details identify the specific constraint.'),
|
|
211
218
|
column_type_mismatch: wire('validation', 400, false, 'A structured (JSON) value was written to a column whose database type cannot hold it. Ablo adapts a json field to either a jsonb column (native) or a text column (serialized) — but a scalar column (integer, boolean, uuid, timestamp, …) cannot store a JSON object or array. Use a jsonb or text column for this field. Ablo adapts to your column; it does not alter your schema.'),
|
|
212
219
|
// ── tenant / unknown model (400) ───────────────────────────────────
|
|
213
220
|
server_execute_unknown_model: wire('tenant', 400, false, 'Wrote to a model the server does not know. The server keeps its own copy of the schema — run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` before writing to new or changed models.'),
|
|
214
221
|
mutate_create_unknown_model: wire('tenant', 400, false, 'Created a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` first — the server keeps its own copy of the schema.'),
|
|
215
|
-
tenant_model_columns_unknown: wire('tenant', 400, false,
|
|
216
|
-
tenant_model_missing_organization_id: wire('tenant', 400, false,
|
|
222
|
+
tenant_model_columns_unknown: wire('tenant', 400, false, 'The columns for this model could not be resolved in the tenant database, so the operation cannot be mapped onto its table.'),
|
|
223
|
+
tenant_model_missing_organization_id: wire('tenant', 400, false, "This model's table has no `organization_id` column, which Ablo requires to isolate rows by organization. Add the column before syncing this model."),
|
|
217
224
|
// ── schema migration / declaration (validation) ────────────────────
|
|
218
|
-
schema_mutable_missing_meta: wire('schema', 400, false, '
|
|
219
|
-
schema_scope_kind_invalid: wire('schema', 400, false, 'A scope
|
|
220
|
-
schema_field_not_camelcase: wire('schema', 400, false, 'A schema field name is not camelCase.'),
|
|
221
|
-
schema_field_consecutive_caps: wire('schema', 400, false, 'A schema field name
|
|
225
|
+
schema_mutable_missing_meta: wire('schema', 400, false, 'The schema is declared mutable but is missing its required `meta` block.'),
|
|
226
|
+
schema_scope_kind_invalid: wire('schema', 400, false, 'A scope declaration in the schema uses a kind the engine does not recognize.'),
|
|
227
|
+
schema_field_not_camelcase: wire('schema', 400, false, 'A schema field name is not camelCase. Rename the field (for example `dueDate`) — Ablo derives column names from camelCase field names.'),
|
|
228
|
+
schema_field_consecutive_caps: wire('schema', 400, false, 'A schema field name contains consecutive capital letters, which cannot be mapped to a column name unambiguously. Write acronyms in lower case (`apiKey`, not `APIKey`).'),
|
|
222
229
|
schema_reserved_field: client('schema', 'A model redeclared a reserved base field (id, createdAt, updatedAt, organizationId, createdBy) that the SDK provides automatically.'),
|
|
223
|
-
schema_grants_shape_invalid: wire('schema', 400, false, 'A grants declaration has an invalid shape.'),
|
|
224
|
-
schema_grants_identifier_unsafe: wire('schema', 400, false, 'A grants declaration
|
|
225
|
-
schema_grants_relation_kind: wire('schema', 400, false, 'A grants relation
|
|
226
|
-
schema_grants_relation_missing: wire('schema', 400, false, 'A grants declaration
|
|
227
|
-
schema_grants_target_not_scope_root: wire('schema', 400, false, 'A grants
|
|
228
|
-
drop_field: client('schema', '
|
|
229
|
-
drop_model: client('schema', '
|
|
230
|
-
lossy_recreate: client('schema', '
|
|
231
|
-
made_required: client('schema', '
|
|
232
|
-
required_field_added: client('schema', '
|
|
233
|
-
enum_value_removed: client('schema', '
|
|
234
|
-
risky_cast: client('schema', '
|
|
230
|
+
schema_grants_shape_invalid: wire('schema', 400, false, 'A `grants` declaration in the schema has an invalid shape and could not be parsed.'),
|
|
231
|
+
schema_grants_identifier_unsafe: wire('schema', 400, false, 'A `grants` declaration references an identifier that is not safe to use in SQL. Use plain column and relation names.'),
|
|
232
|
+
schema_grants_relation_kind: wire('schema', 400, false, 'A `grants` declaration references a relation of a kind it cannot traverse.'),
|
|
233
|
+
schema_grants_relation_missing: wire('schema', 400, false, 'A `grants` declaration references a relation the model does not define. Check the relation name against the model.'),
|
|
234
|
+
schema_grants_target_not_scope_root: wire('schema', 400, false, 'A `grants` declaration targets a model that is not a scope root, so access cannot be derived from it.'),
|
|
235
|
+
drop_field: client('schema', 'This migration would drop an existing field, destroying the data stored in it.'),
|
|
236
|
+
drop_model: client('schema', 'This migration would drop an entire model and its table, destroying the rows stored in it.'),
|
|
237
|
+
lossy_recreate: client('schema', 'This migration can only apply by recreating the table, which would not preserve its existing rows.'),
|
|
238
|
+
made_required: client('schema', 'This migration makes an existing optional field required, which rows without a value for it would violate.'),
|
|
239
|
+
required_field_added: client('schema', 'This migration adds a new required field that existing rows have no value for.'),
|
|
240
|
+
enum_value_removed: client('schema', 'This migration removes an enum value that existing rows may still hold.'),
|
|
241
|
+
risky_cast: client('schema', 'This migration changes a column to a type its current values may not convert to cleanly.'),
|
|
235
242
|
// ── claim / lease (409 / transport) ───────────────────────────────
|
|
236
|
-
claim_lease_unavailable: wire('claim', 503, true, 'The claim-lease coordination subsystem is unavailable
|
|
237
|
-
claim_not_wired: client('claim', '
|
|
238
|
-
claim_queued: wire('claim', 409, true, 'The claim was queued behind
|
|
239
|
-
claim_wait_aborted: wire('claim', 409, true, '
|
|
240
|
-
claim_wait_poll_interval_required: client('claim', '
|
|
241
|
-
grant_timeout: wire('claim', 504, true, '
|
|
242
|
-
slide_intent_missing_deck_id: wire('claim', 400, false, '
|
|
243
|
-
slide_intent_unknown_sibling: wire('claim', 400, false, '
|
|
243
|
+
claim_lease_unavailable: wire('claim', 503, true, 'The claim-lease coordination subsystem is temporarily unavailable, so the claim could not be processed. Retry shortly.'),
|
|
244
|
+
claim_not_wired: client('claim', 'Claims were used, but this runtime has no claim support wired in. The standard `Ablo({ schema, apiKey })` client wires it up automatically.'),
|
|
245
|
+
claim_queued: wire('claim', 409, true, 'The claim was queued behind the current lease holder and will be granted in turn. Wait, or read `claim.queue` to see your position.'),
|
|
246
|
+
claim_wait_aborted: wire('claim', 409, true, 'The wait for this claim lease was aborted before the lease was granted.'),
|
|
247
|
+
claim_wait_poll_interval_required: client('claim', 'Waiting on a claim requires a poll interval, and none was provided.'),
|
|
248
|
+
grant_timeout: wire('claim', 504, true, 'The wait for a capability grant timed out before one arrived. Retry the request.'),
|
|
249
|
+
slide_intent_missing_deck_id: wire('claim', 400, false, 'This slide claim is missing the id of the deck it belongs to.'),
|
|
250
|
+
slide_intent_unknown_sibling: wire('claim', 400, false, 'This slide claim references a sibling slide that does not exist in the deck.'),
|
|
244
251
|
// ── bootstrap (transport) ──────────────────────────────────────────
|
|
245
|
-
bootstrap_fetch_timeout: wire('bootstrap', 504, true, 'The bootstrap fetch timed out.'),
|
|
246
|
-
bootstrap_offline: wire('bootstrap', 503, true, 'Bootstrap could not run because the client is offline.'),
|
|
247
|
-
bootstrap_offline_no_cache: wire('bootstrap', 503, false, '
|
|
248
|
-
bootstrap_response_invalid: wire('bootstrap', 502, true, 'The bootstrap response
|
|
249
|
-
bootstrap_response_schema_invalid: wire('bootstrap', 502, true, 'The bootstrap response failed schema validation.'),
|
|
252
|
+
bootstrap_fetch_timeout: wire('bootstrap', 504, true, 'The initial bootstrap fetch timed out before the server responded. Retry shortly.'),
|
|
253
|
+
bootstrap_offline: wire('bootstrap', 503, true, 'Bootstrap could not run because the client is offline. It can proceed once the network returns.'),
|
|
254
|
+
bootstrap_offline_no_cache: wire('bootstrap', 503, false, 'The client is offline and no cached snapshot is available to start from, so there is no data to load until the network returns.'),
|
|
255
|
+
bootstrap_response_invalid: wire('bootstrap', 502, true, 'The bootstrap response could not be parsed. Retrying may succeed.'),
|
|
256
|
+
bootstrap_response_schema_invalid: wire('bootstrap', 502, true, 'The bootstrap response parsed but failed schema validation, so it was not applied. Retrying may succeed.'),
|
|
250
257
|
// ── transport / connection ─────────────────────────────────────────
|
|
251
|
-
exchange_malformed_response: wire('transport', 502, true, 'The credential exchange returned a
|
|
252
|
-
exchange_network_error: wire('transport', 503, true, 'A network error
|
|
253
|
-
source_network_error: wire('transport', 503, true, 'A network error occurred talking to the source.'),
|
|
254
|
-
identity_network_error: wire('transport', 503, true, 'A network error occurred resolving identity.'),
|
|
255
|
-
commit_no_result: wire('transport', 504, true, 'The commit was sent but no result frame arrived.'),
|
|
256
|
-
commit_failed: wire('transport', 500, true, 'The commit failed to apply.'),
|
|
257
|
-
commit_offline_grace_expired: wire('transport', 503, false,
|
|
258
|
-
queue_too_deep: wire('transport', 503, true, 'The transaction queue
|
|
259
|
-
flush_timeout: wire('transport', 504, true, '
|
|
260
|
-
wait_for_timeout: wire('transport', 504, true, 'A wait-for condition timed out.'),
|
|
258
|
+
exchange_malformed_response: wire('transport', 502, true, 'The credential exchange returned a response that could not be parsed. Retrying may succeed.'),
|
|
259
|
+
exchange_network_error: wire('transport', 503, true, 'A network error interrupted the credential exchange. Check connectivity and retry.'),
|
|
260
|
+
source_network_error: wire('transport', 503, true, 'A network error occurred while talking to the data source. Check connectivity and retry.'),
|
|
261
|
+
identity_network_error: wire('transport', 503, true, 'A network error occurred while resolving your identity. Check connectivity and retry.'),
|
|
262
|
+
commit_no_result: wire('transport', 504, true, 'The commit was sent, but no result frame arrived, so its outcome is unknown. It is safe to retry.'),
|
|
263
|
+
commit_failed: wire('transport', 500, true, 'The commit reached the server but failed to apply. Retrying may succeed.'),
|
|
264
|
+
commit_offline_grace_expired: wire('transport', 503, false, 'The offline grace window expired before this commit could be sent, so it was not applied. Re-apply the change once the connection returns.'),
|
|
265
|
+
queue_too_deep: wire('transport', 503, true, 'The transaction queue is over its depth limit, so new writes are being rejected until it drains. Retry shortly.'),
|
|
266
|
+
flush_timeout: wire('transport', 504, true, 'Flushing the transaction queue timed out before every pending write was sent. Retry once connectivity stabilizes.'),
|
|
267
|
+
wait_for_timeout: wire('transport', 504, true, 'A wait-for condition timed out before it was satisfied. Retry, or extend the timeout.'),
|
|
261
268
|
instance_at_capacity: wire('transport', 503, true, 'The server is at connection capacity. Retry shortly — transient and not specific to your credentials.'),
|
|
262
|
-
fetch_unavailable: client('transport', '
|
|
263
|
-
base_url_missing: client('transport', '
|
|
264
|
-
sync_not_ready: client('transport', 'A sync operation
|
|
265
|
-
ws_not_ready: client('transport', 'A frame was sent before the WebSocket was
|
|
269
|
+
fetch_unavailable: client('transport', 'This environment provides no `fetch` implementation, so HTTP requests cannot be made. Run on a platform with `fetch` (Node 18+, modern browsers) or supply a polyfill.'),
|
|
270
|
+
base_url_missing: client('transport', 'The client has no base URL configured, so it cannot address the server. Set the base URL when constructing the client.'),
|
|
271
|
+
sync_not_ready: client('transport', 'A sync operation ran before the client finished initializing. Wait for the client to be ready before syncing.'),
|
|
272
|
+
ws_not_ready: client('transport', 'A frame was sent before the WebSocket connection was established. Wait for the connection to open before sending.'),
|
|
266
273
|
// ── quota / rate limit (429) ──────────────────────────────────────
|
|
267
|
-
quota_exceeded: wire('rate_limit', 429, true, '
|
|
274
|
+
quota_exceeded: wire('rate_limit', 429, true, 'Your organization has used up its configured usage quota. Requests will succeed again once the quota resets or the limit is raised.'),
|
|
268
275
|
connection_limit_exceeded: wire('rate_limit', 429, true, 'Too many concurrent WebSocket connections for this principal or organization. Close idle connections, or retry once others drain.'),
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// so one noisy key backs off without affecting
|
|
272
|
-
// `Retry-After` header carries the
|
|
273
|
-
|
|
276
|
+
// A per-key request-rate limit — the fast, requests-per-second axis, as
|
|
277
|
+
// opposed to `quota_exceeded`, which is the slower organization-wide usage
|
|
278
|
+
// limit. It is keyed per API key, so one noisy key backs off without affecting
|
|
279
|
+
// the rest of the organization. The `Retry-After` header carries the delay
|
|
280
|
+
// before the next request is allowed.
|
|
281
|
+
rate_limit_exceeded: wire('rate_limit', 429, true, 'This API key is sending requests faster than its rate limit allows. Slow down and retry after the delay in the `Retry-After` header.'),
|
|
274
282
|
// ── server (5xx) ───────────────────────────────────────────────────
|
|
275
|
-
internal_error: wire('server', 500, true, '
|
|
276
|
-
quota_lookup_failed: wire('server', 503, true,
|
|
277
|
-
// The
|
|
278
|
-
//
|
|
279
|
-
//
|
|
283
|
+
internal_error: wire('server', 500, true, "Something went wrong on Ablo's side — an unexpected server error. It is safe to retry."),
|
|
284
|
+
quota_lookup_failed: wire('server', 503, true, "The server could not load this organization's quota state, so the request was rejected rather than admitted unchecked. Retry shortly."),
|
|
285
|
+
// The rate-limiter backend was unreachable and this endpoint is configured to
|
|
286
|
+
// fail closed, so the request was rejected rather than admitted unchecked. It
|
|
287
|
+
// is retryable: the next attempt re-probes the backend.
|
|
280
288
|
rate_limiter_unavailable: wire('server', 503, true, 'The rate-limiter backend is unavailable and this endpoint is configured to fail closed; retry shortly.'),
|
|
281
|
-
turn_open_failed: wire('server', 500, true, 'The agent turn
|
|
282
|
-
turn_close_failed: wire('server', 500, true, 'The agent turn
|
|
289
|
+
turn_open_failed: wire('server', 500, true, 'The agent turn could not be opened on the server. It is safe to retry.'),
|
|
290
|
+
turn_close_failed: wire('server', 500, true, 'The agent turn could not be closed cleanly on the server. It is safe to retry the close.'),
|
|
283
291
|
// ── client-only invariants (never serialized) ──────────────────────
|
|
284
292
|
invalid_options: client('client', 'The Ablo client was constructed with invalid or incomplete options.'),
|
|
285
293
|
no_ablo_provider: client('client', 'An Ablo hook was used outside of an Ablo provider.'),
|
|
@@ -319,85 +327,95 @@ export const ERROR_CODES = {
|
|
|
319
327
|
undo_entry_invalid: client('client', 'An undo entry failed inverse-op schema validation.'),
|
|
320
328
|
mock_mutation_failed: client('client', 'A mock mutation adapter was configured to fail.'),
|
|
321
329
|
mock_unsupported_operation: client('client', 'A mock adapter received an unsupported operation.'),
|
|
322
|
-
// ── HTTP route edge codes
|
|
330
|
+
// ── HTTP route edge codes ──────────────────────────────────────────
|
|
323
331
|
invalid_body: wire('validation', 400, false, 'The request body was missing, unparseable, or the wrong shape.'),
|
|
324
332
|
invalid_json: wire('validation', 400, false, 'The request body was not valid JSON.'),
|
|
325
333
|
capability_id_required: wire('validation', 400, false, 'A capability id is required for this request.'),
|
|
326
334
|
organization_mismatch: wire('permission', 403, false, 'The request targeted an organization the caller is not scoped to.'),
|
|
327
335
|
project_scope_denied: wire('permission', 403, false, "The request targeted a project the caller's key is not scoped to."),
|
|
328
|
-
project_slug_taken: wire('validation', 409, false, 'A project with this slug already exists in the organization.'),
|
|
336
|
+
project_slug_taken: wire('validation', 409, false, 'A project with this slug already exists in the organization. Choose a different slug.'),
|
|
329
337
|
forbidden: wire('permission', 403, false, 'The caller lacks permission for this operation.'),
|
|
330
|
-
source_api_key_unresolved: wire('auth', 401, false, 'The
|
|
338
|
+
source_api_key_unresolved: wire('auth', 401, false, 'The API key presented for this data source could not be resolved to a known key. Check the key and its environment.'),
|
|
331
339
|
capability_auth_disabled: wire('server', 503, false, 'Capability authentication is disabled on this server.'),
|
|
332
|
-
provisioner_unavailable: wire('server', 503, false, '
|
|
333
|
-
invalid_model: wire('validation', 400, false, 'The request
|
|
334
|
-
invalid_id: wire('validation', 400, false, 'The request
|
|
340
|
+
provisioner_unavailable: wire('server', 503, false, 'This deployment has no database provisioner configured, so tables cannot be created here.'),
|
|
341
|
+
invalid_model: wire('validation', 400, false, 'The model name in the request is not a valid model identifier.'),
|
|
342
|
+
invalid_id: wire('validation', 400, false, 'The id in the request is not a valid identifier.'),
|
|
335
343
|
unknown_model: wire('tenant', 400, false, 'Named a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` — the server keeps its own copy of the schema.'),
|
|
336
|
-
model_not_tenant_scoped: wire('tenant', 400, false, '
|
|
344
|
+
model_not_tenant_scoped: wire('tenant', 400, false, 'This model is not tenant-scoped, so it cannot be queried through the tenant-scoped read path.'),
|
|
337
345
|
schema_table_invalid: wire('schema', 500, false, "The model's table identifier is invalid."),
|
|
338
346
|
schema_scope_invalid: wire('schema', 500, false, "The model's scope predicate could not be built."),
|
|
339
|
-
entity_fetch_failed: wire('server', 500, true, 'The
|
|
340
|
-
events_required: wire('validation', 400, false, 'The request must include a non-empty events array.'),
|
|
341
|
-
ingest_failed: wire('validation', 400, false, 'The source-event ingest
|
|
342
|
-
migration_failed: wire('server', 500, false, 'The schema migration failed
|
|
343
|
-
|
|
344
|
-
|
|
347
|
+
entity_fetch_failed: wire('server', 500, true, 'The server failed to fetch the requested entity. It is safe to retry.'),
|
|
348
|
+
events_required: wire('validation', 400, false, 'The request must include a non-empty `events` array.'),
|
|
349
|
+
ingest_failed: wire('validation', 400, false, 'The source-event batch was rejected during ingest and nothing was appended. Check the events against the expected shape and re-send.'),
|
|
350
|
+
migration_failed: wire('server', 500, false, 'The schema migration failed while applying and did not complete.'),
|
|
351
|
+
schema_provisioning_forbidden: wire('permission', 403, false, 'Schema registration could not create tables in the target database: the engine is not permitted to run DDL there.'),
|
|
352
|
+
model_query_failed: wire('validation', 400, false, 'The model query failed to execute. Check the query filters and operators.'),
|
|
353
|
+
queries_required: wire('validation', 400, false, 'The request must include a non-empty `queries` array.'),
|
|
345
354
|
query_unsupported_operator: wire('validation', 400, false, 'The query used an unsupported operator.'),
|
|
346
|
-
|
|
347
|
-
|
|
355
|
+
query_invalid_like_pattern: wire('validation', 400, false, 'The `LIKE` pattern must not end with an escape character.'),
|
|
356
|
+
query_invalid_boolean: wire('validation', 400, false, 'The query compared a boolean column against an invalid boolean literal.'),
|
|
357
|
+
protocol_version_unsupported: wire('transport', 426, false, 'The client sync-protocol version is outside the range this server supports — upgrade the SDK (or the server was rolled back mid-fleet).'),
|
|
358
|
+
database_unreachable: wire('validation', 400, false, "Ablo could not reach this database to check that it can stream replication. The connection string may be wrong, the host may not be reachable from Ablo's servers, or the credentials may not be accepted."),
|
|
359
|
+
database_not_replication_ready: wire('validation', 400, false, 'This database is not set up for logical replication yet. Every failing item — wal_level, the publication, the replication grant, a replica identity — is listed in the error details with its exact fix. `ablo connect` prints the one-time setup; `ablo connect --check` verifies it.'),
|
|
360
|
+
replication_publication_drift: wire('validation', 400, false, 'Your schema maps to tables that are not members of the replication publication, so their changes silently never stream and the source looks frozen. The missing tables and the exact `ALTER PUBLICATION … ADD TABLE …` to add them are in the error details — Ablo never alters your database for you.'),
|
|
361
|
+
query_unknown_relation: wire('validation', 400, false, 'The query references a relation the model does not define. Check the relation name against the schema.'),
|
|
362
|
+
query_relation_target_unknown: wire('schema', 500, false, 'A relation in the query targets a model the schema does not define.'),
|
|
348
363
|
query_invalid_identifier: wire('validation', 400, false, 'The query contained an invalid identifier.'),
|
|
349
364
|
org_id_required: wire('validation', 400, false, 'An organization id is required for this request.'),
|
|
350
|
-
presence_identity_required: wire('validation', 400, false, '
|
|
351
|
-
upload_fields_required: wire('validation', 400, false, '
|
|
352
|
-
upload_items_required: wire('validation', 400, false, 'The request must include a non-empty items array.'),
|
|
353
|
-
presigned_url_failed: wire('server', 500, true, '
|
|
365
|
+
presence_identity_required: wire('validation', 400, false, 'Presence requests must carry both `userId` and `organizationId`.'),
|
|
366
|
+
upload_fields_required: wire('validation', 400, false, 'The upload request is missing a required field.'),
|
|
367
|
+
upload_items_required: wire('validation', 400, false, 'The request must include a non-empty `items` array.'),
|
|
368
|
+
presigned_url_failed: wire('server', 500, true, 'The server could not generate a presigned upload URL. It is safe to retry.'),
|
|
369
|
+
upload_not_configured: wire('server', 503, false, 'Uploads are not configured on this deployment: the upload storage bucket and CDN domain are unset.'),
|
|
354
370
|
task_id_required: wire('validation', 400, false, 'A task id is required for this request.'),
|
|
355
|
-
claim_id_required: wire('validation', 400, false, '
|
|
371
|
+
claim_id_required: wire('validation', 400, false, 'A claim id is required for this request.'),
|
|
356
372
|
commit_operation_action_required: wire('validation', 400, false, 'A commit operation is missing its `action`.'),
|
|
357
373
|
commit_operation_unsupported: wire('validation', 400, false, 'A commit operation used an unsupported `action`.'),
|
|
358
374
|
usage_invalid: wire('validation', 400, false, 'The usage request was invalid.'),
|
|
359
375
|
invalid_request: wire('validation', 400, false, 'The request parameters were invalid.'),
|
|
360
376
|
capability_not_found: wire('not_found', 404, false, 'No capability exists with the given id.'),
|
|
361
|
-
invalid_participant_kind: wire('validation', 400, false, 'The participant kind is
|
|
362
|
-
invalid_sync_group: wire('validation', 400, false, 'Sync groups must be
|
|
363
|
-
narrow_scope_required: wire('validation', 400, false, '
|
|
364
|
-
wide_scope_forbidden: wire('permission', 403, false, '
|
|
365
|
-
capability_required: wire('auth', 401, false, 'This operation requires a capability.'),
|
|
377
|
+
invalid_participant_kind: wire('validation', 400, false, 'The participant kind is not one the server recognizes.'),
|
|
378
|
+
invalid_sync_group: wire('validation', 400, false, 'Sync groups must be `default` or `<namespace>:<id>`.'),
|
|
379
|
+
narrow_scope_required: wire('validation', 400, false, 'This request requires a scope narrowed to specific resources; the presented scope is too broad.'),
|
|
380
|
+
wide_scope_forbidden: wire('permission', 403, false, 'This caller may not use a wide scope. Request a scope narrowed to the resources you need.'),
|
|
381
|
+
capability_required: wire('auth', 401, false, 'This operation requires a capability, and none was presented.'),
|
|
366
382
|
parent_turn_not_found: wire('not_found', 404, false, 'The referenced parent turn does not exist.'),
|
|
367
|
-
parent_turn_foreign_agent: wire('permission', 403, false, 'The parent turn belongs to a different agent.'),
|
|
383
|
+
parent_turn_foreign_agent: wire('permission', 403, false, 'The referenced parent turn belongs to a different agent, so it cannot be used here.'),
|
|
368
384
|
turn_not_found: wire('not_found', 404, false, 'The referenced turn does not exist.'),
|
|
369
|
-
turn_foreign_agent: wire('permission', 403, false, '
|
|
370
|
-
invalid_intent: wire('validation', 400, false, 'The claim request was
|
|
385
|
+
turn_foreign_agent: wire('permission', 403, false, 'This turn belongs to a different agent, so the caller cannot act on it.'),
|
|
386
|
+
invalid_intent: wire('validation', 400, false, 'The claim request was malformed and could not be processed.'),
|
|
371
387
|
schema_too_large: wire('validation', 413, false, 'The submitted schema exceeds the maximum size.'),
|
|
388
|
+
request_too_large: wire('validation', 413, false, 'The request body exceeds the maximum size.'),
|
|
372
389
|
invalid_schema: wire('validation', 400, false, 'The submitted schema could not be parsed.'),
|
|
373
|
-
incompatible_change: wire('conflict', 409, false, 'The schema change is incompatible with the
|
|
390
|
+
incompatible_change: wire('conflict', 409, false, 'The schema change is incompatible with the schema currently deployed and cannot be applied as-is.'),
|
|
374
391
|
};
|
|
375
|
-
/**
|
|
376
|
-
* `policy:*` family and for any
|
|
377
|
-
*
|
|
392
|
+
/** Looks up the {@link ErrorCodeSpec} for a code. Returns `undefined` for the
|
|
393
|
+
* dynamic `policy:*` family and for any newer code this client does not yet
|
|
394
|
+
* recognize. */
|
|
378
395
|
export function errorCodeSpec(code) {
|
|
379
396
|
return ERROR_CODES[code];
|
|
380
397
|
}
|
|
381
|
-
/**
|
|
382
|
-
* default to non-retryable
|
|
398
|
+
/** Reports whether a code is marked retryable. Unknown and dynamic codes
|
|
399
|
+
* default to non-retryable, so an unrecognized failure is never retried
|
|
400
|
+
* automatically. */
|
|
383
401
|
export function isRetryableCode(code) {
|
|
384
402
|
return errorCodeSpec(code)?.retryable ?? false;
|
|
385
403
|
}
|
|
386
404
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
405
|
+
* Classifies a code into its {@link RecoveryClass} — the single value the
|
|
406
|
+
* connection layer and the network probe branch on to decide how to recover.
|
|
389
407
|
*
|
|
390
|
-
* The registry
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
* - retryable
|
|
394
|
-
* - 403
|
|
395
|
-
* -
|
|
396
|
-
* -
|
|
408
|
+
* The registry is the source of truth. An explicit `recovery` on the code's spec
|
|
409
|
+
* wins; it is set only on the few authentication codes whose remedy the HTTP
|
|
410
|
+
* status cannot reveal. Every other code is derived from its spec:
|
|
411
|
+
* - retryable → `transient`
|
|
412
|
+
* - HTTP 403 → `permission`
|
|
413
|
+
* - remaining `auth` category → `auth_blocked` (the credential-type 401s)
|
|
414
|
+
* - anything else, or unknown → `none`
|
|
397
415
|
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
416
|
+
* An unknown code, a dynamic `policy:*` code, or a code this client predates
|
|
417
|
+
* (no spec) defaults to `none`, the same safe default as {@link isRetryableCode}:
|
|
418
|
+
* an unrecognized code is never treated as a credential expiry or a sign-out.
|
|
401
419
|
*/
|
|
402
420
|
export function classifyRecovery(code) {
|
|
403
421
|
const spec = errorCodeSpec(code);
|
|
@@ -414,10 +432,9 @@ export function classifyRecovery(code) {
|
|
|
414
432
|
return 'none';
|
|
415
433
|
}
|
|
416
434
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* closed-union discipline `ERROR_CODES` itself uses via `satisfies`.)
|
|
435
|
+
* A compile-time exhaustiveness guard: it forces every {@link RecoveryClass} to
|
|
436
|
+
* be listed here, so adding a class to {@link RECOVERY_CLASSES} without deciding
|
|
437
|
+
* its meaning is a type error rather than a silent gap.
|
|
421
438
|
*/
|
|
422
439
|
const _RECOVERY_CLASS_EXHAUSTIVE = {
|
|
423
440
|
access_credential_expiry: true,
|