@abloatai/ablo 0.28.0 → 0.29.1

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 (96) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/Database.js +9 -6
  3. package/dist/SyncClient.d.ts +2 -2
  4. package/dist/SyncClient.js +22 -10
  5. package/dist/adapters/inMemoryStorage.js +18 -14
  6. package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
  7. package/dist/auth/schemas.d.ts +6 -0
  8. package/dist/auth/schemas.js +7 -0
  9. package/dist/cli.cjs +815 -400
  10. package/dist/client/Ablo.d.ts +29 -23
  11. package/dist/client/Ablo.js +8 -196
  12. package/dist/client/claimHeartbeatLoop.d.ts +1 -1
  13. package/dist/client/claimHeartbeatLoop.js +1 -1
  14. package/dist/client/createInternalComponents.d.ts +4 -2
  15. package/dist/client/createInternalComponents.js +3 -1
  16. package/dist/client/durableWrites.d.ts +21 -0
  17. package/dist/client/durableWrites.js +46 -0
  18. package/dist/client/httpClient.d.ts +19 -45
  19. package/dist/client/httpClient.js +104 -26
  20. package/dist/client/httpTransport.d.ts +8 -0
  21. package/dist/client/{ApiClient.js → httpTransport.js} +78 -291
  22. package/dist/client/options.d.ts +43 -19
  23. package/dist/client/options.js +3 -2
  24. package/dist/client/resourceTypes.d.ts +9 -85
  25. package/dist/client/resourceTypes.js +1 -1
  26. package/dist/client/sessionMint.d.ts +5 -6
  27. package/dist/client/sessionMint.js +4 -5
  28. package/dist/client/validateAbloOptions.js +3 -3
  29. package/dist/coordination/schema.d.ts +30 -0
  30. package/dist/coordination/schema.js +14 -0
  31. package/dist/core/index.d.ts +1 -1
  32. package/dist/errors.d.ts +4 -40
  33. package/dist/errors.js +5 -5
  34. package/dist/index.d.ts +14 -8
  35. package/dist/index.js +7 -5
  36. package/dist/interfaces/index.d.ts +3 -0
  37. package/dist/mutators/defineMutators.d.ts +18 -0
  38. package/dist/mutators/defineMutators.js +4 -8
  39. package/dist/react/index.d.ts +1 -1
  40. package/dist/schema/index.d.ts +1 -1
  41. package/dist/schema/index.js +1 -1
  42. package/dist/schema/select.d.ts +15 -0
  43. package/dist/schema/select.js +27 -3
  44. package/dist/source/connector.d.ts +3 -3
  45. package/dist/source/factory.d.ts +4 -6
  46. package/dist/source/factory.js +4 -7
  47. package/dist/source/index.d.ts +4 -4
  48. package/dist/source/index.js +2 -2
  49. package/dist/source/signing.d.ts +0 -3
  50. package/dist/source/types.d.ts +0 -26
  51. package/dist/stores/ObjectStore.js +8 -8
  52. package/dist/surface.d.ts +1 -1
  53. package/dist/surface.js +1 -0
  54. package/dist/sync/BootstrapFetcher.d.ts +10 -0
  55. package/dist/sync/BootstrapFetcher.js +27 -4
  56. package/dist/sync/createClaimStream.d.ts +11 -2
  57. package/dist/sync/createClaimStream.js +4 -3
  58. package/dist/testing/mocks/FakeDatabase.d.ts +1 -1
  59. package/dist/transactions/TransactionQueue.d.ts +2 -2
  60. package/dist/transactions/TransactionQueue.js +3 -3
  61. package/dist/transactions/commitEnvelope.d.ts +1 -1
  62. package/dist/transactions/commitEnvelope.js +2 -2
  63. package/dist/transactions/commitOutboxStore.d.ts +19 -19
  64. package/dist/transactions/durableWriteStore.d.ts +123 -0
  65. package/dist/transactions/durableWriteStore.js +30 -0
  66. package/dist/transactions/httpCommitEnvelope.d.ts +1 -0
  67. package/dist/transactions/httpCommitEnvelope.js +14 -12
  68. package/dist/transactions/idempotencyKey.d.ts +10 -0
  69. package/dist/transactions/idempotencyKey.js +9 -0
  70. package/dist/types/global.d.ts +10 -29
  71. package/dist/types/global.js +4 -7
  72. package/dist/types/streams.d.ts +2 -28
  73. package/dist/wire/frames.d.ts +6 -257
  74. package/dist/wire/frames.js +4 -25
  75. package/dist/wire/index.d.ts +4 -3
  76. package/dist/wire/index.js +2 -2
  77. package/dist/wire/protocolVersion.d.ts +30 -17
  78. package/dist/wire/protocolVersion.js +34 -18
  79. package/docs/agents.md +8 -3
  80. package/docs/api.md +6 -4
  81. package/docs/client-behavior.md +6 -3
  82. package/docs/coordination.md +2 -3
  83. package/docs/data-sources.md +5 -8
  84. package/docs/guarantees.md +21 -0
  85. package/docs/integration-guide.md +1 -0
  86. package/docs/migration.md +21 -1
  87. package/docs/quickstart.md +11 -0
  88. package/docs/react.md +0 -46
  89. package/examples/README.md +6 -5
  90. package/llms.txt +15 -15
  91. package/package.json +2 -7
  92. package/dist/client/ApiClient.d.ts +0 -177
  93. package/dist/commit/contract.d.ts +0 -493
  94. package/dist/commit/contract.js +0 -187
  95. package/dist/commit/index.d.ts +0 -6
  96. package/dist/commit/index.js +0 -5
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The shared resource types of the client: {@link ModelRead}, {@link ModelClient},
2
+ * Shared resource types for the typed clients and private HTTP transport.
3
3
  * the commit and claim shapes, the session-mint params and resource, and the
