@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.
- package/CHANGELOG.md +46 -1
- package/README.md +33 -28
- package/dist/BaseSyncedStore.d.ts +83 -0
- package/dist/BaseSyncedStore.js +194 -2
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/agent/session.js +3 -3
- package/dist/ai-sdk/coordination-context.js +4 -0
- package/dist/ai-sdk/index.d.ts +56 -47
- package/dist/ai-sdk/index.js +56 -47
- package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
- package/dist/ai-sdk/intent-broadcast.js +11 -4
- package/dist/ai-sdk/wrap.d.ts +14 -11
- package/dist/ai-sdk/wrap.js +11 -13
- package/dist/auth/credentialSource.d.ts +34 -0
- package/dist/auth/credentialSource.js +63 -0
- package/dist/auth/index.d.ts +2 -22
- package/dist/auth/index.js +4 -42
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +160 -42
- package/dist/client/Ablo.js +145 -75
- package/dist/client/ApiClient.d.ts +20 -4
- package/dist/client/ApiClient.js +166 -28
- package/dist/client/auth.d.ts +14 -5
- package/dist/client/auth.js +60 -7
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +130 -66
- package/dist/client/createModelProxy.js +152 -49
- package/dist/client/httpClient.d.ts +71 -0
- package/dist/client/httpClient.js +69 -0
- package/dist/client/identity.d.ts +2 -6
- package/dist/client/identity.js +49 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +3 -3
- package/dist/client/registerDataSource.js +11 -9
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/core/DatabaseManager.js +30 -2
- package/dist/core/openIDBWithTimeout.d.ts +36 -0
- package/dist/core/openIDBWithTimeout.js +88 -1
- package/dist/errorCodes.d.ts +70 -1
- package/dist/errorCodes.js +108 -9
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +72 -22
- package/dist/index.d.ts +17 -8
- package/dist/index.js +15 -6
- package/dist/keys/index.d.ts +16 -1
- package/dist/keys/index.js +26 -6
- package/dist/mutators/UndoManager.d.ts +158 -50
- package/dist/mutators/UndoManager.js +345 -22
- package/dist/mutators/inverseOp.d.ts +129 -0
- package/dist/mutators/inverseOp.js +74 -0
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +42 -0
- package/dist/mutators/undoApply.js +143 -0
- package/dist/query/client.d.ts +10 -9
- package/dist/query/client.js +3 -6
- package/dist/react/AbloProvider.d.ts +23 -126
- package/dist/react/AbloProvider.js +62 -199
- package/dist/react/context.d.ts +31 -0
- package/dist/react/useAblo.d.ts +2 -2
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- package/dist/schema/ddl.d.ts +34 -3
- package/dist/schema/ddl.js +162 -4
- package/dist/schema/index.d.ts +5 -1
- package/dist/schema/index.js +13 -1
- package/dist/schema/model.d.ts +11 -0
- package/dist/schema/model.js +2 -0
- package/dist/schema/openapi.d.ts +28 -0
- package/dist/schema/openapi.js +118 -0
- package/dist/schema/plane.d.ts +23 -0
- package/dist/schema/plane.js +19 -0
- package/dist/schema/relation.d.ts +20 -0
- package/dist/schema/serialize.d.ts +4 -0
- package/dist/schema/serialize.js +4 -0
- package/dist/schema/sync-delta-row.d.ts +157 -0
- package/dist/schema/sync-delta-row.js +102 -0
- package/dist/schema/sync-delta-wire.d.ts +180 -0
- package/dist/schema/sync-delta-wire.js +102 -0
- package/dist/server/adapter.d.ts +156 -0
- package/dist/server/adapter.js +19 -0
- package/dist/server/commit.d.ts +82 -0
- package/dist/server/commit.js +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +1 -0
- package/dist/server/next.d.ts +51 -0
- package/dist/server/next.js +47 -0
- package/dist/server/read-config.d.ts +60 -0
- package/dist/server/read-config.js +8 -0
- package/dist/server/storage-mode.d.ts +17 -0
- package/dist/server/storage-mode.js +12 -0
- package/dist/source/adapter.d.ts +65 -0
- package/dist/source/adapter.js +20 -0
- package/dist/source/adapters/drizzle.d.ts +43 -0
- package/dist/source/adapters/drizzle.js +185 -0
- package/dist/source/adapters/memory.d.ts +12 -0
- package/dist/source/adapters/memory.js +114 -0
- package/dist/source/adapters/prisma.d.ts +57 -0
- package/dist/source/adapters/prisma.js +176 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +144 -0
- package/dist/source/contract.js +99 -0
- package/dist/source/index.d.ts +62 -10
- package/dist/source/index.js +99 -0
- package/dist/source/migrations.d.ts +14 -0
- package/dist/source/migrations.js +39 -0
- package/dist/source/next.d.ts +33 -0
- package/dist/source/next.js +26 -0
- package/dist/sync/BootstrapHelper.d.ts +10 -0
- package/dist/sync/BootstrapHelper.js +10 -15
- package/dist/sync/ConnectionManager.d.ts +55 -1
- package/dist/sync/ConnectionManager.js +155 -16
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +238 -39
- package/dist/sync/NetworkProbe.d.ts +58 -24
- package/dist/sync/NetworkProbe.js +118 -42
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +70 -36
- package/dist/sync/createIntentStream.js +10 -1
- package/dist/types/streams.d.ts +9 -0
- package/dist/utils/mobx-setup.js +1 -0
- package/dist/webhooks/events.d.ts +38 -0
- package/dist/webhooks/events.js +40 -0
- package/dist/webhooks/index.d.ts +10 -0
- package/dist/webhooks/index.js +10 -0
- package/dist/wire/errorEnvelope.d.ts +34 -0
- package/dist/wire/errorEnvelope.js +86 -0
- package/dist/wire/frames.d.ts +119 -0
- package/dist/wire/frames.js +1 -0
- package/dist/wire/index.d.ts +24 -0
- package/dist/wire/index.js +21 -0
- package/dist/wire/listEnvelope.d.ts +45 -0
- package/dist/wire/listEnvelope.js +17 -0
- package/docs/api.md +47 -44
- package/docs/cli.md +44 -44
- package/docs/client-behavior.md +30 -30
- package/docs/coordination.md +33 -36
- package/docs/data-sources.md +35 -15
- package/docs/examples/agent-human.md +45 -43
- package/docs/examples/ai-sdk-tool.md +20 -16
- package/docs/examples/existing-python-backend.md +16 -12
- package/docs/examples/nextjs.md +14 -12
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/examples/server-agent.md +24 -21
- package/docs/guarantees.md +15 -13
- package/docs/index.md +2 -2
- package/docs/integration-guide.md +30 -30
- package/docs/interaction-model.md +19 -23
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +2 -2
- package/docs/mcp.md +6 -6
- package/docs/quickstart.md +41 -31
- package/docs/react.md +13 -9
- package/docs/schema-contract.md +12 -10
- package/docs/the-loop.md +21 -0
- package/examples/data-source/README.md +4 -5
- package/examples/data-source/customer-server.ts +27 -25
- package/llms.txt +28 -5
- 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
|
+
}
|
package/dist/query/client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Thin wrapper over fetch() that:
|
|
5
5
|
* - POSTs a QueryBatch as JSON
|
|
6
|
-
* -
|
|
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
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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
|
}
|
package/dist/query/client.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Thin wrapper over fetch() that:
|
|
5
5
|
* - POSTs a QueryBatch as JSON
|
|
6
|
-
* -
|
|
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 {
|
|
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
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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
|
-
|
|
58
|
+
client: Ablo<R>;
|
|
61
59
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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
|
-
*
|
|
83
|
-
*
|
|
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
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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
|
|
130
|
-
*
|
|
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
|
-
|
|
136
|
-
|
|
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` →
|