@abloatai/ablo 0.8.0 → 0.9.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 (165) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/README.md +33 -28
  3. package/dist/BaseSyncedStore.d.ts +83 -0
  4. package/dist/BaseSyncedStore.js +194 -2
  5. package/dist/Model.d.ts +42 -0
  6. package/dist/Model.js +103 -44
  7. package/dist/agent/session.js +3 -3
  8. package/dist/ai-sdk/coordination-context.js +4 -0
  9. package/dist/ai-sdk/index.d.ts +56 -47
  10. package/dist/ai-sdk/index.js +56 -47
  11. package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
  12. package/dist/ai-sdk/intent-broadcast.js +11 -4
  13. package/dist/ai-sdk/wrap.d.ts +14 -11
  14. package/dist/ai-sdk/wrap.js +11 -13
  15. package/dist/auth/credentialSource.d.ts +34 -0
  16. package/dist/auth/credentialSource.js +63 -0
  17. package/dist/auth/index.d.ts +2 -22
  18. package/dist/auth/index.js +4 -42
  19. package/dist/auth/schemas.d.ts +35 -0
  20. package/dist/auth/schemas.js +53 -0
  21. package/dist/client/Ablo.d.ts +160 -42
  22. package/dist/client/Ablo.js +145 -75
  23. package/dist/client/ApiClient.d.ts +20 -4
  24. package/dist/client/ApiClient.js +166 -28
  25. package/dist/client/auth.d.ts +14 -5
  26. package/dist/client/auth.js +60 -7
  27. package/dist/client/createInternalComponents.d.ts +2 -0
  28. package/dist/client/createInternalComponents.js +8 -1
  29. package/dist/client/createModelProxy.d.ts +130 -66
  30. package/dist/client/createModelProxy.js +152 -49
  31. package/dist/client/httpClient.d.ts +71 -0
  32. package/dist/client/httpClient.js +69 -0
  33. package/dist/client/identity.d.ts +2 -6
  34. package/dist/client/identity.js +49 -11
  35. package/dist/client/index.d.ts +1 -0
  36. package/dist/client/index.js +1 -0
  37. package/dist/client/registerDataSource.d.ts +3 -3
  38. package/dist/client/registerDataSource.js +11 -9
  39. package/dist/client/validateAbloOptions.js +1 -1
  40. package/dist/core/DatabaseManager.js +30 -2
  41. package/dist/core/openIDBWithTimeout.d.ts +36 -0
  42. package/dist/core/openIDBWithTimeout.js +88 -1
  43. package/dist/errorCodes.d.ts +70 -1
  44. package/dist/errorCodes.js +108 -9
  45. package/dist/errors.d.ts +2 -2
  46. package/dist/errors.js +72 -22
  47. package/dist/index.d.ts +17 -8
  48. package/dist/index.js +15 -6
  49. package/dist/keys/index.d.ts +16 -1
  50. package/dist/keys/index.js +26 -6
  51. package/dist/mutators/UndoManager.d.ts +158 -50
  52. package/dist/mutators/UndoManager.js +345 -22
  53. package/dist/mutators/inverseOp.d.ts +129 -0
  54. package/dist/mutators/inverseOp.js +74 -0
  55. package/dist/mutators/readerActions.d.ts +1 -1
  56. package/dist/mutators/undoApply.d.ts +42 -0
  57. package/dist/mutators/undoApply.js +143 -0
  58. package/dist/query/client.d.ts +10 -9
  59. package/dist/query/client.js +3 -6
  60. package/dist/react/AbloProvider.d.ts +23 -126
  61. package/dist/react/AbloProvider.js +62 -199
  62. package/dist/react/context.d.ts +31 -0
  63. package/dist/react/useAblo.d.ts +2 -2
  64. package/dist/react/useCurrentUserId.d.ts +1 -1
  65. package/dist/react/useCurrentUserId.js +1 -1
  66. package/dist/react/useMutators.js +19 -12
  67. package/dist/schema/ddl.d.ts +34 -3
  68. package/dist/schema/ddl.js +162 -4
  69. package/dist/schema/index.d.ts +5 -1
  70. package/dist/schema/index.js +13 -1
  71. package/dist/schema/model.d.ts +11 -0
  72. package/dist/schema/model.js +2 -0
  73. package/dist/schema/openapi.d.ts +28 -0
  74. package/dist/schema/openapi.js +118 -0
  75. package/dist/schema/plane.d.ts +23 -0
  76. package/dist/schema/plane.js +19 -0
  77. package/dist/schema/relation.d.ts +20 -0
  78. package/dist/schema/serialize.d.ts +4 -0
  79. package/dist/schema/serialize.js +4 -0
  80. package/dist/schema/sync-delta-row.d.ts +157 -0
  81. package/dist/schema/sync-delta-row.js +102 -0
  82. package/dist/schema/sync-delta-wire.d.ts +180 -0
  83. package/dist/schema/sync-delta-wire.js +102 -0
  84. package/dist/server/adapter.d.ts +156 -0
  85. package/dist/server/adapter.js +19 -0
  86. package/dist/server/commit.d.ts +82 -0
  87. package/dist/server/commit.js +1 -0
  88. package/dist/server/index.d.ts +14 -0
  89. package/dist/server/index.js +1 -0
  90. package/dist/server/next.d.ts +51 -0
  91. package/dist/server/next.js +47 -0
  92. package/dist/server/read-config.d.ts +60 -0
  93. package/dist/server/read-config.js +8 -0
  94. package/dist/server/storage-mode.d.ts +17 -0
  95. package/dist/server/storage-mode.js +12 -0
  96. package/dist/source/adapter.d.ts +65 -0
  97. package/dist/source/adapter.js +20 -0
  98. package/dist/source/adapters/drizzle.d.ts +43 -0
  99. package/dist/source/adapters/drizzle.js +185 -0
  100. package/dist/source/adapters/memory.d.ts +12 -0
  101. package/dist/source/adapters/memory.js +114 -0
  102. package/dist/source/adapters/prisma.d.ts +57 -0
  103. package/dist/source/adapters/prisma.js +176 -0
  104. package/dist/source/conformance.d.ts +32 -0
  105. package/dist/source/conformance.js +134 -0
  106. package/dist/source/contract.d.ts +144 -0
  107. package/dist/source/contract.js +99 -0
  108. package/dist/source/index.d.ts +62 -10
  109. package/dist/source/index.js +99 -0
  110. package/dist/source/migrations.d.ts +14 -0
  111. package/dist/source/migrations.js +39 -0
  112. package/dist/source/next.d.ts +33 -0
  113. package/dist/source/next.js +26 -0
  114. package/dist/sync/BootstrapHelper.d.ts +10 -0
  115. package/dist/sync/BootstrapHelper.js +10 -15
  116. package/dist/sync/ConnectionManager.d.ts +55 -1
  117. package/dist/sync/ConnectionManager.js +155 -16
  118. package/dist/sync/HydrationCoordinator.d.ts +93 -17
  119. package/dist/sync/HydrationCoordinator.js +238 -39
  120. package/dist/sync/NetworkProbe.d.ts +58 -24
  121. package/dist/sync/NetworkProbe.js +118 -42
  122. package/dist/sync/SyncWebSocket.d.ts +45 -70
  123. package/dist/sync/SyncWebSocket.js +70 -36
  124. package/dist/sync/createIntentStream.js +10 -1
  125. package/dist/types/streams.d.ts +9 -0
  126. package/dist/utils/mobx-setup.js +1 -0
  127. package/dist/webhooks/events.d.ts +38 -0
  128. package/dist/webhooks/events.js +40 -0
  129. package/dist/webhooks/index.d.ts +10 -0
  130. package/dist/webhooks/index.js +10 -0
  131. package/dist/wire/errorEnvelope.d.ts +34 -0
  132. package/dist/wire/errorEnvelope.js +86 -0
  133. package/dist/wire/frames.d.ts +119 -0
  134. package/dist/wire/frames.js +1 -0
  135. package/dist/wire/index.d.ts +24 -0
  136. package/dist/wire/index.js +21 -0
  137. package/dist/wire/listEnvelope.d.ts +45 -0
  138. package/dist/wire/listEnvelope.js +17 -0
  139. package/docs/api.md +47 -44
  140. package/docs/cli.md +44 -44
  141. package/docs/client-behavior.md +30 -30
  142. package/docs/coordination.md +33 -36
  143. package/docs/data-sources.md +35 -15
  144. package/docs/examples/agent-human.md +45 -43
  145. package/docs/examples/ai-sdk-tool.md +20 -16
  146. package/docs/examples/existing-python-backend.md +16 -12
  147. package/docs/examples/nextjs.md +14 -12
  148. package/docs/examples/scoped-agent.md +1 -1
  149. package/docs/examples/server-agent.md +24 -21
  150. package/docs/guarantees.md +15 -13
  151. package/docs/index.md +2 -2
  152. package/docs/integration-guide.md +30 -30
  153. package/docs/interaction-model.md +19 -23
  154. package/docs/mcp/claude-code.md +3 -3
  155. package/docs/mcp/cursor.md +1 -1
  156. package/docs/mcp/windsurf.md +2 -2
  157. package/docs/mcp.md +6 -6
  158. package/docs/quickstart.md +41 -31
  159. package/docs/react.md +13 -9
  160. package/docs/schema-contract.md +12 -10
  161. package/docs/the-loop.md +21 -0
  162. package/examples/data-source/README.md +4 -5
  163. package/examples/data-source/customer-server.ts +27 -25
  164. package/llms.txt +28 -5
  165. package/package.json +43 -3
