@abloatai/ablo 0.24.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.
Files changed (246) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +33 -0
  3. package/README.md +45 -36
  4. package/dist/BaseSyncedStore.d.ts +72 -195
  5. package/dist/BaseSyncedStore.js +240 -625
  6. package/dist/Database.d.ts +8 -9
  7. package/dist/Database.js +9 -14
  8. package/dist/LazyReferenceCollection.js +5 -0
  9. package/dist/Model.js +6 -8
  10. package/dist/ModelRegistry.d.ts +25 -6
  11. package/dist/ModelRegistry.js +12 -6
  12. package/dist/NetworkMonitor.js +1 -1
  13. package/dist/ObjectPool.js +9 -3
  14. package/dist/SyncClient.d.ts +15 -5
  15. package/dist/SyncClient.js +152 -66
  16. package/dist/SyncEngineContext.d.ts +1 -3
  17. package/dist/SyncEngineContext.js +1 -2
  18. package/dist/agent/Agent.d.ts +21 -8
  19. package/dist/agent/Agent.js +26 -13
  20. package/dist/ai-sdk/coordination-context.js +9 -3
  21. package/dist/ai-sdk/wrap.d.ts +1 -1
  22. package/dist/ai-sdk/wrap.js +1 -1
  23. package/dist/auth/credentialPolicy.d.ts +10 -2
  24. package/dist/auth/credentialSource.d.ts +1 -13
  25. package/dist/auth/credentialSource.js +6 -13
  26. package/dist/auth/index.js +3 -3
  27. package/dist/cli.cjs +583 -287
  28. package/dist/client/Ablo.d.ts +35 -764
  29. package/dist/client/Ablo.js +64 -688
  30. package/dist/client/ApiClient.d.ts +14 -1
  31. package/dist/client/ApiClient.js +63 -9
  32. package/dist/client/auth.d.ts +8 -17
  33. package/dist/client/auth.js +42 -6
  34. package/dist/client/consoleLogger.d.ts +36 -0
  35. package/dist/client/consoleLogger.js +45 -0
  36. package/dist/client/createInternalComponents.js +1 -1
  37. package/dist/client/createModelProxy.js +10 -6
  38. package/dist/client/credentialEndpoint.d.ts +63 -0
  39. package/dist/client/credentialEndpoint.js +87 -0
  40. package/dist/client/hostedEndpoints.d.ts +24 -0
  41. package/dist/client/hostedEndpoints.js +24 -0
  42. package/dist/client/httpClient.d.ts +1 -1
  43. package/dist/client/identity.js +2 -1
  44. package/dist/client/modelRegistration.d.ts +14 -0
  45. package/dist/client/modelRegistration.js +321 -0
  46. package/dist/client/options.d.ts +396 -0
  47. package/dist/client/options.js +10 -0
  48. package/dist/client/resourceTypes.d.ts +344 -0
  49. package/dist/client/resourceTypes.js +13 -0
  50. package/dist/client/schemaConfig.d.ts +56 -0
  51. package/dist/client/schemaConfig.js +188 -0
  52. package/dist/client/sessionMint.d.ts +1 -1
  53. package/dist/client/validateAbloOptions.js +1 -1
  54. package/dist/client/wsMutationExecutor.d.ts +33 -0
  55. package/dist/client/wsMutationExecutor.js +77 -0
  56. package/dist/context.js +0 -3
  57. package/dist/coordination/index.d.ts +8 -1
  58. package/dist/coordination/index.js +24 -1
  59. package/dist/coordination/trace.d.ts +0 -1
  60. package/dist/coordination/trace.js +0 -1
  61. package/dist/core/DatabaseManager.d.ts +0 -1
  62. package/dist/core/DatabaseManager.js +26 -24
  63. package/dist/core/QueryProcessor.js +7 -8
  64. package/dist/core/QueryView.d.ts +14 -2
  65. package/dist/core/QueryView.js +4 -5
  66. package/dist/core/StoreManager.d.ts +2 -2
  67. package/dist/core/StoreManager.js +5 -1
  68. package/dist/core/index.d.ts +1 -1
  69. package/dist/core/openIDBWithTimeout.js +15 -11
  70. package/dist/core/query-utils.js +1 -1
  71. package/dist/core/storeContract.d.ts +143 -0
  72. package/dist/core/storeContract.js +16 -0
  73. package/dist/errorCodes.d.ts +11 -2
  74. package/dist/errorCodes.js +155 -137
  75. package/dist/errors.d.ts +18 -7
  76. package/dist/errors.js +14 -1
  77. package/dist/index.js +5 -0
  78. package/dist/interfaces/index.d.ts +6 -20
  79. package/dist/keys/index.js +18 -9
  80. package/dist/mutators/UndoManager.js +6 -1
  81. package/dist/mutators/defineMutators.d.ts +1 -3
  82. package/dist/policy/types.d.ts +1 -1
  83. package/dist/query/client.d.ts +11 -0
  84. package/dist/query/client.js +81 -52
  85. package/dist/react/AbloProvider.d.ts +6 -10
  86. package/dist/react/AbloProvider.js +22 -15
  87. package/dist/react/context.d.ts +2 -118
  88. package/dist/react/useAblo.js +1 -1
  89. package/dist/react/useErrorListener.js +1 -1
  90. package/dist/react/useMutationFailureListener.js +1 -1
  91. package/dist/react/useMutators.js +7 -1
  92. package/dist/react/useReactive.js +0 -1
  93. package/dist/react/useUndoScope.js +2 -2
  94. package/dist/schema/ddlLock.d.ts +39 -0
  95. package/dist/schema/ddlLock.js +51 -0
  96. package/dist/schema/field.js +1 -1
  97. package/dist/schema/index.d.ts +4 -3
  98. package/dist/schema/index.js +15 -5
  99. package/dist/schema/model.d.ts +3 -3
  100. package/dist/schema/model.js +2 -2
  101. package/dist/schema/openapi.js +5 -1
  102. package/dist/schema/queries.d.ts +1 -1
  103. package/dist/schema/queries.js +1 -0
  104. package/dist/schema/residency.d.ts +35 -0
  105. package/dist/schema/residency.js +27 -0
  106. package/dist/schema/roles.d.ts +25 -0
  107. package/dist/schema/roles.js +37 -0
  108. package/dist/schema/schema.d.ts +3 -3
  109. package/dist/schema/schema.js +11 -7
  110. package/dist/schema/serialize.d.ts +8 -8
  111. package/dist/schema/sync-delta-row.d.ts +2 -2
  112. package/dist/schema/sync-delta-row.js +2 -2
  113. package/dist/server/storage-mode.d.ts +8 -1
  114. package/dist/server/storage-mode.js +14 -4
  115. package/dist/source/adapter.d.ts +1 -1
  116. package/dist/source/adapters/drizzle.js +4 -3
  117. package/dist/source/adapters/kysely.js +4 -3
  118. package/dist/source/adapters/memory.js +1 -1
  119. package/dist/source/adapters/prisma.js +5 -4
  120. package/dist/source/conformance.js +10 -8
  121. package/dist/source/connector.js +4 -2
  122. package/dist/source/factory.d.ts +106 -0
  123. package/dist/source/factory.js +273 -0
  124. package/dist/source/index.d.ts +11 -461
  125. package/dist/source/index.js +14 -416
  126. package/dist/source/next.d.ts +1 -1
  127. package/dist/source/next.js +1 -1
  128. package/dist/source/pushQueue.d.ts +1 -1
  129. package/dist/source/pushQueue.js +6 -3
  130. package/dist/source/signing.d.ts +59 -0
  131. package/dist/source/signing.js +142 -0
  132. package/dist/source/types.d.ts +334 -0
  133. package/dist/source/types.js +44 -0
  134. package/dist/stores/ObjectStore.d.ts +1 -1
  135. package/dist/stores/ObjectStore.js +23 -23
  136. package/dist/stores/SyncActionStore.d.ts +1 -1
  137. package/dist/stores/SyncActionStore.js +70 -35
  138. package/dist/surface.d.ts +1 -1
  139. package/dist/surface.js +1 -0
  140. package/dist/sync/AreaOfInterestManager.d.ts +2 -2
  141. package/dist/sync/AreaOfInterestManager.js +1 -1
  142. package/dist/sync/BootstrapHelper.d.ts +1 -3
  143. package/dist/sync/BootstrapHelper.js +29 -8
  144. package/dist/sync/ConnectionManager.d.ts +8 -0
  145. package/dist/sync/ConnectionManager.js +19 -4
  146. package/dist/sync/HydrationCoordinator.d.ts +12 -0
  147. package/dist/sync/HydrationCoordinator.js +25 -5
  148. package/dist/sync/NetworkProbe.d.ts +3 -2
  149. package/dist/sync/NetworkProbe.js +7 -6
  150. package/dist/sync/SyncWebSocket.d.ts +71 -120
  151. package/dist/sync/SyncWebSocket.js +324 -630
  152. package/dist/sync/awaitClaimGrant.js +27 -19
  153. package/dist/sync/bootstrapApply.d.ts +60 -0
  154. package/dist/sync/bootstrapApply.js +65 -0
  155. package/dist/sync/commitFrames.d.ts +43 -0
  156. package/dist/sync/commitFrames.js +94 -0
  157. package/dist/sync/createClaimStream.js +3 -2
  158. package/dist/sync/createSnapshot.d.ts +1 -3
  159. package/dist/sync/credentialLifecycle.d.ts +175 -0
  160. package/dist/sync/credentialLifecycle.js +329 -0
  161. package/dist/sync/deltaPipeline.d.ts +111 -0
  162. package/dist/sync/deltaPipeline.js +262 -0
  163. package/dist/sync/groupChange.d.ts +111 -0
  164. package/dist/sync/groupChange.js +246 -0
  165. package/dist/sync/heartbeat.d.ts +62 -0
  166. package/dist/sync/heartbeat.js +91 -0
  167. package/dist/sync/participants.d.ts +8 -8
  168. package/dist/sync/syncCursor.d.ts +44 -0
  169. package/dist/sync/syncCursor.js +59 -0
  170. package/dist/sync/syncPlan.d.ts +62 -0
  171. package/dist/sync/syncPlan.js +51 -0
  172. package/dist/sync/wsFrameHandlers.d.ts +117 -0
  173. package/dist/sync/wsFrameHandlers.js +378 -0
  174. package/dist/testing/fixtures/bootstrap.d.ts +9 -11
  175. package/dist/testing/fixtures/deltas.d.ts +1 -1
  176. package/dist/testing/fixtures/models.js +1 -0
  177. package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
  178. package/dist/testing/helpers/sync-engine-harness.js +2 -1
  179. package/dist/testing/index.d.ts +1 -1
  180. package/dist/testing/index.js +1 -1
  181. package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
  182. package/dist/testing/mocks/MockSyncContext.js +1 -32
  183. package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
  184. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  185. package/dist/transactions/TransactionQueue.d.ts +20 -56
  186. package/dist/transactions/TransactionQueue.js +101 -487
  187. package/dist/transactions/TransactionStore.d.ts +18 -0
  188. package/dist/transactions/TransactionStore.js +51 -0
  189. package/dist/transactions/coalesceRules.d.ts +34 -0
  190. package/dist/transactions/coalesceRules.js +117 -0
  191. package/dist/transactions/commitPayload.d.ts +134 -0
  192. package/dist/transactions/commitPayload.js +152 -0
  193. package/dist/transactions/deltaConfirmation.d.ts +60 -0
  194. package/dist/transactions/deltaConfirmation.js +223 -0
  195. package/dist/transactions/optimistic.d.ts +24 -0
  196. package/dist/transactions/optimistic.js +45 -0
  197. package/dist/transactions/persistedReplay.d.ts +93 -0
  198. package/dist/transactions/persistedReplay.js +105 -0
  199. package/dist/types/index.d.ts +3 -3
  200. package/dist/types/index.js +0 -5
  201. package/dist/types/modelData.d.ts +12 -0
  202. package/dist/types/modelData.js +11 -0
  203. package/dist/types/participant.d.ts +21 -0
  204. package/dist/types/participant.js +12 -0
  205. package/dist/types/streams.d.ts +8 -14
  206. package/dist/utils/mobx-setup.d.ts +1 -1
  207. package/dist/utils/mobx-setup.js +2 -2
  208. package/dist/wire/errorEnvelope.d.ts +11 -2
  209. package/dist/wire/errorEnvelope.js +14 -2
  210. package/dist/wire/frames.d.ts +90 -0
  211. package/dist/wire/frames.js +55 -1
  212. package/dist/wire/index.d.ts +4 -1
  213. package/dist/wire/index.js +20 -1
  214. package/dist/wire/protocol.d.ts +45 -0
  215. package/dist/wire/protocol.js +45 -0
  216. package/dist/wire/protocolVersion.d.ts +56 -0
  217. package/dist/wire/protocolVersion.js +63 -0
  218. package/docs/api-keys.md +4 -3
  219. package/docs/data-sources.md +12 -5
  220. package/docs/examples/existing-python-backend.md +3 -3
  221. package/docs/identity.md +4 -4
  222. package/docs/integration-guide.md +1 -1
  223. package/docs/react.md +1 -1
  224. package/docs/sessions.md +5 -7
  225. package/llms.txt +4 -2
  226. package/package.json +15 -13
  227. package/dist/client/index.d.ts +0 -36
  228. package/dist/client/index.js +0 -33
  229. package/dist/config/index.d.ts +0 -10
  230. package/dist/config/index.js +0 -12
  231. package/dist/interfaces/headless.d.ts +0 -95
  232. package/dist/interfaces/headless.js +0 -41
  233. package/dist/query/index.d.ts +0 -6
  234. package/dist/query/index.js +0 -5
  235. package/dist/realtime/index.d.ts +0 -10
  236. package/dist/realtime/index.js +0 -9
  237. package/dist/schema/plane.d.ts +0 -23
  238. package/dist/schema/plane.js +0 -19
  239. package/dist/server/next.d.ts +0 -51
  240. package/dist/server/next.js +0 -47
  241. package/dist/sync/OfflineFlush.d.ts +0 -9
  242. package/dist/sync/OfflineFlush.js +0 -22
  243. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  244. package/dist/sync/OfflineTransactionStore.js +0 -263
  245. package/dist/transactions/index.d.ts +0 -16
  246. package/dist/transactions/index.js +0 -7