4
4
  * {@link HttpClaimApi} derivation. This module holds only types and has no runtime
5
5
  * imports.
@@ -10,8 +10,7 @@ export type { ModelTarget, ModelClaim };
10
10
  import type { SchemaRecord } from '../schema/schema.js';
11
11
  import type { SyncGroupInput } from '../schema/roles.js';
12
12
  import type { Claim, ClaimStream, ClaimWaitOptions, Duration, HeldClaim } from '../types/streams.js';
13
- import type { ModelUpdater, ContentionOptions } from './functionalUpdate.js';
14
- import type { ClaimOptions, ClaimParams, ClaimReadApi, AwaitedClaimMethod, ServerReadOptions } from './createModelProxy.js';
13
+ import type { ClaimOptions, ClaimParams, ClaimReadApi, AwaitedClaimMethod } from './createModelProxy.js';
15
14
  /**
16
15
  * The operations available on each model in the sync engine:
17
16
  * `retrieve({ id })` — an async single-row server read
@@ -23,24 +22,13 @@ import type { ClaimOptions, ClaimParams, ClaimReadApi, AwaitedClaimMethod, Serve
23
22
  export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, ModelOperations, } from './createModelProxy.js';
24
23
  export type ModelOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
25
24
  export type CommitWait = 'queued' | 'confirmed';
26
- export interface ModelRead<T = Record<string, unknown>> {
27
- /**
28
- * The row, or `undefined` when no row matched the id (or it's outside the
29
- * caller's scope). A miss is data-absence, not an error — `retrieve` never
30
- * throws "not found", mirroring the WebSocket client's `T | undefined`.
31
- * Branch on it: `const deal = (await ablo.deals.retrieve({ id })).data; if (!deal) …`.
32
- */
33
- readonly data: T | undefined;
34
- readonly stamp: number;
35
- readonly claims: readonly ModelClaim[];
36
- }
37
25
  export type IfClaimedPolicy = 'return' | 'fail';
38
26
  export interface ClaimedOptions {
39
27
  /**
40
- * What to do when another participant has claimed the target: `return`
41
- * includes active claim metadata in the response; `fail` throws
42
- * `AbloClaimedError`. Waiting for a claim to clear is a claim-side concern —
43
- * take `ablo.<model>.claim({ id })` (it queues fairly); reads never block.
28
+ * What to do when another participant has claimed the target: `return` lets
29
+ * the read proceed; `fail` throws `AbloClaimedError`. Inspect claim state via
30
+ * `ablo.<model>.claim.state({ id })`. Waiting is a claim-side concern — take
31
+ * `ablo.<model>.claim({ id })` (it queues fairly); reads never block.
44
32
  */
45
33
  readonly ifClaimed?: IfClaimedPolicy;
46
34
  }