@@ -0,0 +1,143 @@
1
+ /**
2
+ * undoApply.ts — conflict-aware resolution of undo/redo ops (per-user undo).
3
+ *
4
+ * The undo stack is already per-client (only local mutator invocations call
5
+ * `UndoScope.record`; a collaborator's edits arrive as inbound sync deltas and
6
+ * never land here). What this module adds is the second half of "undo per
7
+ * user": when replaying a recorded op, only touch a field whose CURRENT value
8
+ * still equals what THIS op established — so undo reverts your own change only
9
+ * where it still stands, and never clobbers a field a collaborator changed
10
+ * after you (the Yjs/CRDT "selective undo" principle, adapted to our
11
+ * field-level last-writer-wins model).
12
+ *
13
+ * `resolveOps(apply, paired, store, policy)`:
14
+ * - `apply` — the ops we're about to replay (inverses on undo, forwards on redo).
15
+ * - `paired` — their counterparts, carrying the value this op established
16
+ * (forwards on undo = "what I set"; inverses on redo = "what undo restored").
17
+ * - For `update`/`updateMany` ops it drops fields whose live value no longer
18
+ * matches the established value. `create`/`delete` families are structural
19
+ * and applied unconditionally (undoing your create removes the row you
20
+ * added; undoing your delete restores it).
21
+ *
22
+ * With no collaborator, the live value always equals what you set, so nothing
23
+ * is dropped — single-user undo is byte-for-byte unchanged.
24
+ */
25
+ export const DEFAULT_UNDO_CONFLICT_POLICY = 'skip-stale';
26
+ /** Structural equality for JSON-shaped values (scalars, arrays, plain objects). */
27
+ export function deepEqual(a, b) {
28
+ if (a === b)
29
+ return true;
30
+ if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
31
+ return false;
32
+ }
33
+ const aArr = Array.isArray(a);
34
+ if (aArr !== Array.isArray(b))
35
+ return false;
36
+ if (aArr) {
37
+ const av = a;
38
+ const bv = b;
39
+ if (av.length !== bv.length)
40
+ return false;
41
+ for (let i = 0; i < av.length; i++) {
42
+ if (!deepEqual(av[i], bv[i]))
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+ const ao = a;
48
+ const bo = b;
49
+ const ak = Object.keys(ao);
50
+ const bk = Object.keys(bo);
51
+ if (ak.length !== bk.length)
52
+ return false;
53
+ for (const k of ak) {
54
+ if (!Object.prototype.hasOwnProperty.call(bo, k))
55
+ return false;
56
+ if (!deepEqual(ao[k], bo[k]))
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ /**
62
+ * Map `id → { field: establishedValue }` from the paired ops. Only update-family
63
+ * ops carry per-field values worth comparing.
64
+ */
65
+ function buildEstablished(paired) {
66
+ const map = new Map();
67
+ for (const op of paired) {
68
+ if (op.kind === 'update') {
69
+ map.set(op.patch.id, op.patch);
70
+ }
71
+ else if (op.kind === 'updateMany') {
72
+ for (const p of op.patches)
73
+ map.set(p.id, p);
74
+ }
75
+ }
76
+ return map;
77
+ }
78
+ /** Read the live value of a field from the store's pool, or `undefined`. */
79
+ function readCurrentField(store, id, field) {
80
+ const model = store.pool.get(id);
81
+ if (!model)
82
+ return undefined;
83
+ const json = model.toJSON?.();
84
+ return json ? json[field] : undefined;
85
+ }
86
+ /**
87
+ * Keep only the fields whose live value still equals what this op established
88
+ * (`established[field]`). Returns `null` if nothing survives (the whole op is a
89
+ * no-op — every field was superseded by a collaborator).
90
+ */
91
+ function filterStalePatch(store, patch, established) {
92
+ const out = { id: patch.id };
93
+ let kept = 0;
94
+ for (const field of Object.keys(patch)) {
95
+ if (field === 'id')
96
+ continue;
97
+ if (established && field in established) {
98
+ // Apply only if the field still holds the value WE established — i.e. no
99
+ // collaborator overwrote it since. Otherwise skip (don't clobber them).
100
+ if (deepEqual(readCurrentField(store, patch.id, field), established[field])) {
101
+ out[field] = patch[field];
102
+ kept++;
103
+ }
104
+ }
105
+ else {
106
+ // No paired value to compare against. The recorder always pairs fields,
107
+ // so this is theoretical; apply to preserve undo functionality.
108
+ out[field] = patch[field];
109
+ kept++;
110
+ }
111
+ }
112
+ return kept > 0 ? out : null;
113
+ }
114
+ /**
115
+ * Filter the ops to apply so they don't clobber concurrent collaborator edits.
116
+ * See the module docblock. `last-writer-wins` returns the ops unchanged.
117
+ */
118
+ export function resolveOps(apply, paired, store, policy) {
119
+ if (policy === 'last-writer-wins')
120
+ return apply;
121
+ const established = buildEstablished(paired);
122
+ const out = [];
123
+ for (const op of apply) {
124
+ if (op.kind === 'update') {
125
+ const filtered = filterStalePatch(store, op.patch, established.get(op.patch.id));
126
+ if (filtered)
127
+ out.push({ kind: 'update', modelKey: op.modelKey, patch: filtered });
128
+ }
129
+ else if (op.kind === 'updateMany') {
130
+ const patches = op.patches
131
+ .map((p) => filterStalePatch(store, p, established.get(p.id)))
132
+ .filter((p) => p !== null);
133
+ if (patches.length > 0) {
134
+ out.push({ kind: 'updateMany', modelKey: op.modelKey, patches });
135
+ }
136
+ }
137
+ else {
138
+ // create / createMany / delete / deleteMany — structural, applied as-is.
139
+ out.push(op);
140
+ }
141
+ }
142
+ return out;
143
+ }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Thin wrapper over fetch() that:
5
5
  * - POSTs a QueryBatch as JSON
6
- * - Handles auth via `credentials: 'include'` (session cookie)
6
+ * - Sends the bearer credential via withAuthHeaders (Authorization header)
7
7
  * - Throws on non-2xx responses
8
8
  * - Parses the response into a typed QueryBatchResult
9
9
  *
@@ -12,6 +12,7 @@
12
12
  * without duplicating the fetch boilerplate.
13
13
  */
14
14
  import type { QueryBatch, QueryBatchResult } from './types.js';
15
+ import { type AuthTokenGetter } from '../auth/credentialSource.js';
15
16
  export interface PostQueryOptions {
16
17
  /**
17
18
  * Full base URL of the sync server including the `/api` prefix.
@@ -22,14 +23,14 @@ export interface PostQueryOptions {
22
23
  /** Timeout in ms for the fetch request. Default: 30000. */
23
24
  fetchTimeout?: number;
24
25
  /**
25
- * Bearer credential a restricted (`rk_`) API key — attached as
26
- * `Authorization: Bearer <token>`. Required for Node consumers
27
- * (agent-worker, server-side tests) that have no session cookie to
28
- * ride. Browser consumers can omit this and fall back to
29
- * `credentials: 'include'`. When both are present the server prefers
30
- * the Bearer header (see `apiKeyProvider` in
31
- * `apps/sync-server/src/auth`), so passing the token in browser code
32
- * is harmless. (Field name predates the Biscuit→opaque-key migration.)
26
+ * Live bearer credential getter. Preferred over `capabilityToken` because it
27
+ * is read per request, so token refreshes propagate without reconstructing
28
+ * query helpers.
29
+ */
30
+ getAuthToken?: AuthTokenGetter;
31
+ /**
32
+ * Compatibility fallback for callers that have only a copied token string.
33
+ * New SDK internals should pass `getAuthToken`.
33
34
  */
34
35
  capabilityToken?: string;
35
36
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Thin wrapper over fetch() that:
5
5
  * - POSTs a QueryBatch as JSON
6
- * - Handles auth via `credentials: 'include'` (session cookie)
6
+ * - Sends the bearer credential via withAuthHeaders (Authorization header)
7
7
  * - Throws on non-2xx responses
8
8
  * - Parses the response into a typed QueryBatchResult
9
9
  *
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { z } from 'zod';
15
15
  import { translateHttpError } from '../errors.js';
16
+ import { withAuthHeaders } from '../auth/credentialSource.js';
16
17
  // ── Response validation ─────────────────────────────────────────────────
17
18
  //
18
19
  // Each result slot is an array of rows (or an object for bundled
@@ -49,14 +50,10 @@ export async function postQuery(options, batch) {
49
50
  const controller = new AbortController();
50
51
  const timer = setTimeout(() => controller.abort(), timeout);
51
52
  try {
52
- const headers = { 'Content-Type': 'application/json' };
53
- if (options.capabilityToken) {
54
- headers.Authorization = `Bearer ${options.capabilityToken}`;
55
- }
53
+ const headers = withAuthHeaders(options.getAuthToken, { 'Content-Type': 'application/json' }, options.capabilityToken);
56
54
  const response = await fetch(url, {
57
55
  method: 'POST',
58
56
  headers,
59
- credentials: 'include',
60
57
  body: JSON.stringify(batch),
61
58
  signal: controller.signal,
62
59
  });
@@ -1,10 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { Schema, SchemaRecord } from '../schema/schema.js';
2
+ import type { SchemaRecord } from '../schema/schema.js';
3
3
  import { Ablo } from '../client/Ablo.js';
4
- import type { AbloPersistence } from '../client/persistence.js';
5
- import type { SyncEngineConfig, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, SyncLogger, SyncObservabilityProvider } from '../config/index.js';
6
- import type { UseMutatorsOptions } from './useMutators.js';
7
- import type { MutatorDefs } from '../mutators/defineMutators.js';
8
4
  import type { ActiveIntent, Peer } from '../types/streams.js';
9
5
  import type { EngineParticipant, ParticipantScope, ParticipantStatus } from '../sync/participants.js';
10
6
  import { type SyncStoreContract } from './context.js';
@@ -49,140 +45,41 @@ import { type SyncStoreContract } from './context.js';
49
45
  */
50
46
  export interface AbloProviderProps<R extends SchemaRecord = SchemaRecord> {
51
47
  /**
52
- * Schema from `defineSchema()`. Determines the typed hook surface.
53
- * This is the only prop most apps pass start here.
54
- */
55
- schema: Schema<R>;
56
- /**
57
- * WebSocket URL of the sync server (`wss://...` or `ws://...`).
58
- * Hosted apps omit this.
48
+ * A prebuilt {@link Ablo} client — **the only way to configure the engine.**
49
+ * Construct it yourself with `Ablo({ schema, apiKey, ... })` and pass the
50
+ * instance: the CLIENT owns auth, the credential lifecycle, transport, and
51
+ * connection; this provider is the thin REACTIVE binding over it (context,
52
+ * the bootstrap gate, error/​session forwarding). Mirrors Stripe
53
+ * `<Elements stripe={...}>` and a Supabase client passed into a context.
54
+ *
55
+ * Memoize it (build it once, e.g. with `useMemo` or module scope) — a new
56
+ * instance each render re-keys the bootstrap gate and tears down the socket.
59
57
  */
60
- url?: string;
58
+ client: Ablo<R>;
61
59
  /**
62
- * Optional app user id for app-owned fields. Ablo resolves sync
63
- * participant identity from auth; this is not required to connect.
60
+ * The app user id, surfaced via `useCurrentUserId()` for app-owned fields.
61
+ * Purely informational for the React tree — sync identity is resolved by the
62
+ * client from its auth, not from this. Optional.
64
63
  */
65
64
  userId?: string;
66
- /** Team IDs the user belongs to. Expanded into sync groups. */
67
- teamIds?: string[];
68
- /**
69
- * API key for engine bootstrap auth. Used by the bootstrap fetch
70
- * path; falls back to `credentials: 'include'` (session cookie)
71
- * when unset. Browser apps typically omit this and rely on
72
- * same-origin session cookies.
73
- */
74
- apiKey?: string;
75
- /**
76
- * Static bearer auth token, sent as `Authorization: Bearer <token>` on the
77
- * WebSocket upgrade + HTTP. For a token that must be refreshed (e.g. a
78
- * short-lived JWT), prefer {@link getToken}.
79
- */
80
- authToken?: string | null;
81
65
  /**
82
- * Async resolver for a short-lived bearer token. Called once before the
83
- * engine connects (so the first connection carries a fresh token) and then on
84
- * a refresh interval ahead of expiry — each result is pushed via
85
- * `engine.setAuthToken` without tearing down the connection. Wire this to
86
- * a resolver that mints a short-lived session token (`ek_`/`rk_`) — e.g.
87
- * `getSyncCapabilityToken` — or your own. Takes precedence over
88
- * {@link authEndpoint}.
89
- */
90
- getToken?: () => Promise<string | null>;
91
- /**
92
- * Liveblocks/Stripe-style auth endpoint: a URL on YOUR backend that returns
93
- * `{ token }` — the `ek_` ephemeral key your server minted for the logged-in
94
- * user with `ablo.sessions.create({ user: { id } })`. The provider POSTs to it
95
- * (with cookies) to fetch + refresh the bearer, so the browser carries no
96
- * secret. Shorthand for a {@link getToken} that does the fetch; ignored when
97
- * `getToken` is set.
98
- */
99
- authEndpoint?: string;
100
- /** Optional Zero-style custom mutators. */
101
- mutators?: MutatorDefs<Schema<R>>;
102
- /** Options forwarded to the internal `useMutators` call (e.g., `undoScope`). */
103
- mutatorOptions?: UseMutatorsOptions<Schema<R>>;
104
- /**
105
- * Block browser tab close when there are unsynced local writes.
106
- * Triggers the standard `beforeunload` "Leave site?" prompt.
107
- * Browsers ignore custom messages — do not pass one. Consumers
108
- * who want telemetry should read
109
- * `useSyncStatus().hasUnsyncedChanges` directly.
66
+ * Block tab close while there are unsynced local writes (the standard
67
+ * `beforeunload` prompt). Browsers ignore custom messages don't pass one.
110
68
  */
111
69
  preventUnsavedChanges?: boolean;
112
70
  /**
113
- * Milliseconds to tolerate connection loss before `useSyncStatus()`
114
- * flips to `disconnected`. Defaults to 5000. Set to 0 to
115
- * disable the grace period (immediate transition).
116
- *
117
- * v0.3.0 scope: reserved for future wiring. Current transition is
118
- * driven by the engine's built-in state machine.
119
- */
120
- lostConnectionTimeout?: number;
121
- /**
122
- * Fired when the server rejects the session. The provider has
123
- * ALREADY called `engine.purge()` (disposed + wiped IndexedDB) by
124
- * the time this runs — the callback is for app-level side effects
125
- * (e.g., redirect to sign-in, clear analytics identity).
71
+ * Fired when the server rejects the session. The provider has ALREADY called
72
+ * `client.purge()` (disposed + wiped IndexedDB) by the time this runs — use it
73
+ * for app side effects (redirect to sign-in, clear analytics identity).
126
74
  */
127
75
  onSessionExpired?: () => void | Promise<void>;
128
76
  /**
129
- * Fired on any error the provider surfaces: engine errors,
130
- * WebSocket errors, uncaught `postBootstrap` exceptions. Use for
131
- * Sentry / Datadog. Consumers who only want errors inside React
132
- * can use the `useErrorListener()` hook instead.
77
+ * Fired on any error the provider surfaces (engine/WebSocket/bootstrap). For
78
+ * Sentry/Datadog. React-only consumers can use `useErrorListener()` instead.
133
79
  */
134
80
  onError?: (error: Error) => void;
135
- observability?: SyncObservabilityProvider;
136
- logger?: SyncLogger;
137
- mutationExecutor?: MutationExecutor;
138
- mutationDispatcher?: MutationDispatcher;
139
- sessionErrorDetector?: SessionErrorDetector;
140
- onlineStatus?: OnlineStatusProvider;
141
- configOverrides?: SyncEngineConfig;
142
- /**
143
- * Raw sync-group strings for the initial connection. Prefer {@link scope} —
144
- * the model form (`{ decks: deckId }`) that the engine resolves through the
145
- * schema's `scope`, so you never hand-write a `deck:<id>` string. Both merge.
146
- */
147
- syncGroups?: string[];
148
- /**
149
- * Model-form connection scope: `{ decks: deckId, documents: documentId }` or
150
- * entity refs. Resolved through the schema's per-model `scope` into group
151
- * strings (so typename `SlideDeck` → `deck:<id>`), unioned with {@link syncGroups}.
152
- * Memoize the object if it's derived, to avoid rotating the engine each render.
153
- */
154
- scope?: ParticipantScope;
155
- bootstrapBaseUrl?: string;
156
- maxPoolSize?: number;
157
- /**
158
- * Local persistence mode for the underlying `Ablo` client. Defaults
159
- * to `volatile` — pass `'indexeddb'` to opt back into offline-queue +
160
- * reload-surviving cache in a browser. See `AbloOptions.persistence`
161
- * for the full semantics.
162
- */
163
- persistence?: AbloPersistence;
164
- /**
165
- * How aggressively this provider pulls baseline state at startup.
166
- *
167
- * - `'full'` (default): pull every delta in the configured sync
168
- * groups before the engine reports ready — a local replica of the
169
- * org's tenant plane. Right for collaborative editors and any page
170
- * that reads a lot of shared state.
171
- * - `'none'`: open the connection and process live deltas only — no
172
- * baseline fetch. Reads round-trip via `ablo.<model>.retrieve(...)`
173
- * and subscriptions populate the pool lazily. Right for read-light
174
- * pages (a mostly-static dashboard, a settings screen) that don't
175
- * want to download the whole org to render.
176
- *
177
- * Note: `'none'` still opens the realtime connection — it skips the
178
- * baseline pull, not the socket. A fully connection-free mode for
179
- * pages that do zero multiplayer is a separate follow-up (the socket
180
- * open lives inside `engine.ready()`, so deferring it needs
181
- * engine-level lazy-connect support, not just a provider prop).
182
- *
183
- * Mirrors `AbloOptions.bootstrapMode`. Changing it rotates the engine.
184
- */
185
- bootstrapMode?: 'full' | 'none';
81
+ /** @internal placeholder so the old WS-URL prop shape doesn't silently leak in. */
82
+ url?: never;
186
83
  /**
187
84
  * Rendered in place of `children` during the *first* bootstrap pass —
188
85
  * while the engine is actively transitioning from `initial` →