@@ -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 && descriptor.get)
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 && descriptor.set)
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?: ReadonlyArray<{
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
- const message = err instanceof Error ? err.message : String(err);
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
  },
@@ -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
@@ -1 +1,55 @@
1
- export {};
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;
@@ -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';
@@ -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, } from '../errors.js';
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** | a function that fetches a short-lived per-user token from your own backend | `Ablo({ schema, apiKey: () => fetch('/api/ablo-session').then((r) => r.text()) })` |
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`, `authEndpoint`, or `as` option — `apiKey` (a string,
52
- or a function for the browser-write case) is the single credential knob.
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
 
@@ -6,7 +6,8 @@ schema, and never migrates it**. Your application keeps writing to its own
6
6
  Postgres through its own backend, exactly as it does today; Ablo only tails the
7
7
  changes and fans the confirmed rows out to every connected human and agent. This
8
8
  is the same model ElectricSQL, PowerSync, and Zero use — a publication plus a
9
- replication slot, read-only.
9
+ replication slot. Ablo consumes the logical-replication stream; your application
10
+ continues to own the write path.
10
11
 
11
12
  > **Just trying Ablo?** You don't need a database at all to start. The hosted
12
13
  > **sandbox** can host rows in Ablo's test plane — pass an `apiKey` only and omit
@@ -94,8 +95,10 @@ Re-run it until every item is green.
94
95
 
95
96
  ### 4. Point Ablo at the database with the replication role
96
97
 
97
- Give Ablo the connection string for the **replication role** you created. This is
98
- a read-only WAL connection the same value `--check` validated:
98
+ Give Ablo the connection string for the **replication role** you created a
99
+ `REPLICATION`-attributed role that streams the WAL and `SELECT`s, nothing more
100
+ (not a read-only account; see the privilege note below). The same value `--check`
101
+ validated:
99
102
 
100
103
  ```bash
