@abloatai/ablo 0.25.0 → 0.26.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 +20 -0
- package/README.md +3 -3
- package/dist/BaseSyncedStore.d.ts +72 -195
- package/dist/BaseSyncedStore.js +240 -625
- package/dist/Database.d.ts +8 -9
- package/dist/Database.js +9 -14
- package/dist/LazyReferenceCollection.js +5 -0
- package/dist/Model.js +6 -8
- package/dist/ModelRegistry.d.ts +25 -6
- package/dist/ModelRegistry.js +12 -6
- package/dist/NetworkMonitor.js +1 -1
- package/dist/ObjectPool.js +9 -3
- package/dist/SyncClient.d.ts +15 -5
- package/dist/SyncClient.js +152 -66
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/agent/Agent.d.ts +21 -8
- package/dist/agent/Agent.js +26 -13
- package/dist/ai-sdk/coordination-context.js +9 -3
- package/dist/ai-sdk/wrap.d.ts +1 -1
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +10 -2
- package/dist/auth/credentialSource.d.ts +1 -13
- package/dist/auth/credentialSource.js +6 -13
- package/dist/auth/index.js +3 -3
- package/dist/cli.cjs +328 -262
- package/dist/client/Ablo.d.ts +35 -764
- package/dist/client/Ablo.js +64 -688
- package/dist/client/ApiClient.d.ts +14 -1
- package/dist/client/ApiClient.js +63 -9
- package/dist/client/auth.d.ts +8 -17
- package/dist/client/auth.js +42 -6
- package/dist/client/consoleLogger.d.ts +36 -0
- package/dist/client/consoleLogger.js +45 -0
- package/dist/client/createInternalComponents.js +1 -1
- package/dist/client/createModelProxy.js +10 -6
- package/dist/client/credentialEndpoint.d.ts +63 -0
- package/dist/client/credentialEndpoint.js +87 -0
- package/dist/client/hostedEndpoints.d.ts +24 -0
- package/dist/client/hostedEndpoints.js +24 -0
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/identity.js +2 -1
- package/dist/client/modelRegistration.d.ts +14 -0
- package/dist/client/modelRegistration.js +321 -0
- package/dist/client/options.d.ts +396 -0
- package/dist/client/options.js +10 -0
- package/dist/client/resourceTypes.d.ts +344 -0
- package/dist/client/resourceTypes.js +13 -0
- package/dist/client/schemaConfig.d.ts +56 -0
- package/dist/client/schemaConfig.js +188 -0
- package/dist/client/sessionMint.d.ts +1 -1
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +33 -0
- package/dist/client/wsMutationExecutor.js +77 -0
- package/dist/context.js +0 -3
- package/dist/coordination/index.d.ts +8 -1
- package/dist/coordination/index.js +24 -1
- package/dist/coordination/trace.d.ts +0 -1
- package/dist/coordination/trace.js +0 -1
- package/dist/core/DatabaseManager.d.ts +0 -1
- package/dist/core/DatabaseManager.js +26 -24
- package/dist/core/QueryProcessor.js +7 -8
- package/dist/core/QueryView.d.ts +14 -2
- package/dist/core/QueryView.js +4 -5
- package/dist/core/StoreManager.d.ts +2 -2
- package/dist/core/StoreManager.js +5 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/openIDBWithTimeout.js +15 -11
- package/dist/core/query-utils.js +1 -1
- package/dist/core/storeContract.d.ts +143 -0
- package/dist/core/storeContract.js +16 -0
- package/dist/errorCodes.d.ts +11 -2
- package/dist/errorCodes.js +155 -137
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +14 -1
- package/dist/index.js +5 -0
- package/dist/interfaces/index.d.ts +6 -20
- package/dist/keys/index.js +18 -9
- package/dist/mutators/UndoManager.js +6 -1
- package/dist/mutators/defineMutators.d.ts +1 -3
- package/dist/policy/types.d.ts +1 -1
- package/dist/query/client.d.ts +11 -0
- package/dist/query/client.js +81 -52
- package/dist/react/AbloProvider.d.ts +6 -10
- package/dist/react/AbloProvider.js +22 -15
- package/dist/react/context.d.ts +2 -118
- package/dist/react/useAblo.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.js +7 -1
- package/dist/react/useReactive.js +0 -1
- package/dist/react/useUndoScope.js +2 -2
- package/dist/schema/ddlLock.d.ts +39 -0
- package/dist/schema/ddlLock.js +51 -0
- package/dist/schema/field.js +1 -1
- package/dist/schema/index.d.ts +4 -3
- package/dist/schema/index.js +15 -5
- package/dist/schema/model.d.ts +3 -3
- package/dist/schema/model.js +2 -2
- package/dist/schema/openapi.js +5 -1
- package/dist/schema/queries.d.ts +1 -1
- package/dist/schema/queries.js +1 -0
- package/dist/schema/residency.d.ts +35 -0
- package/dist/schema/residency.js +27 -0
- package/dist/schema/roles.d.ts +25 -0
- package/dist/schema/roles.js +37 -0
- package/dist/schema/schema.d.ts +3 -3
- package/dist/schema/schema.js +11 -7
- package/dist/schema/serialize.d.ts +8 -8
- package/dist/schema/sync-delta-row.d.ts +2 -2
- package/dist/schema/sync-delta-row.js +2 -2
- package/dist/server/storage-mode.d.ts +8 -1
- package/dist/server/storage-mode.js +14 -4
- package/dist/source/adapter.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +4 -3
- package/dist/source/adapters/kysely.js +4 -3
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.js +5 -4
- package/dist/source/conformance.js +10 -8
- package/dist/source/connector.js +4 -2
- package/dist/source/factory.d.ts +106 -0
- package/dist/source/factory.js +273 -0
- package/dist/source/index.d.ts +11 -461
- package/dist/source/index.js +14 -416
- package/dist/source/next.d.ts +1 -1
- package/dist/source/next.js +1 -1
- package/dist/source/pushQueue.d.ts +1 -1
- package/dist/source/pushQueue.js +6 -3
- package/dist/source/signing.d.ts +59 -0
- package/dist/source/signing.js +142 -0
- package/dist/source/types.d.ts +334 -0
- package/dist/source/types.js +44 -0
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/stores/ObjectStore.js +23 -23
- package/dist/stores/SyncActionStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.js +70 -35
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +1 -0
- package/dist/sync/AreaOfInterestManager.d.ts +2 -2
- package/dist/sync/AreaOfInterestManager.js +1 -1
- package/dist/sync/BootstrapHelper.d.ts +1 -3
- package/dist/sync/BootstrapHelper.js +29 -8
- package/dist/sync/ConnectionManager.d.ts +8 -0
- package/dist/sync/ConnectionManager.js +19 -4
- package/dist/sync/HydrationCoordinator.d.ts +12 -0
- package/dist/sync/HydrationCoordinator.js +25 -5
- package/dist/sync/NetworkProbe.d.ts +3 -2
- package/dist/sync/NetworkProbe.js +7 -6
- package/dist/sync/SyncWebSocket.d.ts +71 -120
- package/dist/sync/SyncWebSocket.js +324 -630
- package/dist/sync/awaitClaimGrant.js +27 -19
- package/dist/sync/bootstrapApply.d.ts +60 -0
- package/dist/sync/bootstrapApply.js +65 -0
- package/dist/sync/commitFrames.d.ts +43 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.js +3 -2
- package/dist/sync/createSnapshot.d.ts +1 -3
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +329 -0
- package/dist/sync/deltaPipeline.d.ts +111 -0
- package/dist/sync/deltaPipeline.js +262 -0
- package/dist/sync/groupChange.d.ts +111 -0
- package/dist/sync/groupChange.js +246 -0
- package/dist/sync/heartbeat.d.ts +62 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +8 -8
- package/dist/sync/syncCursor.d.ts +44 -0
- package/dist/sync/syncCursor.js +59 -0
- package/dist/sync/syncPlan.d.ts +62 -0
- package/dist/sync/syncPlan.js +51 -0
- package/dist/sync/wsFrameHandlers.d.ts +117 -0
- package/dist/sync/wsFrameHandlers.js +378 -0
- package/dist/testing/fixtures/bootstrap.d.ts +9 -11
- package/dist/testing/fixtures/deltas.d.ts +1 -1
- package/dist/testing/fixtures/models.js +1 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
- package/dist/testing/helpers/sync-engine-harness.js +2 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
- package/dist/testing/mocks/MockSyncContext.js +1 -32
- package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
- package/dist/transactions/TransactionQueue.d.ts +20 -56
- package/dist/transactions/TransactionQueue.js +101 -487
- package/dist/transactions/TransactionStore.d.ts +18 -0
- package/dist/transactions/TransactionStore.js +51 -0
- package/dist/transactions/coalesceRules.d.ts +34 -0
- package/dist/transactions/coalesceRules.js +117 -0
- package/dist/transactions/commitPayload.d.ts +134 -0
- package/dist/transactions/commitPayload.js +152 -0
- package/dist/transactions/deltaConfirmation.d.ts +60 -0
- package/dist/transactions/deltaConfirmation.js +223 -0
- package/dist/transactions/optimistic.d.ts +24 -0
- package/dist/transactions/optimistic.js +45 -0
- package/dist/transactions/persistedReplay.d.ts +93 -0
- package/dist/transactions/persistedReplay.js +105 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +0 -5
- package/dist/types/modelData.d.ts +12 -0
- package/dist/types/modelData.js +11 -0
- package/dist/types/participant.d.ts +21 -0
- package/dist/types/participant.js +12 -0
- package/dist/types/streams.d.ts +8 -14
- package/dist/utils/mobx-setup.d.ts +1 -1
- package/dist/utils/mobx-setup.js +2 -2
- package/dist/wire/errorEnvelope.d.ts +11 -2
- package/dist/wire/errorEnvelope.js +14 -2
- package/dist/wire/frames.d.ts +90 -0
- package/dist/wire/frames.js +55 -1
- package/dist/wire/index.d.ts +4 -1
- package/dist/wire/index.js +20 -1
- package/dist/wire/protocol.d.ts +45 -0
- package/dist/wire/protocol.js +45 -0
- package/dist/wire/protocolVersion.d.ts +56 -0
- package/dist/wire/protocolVersion.js +63 -0
- package/docs/api-keys.md +4 -3
- 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 +15 -13
- 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/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/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- 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/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
package/dist/utils/mobx-setup.js
CHANGED
|
@@ -21,7 +21,7 @@ export function M1(target, propertyMetadata, referenceMetadata) {
|
|
|
21
21
|
let obj = target;
|
|
22
22
|
while (obj) {
|
|
23
23
|
const descriptor = Object.getOwnPropertyDescriptor(obj, propName);
|
|
24
|
-
if (descriptor
|
|
24
|
+
if (descriptor?.get)
|
|
25
25
|
return true;
|
|
26
26
|
obj = Object.getPrototypeOf(obj);
|
|
27
27
|
if (obj === Object.prototype)
|
|
@@ -34,7 +34,7 @@ export function M1(target, propertyMetadata, referenceMetadata) {
|
|
|
34
34
|
let obj = target;
|
|
35
35
|
while (obj) {
|
|
36
36
|
const descriptor = Object.getOwnPropertyDescriptor(obj, propName);
|
|
37
|
-
if (descriptor
|
|
37
|
+
if (descriptor?.set)
|
|
38
38
|
return true;
|
|
39
39
|
obj = Object.getPrototypeOf(obj);
|
|
40
40
|
if (obj === Object.prototype)
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The constant public message for an unclassified 500. Mirrors
|
|
3
|
+
* `apps/sync-server/src/errors.ts`'s `INTERNAL_ERROR_PUBLIC_MESSAGE` —
|
|
4
|
+
* the parity test in apps/sync-server pins the two producers together. A raw
|
|
5
|
+
* `err.message` (driver text, connection strings, stack fragments) must never
|
|
6
|
+
* be the wire message; callers that need the detail log the original error
|
|
7
|
+
* server-side before/at the envelope call.
|
|
8
|
+
*/
|
|
9
|
+
export declare const INTERNAL_ERROR_PUBLIC_MESSAGE = "An internal error occurred.";
|
|
1
10
|
/** The canonical wire envelope — Stripe's error-object shape. Every HTTP error
|
|
2
11
|
* response and every structured frame error carries this exact set of keys,
|
|
3
12
|
* regardless of which route or transport produced it. */
|
|
@@ -12,11 +21,11 @@ export interface ErrorEnvelope {
|
|
|
12
21
|
* at once (schema push, batch commit, CLI-arg validation) instead of failing
|
|
13
22
|
* on the first. `param` stays the single-field convenience case. (RFC 9457
|
|
14
23
|
* `errors[]` / JSON:API `errors[]` / Google `BadRequest.fieldViolations[]`.) */
|
|
15
|
-
readonly errors?:
|
|
24
|
+
readonly errors?: readonly {
|
|
16
25
|
readonly code?: string;
|
|
17
26
|
readonly message: string;
|
|
18
27
|
readonly param?: string;
|
|
19
|
-
}
|
|
28
|
+
}[];
|
|
20
29
|
/** Typed-details slot: `AbloError.toJSON()` spreads its `details` (e.g.
|
|
21
30
|
* `missingIds`, `conflicts`, `retryAfterSeconds`) as top-level members.
|
|
22
31
|
* Consumers MUST ignore members they don't recognize (forward-compat). */
|
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { AbloError, docUrlForCode } from '../errors.js';
|
|
20
20
|
import { errorCodeSpec } from '../errorCodes.js';
|
|
21
|
+
/**
|
|
22
|
+
* The constant public message for an unclassified 500. Mirrors
|
|
23
|
+
* `apps/sync-server/src/errors.ts`'s `INTERNAL_ERROR_PUBLIC_MESSAGE` —
|
|
24
|
+
* the parity test in apps/sync-server pins the two producers together. A raw
|
|
25
|
+
* `err.message` (driver text, connection strings, stack fragments) must never
|
|
26
|
+
* be the wire message; callers that need the detail log the original error
|
|
27
|
+
* server-side before/at the envelope call.
|
|
28
|
+
*/
|
|
29
|
+
export const INTERNAL_ERROR_PUBLIC_MESSAGE = 'An internal error occurred.';
|
|
21
30
|
/** {@link AbloError} subclass → default HTTP status. The subclass is chosen to
|
|
22
31
|
* match status semantics (a validation error is a 400, a permission error a
|
|
23
32
|
* 403), so a throw site only picks the right class + code and the status
|
|
@@ -72,12 +81,15 @@ export function errorEnvelope(err, requestId) {
|
|
|
72
81
|
status,
|
|
73
82
|
};
|
|
74
83
|
}
|
|
75
|
-
|
|
84
|
+
// Unknown throw → mask. The server copy deliberately never echoes a raw
|
|
85
|
+
// message on an unclassified 500 (it can carry pg/driver/internal detail);
|
|
86
|
+
// this producer must not either — sync-web's dashboard routes serve THIS
|
|
87
|
+
// envelope to browsers. The raw error stays with the caller for logging.
|
|
76
88
|
return {
|
|
77
89
|
body: {
|
|
78
90
|
type: 'AbloServerError',
|
|
79
91
|
code: 'internal_error',
|
|
80
|
-
message,
|
|
92
|
+
message: INTERNAL_ERROR_PUBLIC_MESSAGE,
|
|
81
93
|
doc_url: docUrlForCode('internal_error'),
|
|
82
94
|
...(requestId ? { request_id: requestId } : {}),
|
|
83
95
|
},
|
package/dist/wire/frames.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* Changing any shape here is a wire-contract change — it requires
|
|
19
19
|
* coordinated client + server updates.
|
|
20
20
|
*/
|
|
21
|
+
import { z } from 'zod';
|
|
21
22
|
import type { OnStaleMode, StaleNotification, ReadDependency } from '../coordination/index.js';
|
|
22
23
|
import type { ErrorCode, RequiredCapability } from '../errors.js';
|
|
23
24
|
/**
|
|
@@ -50,7 +51,47 @@ export interface CommitOperation {
|
|
|
50
51
|
* to resolve.
|
|
51
52
|
*/
|
|
52
53
|
onStale?: OnStaleMode | null;
|
|
54
|
+
/**
|
|
55
|
+
* Write even if another participant holds a claim on this entity. The
|
|
56
|
+
* default (`false`) rejects with `AbloClaimedError` when claimed — `bypass`
|
|
57
|
+
* is the explicit, recorded override, honored only for participants the
|
|
58
|
+
* claim guard trusts (humans / framework identities; agent `bypass` is
|
|
59
|
+
* ignored). Previously honored by the server's commit executor without
|
|
60
|
+
* being declared here — the exact contract drift this file exists to
|
|
61
|
+
* prevent.
|
|
62
|
+
*/
|
|
63
|
+
bypass?: boolean | null;
|
|
53
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Runtime validator for {@link CommitOperation} — the per-op ingest gate both
|
|
67
|
+
* commit transports (WS `commit` frame, HTTP `/v1/commits`) run before an
|
|
68
|
+
* operation reaches the executor. Extends the canonical coordination-layer
|
|
69
|
+
* schema (writeGuard `readAt`/`onStale`/`bypass` + op identity), widening only
|
|
70
|
+
* `bypass` to `nullish` to match the interface (`boolean | null`).
|
|
71
|
+
*
|
|
72
|
+
* `readAt` is `z.number()` — a string watermark previously flowed into the
|
|
73
|
+
* stale-guard SQL (`id > $3`) unvalidated.
|
|
74
|
+
*/
|
|
75
|
+
export declare const commitOperationSchema: z.ZodObject<{
|
|
76
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
77
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
78
|
+
reject: "reject";
|
|
79
|
+
overwrite: "overwrite";
|
|
80
|
+
notify: "notify";
|
|
81
|
+
}>>>;
|
|
82
|
+
type: z.ZodEnum<{
|
|
83
|
+
CREATE: "CREATE";
|
|
84
|
+
UPDATE: "UPDATE";
|
|
85
|
+
DELETE: "DELETE";
|
|
86
|
+
ARCHIVE: "ARCHIVE";
|
|
87
|
+
UNARCHIVE: "UNARCHIVE";
|
|
88
|
+
}>;
|
|
89
|
+
model: z.ZodString;
|
|
90
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
91
|
+
input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
92
|
+
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
93
|
+
bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
94
|
+
}, z.core.$strip>;
|
|
54
95
|
/**
|
|
55
96
|
* Client → Server single named-mutation frame. The named-mutator write
|
|
56
97
|
* primitive (claim + args), as opposed to the raw-op {@link CommitMessage}
|
|
@@ -96,6 +137,55 @@ export interface CommitMessage {
|
|
|
96
137
|
reads?: ReadDependency[] | null;
|
|
97
138
|
};
|
|
98
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Runtime validator for {@link CommitMessage}'s payload — every field the
|
|
142
|
+
* server's commit path actually honors (`operations`, `clientTxId`,
|
|
143
|
+
* `causedByTaskId`, `reads`), each entry validated by
|
|
144
|
+
* {@link commitOperationSchema} / the canonical `readDependencySchema`.
|
|
145
|
+
*/
|
|
146
|
+
export declare const commitPayloadSchema: z.ZodObject<{
|
|
147
|
+
operations: z.ZodArray<z.ZodObject<{
|
|
148
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
149
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
150
|
+
reject: "reject";
|
|
151
|
+
overwrite: "overwrite";
|
|
152
|
+
notify: "notify";
|
|
153
|
+
}>>>;
|
|
154
|
+
type: z.ZodEnum<{
|
|
155
|
+
CREATE: "CREATE";
|
|
156
|
+
UPDATE: "UPDATE";
|
|
157
|
+
DELETE: "DELETE";
|
|
158
|
+
ARCHIVE: "ARCHIVE";
|
|
159
|
+
UNARCHIVE: "UNARCHIVE";
|
|
160
|
+
}>;
|
|
161
|
+
model: z.ZodString;
|
|
162
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
163
|
+
input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
164
|
+
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
165
|
+
bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
166
|
+
}, z.core.$strip>>;
|
|
167
|
+
clientTxId: z.ZodString;
|
|
168
|
+
causedByTaskId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
169
|
+
reads: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
170
|
+
model: z.ZodString;
|
|
171
|
+
id: z.ZodString;
|
|
172
|
+
readAt: z.ZodNumber;
|
|
173
|
+
fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
174
|
+
onStale: z.ZodOptional<z.ZodEnum<{
|
|
175
|
+
reject: "reject";
|
|
176
|
+
overwrite: "overwrite";
|
|
177
|
+
notify: "notify";
|
|
178
|
+
}>>;
|
|
179
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
180
|
+
group: z.ZodString;
|
|
181
|
+
readAt: z.ZodNumber;
|
|
182
|
+
onStale: z.ZodOptional<z.ZodEnum<{
|
|
183
|
+
reject: "reject";
|
|
184
|
+
overwrite: "overwrite";
|
|
185
|
+
notify: "notify";
|
|
186
|
+
}>>;
|
|
187
|
+
}, z.core.$strip>]>>>>;
|
|
188
|
+
}, z.core.$strip>;
|
|
99
189
|
/**
|
|
100
190
|
* Wire ack for a `commit` frame. Payload mirrors the canonical
|
|
101
191
|
* `CommitReceipt` shape so WebSocket, HTTP `/v1/commits`, and persisted
|
package/dist/wire/frames.js
CHANGED
|
@@ -1 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `@abloatai/ablo/wire` — canonical COMMIT-PATH frame contract.
|
|
3
|
+
*
|
|
4
|
+
* These are the WebSocket (and HTTP-fallback) message shapes for the
|
|
5
|
+
* write path: the client's `commit` / `mutation` frames and the server's
|
|
6
|
+
* `mutation_result` ack. They live here — not in the server app and not
|
|
7
|
+
* inlined in the SDK's `SyncWebSocket` — so the client, the server, and
|
|
8
|
+
* any future `@abloatai/ablo/server` host all import ONE definition
|
|
9
|
+
* and cannot drift.
|
|
10
|
+
*
|
|
11
|
+
* Scope note: the delta/sync frames (`sync_response`, `delta`) are NOT
|
|
12
|
+
* here yet — they reference `SyncDelta`, which currently has two
|
|
13
|
+
* definitions (server `db/deltas` vs package `core`) pending unification.
|
|
14
|
+
* They stay server-local until that lands. Everything in this file
|
|
15
|
+
* depends only on package-canonical types (`OnStaleMode`, `ErrorCode`,
|
|
16
|
+
* `RequiredCapability`), so it is safe to share today.
|
|
17
|
+
*
|
|
18
|
+
* Changing any shape here is a wire-contract change — it requires
|
|
19
|
+
* coordinated client + server updates.
|
|
20
|
+
*/
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
// Runtime schema primitives come from the coordination LEAF module (a pure
|
|
23
|
+
// zod file), not the barrel — keeps `wire/` lean (zod-leaf runtime deps only).
|
|
24
|
+
import { commitOperationSchema as coordinationCommitOperationSchema, readDependencySchema, } from '../coordination/schema.js';
|
|
25
|
+
/**
|
|
26
|
+
* Runtime validator for {@link CommitOperation} — the per-op ingest gate both
|
|
27
|
+
* commit transports (WS `commit` frame, HTTP `/v1/commits`) run before an
|
|
28
|
+
* operation reaches the executor. Extends the canonical coordination-layer
|
|
29
|
+
* schema (writeGuard `readAt`/`onStale`/`bypass` + op identity), widening only
|
|
30
|
+
* `bypass` to `nullish` to match the interface (`boolean | null`).
|
|
31
|
+
*
|
|
32
|
+
* `readAt` is `z.number()` — a string watermark previously flowed into the
|
|
33
|
+
* stale-guard SQL (`id > $3`) unvalidated.
|
|
34
|
+
*/
|
|
35
|
+
export const commitOperationSchema = coordinationCommitOperationSchema.extend({
|
|
36
|
+
bypass: z.boolean().nullish(),
|
|
37
|
+
});
|
|
38
|
+
// z.infer-bound: the schema and the interface cannot drift in either direction.
|
|
39
|
+
const _commitOperationContract = true;
|
|
40
|
+
void _commitOperationContract;
|
|
41
|
+
/**
|
|
42
|
+
* Runtime validator for {@link CommitMessage}'s payload — every field the
|
|
43
|
+
* server's commit path actually honors (`operations`, `clientTxId`,
|
|
44
|
+
* `causedByTaskId`, `reads`), each entry validated by
|
|
45
|
+
* {@link commitOperationSchema} / the canonical `readDependencySchema`.
|
|
46
|
+
*/
|
|
47
|
+
export const commitPayloadSchema = z.object({
|
|
48
|
+
operations: z.array(commitOperationSchema),
|
|
49
|
+
clientTxId: z.string(),
|
|
50
|
+
causedByTaskId: z.string().nullish(),
|
|
51
|
+
reads: z.array(readDependencySchema).nullish(),
|
|
52
|
+
});
|
|
53
|
+
// z.infer-bound: payload schema and CommitMessage['payload'] cannot drift.
|
|
54
|
+
const _commitPayloadContract = true;
|
|
55
|
+
void _commitPayloadContract;
|
package/dist/wire/index.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export { errorEnvelope, statusForType } from './errorEnvelope.js';
|
|
|
19
19
|
export type { ErrorEnvelope } from './errorEnvelope.js';
|
|
20
20
|
export { listEnvelope } from './listEnvelope.js';
|
|
21
21
|
export type { ListEnvelope } from './listEnvelope.js';
|
|
22
|
+
export { commitOperationSchema, commitPayloadSchema } from './frames.js';
|
|
23
|
+
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, } from './protocolVersion.js';
|
|
22
24
|
export type { CommitOperation, MutationMessage, CommitMessage, MutationResultMessage, } from './frames.js';
|
|
23
|
-
export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION, } from '../errors.js';
|
|
25
|
+
export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION, errorCodeSpec, } from '../errors.js';
|
|
24
26
|
export type { ErrorCode, WireErrorCode } from '../errors.js';
|
|
27
|
+
export { PING_INTERVAL_MS, LEASE_TTL_MS, WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL, } from './protocol.js';
|
package/dist/wire/index.js
CHANGED
|
@@ -17,5 +17,24 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export { errorEnvelope, statusForType } from './errorEnvelope.js';
|
|
19
19
|
export { listEnvelope } from './listEnvelope.js';
|
|
20
|
+
// Commit-path frame contract — the canonical write-path message shapes shared
|
|
21
|
+
// by the SDK client, the sync-server, and any `@abloatai/ablo/server` host.
|
|
22
|
+
// The runtime Zod validators live beside the interfaces (z.infer-bound so the
|
|
23
|
+
// two cannot drift) — the per-op / per-payload ingest gates for both commit
|
|
24
|
+
// transports.
|
|
25
|
+
export { commitOperationSchema, commitPayloadSchema } from './frames.js';
|
|
26
|
+
// Protocol versioning — the one integer client and server compare to know
|
|
27
|
+
// they can speak, plus the typed WS rejection close code. See the module's
|
|
28
|
+
// changelog + deploy contract.
|
|
29
|
+
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, } from './protocolVersion.js';
|
|
20
30
|
// The error surface a wire consumer needs to throw, classify, and serialize.
|
|
21
|
-
export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION,
|
|
31
|
+
export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION,
|
|
32
|
+
// The code→{httpStatus,retryable} registry table — dependency-free data a
|
|
33
|
+
// server needs to resolve a code's canonical status exactly like the SDK's
|
|
34
|
+
// wire producer does (pinned by the sync-server envelope parity test).
|
|
35
|
+
errorCodeSpec, } from '../errors.js';
|
|
36
|
+
// Protocol timing constants — the 30s ping cadence + the 3×-ping claim/
|
|
37
|
+
// presence lease window shared by the SDK heartbeat, the Hub keepalive,
|
|
38
|
+
// the claim coordinator, and the presence reaper (see protocol.ts) — plus
|
|
39
|
+
// the WS auth-handshake subprotocols shared by SyncWebSocket and the Hub.
|
|
40
|
+
export { PING_INTERVAL_MS, LEASE_TTL_MS, WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL, } from './protocol.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-boundary protocol TIMING constants — the one place the 30s ping
|
|
3
|
+
* cadence and the claim/presence lease window are defined. Before this leaf
|
|
4
|
+
* existed the pair was copy-pasted across five sites in four modules, kept
|
|
5
|
+
* in sync only by comments ("~3× the 30s ping"); changing the server ping
|
|
6
|
+
* silently skewed the SDK's claim-expiry estimate and presence reaping.
|
|
7
|
+
*
|
|
8
|
+
* Consumers (SDK side, relative import):
|
|
9
|
+
* - `sync/heartbeat.ts` — `HEARTBEAT_INTERVAL_MS`, the SDK's
|
|
10
|
+
* application-level `{ type: 'ping' }` cadence.
|
|
11
|
+
* - `client/createModelProxy.ts` — `DEFAULT_LEASE_TTL_MS`, the client's
|
|
12
|
+
* expiry estimate for a claim taken without an explicit TTL.
|
|
13
|
+
*
|
|
14
|
+
* Consumers (server side, via `@abloatai/ablo/wire`):
|
|
15
|
+
* - `apps/sync-server/src/hub/Hub.ts` — the RFC 6455 `ws.ping()`
|
|
16
|
+
* keepalive interval (the tick that renews claim leases).
|
|
17
|
+
* - `apps/sync-server/src/hub/claimCoordinator.ts` —
|
|
18
|
+
* `LEASE_RENEW_TTL_MS`, the lease lifetime granted per keepalive tick.
|
|
19
|
+
* - `apps/sync-server/src/presence/PresenceStore.ts` — the default
|
|
20
|
+
* presence-entry TTL (a silently-dead client leaves the roster within
|
|
21
|
+
* one lease window).
|
|
22
|
+
*
|
|
23
|
+
* INVARIANT: `LEASE_TTL_MS === 3 * PING_INTERVAL_MS`. The lease is renewed
|
|
24
|
+
* on every ping, so a live holder always has ≥ 2 ping intervals of runway,
|
|
25
|
+
* and a silent one lapses ~2 missed pings after it stops renewing. TTL is
|
|
26
|
+
* liveness, not work-duration — never widen the lease without widening the
|
|
27
|
+
* ping (or holders will flap), and never derive either value locally.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PING_INTERVAL_MS = 30000;
|
|
30
|
+
export declare const LEASE_TTL_MS: number;
|
|
31
|
+
/**
|
|
32
|
+
* WebSocket subprotocols used to carry the bearer credential OUT of the URL.
|
|
33
|
+
*
|
|
34
|
+
* Browsers cannot set an `Authorization` header on a WebSocket, so the SDK
|
|
35
|
+
* offers the token as a `Sec-WebSocket-Protocol` value — `ablo.bearer.<token>` —
|
|
36
|
+
* alongside the real `ablo.sync.v1` protocol the server selects. This keeps the
|
|
37
|
+
* credential out of the query string, which ALB access logs, proxies, and
|
|
38
|
+
* browser history capture. The server reads the token from the subprotocol and
|
|
39
|
+
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Lives in
|
|
40
|
+
* `wire/` (not `auth/`) because it IS the wire contract: client and server
|
|
41
|
+
* import the same constants so the handshake format can never drift.
|
|
42
|
+
* Re-exported from `auth/credentialSource.ts` for existing SDK importers.
|
|
43
|
+
*/
|
|
44
|
+
export declare const WS_BEARER_SUBPROTOCOL_PREFIX = "ablo.bearer.";
|
|
45
|
+
export declare const WS_SYNC_SUBPROTOCOL = "ablo.sync.v1";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-boundary protocol TIMING constants — the one place the 30s ping
|
|
3
|
+
* cadence and the claim/presence lease window are defined. Before this leaf
|
|
4
|
+
* existed the pair was copy-pasted across five sites in four modules, kept
|
|
5
|
+
* in sync only by comments ("~3× the 30s ping"); changing the server ping
|
|
6
|
+
* silently skewed the SDK's claim-expiry estimate and presence reaping.
|
|
7
|
+
*
|
|
8
|
+
* Consumers (SDK side, relative import):
|
|
9
|
+
* - `sync/heartbeat.ts` — `HEARTBEAT_INTERVAL_MS`, the SDK's
|
|
10
|
+
* application-level `{ type: 'ping' }` cadence.
|
|
11
|
+
* - `client/createModelProxy.ts` — `DEFAULT_LEASE_TTL_MS`, the client's
|
|
12
|
+
* expiry estimate for a claim taken without an explicit TTL.
|
|
13
|
+
*
|
|
14
|
+
* Consumers (server side, via `@abloatai/ablo/wire`):
|
|
15
|
+
* - `apps/sync-server/src/hub/Hub.ts` — the RFC 6455 `ws.ping()`
|
|
16
|
+
* keepalive interval (the tick that renews claim leases).
|
|
17
|
+
* - `apps/sync-server/src/hub/claimCoordinator.ts` —
|
|
18
|
+
* `LEASE_RENEW_TTL_MS`, the lease lifetime granted per keepalive tick.
|
|
19
|
+
* - `apps/sync-server/src/presence/PresenceStore.ts` — the default
|
|
20
|
+
* presence-entry TTL (a silently-dead client leaves the roster within
|
|
21
|
+
* one lease window).
|
|
22
|
+
*
|
|
23
|
+
* INVARIANT: `LEASE_TTL_MS === 3 * PING_INTERVAL_MS`. The lease is renewed
|
|
24
|
+
* on every ping, so a live holder always has ≥ 2 ping intervals of runway,
|
|
25
|
+
* and a silent one lapses ~2 missed pings after it stops renewing. TTL is
|
|
26
|
+
* liveness, not work-duration — never widen the lease without widening the
|
|
27
|
+
* ping (or holders will flap), and never derive either value locally.
|
|
28
|
+
*/
|
|
29
|
+
export const PING_INTERVAL_MS = 30_000;
|
|
30
|
+
export const LEASE_TTL_MS = 3 * PING_INTERVAL_MS;
|
|
31
|
+
/**
|
|
32
|
+
* WebSocket subprotocols used to carry the bearer credential OUT of the URL.
|
|
33
|
+
*
|
|
34
|
+
* Browsers cannot set an `Authorization` header on a WebSocket, so the SDK
|
|
35
|
+
* offers the token as a `Sec-WebSocket-Protocol` value — `ablo.bearer.<token>` —
|
|
36
|
+
* alongside the real `ablo.sync.v1` protocol the server selects. This keeps the
|
|
37
|
+
* credential out of the query string, which ALB access logs, proxies, and
|
|
38
|
+
* browser history capture. The server reads the token from the subprotocol and
|
|
39
|
+
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Lives in
|
|
40
|
+
* `wire/` (not `auth/`) because it IS the wire contract: client and server
|
|
41
|
+
* import the same constants so the handshake format can never drift.
|
|
42
|
+
* Re-exported from `auth/credentialSource.ts` for existing SDK importers.
|
|
43
|
+
*/
|
|
44
|
+
export const WS_BEARER_SUBPROTOCOL_PREFIX = 'ablo.bearer.';
|
|
45
|
+
export const WS_SYNC_SUBPROTOCOL = 'ablo.sync.v1';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sync protocol version — ONE monotonically increasing integer covering
|
|
3
|
+
* everything client and server must agree on to speak: WS frame shapes
|
|
4
|
+
* (hub `ClientMessage`/`ServerMessage`), HTTP request/response envelopes, and
|
|
5
|
+
* the persisted delta encodings a client replays. Zero's recipe: schemaHash
|
|
6
|
+
* (WS close 4009) only detects APP-schema drift; this detects PROTOCOL drift —
|
|
7
|
+
* before it, every main-push deploy was an old-client/new-server encounter
|
|
8
|
+
* with no detector at all.
|
|
9
|
+
*
|
|
10
|
+
* Deploy contract: SERVER DEPLOYS FIRST. The server accepts every version in
|
|
11
|
+
* `[MIN_SUPPORTED_PROTOCOL_VERSION, PROTOCOL_VERSION]`; a client never
|
|
12
|
+
* connects to a server older than itself (if it does — a rollback mid-fleet —
|
|
13
|
+
* the too-new rejection below makes it visible instead of undefined behavior).
|
|
14
|
+
*
|
|
15
|
+
* How to change the protocol:
|
|
16
|
+
* 1. Make the wire change backward-tolerant where possible (the server's
|
|
17
|
+
* `clientMessageSchema` accepts-and-ignores unknown payload keys — an
|
|
18
|
+
* ADDITIVE field usually needs NO version bump).
|
|
19
|
+
* 2. For a breaking change: bump `PROTOCOL_VERSION`, append a changelog
|
|
20
|
+
* entry below, and keep `MIN_SUPPORTED_PROTOCOL_VERSION` covering every
|
|
21
|
+
* SDK version still in the wild; raise it only with a deprecation window.
|
|
22
|
+
* 3. The contract test (`__tests__/protocolVersion.test.ts`) fails on any
|
|
23
|
+
* bump — update it in the same change, deliberately.
|
|
24
|
+
*
|
|
25
|
+
* CHANGELOG
|
|
26
|
+
* v1 (2026-07-03) — the protocol as of the version field's introduction:
|
|
27
|
+
* sync_request{cursor,lastSyncId,capabilities,protocolVersion?},
|
|
28
|
+
* commit/mutation/claim/release/ack/presence_update frames, bootstrap +
|
|
29
|
+
* delta batches, HTTP envelopes per `wire/errorEnvelope` +
|
|
30
|
+
* `wire/listEnvelope`. Clients that predate the field send NO
|
|
31
|
+
* `protocolVersion` — treated as v1 (the field's introduction changed no
|
|
32
|
+
* semantics).
|
|
33
|
+
*/
|
|
34
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
35
|
+
/**
|
|
36
|
+
* Oldest client protocol this build still serves. Raising it is a BREAKING
|
|
37
|
+
* cut for un-upgraded clients — do it only with a deprecation window and a
|
|
38
|
+
* changelog entry.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MIN_SUPPORTED_PROTOCOL_VERSION = 1;
|
|
41
|
+
/**
|
|
42
|
+
* WS application close code for a protocol-version rejection (4001 =
|
|
43
|
+
* credential, 4009 = app-schema drift). The reason string is the error code
|
|
44
|
+
* `protocol_version_unsupported`; the SDK treats this close as TERMINAL —
|
|
45
|
+
* reconnecting cannot heal a version mismatch, upgrading the SDK (or rolling
|
|
46
|
+
* the server forward) can.
|
|
47
|
+
*/
|
|
48
|
+
export declare const WS_CLOSE_PROTOCOL_VERSION = 4010;
|
|
49
|
+
/**
|
|
50
|
+
* Classify a peer's announced protocol version. `undefined` (a pre-versioning
|
|
51
|
+
* client) is v1 by definition. Non-integer garbage classifies as `too_old` —
|
|
52
|
+
* fail closed, visibly.
|
|
53
|
+
*/
|
|
54
|
+
export declare function protocolVersionProblem(announced: number | undefined): 'too_old' | 'too_new' | null;
|
|
55
|
+
/** HTTP request header carrying the client's protocol version. */
|
|
56
|
+
export declare const PROTOCOL_VERSION_HEADER = "Ablo-Protocol-Version";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sync protocol version — ONE monotonically increasing integer covering
|
|
3
|
+
* everything client and server must agree on to speak: WS frame shapes
|
|
4
|
+
* (hub `ClientMessage`/`ServerMessage`), HTTP request/response envelopes, and
|
|
5
|
+
* the persisted delta encodings a client replays. Zero's recipe: schemaHash
|
|
6
|
+
* (WS close 4009) only detects APP-schema drift; this detects PROTOCOL drift —
|
|
7
|
+
* before it, every main-push deploy was an old-client/new-server encounter
|
|
8
|
+
* with no detector at all.
|
|
9
|
+
*
|
|
10
|
+
* Deploy contract: SERVER DEPLOYS FIRST. The server accepts every version in
|
|
11
|
+
* `[MIN_SUPPORTED_PROTOCOL_VERSION, PROTOCOL_VERSION]`; a client never
|
|
12
|
+
* connects to a server older than itself (if it does — a rollback mid-fleet —
|
|
13
|
+
* the too-new rejection below makes it visible instead of undefined behavior).
|
|
14
|
+
*
|
|
15
|
+
* How to change the protocol:
|
|
16
|
+
* 1. Make the wire change backward-tolerant where possible (the server's
|
|
17
|
+
* `clientMessageSchema` accepts-and-ignores unknown payload keys — an
|
|
18
|
+
* ADDITIVE field usually needs NO version bump).
|
|
19
|
+
* 2. For a breaking change: bump `PROTOCOL_VERSION`, append a changelog
|
|
20
|
+
* entry below, and keep `MIN_SUPPORTED_PROTOCOL_VERSION` covering every
|
|
21
|
+
* SDK version still in the wild; raise it only with a deprecation window.
|
|
22
|
+
* 3. The contract test (`__tests__/protocolVersion.test.ts`) fails on any
|
|
23
|
+
* bump — update it in the same change, deliberately.
|
|
24
|
+
*
|
|
25
|
+
* CHANGELOG
|
|
26
|
+
* v1 (2026-07-03) — the protocol as of the version field's introduction:
|
|
27
|
+
* sync_request{cursor,lastSyncId,capabilities,protocolVersion?},
|
|
28
|
+
* commit/mutation/claim/release/ack/presence_update frames, bootstrap +
|
|
29
|
+
* delta batches, HTTP envelopes per `wire/errorEnvelope` +
|
|
30
|
+
* `wire/listEnvelope`. Clients that predate the field send NO
|
|
31
|
+
* `protocolVersion` — treated as v1 (the field's introduction changed no
|
|
32
|
+
* semantics).
|
|
33
|
+
*/
|
|
34
|
+
export const PROTOCOL_VERSION = 1;
|
|
35
|
+
/**
|
|
36
|
+
* Oldest client protocol this build still serves. Raising it is a BREAKING
|
|
37
|
+
* cut for un-upgraded clients — do it only with a deprecation window and a
|
|
38
|
+
* changelog entry.
|
|
39
|
+
*/
|
|
40
|
+
export const MIN_SUPPORTED_PROTOCOL_VERSION = 1;
|
|
41
|
+
/**
|
|
42
|
+
* WS application close code for a protocol-version rejection (4001 =
|
|
43
|
+
* credential, 4009 = app-schema drift). The reason string is the error code
|
|
44
|
+
* `protocol_version_unsupported`; the SDK treats this close as TERMINAL —
|
|
45
|
+
* reconnecting cannot heal a version mismatch, upgrading the SDK (or rolling
|
|
46
|
+
* the server forward) can.
|
|
47
|
+
*/
|
|
48
|
+
export const WS_CLOSE_PROTOCOL_VERSION = 4010;
|
|
49
|
+
/**
|
|
50
|
+
* Classify a peer's announced protocol version. `undefined` (a pre-versioning
|
|
51
|
+
* client) is v1 by definition. Non-integer garbage classifies as `too_old` —
|
|
52
|
+
* fail closed, visibly.
|
|
53
|
+
*/
|
|
54
|
+
export function protocolVersionProblem(announced) {
|
|
55
|
+
const v = announced ?? 1;
|
|
56
|
+
if (!Number.isInteger(v) || v < MIN_SUPPORTED_PROTOCOL_VERSION)
|
|
57
|
+
return 'too_old';
|
|
58
|
+
if (v > PROTOCOL_VERSION)
|
|
59
|
+
return 'too_new';
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/** HTTP request header carrying the client's protocol version. */
|
|
63
|
+
export const PROTOCOL_VERSION_HEADER = 'Ablo-Protocol-Version';
|
package/docs/api-keys.md
CHANGED
|
@@ -21,7 +21,7 @@ Pick your row:
|
|
|
21
21
|
|---|---|---|
|
|
22
22
|
| **Server / worker / CLI** (can hold a secret) | your secret `sk_` — it defaults to `ABLO_API_KEY`, so usually pass **nothing** | `Ablo({ schema })` |
|
|
23
23
|
| **Browser — read-only** | a publishable `pk_` (safe to ship, like a Stripe `pk_`) | `Ablo({ schema, apiKey: process.env.NEXT_PUBLIC_ABLO_PUBLISHABLE_KEY })` |
|
|
24
|
-
| **Browser — writing as the signed-in user** |
|
|
24
|
+
| **Browser — writing as the signed-in user** | `authEndpoint` — the route on your own backend that mints a short-lived per-user token | `Ablo({ schema, authEndpoint: '/api/ablo-session' })` |
|
|
25
25
|
|
|
26
26
|
That's the whole story: one knob, filled by audience.
|
|
27
27
|
|
|
@@ -48,8 +48,9 @@ session model. For a read-only app you don't need any of this — just the `pk_`
|
|
|
48
48
|
|
|
49
49
|
Server-side, because `apiKey` defaults to `process.env.ABLO_API_KEY`, most backend and agent
|
|
50
50
|
code passes nothing. The secret `sk_` (and `databaseUrl`) are **server-only** — never in a
|
|
51
|
-
browser bundle. There is no `getToken
|
|
52
|
-
|
|
51
|
+
browser bundle. There is no `getToken` or `as` option — `apiKey` (the key a server holds)
|
|
52
|
+
and `authEndpoint` (the mint route a browser points at) are the two credential
|
|
53
|
+
knobs, and you set exactly one.
|
|
53
54
|
|
|
54
55
|
### Minting per-user / agent tokens (server-side, with your `sk_`)
|
|
55
56
|
|
|
@@ -67,11 +67,11 @@ import Ablo from '@abloatai/ablo';
|
|
|
67
67
|
import { AbloProvider } from '@abloatai/ablo/react';
|
|
68
68
|
import { schema } from '@/ablo/schema';
|
|
69
69
|
|
|
70
|
-
// Browser client: no secret key —
|
|
71
|
-
//
|
|
70
|
+
// Browser client: no secret key — `authEndpoint` points at the session route
|
|
71
|
+
// your server exposes (below); the SDK fetches and refreshes the token.
|
|
72
72
|
const ablo = Ablo({
|
|
73
73
|
schema,
|
|
74
|
-
|
|
74
|
+
authEndpoint: '/api/ablo-session',
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
export function Providers({ children }: { children: React.ReactNode }) {
|
package/docs/identity.md
CHANGED
|
@@ -359,10 +359,10 @@ import { schema } from '@/ablo/schema';
|
|
|
359
359
|
export function makeAblo(user: { teamIds: string[] }) {
|
|
360
360
|
return Ablo({
|
|
361
361
|
schema,
|
|
362
|
-
// The browser holds no secret —
|
|
363
|
-
// short-lived session token
|
|
364
|
-
//
|
|
365
|
-
|
|
362
|
+
// The browser holds no secret — `authEndpoint` points at the route that
|
|
363
|
+
// mints the short-lived session token, and the client keeps it fresh
|
|
364
|
+
// before expiry.
|
|
365
|
+
authEndpoint: '/api/ablo-session',
|
|
366
366
|
teamIds: user.teamIds,
|
|
367
367
|
});
|
|
368
368
|
}
|
|
@@ -190,7 +190,7 @@ import { schema } from '@/ablo/schema';
|
|
|
190
190
|
// from your session route (see below) and refreshes it before expiry.
|
|
191
191
|
export const ablo = Ablo({
|
|
192
192
|
schema,
|
|
193
|
-
|
|
193
|
+
authEndpoint: '/api/ablo-session',
|
|
194
194
|
});
|
|
195
195
|
```
|
|
196
196
|
|
package/docs/react.md
CHANGED
package/docs/sessions.md
CHANGED
|
@@ -53,7 +53,7 @@ return Response.json({ token }); // return ONLY the token to the browser
|
|
|
53
53
|
A user session has **full data authority** within its org — no operation
|
|
54
54
|
allowlist. It's the human acting as themselves.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Point a browser `Ablo` client's `authEndpoint` at that route, and pass
|
|
57
57
|
the **instance** to [`<AbloProvider>`](/react). The client fetches the token,
|
|
58
58
|
opens the connection, and re-mints before expiry — your app writes no token
|
|
59
59
|
plumbing:
|
|
@@ -67,10 +67,7 @@ import { schema } from '@/ablo.schema';
|
|
|
67
67
|
|
|
68
68
|
const ablo = Ablo({
|
|
69
69
|
schema,
|
|
70
|
-
|
|
71
|
-
fetch('/api/ablo-session', { method: 'POST' })
|
|
72
|
-
.then((r) => r.json())
|
|
73
|
-
.then((d) => d.token),
|
|
70
|
+
authEndpoint: '/api/ablo-session',
|
|
74
71
|
});
|
|
75
72
|
|
|
76
73
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
@@ -81,8 +78,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
81
78
|
The client owns auth, the credential lifecycle, and the connection; the provider
|
|
82
79
|
is the thin reactive binding over it (Stripe's `<Elements stripe={...}>` model).
|
|
83
80
|
Build the client **once** at module scope — a new instance per render tears down
|
|
84
|
-
the socket.
|
|
85
|
-
`
|
|
81
|
+
the socket. Need custom headers or a
|
|
82
|
+
body on the exchange? `authEndpoint` also accepts an async resolver that
|
|
83
|
+
returns the token.
|
|
86
84
|
|
|
87
85
|
## Agent sessions (`rk_`)
|
|
88
86
|
|