@@ -73,8 +61,7 @@ export interface ClaimCreateOptions {
73
61
  export interface CommitOperationInput {
74
62
  readonly action: ModelOperationAction;
75
63
  /** The model name — matches `ablo.<model>` and the schema's `model()`. */
76
- readonly model?: string;
77
- readonly target?: ModelTarget;
64
+ readonly model: string;
78
65
  readonly id?: string | null;
79
66
  readonly data?: Record<string, unknown> | null;
80
67
  readonly transactionId?: string | null;
@@ -82,9 +69,6 @@ export interface CommitOperationInput {
82
69
  readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
83
70
  }
84
71
  export interface CommitCreateOptions {
85
- readonly claimRef?: string | {
86
- readonly id: string;
87
- } | null;
88
72
  readonly idempotencyKey?: string | null;
89
73
  readonly readAt?: number | null;
90
74
  readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
@@ -97,8 +81,8 @@ export interface CommitCreateOptions {
97
81
  * Explicit `readAt`/`onStale` on the options win.
98
82
  */
99
83
  readonly claim?: Claim | null;
100
- readonly operation?: CommitOperationInput;
101
- readonly operations?: readonly CommitOperationInput[];
84
+ /** One atomic batch. Use a one-element array for a single operation. */
85
+ readonly operations: readonly CommitOperationInput[];
102
86
  readonly wait?: CommitWait;
103
87
  /**
104
88
  * Batch-level read dependencies — the "did anything I looked at change?" guard.
@@ -167,66 +151,6 @@ export interface ModelMutationOptions extends ClaimedOptions {
167
151
  export type HttpClaimApi<T = Record<string, unknown>> = ((params: ClaimParams<T>) => Promise<HeldClaim<T>>) & {
168
152
  [K in keyof ClaimReadApi<T>]: AwaitedClaimMethod<ClaimReadApi<T>[K]>;
169
153
  };
170
- export interface ModelClient<T = Record<string, unknown>> {
171
- /**
172
- * Single-row read over HTTP. **Returns an envelope, not the bare row** — the
173
- * row is on `.data`, alongside the `.stamp` watermark (for stale-context
174
- * guards on the following write) and any active `.claims`. A stateless HTTP
175
- * client can't synthesize the watermark from a local snapshot, so the
176
- * envelope is load-bearing here (the WebSocket client's `retrieve` returns
177
- * `T | undefined` because it reads from its local cache).
178
- *
179
- * ```ts
180
- * const deal = await ablo.deals.retrieve({ id });
181
- * deal.data?.recommendation; // ← the row is on .data
182
- * deal.stamp; // watermark — pass to the next write's readAt
183
- * ```
184
- */
185
- retrieve(params: ModelReadOptions & {
186
- readonly id: string;
187
- }): Promise<ModelRead<T>>;
188
- /**
189
- * Collection read over HTTP (server round-trip). Equality `where`, `orderBy`,
190
- * `limit`. Present on the stateless protocol client; the store-backed
191
- * `.model(name)` accessor omits it (use the typed `ablo.<model>.list` there).
192
- */
193
- list?(options?: ServerReadOptions<T>): Promise<T[]>;
194
- /**
195
- * Creates a row and returns the confirmed server row, including framework
196
- * defaults such as `createdAt` and `createdBy`. Matches the stateful client's
197
- * `create`. Passing an id that already exists is idempotent: the existing row is
198
- * returned, not the input.
199
- */
200
- create(params: ModelMutationOptions & {
201
- readonly data: Record<string, unknown>;
202
- readonly id?: string | null;
203
- }): Promise<T>;
204
- update(params: ModelMutationOptions & {
205
- readonly id: string;
206
- readonly data: Record<string, unknown>;
207
- }): Promise<CommitReceipt>;
208
- /**
209
- * Update under contention with a function of the latest state —
210
- * `update(id, current => next)`, the `setState(prev => next)` of the data
211
- * layer. The SDK reads the freshest row, runs your updater, writes it as a
212
- * compare-and-swap against the row's watermark, and re-reads + re-runs on any
213
- * concurrent write. No claim, no identity, no conflict codes surface: the
214
- * write either lands or throws {@link AbloContentionError} once its reconcile
215
- * budget is spent. Return `null`/`undefined` from the updater to skip the
216
- * write (resolves to `undefined`).
217
- */
218
- update(id: string, updater: ModelUpdater<T>, options?: ContentionOptions): Promise<CommitReceipt | undefined>;
219
- delete(params: ModelMutationOptions & {
220
- readonly id: string;
221
- }): Promise<CommitReceipt>;
222
- /**
223
- * Durable lease + FIFO wait-line over HTTP — coordination without a socket.
224
- * Present on the stateless protocol client (`Ablo({ schema: null })` /
225
- * `createAbloHttpClient`); the store-backed `.model(name)` accessor omits it
226
- * (the typed `ablo.<model>.claim` proxy is the full reactive namespace there).
227
- */
228
- claim?: HttpClaimApi<T>;
229
- }
230
154
  /** A single data operation a scoped **agent** session may perform on a model. */
231
155
  export type SessionOperation = 'read' | 'create' | 'update' | 'delete';
232
156
  /** Parameters for minting an end-user session — full data authority within the
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The shared resource types of the client: {@link ModelRead}, {@link ModelClient},
2
+ * Shared resource types for the typed clients and private HTTP transport.
3
3
  * the commit and claim shapes, the session-mint params and resource, and the
4
4
  * {@link HttpClaimApi} derivation. This module holds only types and has no runtime
5
5
  * imports.
@@ -10,14 +10,13 @@ export interface MintSessionContext {
10
10
  readonly baseUrl: string;
11
11
  readonly fetch?: typeof fetch;
12
12
  /**
13
- * Maps each schema key to its wire type name. Only the schema-aware client
14
- * supplies this. A capability is scoped by the lowercased type name the
15
- * server checks, but `can` is keyed by schema key so
13
+ * Maps each schema key to its wire type name. Every public client supplies
14
+ * this; it stays optional only for isolated private-transport use. A
15
+ * capability is scoped by the lowercased type name the server checks, but
16
+ * `can` is keyed by schema key — so
16
17
  * `can: { documents: ['update'] }` on a model whose type name is overridden
17
18
  * to `Document` must mint `document.update`, not `documents.update`, or the
18
- * server denies the write with `capability_scope_denied`. The schemaless
19
- * client omits this map, because there the `can` key is already the wire
20
- * token and needs no translation.
19
+ * server denies the write with `capability_scope_denied`.
21
20
  */
22
21
  readonly modelTypenames?: Readonly<Record<string, string>>;
23
22
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Mints a session token. This is the single implementation behind
3
- * `sessions.create`, shared by the schema-aware client and the schemaless HTTP
4
- * client so the two never disagree on how a token is produced.
3
+ * `sessions.create`, shared by the WebSocket and HTTP clients so the two never
4
+ * disagree on how a token is produced.
5
5
  *
6
6
  * Minting is a plain control-plane HTTP call — no socket, no synced data. A
7
7
  * backend holding a secret key (`sk_`) exchanges it for a short-lived, scoped
@@ -60,9 +60,8 @@ export async function mintSession(params, ctx) {
60
60
  }
61
61
  const operations = Object.entries(params.can).flatMap(([model, ops]) => {
62
62
  // Translate the schema key the developer used in `can` into the wire type
63
- // name the server checks — see `modelTypenames` above. Falls back to the
64
- // key itself when no map is supplied (the schemaless client) or the key is
65
- // absent from it.
63
+ // name the server checks — see `modelTypenames` above. The key fallback is
64
+ // private transport tolerance; every public client supplies the schema map.
66
65
  const ns = ctx.modelTypenames?.[model] ?? model;
67
66
  return (ops ?? []).map((op) => `${ns.toLowerCase()}.${op}`);
68
67
  });
@@ -17,9 +17,9 @@ export function validateAbloOptions(input) {
17
17
  return new AbloValidationError('Ablo: `url` is required. Pass the sync server URL, e.g. ' +
18
18
  `Ablo({ baseURL: 'wss://api.abloatai.com', schema, user })`, { code: 'base_url_missing' });
19
19
  }
20
- // Schema is optional for the model-first API:
21
- // Ablo({ apiKey }).model('clauses').retrieve(...)
22
- // Passing a schema only enables typed model sugar (`ablo.weatherReports.update(...)`).
20
+ if (!options.schema?.models) {
21
+ return new AbloValidationError('Ablo: `schema` is required. Define it once and access models as `ablo.<model>`.', { code: 'invalid_options', param: 'schema' });
22
+ }
23
23
  if (!configuredApiKey &&
24
24
  !configuredAuthToken &&
25
25
  !options.capabilityToken &&
@@ -157,6 +157,9 @@ export type StaleNotification = z.infer<typeof staleNotificationSchema>;
157
157
  * • Group — `{ group, readAt }`: did anything in this sync group change?
158
158
  * `group` is a sync-group key such as `deck:abc` or `slide:s1`, the
159
159
  * same unit a participant watches and claims.
160
+ *
161
+ * See `packages/sync-engine/docs/concurrency-convention.md` (§4) for the
162
+ * governing convention and the receive → reconcile loop.
160
163
  */
161
164
  export declare const readDependencySchema: z.ZodUnion<readonly [z.ZodObject<{
162
165
  model: z.ZodString;
@@ -304,6 +307,33 @@ export declare const claimRejectionSchema: z.ZodObject<{
304
307
  policyReason: z.ZodOptional<z.ZodString>;
305
308
  }, z.core.$strip>;
306
309
  export type ClaimRejection = z.infer<typeof claimRejectionSchema>;
310
+ /**
311
+ * The point-to-point notification sent to a holder whose lease ended without
312
+ * a successful commit. This remains a wire-shaped target because it arrives
313
+ * directly from the WebSocket; the schema is the single validation boundary
314
+ * before the event reaches public `claims.onLost` listeners.
315
+ */
316
+ export declare const claimLostSchema: z.ZodObject<{
317
+ claimId: z.ZodString;
318
+ reason: z.ZodEnum<{
319
+ expired: "expired";
320
+ preempted: "preempted";
321
+ }>;
322
+ target: z.ZodObject<{
323
+ entityType: z.ZodString;
324
+ entityId: z.ZodString;
325
+ path: z.ZodOptional<z.ZodString>;
326
+ range: z.ZodOptional<z.ZodObject<{
327
+ startLine: z.ZodNumber;
328
+ endLine: z.ZodNumber;
329
+ startColumn: z.ZodOptional<z.ZodNumber>;
330
+ endColumn: z.ZodOptional<z.ZodNumber>;
331
+ }, z.core.$strip>>;
332
+ field: z.ZodOptional<z.ZodString>;
333
+ meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
334
+ }, z.core.$strip>;
335
+ }, z.core.$strip>;
336
+ export type ClaimLost = z.infer<typeof claimLostSchema>;
307
337
  /**
308
338
  * What a {@link ModelClaim} points at — the target locator as SDK callers see
309
339
  * it, keyed by `model` and `id` rather than the wire schema's `entityType` and
@@ -165,6 +165,9 @@ export const staleNotificationSchema = z.object({
165
165
  * • Group — `{ group, readAt }`: did anything in this sync group change?
166
166
  * `group` is a sync-group key such as `deck:abc` or `slide:s1`, the
167
167
  * same unit a participant watches and claims.
168
+ *
169
+ * See `packages/sync-engine/docs/concurrency-convention.md` (§4) for the
170
+ * governing convention and the receive → reconcile loop.
168
171
  */
169
172
  export const readDependencySchema = z.union([
170
173
  z.object({
@@ -249,6 +252,17 @@ export const claimRejectionSchema = z.object({
249
252
  heldByClaim: wireClaimSummarySchema.optional(),
250
253
  policyReason: z.string().optional(),
251
254
  });
255
+ /**
256
+ * The point-to-point notification sent to a holder whose lease ended without
257
+ * a successful commit. This remains a wire-shaped target because it arrives
258
+ * directly from the WebSocket; the schema is the single validation boundary
259
+ * before the event reaches public `claims.onLost` listeners.
260
+ */
261
+ export const claimLostSchema = z.object({
262
+ claimId: z.string(),
263
+ reason: z.enum(['expired', 'preempted']),
264
+ target: targetRefSchema,
265
+ });
252
266
  /**
253
267
  * What a {@link ModelClaim} points at — the target locator as SDK callers see
254
268
  * it, keyed by `model` and `id` rather than the wire schema's `entityType` and
@@ -21,7 +21,7 @@ export { Model } from '../Model.js';
21
21
  export { LazyReferenceCollection, type LazyCollectionOptions, } from '../LazyReferenceCollection.js';
22
22
  export { ModelRegistry, getActiveRegistry, } from '../ModelRegistry.js';
23
23
  export { postQuery, type PostQueryOptions } from '../query/client.js';
24
- export { computeFKDepthPriority, type InternalAbloOptions } from '../client/Ablo.js';
24
+ export { computeFKDepthPriority } from '../client/Ablo.js';
25
25
  export type { SyncLogger, SyncObservabilityProvider, MutationExecutor, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, } from '../interfaces/index.js';
26
26
  export { SyncWebSocket, type SyncDelta, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
27
27
  export { BootstrapFetcher } from '../sync/BootstrapFetcher.js';
package/dist/errors.d.ts CHANGED
@@ -131,10 +131,10 @@ export declare class AbloValidationError extends AbloError {
131
131
  }
132
132
  /**
133
133
  * An update or delete addressed a row that does not exist, or lies outside the
134
- * caller's organization (HTTP 404). Such targets are reported on
135
- * {@link CommitReceipt.missingIds}, and the typed resource methods raise this
136
- * error rather than returning a successful receipt for a write that quietly
137
- * matched zero rows. The absent ids are carried on {@link missingIds}.
134
+ * caller's organization (HTTP 404). Raw commit responses may report those
135
+ * targets as `missingIds`; typed model methods raise this error instead of
136
+ * returning a successful result for a write that quietly matched zero rows.
137
+ * The absent ids are carried on {@link missingIds}.
138
138
  */
139
139
  export declare class AbloNotFoundError extends AbloError {
140
140
  readonly type: "AbloNotFoundError";
@@ -292,42 +292,6 @@ export interface RequiredCapability {
292
292
  * capability. */
293
293
  readonly nonce?: string;
294
294
  }
295
- /**
296
- * The receipt returned for every commit, whether it succeeded or was rejected,
297
- * and regardless of how it was sent — the WebSocket `mutation_result` payload,
298
- * the HTTP `/v1/commits` response body, and an agent job's result all use this
299
- * one shape. It is a correlation receipt, carrying the ids and outcome needed to
300
- * reconcile a commit rather than a cryptographic proof.
301
- */
302
- export interface CommitReceipt {
303
- readonly object: 'commit_receipt';
304
- /** Client-issued idempotency handle. Echoed verbatim. */
305
- readonly clientTxId: string;
306
- /** Server-issued opaque commit id — typically `String(lastSyncId)`. */
307
- readonly serverTxId: string;
308
- /** A convenience boolean, equal to `status === 'confirmed'`. */
309
- readonly success: boolean;
310
- /** `'confirmed'` on apply, `'rejected'` on any failure. */
311
- readonly status: 'confirmed' | 'rejected';
312
- /** Last syncId visible after this commit (or high-water mark on
313
- * rejection). */
314
- readonly lastSyncId?: number;
315
- /** Number of operations metered. Reported on both success and
316
- * rejection so quota systems see attempted work. */
317
- readonly ops?: number;
318
- /** Ids of update or delete targets that matched no row. Present and non-empty
319
- * only when a write missed; the typed resource methods raise
320
- * {@link AbloNotFoundError} from it. */
321
- readonly missingIds?: readonly string[];
322
- /** Present on rejection. When set, `requiredCapability` carries the structured
323
- * hint describing what would let the request succeed on retry. */
324
- readonly error?: {
325
- readonly code: string;
326
- readonly message: string;
327
- readonly field?: string;
328
- readonly requiredCapability?: RequiredCapability;
329
- };
330
- }
331
295
  /**
332
296
  * A scoped credential was denied, either because the key is unknown, revoked, or
333
297
  * expired (`capability_invalid`), or because the connection's scope does not
package/dist/errors.js CHANGED
@@ -150,10 +150,10 @@ export class AbloValidationError extends AbloError {
150
150
  }
151
151
  /**
152
152
  * An update or delete addressed a row that does not exist, or lies outside the
153
- * caller's organization (HTTP 404). Such targets are reported on
154
- * {@link CommitReceipt.missingIds}, and the typed resource methods raise this
155
- * error rather than returning a successful receipt for a write that quietly
156
- * matched zero rows. The absent ids are carried on {@link missingIds}.
153
+ * caller's organization (HTTP 404). Raw commit responses may report those
154
+ * targets as `missingIds`; typed model methods raise this error instead of
155
+ * returning a successful result for a write that quietly matched zero rows.
156
+ * The absent ids are carried on {@link missingIds}.
157
157
  */
158
158
  export class AbloNotFoundError extends AbloError {
159
159
  type = 'AbloNotFoundError';
@@ -442,7 +442,7 @@ const ErrorFieldSchema = z
442
442
  const ErrorBodyShapeSchema = z
443
443
  .object({
444
444
  /** The `error` field may be a flat code string, as some endpoints return,
445
- * or a nested error object, as a {@link CommitReceipt} carries. */
445
+ * or a nested error object, as commit endpoints return on rejection. */
446
446
  error: ErrorFieldSchema,
447
447
  code: OptionalWireStringSchema,
448
448
  reason: OptionalWireStringSchema,
package/dist/index.d.ts CHANGED
@@ -14,9 +14,9 @@
14
14
  * type Entry = Ablo.Peer;
15
15
  * ```
16
16
  *
17
- * `Ablo({ schema, apiKey })` returns typed model clients. `Ablo({ apiKey })`
18
- * returns the stateless HTTP model and commit client, which suits agents, MCP
19
- * route handlers, and custom runtimes.
17
+ * `Ablo({ schema, apiKey })` returns typed model clients. Server-side agents,
18
+ * MCP route handlers, and workers select `transport: 'http'`; both transports
19
+ * keep the same `ablo.<model>` application surface.
20
20
  *
21
21
  * The whole package reaches you through one name: `Ablo` is at once a factory
22
22
  * function, a type, and a namespace. You call model clients with dot access on
@@ -45,7 +45,7 @@
45
45
  *
46
46
  * A handful of exports are for advanced use and are marked "Advanced" at their
47
47
  * declaration below, each with the one situation it is for:
48
- * • `dataSource` / `abloSource` — when your own database stays canonical.
48
+ * • `dataSource` — when your own database stays canonical.
49
49
  * • `defaultPolicy` — when you customize conflict resolution.
50
50
  * • `defineMutators` / `createTransaction` — when you write custom mutators.
51
51
  * If you don't recognize one of these, you don't need it.
@@ -54,12 +54,18 @@ export { Ablo } from './client/Ablo.js';
54
54
  export type { MutationExecutor } from './interfaces/index.js';
55
55
  export type { ModelUpdater, ContentionOptions } from './client/functionalUpdate.js';
56
56
  export { DEFAULT_CONTENTION_RETRIES } from './client/functionalUpdate.js';
57
- export type { HttpClaimApi, InternalAbloOptions } from './client/Ablo.js';
58
57
  export type { AbloReads } from './client/Ablo.js';
59
58
  export { type AbloHttpClientOptions, type AbloHttpClient, type HttpModelClient, } from './client/httpClient.js';
60
59
  export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './client/auth.js';
61
- export type { AbloOptions, LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, ModelOperations, } from './client/Ablo.js';
60
+ export type { AbloOptions, LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, } from './client/Ablo.js';
61
+ /**
62
+ * @deprecated Use `Ablo.Claim.Held`. This compatibility export remains until
63
+ * the next major release because it was explicitly documented in 0.20.1.
64
+ */
65
+ export type { HeldClaim } from './client/Ablo.js';
62
66
  export type { AbloPersistence } from './client/persistence.js';
67
+ export { durableWritesConfigSchema, durableWriteStoreSchema, pendingWriteSchema, } from './transactions/durableWriteStore.js';
68
+ export type { DurableWritesConfig, DurableWriteStore, PendingWrite, } from './transactions/durableWriteStore.js';
63
69
  export type { CommitOutboxRecord, CommitOutboxStore, } from './transactions/commitOutboxStore.js';
64
70
  export { durableCommitEnvelopeSchema, } from './transactions/commitEnvelope.js';
65
71
  export type { CommitOutboxScope, DurableCommitEnvelope, } from './transactions/commitEnvelope.js';
@@ -67,11 +73,11 @@ export { durableHttpCommitEnvelopeSchema, } from './transactions/httpCommitEnvel
67
73
  export type { DurableHttpCommitEnvelope, } from './transactions/httpCommitEnvelope.js';
68
74
  import { Ablo } from './client/Ablo.js';
69
75
  export default Ablo;
70
- export { dataSource, abloSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
76
+ export { dataSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
71
77
  export { createSourceConnector, type SourceConnector, type SourceConnectorOptions, type ConnectorStatus, } from './source/connector.js';
72
78
  export { defaultPolicy, capabilityPreemptPolicy, interpretConflictAxis } from './policy/index.js';
73
79
  export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloNotFoundError, AbloServerError, AbloStaleContextError, AbloClaimedError, AbloContentionError, CapabilityError, translateHttpError, hasWireCode, errorFromWire, toAbloError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errors.js';
74
- export type { CommitReceipt, RequiredCapability } from './errors.js';
80
+ export type { RequiredCapability } from './errors.js';
75
81
  export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errors.js';
76
82
  export { errorEnvelope, statusForType } from './wire/errorEnvelope.js';
77
83
  export type { ErrorEnvelope } from './wire/errorEnvelope.js';
package/dist/index.js CHANGED
@@ -14,9 +14,9 @@
14
14
  * type Entry = Ablo.Peer;
15
15
  * ```
16
16
  *
17
- * `Ablo({ schema, apiKey })` returns typed model clients. `Ablo({ apiKey })`
18
- * returns the stateless HTTP model and commit client, which suits agents, MCP
19
- * route handlers, and custom runtimes.
17
+ * `Ablo({ schema, apiKey })` returns typed model clients. Server-side agents,
18
+ * MCP route handlers, and workers select `transport: 'http'`; both transports
19
+ * keep the same `ablo.<model>` application surface.
20
20
  *
21
21
  * The whole package reaches you through one name: `Ablo` is at once a factory
22
22
  * function, a type, and a namespace. You call model clients with dot access on
@@ -45,7 +45,7 @@
45
45
  *
46
46
  * A handful of exports are for advanced use and are marked "Advanced" at their
47
47
  * declaration below, each with the one situation it is for:
48
- * • `dataSource` / `abloSource` — when your own database stays canonical.
48
+ * • `dataSource` — when your own database stays canonical.
49
49
  * • `defaultPolicy` — when you customize conflict resolution.
50
50
  * • `defineMutators` / `createTransaction` — when you write custom mutators.
51
51
  * If you don't recognize one of these, you don't need it.
@@ -63,6 +63,8 @@ export { DEFAULT_CONTENTION_RETRIES } from './client/functionalUpdate.js';
63
63
  // `AbloHttpClient` type, which is the return type of that call.
64
64
  export {} from './client/httpClient.js';
65
65
  export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './client/auth.js';
66
+ export { durableWritesConfigSchema, durableWriteStoreSchema, pendingWriteSchema, } from './transactions/durableWriteStore.js';
67
+ /* eslint-enable @typescript-eslint/no-deprecated */
66
68
  export { durableCommitEnvelopeSchema, } from './transactions/commitEnvelope.js';
67
69
  export { durableHttpCommitEnvelopeSchema, } from './transactions/httpCommitEnvelope.js';
68
70
  // Participant types live under `Ablo.Participant.*` —
@@ -76,7 +78,7 @@ export default Ablo;
76
78
  // database. The default is Ablo-managed storage; reach for this only when you
77
79
  // have deliberately chosen to keep your database canonical. The matching types
78
80
  // live under `Ablo.Source.*` (`Ablo.Source.Operation`, `Ablo.Source.Commit.Params`).
79
- export { dataSource, abloSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
81
+ export { dataSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
80
82
  // Serves the Data Source `commit`, `load`, and `list` operations over an
81
83
  // outbound WebSocket, so a database with no public inbound URL (running on a
82
84
  // developer's machine or inside a locked-down network) can still be reached by
@@ -230,6 +230,9 @@ export interface MutationOptions {
230
230
  * checks that none of them moved since their `readAt` and applies the entry's
231
231
  * `onStale` behavior to the whole batch. This is distinct from the per-operation
232
232
  * `readAt`, which guards only the row being written.
233
+ *
234
+ * See `packages/sync-engine/docs/concurrency-convention.md` (§3 the two
235
+ * footprints, §4 the read-set) for the governing convention.
233
236
  */
234
237
  reads?: ReadDependency[] | null;
235
238
  }
@@ -13,6 +13,14 @@
13
13
  */
14
14
  import type { Schema } from '../schema/schema.js';
15
15
  import type { Transaction } from './Transaction.js';
16
+ import type { ResolveSchema } from '../types/global.js';
17
+ /**
18
+ * `ResolveSchema` narrowed to satisfy the `Schema` bound — mirrors
19
+ * {@link Ablo.RegisteredSchema}. When nothing is registered, `ResolveSchema`
20
+ * is a loose `{ models }` shape that doesn't extend `Schema`, so we fall back
21
+ * to `Schema` to keep the mutator tree typed rather than collapsing.
22
+ */
23
+ type RegisteredSchema = ResolveSchema extends Schema ? ResolveSchema : Schema;
16
24
  /**
17
25
  * The signature of a single custom mutator. The engine supplies `tx`; you control
18
26
  * `args`, in whatever shape you like, and the resolved return value. `TArgs` and
@@ -40,3 +48,13 @@ export type MutatorDefs<S extends Schema> = {
40
48
  * work here; the function exists purely as a place for type inference to anchor.
41
49
  */
42
50
  export declare function defineMutators<S extends Schema, const M extends MutatorDefs<S>>(_schema: S, mutators: M): M;
51
+ /**
52
+ * Register-anchored overload: omit the schema value and the tree is typed
53
+ * against `ResolveSchema` (this app's registered schema). Shared product code
54
+ * used across apps that bind different schemas should use this form — it moves
55
+ * with each app's `Register` instead of pinning one concrete schema, so the
56
+ * mutator tree stays assignable at every consumer (which reads the same
57
+ * `Register`). See docs/plans/per-product-schema-projections.md.
58
+ */
59
+ export declare function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(mutators: M): M;
60
+ export {};
@@ -11,12 +11,8 @@
11
11
  * `typeof mutators` carries every mutator's precise `args` and result types
12
12
  * through to wherever they are invoked.
13
13
  */
14
- /**
15
- * Returns the mutators object unchanged while constraining its shape against the
16
- * schema. The `S` generic pins the model keys, and the `M` generic is inferred as
17
- * a `const`, so each mutator's literal signature survives. There is no runtime
18
- * work here; the function exists purely as a place for type inference to anchor.
19
- */
20
- export function defineMutators(_schema, mutators) {
21
- return mutators;
14
+ export function defineMutators(schemaOrMutators, maybeMutators) {
15
+ // The schema argument is a type anchor only never read at runtime. With one
16
+ // argument the mutator tree is in the first slot; with two it's in the second.
17
+ return (maybeMutators ?? schemaOrMutators);
22
18
  }
@@ -43,7 +43,7 @@
43
43
  * useAblo((ablo) => ablo.<model>.claim.state(...)) — reactive coordination reads
44
44
  * useWatch({ scope }) — join a scope to get its peers and claims
45
45
  */
46
- export type { DefaultSyncShape, ResolveSchema, ResolvePresence, ResolveClaims, ResolveUserMeta, ResolveModelKey, } from '../types/global.js';
46
+ export type { DefaultSyncShape, ResolveSchema, ResolveUserMeta, ResolveModelKey, } from '../types/global.js';
47
47
  export { AbloProvider, useWatch, usePeers, useSync, useSyncStore, type AbloProviderProps, type ParticipantScope, type ParticipantStatus, type UseWatchOptions, type UseWatchReturn, type MeshParticipantStatus, } from './AbloProvider.js';
48
48
  export { ClientSideSuspense, type ClientSideSuspenseProps, } from './ClientSideSuspense.js';
49
49
  export { DefaultFallback } from './DefaultFallback.js';
@@ -32,7 +32,7 @@ export { coordination, humansOverwrite, humansReject, humansNotify, agentsOverwr
32
32
  export { mutable, readOnly, type SugarOptions } from './sugar.js';
33
33
  export { defineSchema, composeIdentitySyncGroups, type Schema, type SchemaRecord, type Model, type Row, type InferModel, type InferCreate, type InferRow, type InferModelNames, type BaseModelFields, type InsertValue, type UpsertValue, type UpdateValue, type DeleteId, type DefineSchemaOptions, type Casing, type CasingConvention, type CasingFn, composeEntitySyncGroups, intersectRequestedWithAllowed, type IdentityRole, type IdentityContext, type IdentityRoleSource, type EntityRole, type EntityContext, type EntityRoleSource, type RoleSource, type RoleContext, type SyncGroup, type SyncGroupInput, identityRole, entityRole, extractIdentityIds, extractEntityIds, syncGroup, syncGroupSchema, syncGroupInputSchema, isSyncGroupInput, identityRoleSchema, entityRoleSchema, roleSchema, roleSourceSchema, scopeSchema, grantsRefSchema, groupsInputSchema, type GroupsInput, } from './schema.js';
34
34
  export { serializeSchema, parseSchema, toSchemaJSON, fromSchemaJSON, schemaHash, type SchemaJSON, type ModelJSON, type RelationJSON, } from './serialize.js';
35
- export { selectModels } from './select.js';
35
+ export { selectModels, omitModels } from './select.js';
36
36
  export { generateProvisionPlan, generateMigrationPlan, appSchemaName, camelToSnake, snakeToCamel, q, sqlType, type ProvisionPlan, type MigrationPlan, } from './ddl.js';
37
37
  export { PG_LOCK_NOT_AVAILABLE, resolveDdlLockTimeout, resolveDdlMaxLockAttempts, ddlLockRetryBackoffMs, type DdlLockEnv, } from './ddlLock.js';
38
38
  export { diffSchema, classifyMigration, classifyCast, isAutoApplicable, isBlockerResolved, unresolvedBlockers, type BackfillValue, type MigrationStep, type FieldChanges, type FieldColumnChange, type FieldTypeChange, type NullabilityChange, type EnumValuesChange, type IndexChange, type CastSafety, type FieldType, type RenameHints, type MigrationSignal, type MigrationClassification, type WarningCode, type BlockerCode, } from './diff.js';
@@ -57,7 +57,7 @@ export { defineSchema, composeIdentitySyncGroups, composeEntitySyncGroups, inter
57
57
  // Schema ⇄ JSON — serialize a schema for transport and rebuild it on the far side.
58
58
  export { serializeSchema, parseSchema, toSchemaJSON, fromSchemaJSON, schemaHash, } from './serialize.js';
59
59
  // Schema projection — derive an app's subset from one canonical schema.
60
- export { selectModels } from './select.js';
60
+ export { selectModels, omitModels } from './select.js';
61
61
  // Schema → Postgres DDL — shared by the host implementation and the command-line tools.
62
62
  export { generateProvisionPlan, generateMigrationPlan, appSchemaName, camelToSnake, snakeToCamel, q, sqlType, } from './ddl.js';
63
63
  // Safe-DDL locking knobs (lock_timeout plus a bounded retry on lock contention),
@@ -23,3 +23,18 @@
23
23
  */
24
24
  import type { Schema, SchemaRecord } from './schema.js';
25
25
  export declare function selectModels<S extends SchemaRecord, K extends keyof S & string>(schema: Schema<S>, keys: readonly K[]): Schema<Pick<S, K>>;
26
+ /**
27
+ * `omitModels` is `selectModels` from the other side: keep every model EXCEPT
28
+ * the named ones. Use it when an app is the general case and a separate app
29
+ * owns the omitted models — the suite shell drops a specialized store that the
30
+ * standalone app selects for itself:
31
+ *
32
+ * ```ts
33
+ * export const schema = omitModels(full, ['reports', 'reportSections']);
34
+ * ```
35
+ *
36
+ * Same validation as `selectModels`: relations into the omitted set are
37
+ * dropped, and a dropped `parent` edge throws — so a model whose scope routes
38
+ * through an omitted parent cannot be silently kept.
39
+ */
40
+ export declare function omitModels<S extends SchemaRecord, K extends keyof S & string>(schema: Schema<S>, keys: readonly K[]): Schema<Omit<S, K>>;