101
104
  # .env — server runtime only, never the browser
@@ -156,8 +159,12 @@ Operational reality you should know up front:
156
159
  WAL accumulates and consumes disk. **Ablo monitors slot lag and WAL retention**
157
160
  and surfaces it so you're never surprised by disk pressure; an abandoned slot is
158
161
  dropped rather than left to grow unbounded.
159
- - **The role is read-only.** It can stream replication and `SELECT`. It cannot
160
- write, and the recipe never grants it more.
162
+ - **The role's privilege footprint is narrow and precise not a "read-only"
163
+ account.** It carries the `REPLICATION` attribute, which lets it stream the WAL
164
+ and `SELECT`; it cannot `INSERT`/`UPDATE`/`DELETE`, run DDL, or own objects, and
165
+ the recipe never grants it more. (For a security review, state it that way — a
166
+ logical-replication role is a real privilege, just a tightly-scoped one — rather
167
+ than calling it "read-only", which a reviewer will correctly push back on.)
161
168
 
162
169
  What Ablo explicitly does **not** do:
163
170
 
@@ -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 — the `apiKey` resolver fetches the session
71
- // token your server route mints (see the session route below).
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
- apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
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 — the `apiKey` resolver fetches the
363
- // short-lived session token your `/api/ablo-session` route minted, and the
364
- // client keeps it fresh before expiry.
365
- apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
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
- apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
193
+ authEndpoint: '/api/ablo-session',
194
194
  });
195
195
  ```
196
196
 
package/docs/react.md CHANGED
@@ -31,7 +31,7 @@ import { schema } from '@/ablo/schema';
31
31
  // from your own server route (see Identity below).
32
32
  export const ablo = Ablo({
33
33
  schema,
34
- apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
34
+ authEndpoint: '/api/ablo-session',
35
35
  });
36
36
  ```
37
37