@voltro/local-first 0.51.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +356 -0
- package/README.md +1 -1
- package/THIRD-PARTY-NOTICES.md +29 -1
- package/dist/index.d.ts +329 -20
- package/dist/index.js +192 -27
- package/dist/react.d.ts +66 -7
- package/dist/react.js +20 -20
- package/dist/{room-wFNUwEgi.js → room-DZzqWoJq.js} +100 -38
- package/package.json +21 -4
package/dist/index.d.ts
CHANGED
|
@@ -62,18 +62,29 @@ export declare interface ConnectionState {
|
|
|
62
62
|
readonly attempt: number;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/**
|
|
66
|
-
* A CRDT implementation. One per library. The default is {@link yjsBackend}.
|
|
67
|
-
*
|
|
68
|
-
* `merge` and `decodeText` are STATELESS with respect to any live handle: they
|
|
69
|
-
* operate purely on encoded bytes, which is what lets the sync layer and tests
|
|
70
|
-
* converge two states without holding a document open.
|
|
71
|
-
*/
|
|
72
65
|
export declare interface CrdtBackend {
|
|
73
66
|
/** Identifies the library, e.g. `"yjs"`. Surfaced for diagnostics/telemetry. */
|
|
74
67
|
readonly name: string;
|
|
75
68
|
/** Create a fresh text buffer, optionally seeded with `initial`. */
|
|
76
69
|
createText: (initial?: string) => CrdtTextHandle;
|
|
70
|
+
/** Create a whole-document handle, optionally seeded from encoded state. */
|
|
71
|
+
createDoc: (initial?: CrdtState) => CrdtDocHandle;
|
|
72
|
+
/**
|
|
73
|
+
* Re-encode a state through a live document so the backend garbage-collects
|
|
74
|
+
* what it can (Yjs: `mergeUpdates` never GCs tombstones, so a hot document's
|
|
75
|
+
* blob grows monotonically without this). Loses UPDATE HISTORY by design —
|
|
76
|
+
* a version-history feature must snapshot BEFORE compacting.
|
|
77
|
+
*/
|
|
78
|
+
compact: (state: CrdtState) => CrdtState;
|
|
79
|
+
/**
|
|
80
|
+
* HARD compaction for a text state: re-create the document from its
|
|
81
|
+
* MATERIALISED text, dropping all history, tombstones and client ids. The
|
|
82
|
+
* result is minimal — and a NEW EPOCH: it does not merge with updates from
|
|
83
|
+
* the old lineage, so the server treats a rebase as a fresh snapshot for
|
|
84
|
+
* every subscriber (which the subscription lane already handles as a
|
|
85
|
+
* reset). Use when `compact` no longer bounds a hot document.
|
|
86
|
+
*/
|
|
87
|
+
rebaseText: (state: CrdtState) => CrdtState;
|
|
77
88
|
/**
|
|
78
89
|
* Merge two encoded states into one converged encoded state.
|
|
79
90
|
*
|
|
@@ -89,6 +100,54 @@ export declare interface CrdtBackend {
|
|
|
89
100
|
emptyState: () => CrdtState;
|
|
90
101
|
}
|
|
91
102
|
|
|
103
|
+
/**
|
|
104
|
+
* A CRDT implementation. One per library. The default is {@link yjsBackend}.
|
|
105
|
+
*
|
|
106
|
+
* `merge` and `decodeText` are STATELESS with respect to any live handle: they
|
|
107
|
+
* operate purely on encoded bytes, which is what lets the sync layer and tests
|
|
108
|
+
* converge two states without holding a document open.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* A live, mutable CRDT DOCUMENT — the whole-doc generalisation `crdtDoc()`
|
|
112
|
+
* stores (rich text, maps, arrays; whatever the backend's document model
|
|
113
|
+
* holds). `crdtText()` remains the single-field specialisation over the same
|
|
114
|
+
* backend.
|
|
115
|
+
*/
|
|
116
|
+
declare interface CrdtDocHandle {
|
|
117
|
+
/**
|
|
118
|
+
* The underlying library document (a `Y.Doc` for the yjs backend), typed
|
|
119
|
+
* opaquely: an EDITOR BINDING needs the real object (Tiptap's Collaboration
|
|
120
|
+
* extension takes a Y.Doc), while every sync-layer consumer only moves
|
|
121
|
+
* encoded bytes and must never reach in. Cast at the binding, nowhere else.
|
|
122
|
+
*/
|
|
123
|
+
readonly raw: unknown;
|
|
124
|
+
/** Full state, encoded for transport or merge. */
|
|
125
|
+
encodeState: () => CrdtState;
|
|
126
|
+
/** This doc's state VECTOR — the compact "what I have" summary a peer
|
|
127
|
+
* diffs against. */
|
|
128
|
+
stateVector: () => Uint8Array;
|
|
129
|
+
/**
|
|
130
|
+
* Encode only what a peer holding `sinceVector` is missing — the
|
|
131
|
+
* INCREMENTAL update lane. A 1-character edit against a 100 KB doc encodes
|
|
132
|
+
* to a few dozen bytes, not the full state.
|
|
133
|
+
*/
|
|
134
|
+
encodeUpdateSince: (sinceVector: Uint8Array) => CrdtState;
|
|
135
|
+
/** Fold an encoded state or incremental update into this doc, in place. */
|
|
136
|
+
applyState: (state: CrdtState) => void;
|
|
137
|
+
/** Subscribe to update blobs this doc produces (local edits AND applied
|
|
138
|
+
* remote states). Returns the unsubscribe. */
|
|
139
|
+
onUpdate: (handler: (update: CrdtState) => void) => () => void;
|
|
140
|
+
/**
|
|
141
|
+
* Encode a stable ANCHOR at `index` of the named text field — a position
|
|
142
|
+
* that survives concurrent edits (the primitive an inline-comment UI pins
|
|
143
|
+
* threads with; the UI itself is the comments plugin's business).
|
|
144
|
+
*/
|
|
145
|
+
encodeAnchor: (field: string, index: number) => Uint8Array;
|
|
146
|
+
/** Resolve an anchor back to its current index, or `undefined` when the
|
|
147
|
+
* anchored region was deleted. */
|
|
148
|
+
resolveAnchor: (encoded: Uint8Array) => number | undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
92
151
|
/** Identifies one CRDT-managed cell: a `crdtText()` COLUMN of one ROW of one TABLE. */
|
|
93
152
|
export declare interface CrdtDocKey {
|
|
94
153
|
readonly table: string;
|
|
@@ -96,6 +155,14 @@ export declare interface CrdtDocKey {
|
|
|
96
155
|
readonly column: string;
|
|
97
156
|
}
|
|
98
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The client half of the CRDT downstream lane (plan 18): fold a `mergeCells`
|
|
160
|
+
* delta op's INCREMENTAL update into the held cell state. Pass as
|
|
161
|
+
* `@voltro/client`'s `SubscriptionCacheOptions.mergeCell`. A held cell that
|
|
162
|
+
* is not bytes (never delivered yet) takes the update as the new state.
|
|
163
|
+
*/
|
|
164
|
+
export declare const crdtMergeCell: (_column: string, prevValue: unknown, update: unknown) => unknown;
|
|
165
|
+
|
|
99
166
|
/**
|
|
100
167
|
* An encoded CRDT state or delta, as opaque bytes. Produced by
|
|
101
168
|
* {@link CrdtBackend.encodeState} / {@link CrdtTextHandle.encodeState},
|
|
@@ -168,6 +235,26 @@ export declare interface CrdtWritePayload extends CrdtDocKey {
|
|
|
168
235
|
readonly update: CrdtState;
|
|
169
236
|
}
|
|
170
237
|
|
|
238
|
+
/**
|
|
239
|
+
* The fallback-aware entry: durable when the environment allows it, in-memory
|
|
240
|
+
* + online-only otherwise — the plan's storage-fallback rule. The caller gets
|
|
241
|
+
* told which one it got; silence here would make a lockdown browser look like
|
|
242
|
+
* a working offline app until the first reload lost everything.
|
|
243
|
+
*/
|
|
244
|
+
export declare const createDurableKv: (options?: {
|
|
245
|
+
readonly factory?: KvIndexedDbFactory;
|
|
246
|
+
readonly databaseName?: string;
|
|
247
|
+
}) => Promise<DurableKvResult>;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* A durable KvStore over IndexedDB. Throws when no factory is available —
|
|
251
|
+
* `createDurableKv` below is the fallback-aware entry callers should use.
|
|
252
|
+
*/
|
|
253
|
+
export declare const createIndexedDbKv: (options?: {
|
|
254
|
+
readonly factory?: KvIndexedDbFactory;
|
|
255
|
+
readonly databaseName?: string;
|
|
256
|
+
}) => Promise<KvStore>;
|
|
257
|
+
|
|
171
258
|
/**
|
|
172
259
|
* Create a durable {@link PersistenceAdapter} over IndexedDB.
|
|
173
260
|
*
|
|
@@ -181,6 +268,9 @@ export declare const createIndexedDbPersistence: (options?: {
|
|
|
181
268
|
readonly databaseName?: string;
|
|
182
269
|
}) => Promise<PersistenceAdapter>;
|
|
183
270
|
|
|
271
|
+
/** In-memory KvStore — real (copies nothing; values are caller-owned JSON). */
|
|
272
|
+
export declare const createInMemoryKv: () => KvStore;
|
|
273
|
+
|
|
184
274
|
/**
|
|
185
275
|
* An in-memory {@link PersistenceAdapter} backed by Maps. Real, not a stub: it
|
|
186
276
|
* fully satisfies the contract, and copies bytes on the way in and out so a
|
|
@@ -214,6 +304,18 @@ export declare const createInMemoryPresenceChannel: (bus?: InMemoryPresenceBus)
|
|
|
214
304
|
*/
|
|
215
305
|
export declare const createPresenceRoom: <TPresence>(options: PresenceRoomOptions<TPresence>) => PresenceRoom<TPresence>;
|
|
216
306
|
|
|
307
|
+
export declare const createQueryMirror: (kv: KvStore, partition: MirrorPartition, options?: QueryMirrorOptions, now?: () => number) => QueryMirror;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Bind a partitioned {@link QueryMirror} to the subscription cache's mirror
|
|
311
|
+
* seam. Only declared tags persist; the mirror's own encrypted-column strip
|
|
312
|
+
* runs through `metadata` via the tag→table map.
|
|
313
|
+
*
|
|
314
|
+
* NOTE the mirror instance must already carry the CURRENT subject's
|
|
315
|
+
* partition — see the partition-switch note in the file header.
|
|
316
|
+
*/
|
|
317
|
+
export declare const createSubscriptionMirrorBinding: (mirror: QueryMirror, options: MirrorBindingOptions) => SubscriptionMirrorShape;
|
|
318
|
+
|
|
217
319
|
/**
|
|
218
320
|
* Create a sync client over a {@link SyncTransport}.
|
|
219
321
|
*
|
|
@@ -245,6 +347,26 @@ export declare const defaultCrdtBackend: CrdtBackend;
|
|
|
245
347
|
*/
|
|
246
348
|
export declare const deriveSyncStatus: (phase: ConnectionPhase, outstanding: number) => SyncStatus;
|
|
247
349
|
|
|
350
|
+
export declare interface DrainLockOptions {
|
|
351
|
+
/** Injectable locks implementation. Defaults to `navigator.locks` (which
|
|
352
|
+
* node 22+ and every modern browser provide). Pass `null` to force the
|
|
353
|
+
* no-locks path — a host genuinely without the API, or a test. */
|
|
354
|
+
readonly locks?: WebLocksLike | null;
|
|
355
|
+
/** Partition discriminator — one drainer per (subject, tenant) queue. */
|
|
356
|
+
readonly name: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export declare type DrainOutcome = 'drained' | 'held-elsewhere' | 'no-locks';
|
|
360
|
+
|
|
361
|
+
export declare interface DurableKvResult {
|
|
362
|
+
readonly kv: KvStore;
|
|
363
|
+
/** `'durable'` when IndexedDB opened; `'memory'` when the environment has
|
|
364
|
+
* none (non-browser host, lockdown mode) or the open failed (quota, a
|
|
365
|
+
* private tab that refuses at open time). VISIBLE degradation — render it
|
|
366
|
+
* in a sync status — never a crash. */
|
|
367
|
+
readonly durability: 'durable' | 'memory';
|
|
368
|
+
}
|
|
369
|
+
|
|
248
370
|
/** The encoded empty state — the identity element for {@link mergeCrdtStates}. */
|
|
249
371
|
export declare const emptyCrdtState: (backend?: CrdtBackend) => CrdtState;
|
|
250
372
|
|
|
@@ -268,16 +390,35 @@ declare interface IDBDatabaseLike {
|
|
|
268
390
|
createObjectStore: (name: string) => IDBObjectStoreLike;
|
|
269
391
|
}
|
|
270
392
|
|
|
393
|
+
declare interface IDBDatabaseLike_2 {
|
|
394
|
+
transaction(name: string, mode: 'readonly' | 'readwrite'): IDBTransactionLike_2;
|
|
395
|
+
objectStoreNames: {
|
|
396
|
+
contains(name: string): boolean;
|
|
397
|
+
};
|
|
398
|
+
createObjectStore(name: string): unknown;
|
|
399
|
+
}
|
|
400
|
+
|
|
271
401
|
declare interface IDBObjectStoreLike {
|
|
272
402
|
get: (key: string) => IDBRequestLike<unknown>;
|
|
273
403
|
put: (value: unknown, key: string) => IDBRequestLike<unknown>;
|
|
274
404
|
delete: (key: string) => IDBRequestLike<unknown>;
|
|
275
405
|
}
|
|
276
406
|
|
|
407
|
+
declare interface IDBObjectStoreLike_2 {
|
|
408
|
+
get(key: string): IDBRequestLike_2<unknown>;
|
|
409
|
+
put(value: unknown, key: string): IDBRequestLike_2<unknown>;
|
|
410
|
+
delete(key: string): IDBRequestLike_2<unknown>;
|
|
411
|
+
getAllKeys(range?: unknown): IDBRequestLike_2<ReadonlyArray<string>>;
|
|
412
|
+
}
|
|
413
|
+
|
|
277
414
|
declare interface IDBOpenRequestLike extends IDBRequestLike<IDBDatabaseLike> {
|
|
278
415
|
onupgradeneeded: ((this: unknown, ev: unknown) => void) | null;
|
|
279
416
|
}
|
|
280
417
|
|
|
418
|
+
declare interface IDBOpenRequestLike_2 extends IDBRequestLike_2<IDBDatabaseLike_2> {
|
|
419
|
+
onupgradeneeded: ((this: unknown, ev: unknown) => void) | null;
|
|
420
|
+
}
|
|
421
|
+
|
|
281
422
|
declare interface IDBRequestLike<T> {
|
|
282
423
|
result: T;
|
|
283
424
|
error: unknown;
|
|
@@ -285,18 +426,36 @@ declare interface IDBRequestLike<T> {
|
|
|
285
426
|
onerror: ((this: unknown, ev: unknown) => void) | null;
|
|
286
427
|
}
|
|
287
428
|
|
|
429
|
+
declare interface IDBRequestLike_2<T> {
|
|
430
|
+
result: T;
|
|
431
|
+
error: unknown;
|
|
432
|
+
onsuccess: ((this: unknown, ev: unknown) => void) | null;
|
|
433
|
+
onerror: ((this: unknown, ev: unknown) => void) | null;
|
|
434
|
+
}
|
|
435
|
+
|
|
288
436
|
declare interface IDBTransactionLike {
|
|
289
437
|
objectStore: (name: string) => IDBObjectStoreLike;
|
|
290
438
|
}
|
|
291
439
|
|
|
440
|
+
declare interface IDBTransactionLike_2 {
|
|
441
|
+
objectStore(name: string): IDBObjectStoreLike_2;
|
|
442
|
+
}
|
|
443
|
+
|
|
292
444
|
/**
|
|
293
445
|
* DONE — Durable persistence.
|
|
294
446
|
* `createIndexedDbPersistence` (./persistence/indexedDb) is a durable
|
|
295
447
|
* `PersistenceAdapter` over IndexedDB — no WASM, no added dependency, tested
|
|
296
448
|
* against a fake IDB backend that survives a reopen. `createInMemoryPersistence`
|
|
297
|
-
* remains the ephemeral default.
|
|
298
|
-
*
|
|
299
|
-
*
|
|
449
|
+
* remains the ephemeral default. The engine's query mirror adds `KvStore`
|
|
450
|
+
* (in-memory + IndexedDB + `createDurableKv` with a VISIBLE memory
|
|
451
|
+
* fallback) beneath `createQueryMirror` — budget eviction included
|
|
452
|
+
* (`enforceBudget`, oldest-saved first). wa-sqlite was considered and
|
|
453
|
+
* REJECTED with the reasoning recorded in `mirror/queryMirror.ts`: the
|
|
454
|
+
* client's query surface is `(tag, input)`, predicates never exist
|
|
455
|
+
* client-side, so a browser SQL engine would evaluate a language the
|
|
456
|
+
* client never sees. A SQLite BACKING behind `KvStore` remains possible
|
|
457
|
+
* without touching a consumer (mobile's expo-sqlite adapter takes exactly
|
|
458
|
+
* that seam).
|
|
300
459
|
*
|
|
301
460
|
* DONE — Bi-directional sync wire.
|
|
302
461
|
* `createSyncClient` (./sync/syncClient) maps the pure sync-queue reducer onto
|
|
@@ -323,10 +482,14 @@ declare interface IDBTransactionLike {
|
|
|
323
482
|
* ephemeral awareness over a `PresenceChannel` — join/leave, announce-back
|
|
324
483
|
* discovery, cursor propagation, TTL expiry. Tested over the in-memory channel
|
|
325
484
|
* (`createInMemoryPresenceChannel`), which is the SAME dumb string-payload
|
|
326
|
-
* shape as the framework's `BroadcastProvider`.
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
485
|
+
* shape as the framework's `BroadcastProvider`. The runtime binding is DONE
|
|
486
|
+
* (plan 02 phase 1): `usePresenceChannel` in `@voltro/plugin-presence/web`
|
|
487
|
+
* rides the framework's EXISTING presence lane — `presence.heartbeat`
|
|
488
|
+
* carries the payload as member meta, the push-driven `presence.list`
|
|
489
|
+
* roster delivers it — so cross-replica fan-out is plugin-broadcast's and
|
|
490
|
+
* there is ONE presence wire, not two. The shape parity is pinned by
|
|
491
|
+
* `presence/channelParity.test-d.ts` (structural restatement, no
|
|
492
|
+
* production dependency edge).
|
|
330
493
|
*
|
|
331
494
|
* DONE — localFirst mixin + discovery.
|
|
332
495
|
* `localFirst()` (`@voltro/database`) marks a table; the runtime SchemaRegistry
|
|
@@ -355,6 +518,20 @@ export declare interface InMemoryPresenceBus {
|
|
|
355
518
|
readonly rooms: Map<string, Set<(payload: string) => void>>;
|
|
356
519
|
}
|
|
357
520
|
|
|
521
|
+
export declare interface KvIndexedDbFactory {
|
|
522
|
+
open(name: string, version: number): IDBOpenRequestLike_2;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export declare interface KvStore {
|
|
526
|
+
/** Identifies the backing (`"in-memory"` / `"indexeddb"`). Diagnostics. */
|
|
527
|
+
readonly kind: string;
|
|
528
|
+
readonly get: (key: string) => Promise<unknown>;
|
|
529
|
+
readonly put: (key: string, value: unknown) => Promise<void>;
|
|
530
|
+
readonly del: (key: string) => Promise<void>;
|
|
531
|
+
/** All stored keys starting with `prefix`, unordered. */
|
|
532
|
+
readonly keysWithPrefix: (prefix: string) => Promise<ReadonlyArray<string>>;
|
|
533
|
+
}
|
|
534
|
+
|
|
358
535
|
/**
|
|
359
536
|
* Last-write-wins: newer `updatedAt` wins; exact ties break on the (stable,
|
|
360
537
|
* symmetric) tiebreak key so both peers converge on the same value.
|
|
@@ -372,15 +549,82 @@ export declare const lastWriteWins: ConflictResolver;
|
|
|
372
549
|
*/
|
|
373
550
|
export declare const loadPersistedSyncQueue: <T = unknown>(adapter: PersistenceAdapter, online?: boolean) => Promise<PersistedSyncQueue<T>>;
|
|
374
551
|
|
|
552
|
+
export declare const mergeCrdtStates: (a: CrdtState, b: CrdtState, backend?: CrdtBackend) => CrdtState;
|
|
553
|
+
|
|
554
|
+
export declare interface MirrorBindingOptions {
|
|
555
|
+
/** Which rpc tags are backed by which `localFirst()` table — the app's
|
|
556
|
+
* declaration of its sync set. A tag absent here is NOT mirrored. */
|
|
557
|
+
readonly tags: Readonly<Record<string, string>>;
|
|
558
|
+
/** The codegen-emitted metadata (`localFirstTables` from
|
|
559
|
+
* `.framework/localFirst.generated.ts`): table → columns to strip. */
|
|
560
|
+
readonly metadata?: Readonly<Record<string, {
|
|
561
|
+
readonly encryptedColumns: ReadonlyArray<string>;
|
|
562
|
+
}>>;
|
|
563
|
+
/**
|
|
564
|
+
* Schema fingerprint of the CLIENT build (any stable string — a build hash,
|
|
565
|
+
* an app version, `JSON.stringify(localFirstTables)`). The local-DB
|
|
566
|
+
* migration path: entries persist under the fingerprint they were written
|
|
567
|
+
* with, and a load under a DIFFERENT fingerprint returns nothing — a
|
|
568
|
+
* v1-mirrored row set is never rendered into a v2 UI whose columns moved.
|
|
569
|
+
* The DISCARD is visible as a plain cold start (loading, then the server's
|
|
570
|
+
* fresh snapshot), never a crash or a mixed-shape render. The offline
|
|
571
|
+
* QUEUE deliberately does not gate on this: a queued v1 write replays
|
|
572
|
+
* against the v2 server, whose input schema is the authority — a write it
|
|
573
|
+
* rejects surfaces as a failed/conflicted outbox entry the user can see
|
|
574
|
+
* and resolve, which beats silently dropping their work.
|
|
575
|
+
*/
|
|
576
|
+
readonly schemaFingerprint?: string;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export declare interface MirroredQuery {
|
|
580
|
+
/** The rpc tag — kept for diagnostics and schema-migration checks. */
|
|
581
|
+
readonly tag: string;
|
|
582
|
+
/** The materialised rows (or single value) as the client would render them. */
|
|
583
|
+
readonly data: unknown;
|
|
584
|
+
/** The subscription revision `data` corresponds to — presented on
|
|
585
|
+
* re-subscribe as `voltro-resume-from`, so a reload inside the resume
|
|
586
|
+
* window continues with deltas instead of a snapshot. */
|
|
587
|
+
readonly revision: number;
|
|
588
|
+
/** When this entry was last written (ms epoch) — budget eviction input. */
|
|
589
|
+
readonly savedAt: number;
|
|
590
|
+
/** Schema fingerprint of the build that wrote the entry (see the binding's
|
|
591
|
+
* `schemaFingerprint` — the local-DB migration gate). Absent when the app
|
|
592
|
+
* does not stamp builds. */
|
|
593
|
+
readonly fingerprint?: string;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export declare interface MirrorPartition {
|
|
597
|
+
readonly subjectId: string;
|
|
598
|
+
readonly tenantId: string;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Structural mirror of `@voltro/client`'s `OutboxEntry` — kept structural so
|
|
602
|
+
* this package does not depend on the client package (the client consumes
|
|
603
|
+
* US through its `OutboxPersistence` seam, not the other way around). */
|
|
604
|
+
export declare interface OutboxEntryShape {
|
|
605
|
+
readonly id: string;
|
|
606
|
+
readonly tag: string;
|
|
607
|
+
readonly input: unknown;
|
|
608
|
+
readonly status: 'pending' | 'sent' | 'failed' | 'conflict';
|
|
609
|
+
readonly attempts: number;
|
|
610
|
+
readonly error?: unknown;
|
|
611
|
+
}
|
|
612
|
+
|
|
375
613
|
/**
|
|
376
|
-
*
|
|
614
|
+
* Bind an outbox to a {@link PersistenceAdapter}. Pass the result as
|
|
615
|
+
* `useOutbox({ persistence })`.
|
|
377
616
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
617
|
+
* `now` is injectable for determinism (the adapter's `QueuedWrite` carries an
|
|
618
|
+
* `enqueuedAt`); entries that do not decode as outbox entries are DROPPED on
|
|
619
|
+
* load rather than crashing the hydration — a queue poisoned by another
|
|
620
|
+
* writer's shape degrades to the entries it can vouch for.
|
|
382
621
|
*/
|
|
383
|
-
export declare const
|
|
622
|
+
export declare const outboxPersistence: (adapter: PersistenceAdapter, now?: () => number) => OutboxPersistenceBinding;
|
|
623
|
+
|
|
624
|
+
export declare interface OutboxPersistenceBinding {
|
|
625
|
+
readonly load: () => Promise<ReadonlyArray<OutboxEntryShape>>;
|
|
626
|
+
readonly save: (queue: ReadonlyArray<OutboxEntryShape>) => Promise<void>;
|
|
627
|
+
}
|
|
384
628
|
|
|
385
629
|
/** Total outstanding writes (pending + the one in flight). 0 means fully synced. */
|
|
386
630
|
export declare const outstandingCount: (state: SyncQueueState) => number;
|
|
@@ -472,6 +716,25 @@ export declare interface PresenceRoomOptions<TPresence> {
|
|
|
472
716
|
readonly now?: () => number;
|
|
473
717
|
}
|
|
474
718
|
|
|
719
|
+
export declare interface QueryMirror {
|
|
720
|
+
readonly partition: MirrorPartition;
|
|
721
|
+
readonly save: (queryKey: string, entry: Omit<MirroredQuery, 'savedAt'>) => Promise<void>;
|
|
722
|
+
readonly load: (queryKey: string) => Promise<MirroredQuery | undefined>;
|
|
723
|
+
/** Drop every mirrored query of THIS partition (logout, revocation). */
|
|
724
|
+
readonly purge: () => Promise<void>;
|
|
725
|
+
/** Enforce the storage budget: evict least-recently-saved entries until the
|
|
726
|
+
* serialised size estimate fits `maxBytes`. Returns evicted keys. */
|
|
727
|
+
readonly enforceBudget: (maxBytes: number) => Promise<ReadonlyArray<string>>;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export declare interface QueryMirrorOptions {
|
|
731
|
+
/** Column-exposure metadata for `localFirst()` tables, as emitted by the
|
|
732
|
+
* codegen: table → columns that must NEVER be persisted locally
|
|
733
|
+
* (`.encrypted()`). The mirror strips them from every row of a saved
|
|
734
|
+
* entry when `tableOf` resolves the entry's tag to a table. */
|
|
735
|
+
readonly encryptedColumnsOf?: (tag: string) => ReadonlyArray<string> | undefined;
|
|
736
|
+
}
|
|
737
|
+
|
|
475
738
|
/** A single queued write. `payload` is opaque to the queue — the wire seam owns its shape. */
|
|
476
739
|
export declare interface QueuedWrite<T = unknown> {
|
|
477
740
|
readonly id: string;
|
|
@@ -487,6 +750,20 @@ export declare interface RemoteCrdtState extends CrdtDocKey {
|
|
|
487
750
|
readonly state: CrdtState;
|
|
488
751
|
}
|
|
489
752
|
|
|
753
|
+
/**
|
|
754
|
+
* Resolve a conflicted row write: CRDT columns merge, the rest go through the
|
|
755
|
+
* policy. `local`/`remote` carry per-field `{ value, updatedAt, writer }`;
|
|
756
|
+
* CRDT columns' values are their encoded states.
|
|
757
|
+
*/
|
|
758
|
+
export declare const resolveWithPolicy: (policy: ConflictPolicy, local: VersionedRecord, remote: VersionedRecord, options?: ResolveWithPolicyOptions) => Record<string, unknown>;
|
|
759
|
+
|
|
760
|
+
export declare interface ResolveWithPolicyOptions {
|
|
761
|
+
/** Column names that are `crdtText()` cells — merged, never policy-resolved.
|
|
762
|
+
* Comes from the codegen's localFirst column metadata. */
|
|
763
|
+
readonly crdtColumns?: ReadonlyArray<string>;
|
|
764
|
+
readonly backend?: CrdtBackend;
|
|
765
|
+
}
|
|
766
|
+
|
|
490
767
|
/**
|
|
491
768
|
* The genuinely REMAINING runtime seams — provisioned infra + the two app-level
|
|
492
769
|
* NAMES nothing can derive, not un-built framework code:
|
|
@@ -499,6 +776,20 @@ export declare const RUNTIME_SEAMS: readonly ["sync-transport-app-tags", "presen
|
|
|
499
776
|
|
|
500
777
|
export declare type RuntimeSeam = (typeof RUNTIME_SEAMS)[number];
|
|
501
778
|
|
|
779
|
+
/** Structural mirror of `@voltro/client`'s SubscriptionMirror — restated so
|
|
780
|
+
* this package carries no client dependency; the client consumes US. */
|
|
781
|
+
export declare interface SubscriptionMirrorShape {
|
|
782
|
+
readonly load: (key: string) => Promise<{
|
|
783
|
+
readonly data: unknown;
|
|
784
|
+
readonly revision: number;
|
|
785
|
+
} | undefined>;
|
|
786
|
+
readonly save: (key: string, snap: {
|
|
787
|
+
readonly tag: string | undefined;
|
|
788
|
+
readonly data: unknown;
|
|
789
|
+
readonly revision: number;
|
|
790
|
+
}) => Promise<void>;
|
|
791
|
+
}
|
|
792
|
+
|
|
502
793
|
export declare interface SyncClient {
|
|
503
794
|
/** Apply a local edit: merge it into local state immediately (optimistic),
|
|
504
795
|
* persist, queue it, and start draining if online. */
|
|
@@ -609,6 +900,24 @@ export declare interface SyncTransport {
|
|
|
609
900
|
/** A record's fields, each carrying its version metadata. */
|
|
610
901
|
export declare type VersionedRecord = Record<string, FieldValue>;
|
|
611
902
|
|
|
903
|
+
export declare interface WebLocksLike {
|
|
904
|
+
readonly request: (name: string, options: {
|
|
905
|
+
readonly mode: 'exclusive';
|
|
906
|
+
readonly ifAvailable: boolean;
|
|
907
|
+
}, callback: (lock: unknown | null) => Promise<void>) => Promise<void>;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Run `drain` under the partition's exclusive cross-tab lock.
|
|
912
|
+
*
|
|
913
|
+
* `ifAvailable: true` — a tab that finds the lock held does NOT queue behind
|
|
914
|
+
* it (the holder is already draining the same shared queue; a second run the
|
|
915
|
+
* moment it finishes would replay an already-compacted queue). It reports
|
|
916
|
+
* `held-elsewhere` and relies on the holder's drain + its own next trigger
|
|
917
|
+
* (reconnect, enqueue) to try again.
|
|
918
|
+
*/
|
|
919
|
+
export declare const withDrainLock: (options: DrainLockOptions, drain: () => Promise<void>) => Promise<DrainOutcome>;
|
|
920
|
+
|
|
612
921
|
export declare const yjsBackend: CrdtBackend;
|
|
613
922
|
|
|
614
923
|
export { }
|