@book.dev/sdk 1.65.1 → 1.67.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/dist/authors.d.ts CHANGED
@@ -1,4 +1,23 @@
1
1
  import type { PageSnapshot } from './types';
2
+ /**
3
+ * Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
4
+ * their prior author; changed/new blocks get `authorSubject` when it is a
5
+ * verified identity (a non-empty subject), and are otherwise left unattributed
6
+ * (an anonymous/guest edit honestly clears a block's verified author rather than
7
+ * falsely keeping the previous one). Returns a sparse map — only blocks with a
8
+ * known author — or `null` when none are attributed (so the field stays absent
9
+ * on single-user / unverified documents).
10
+ */
11
+ /**
12
+ * Resolve the *verified* author subject for a **changed/new** block. Returns a
13
+ * non-empty subject to attribute the block, or `''`/`undefined` to leave it
14
+ * unattributed (an anonymous/guest edit honestly clears a block's verified
15
+ * author). Only consulted for changed blocks; unchanged blocks keep their prior
16
+ * author regardless. This is the seam that lets one snapshot carry per-block
17
+ * attribution from *different* principals — the server-authoritative persist path
18
+ * (Collab T9), where one durable checkpoint merges edits from several writers.
19
+ */
20
+ export type BlockAuthorResolver = (blockId: string) => string | undefined;
2
21
  /**
3
22
  * Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
4
23
  * their prior author; changed/new blocks get `authorSubject` when it is a
@@ -17,6 +36,18 @@ export declare function computeBlockAuthors(prev: PageSnapshot | null | undefine
17
36
  * is attributed, so it never appears on single-user/unverified snapshots.
18
37
  */
19
38
  export declare function stampSnapshotAuthors(prev: PageSnapshot | null | undefined, next: PageSnapshot, authorSubject: string): PageSnapshot;
39
+ /**
40
+ * Per-block variant of {@link stampSnapshotAuthors} for the server-authoritative
41
+ * persist path (Collab T9). When the SERVER persists one converged snapshot that
42
+ * merges edits from several principals, a single `authorSubject` would misattribute
43
+ * every changed block to one writer. Instead each **changed** block is attributed
44
+ * to `authorByBlock.get(blockId)` — the *verified* subject of the principal whose
45
+ * ingested update last changed that block (`''`/absent ⇒ a guest/unverified change,
46
+ * left unattributed). Unchanged blocks keep their prior author. So attribution
47
+ * reflects who actually made each change, never "the server" and never a forged
48
+ * writer. Idempotent when the document is unchanged.
49
+ */
50
+ export declare function stampSnapshotAuthorsPerBlock(prev: PageSnapshot | null | undefined, next: PageSnapshot, authorByBlock: ReadonlyMap<string, string>): PageSnapshot;
20
51
  /**
21
52
  * The verified author of a snapshot's most-recently-changed attributed block —
22
53
  * the snapshot's "last verified editor", read on the receiving instance to
package/dist/authors.js CHANGED
@@ -13,39 +13,45 @@
13
13
  */
14
14
  import { snapshotBlocks } from './mtime';
15
15
  /**
16
- * Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
17
- * their prior author; changed/new blocks get `authorSubject` when it is a
18
- * verified identity (a non-empty subject), and are otherwise left unattributed
19
- * (an anonymous/guest edit honestly clears a block's verified author rather than
20
- * falsely keeping the previous one). Returns a sparse map — only blocks with a
21
- * known author — or `null` when none are attributed (so the field stays absent
22
- * on single-user / unverified documents).
16
+ * Compute the `[blockId, subject]` authorship for `next` against `prev`. Unchanged
17
+ * blocks (same content hash) keep their prior author; changed/new blocks are
18
+ * attributed to `resolve(blockId)` when it yields a verified (non-empty) subject,
19
+ * and are otherwise left unattributed. Returns a sparse map — only blocks with a
20
+ * known author — or `null` when none are attributed (so the field stays absent on
21
+ * single-user / unverified documents). The single-principal
22
+ * {@link computeBlockAuthors} and the per-block {@link stampSnapshotAuthorsPerBlock}
23
+ * are both thin resolvers over this one diff.
23
24
  */
24
- export function computeBlockAuthors(prev, next, authorSubject) {
25
- const prevBlocks = snapshotBlocks(prev);
25
+ function computeBlockAuthorsBy(prev, next, resolve) {
26
26
  const prevHash = new Map();
27
- for (const b of prevBlocks)
27
+ for (const b of snapshotBlocks(prev))
28
28
  prevHash.set(b.id, b.hash);
29
29
  const prevAuthor = new Map(prev?.authors ?? []);
30
- const verified = authorSubject.length > 0;
31
30
  const out = [];
32
31
  for (const b of snapshotBlocks(next)) {
33
32
  const unchanged = prevHash.get(b.id) === b.hash;
34
- const author = unchanged ? prevAuthor.get(b.id) ?? '' : verified ? authorSubject : '';
33
+ const author = unchanged ? prevAuthor.get(b.id) ?? '' : resolve(b.id) ?? '';
35
34
  if (author)
36
35
  out.push([b.id, author]);
37
36
  }
38
37
  return out.length > 0 ? out : null;
39
38
  }
40
39
  /**
41
- * Return `next` with its `authors` stamped relative to `prev`. `authorSubject` is
42
- * the request's *verified* principal subject (`iss#sub`), or `''` for an
43
- * unverified/guest/local write (which carries no new attribution). Idempotent
44
- * when the document is unchanged. Omits the `authors` key entirely when nothing
45
- * is attributed, so it never appears on single-user/unverified snapshots.
40
+ * Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
41
+ * their prior author; changed/new blocks get `authorSubject` when it is a
42
+ * verified identity (a non-empty subject), and are otherwise left unattributed
43
+ * (an anonymous/guest edit honestly clears a block's verified author rather than
44
+ * falsely keeping the previous one). Returns a sparse map — only blocks with a
45
+ * known author — or `null` when none are attributed (so the field stays absent
46
+ * on single-user / unverified documents).
46
47
  */
47
- export function stampSnapshotAuthors(prev, next, authorSubject) {
48
- const authors = computeBlockAuthors(prev, next, authorSubject);
48
+ export function computeBlockAuthors(prev, next, authorSubject) {
49
+ const author = authorSubject.length > 0 ? authorSubject : '';
50
+ return computeBlockAuthorsBy(prev, next, () => author);
51
+ }
52
+ /** Set `next.authors` from a computed map, or drop the key entirely when nothing
53
+ * is attributed (so it never appears on single-user/unverified snapshots). */
54
+ function withAuthors(next, authors) {
49
55
  if (!authors) {
50
56
  // Nothing attributed: drop any stale `authors` rather than carry an empty map.
51
57
  if (next.authors === undefined)
@@ -56,6 +62,30 @@ export function stampSnapshotAuthors(prev, next, authorSubject) {
56
62
  }
57
63
  return { ...next, authors };
58
64
  }
65
+ /**
66
+ * Return `next` with its `authors` stamped relative to `prev`. `authorSubject` is
67
+ * the request's *verified* principal subject (`iss#sub`), or `''` for an
68
+ * unverified/guest/local write (which carries no new attribution). Idempotent
69
+ * when the document is unchanged. Omits the `authors` key entirely when nothing
70
+ * is attributed, so it never appears on single-user/unverified snapshots.
71
+ */
72
+ export function stampSnapshotAuthors(prev, next, authorSubject) {
73
+ return withAuthors(next, computeBlockAuthors(prev, next, authorSubject));
74
+ }
75
+ /**
76
+ * Per-block variant of {@link stampSnapshotAuthors} for the server-authoritative
77
+ * persist path (Collab T9). When the SERVER persists one converged snapshot that
78
+ * merges edits from several principals, a single `authorSubject` would misattribute
79
+ * every changed block to one writer. Instead each **changed** block is attributed
80
+ * to `authorByBlock.get(blockId)` — the *verified* subject of the principal whose
81
+ * ingested update last changed that block (`''`/absent ⇒ a guest/unverified change,
82
+ * left unattributed). Unchanged blocks keep their prior author. So attribution
83
+ * reflects who actually made each change, never "the server" and never a forged
84
+ * writer. Idempotent when the document is unchanged.
85
+ */
86
+ export function stampSnapshotAuthorsPerBlock(prev, next, authorByBlock) {
87
+ return withAuthors(next, computeBlockAuthorsBy(prev, next, (id) => authorByBlock.get(id)));
88
+ }
59
89
  /**
60
90
  * The verified author of a snapshot's most-recently-changed attributed block —
61
91
  * the snapshot's "last verified editor", read on the receiving instance to
@@ -1 +1 @@
1
- {"version":3,"file":"authors.js","sourceRoot":"","sources":["../src/authors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAGvC;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAqC,EACrC,IAAkB,EAClB,aAAqB;IAErB,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,UAAU;QAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAiB,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;IAE1C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,IAAI,MAAM;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAqC,EACrC,IAAkB,EAClB,aAAqB;IAErB,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,+EAA+E;QAC/E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,IAAI,GAAG,EAAC,GAAG,IAAI,EAAC,CAAC;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAC,GAAG,IAAI,EAAE,OAAO,EAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAqC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAiB,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,IAAI,aAAa,GAAkB,IAAI,CAAC;IACxC,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC;YACvD,SAAS,GAAG,GAAG,CAAC;YAChB,aAAa,GAAG,OAAO,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,OAAO,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC;AAChE,CAAC"}
1
+ {"version":3,"file":"authors.js","sourceRoot":"","sources":["../src/authors.ts"],"names":[],"mappings":"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"}
package/dist/backup.d.ts CHANGED
@@ -86,9 +86,11 @@ export interface BackupStatus {
86
86
  /**
87
87
  * Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
88
88
  * database, remaps every internal reference (`parentId`, `databaseId`,
89
- * `hostedDatabaseId`, a database's `pageId`, and `@`-mention `data-page-id`s
90
- * embedded in block HTML), and returns the rewritten pages/databases plus the
91
- * `oldId → newId` map. References to pages outside the bundle are left as-is.
89
+ * `hostedDatabaseId`, a database's `pageId`, and `@`-mentions — both the EditorJS
90
+ * HTML form (`data-page-id`) and the block-doc run form (an `m` attr, the shape
91
+ * the block-native editor and importers emit)), and returns the rewritten
92
+ * pages/databases plus the `oldId → newId` map. References to pages outside the
93
+ * bundle are left as-is.
92
94
  * Unit-tested; the store layer adds DB-aware name de-duplication on top.
93
95
  */
94
96
  export declare function remapBundle(pages: StoredPage[], databases: StoredDatabase[], newId: () => string): {
package/dist/backup.js CHANGED
@@ -17,9 +17,11 @@ export const DEFAULT_BACKUP_CONFIG = {
17
17
  /**
18
18
  * Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
19
19
  * database, remaps every internal reference (`parentId`, `databaseId`,
20
- * `hostedDatabaseId`, a database's `pageId`, and `@`-mention `data-page-id`s
21
- * embedded in block HTML), and returns the rewritten pages/databases plus the
22
- * `oldId → newId` map. References to pages outside the bundle are left as-is.
20
+ * `hostedDatabaseId`, a database's `pageId`, and `@`-mentions — both the EditorJS
21
+ * HTML form (`data-page-id`) and the block-doc run form (an `m` attr, the shape
22
+ * the block-native editor and importers emit)), and returns the rewritten
23
+ * pages/databases plus the `oldId → newId` map. References to pages outside the
24
+ * bundle are left as-is.
23
25
  * Unit-tested; the store layer adds DB-aware name de-duplication on top.
24
26
  */
25
27
  export function remapBundle(pages, databases, newId) {
@@ -32,7 +34,13 @@ export function remapBundle(pages, databases, newId) {
32
34
  const remapMentions = (data) => {
33
35
  let json = JSON.stringify(data);
34
36
  for (const [oldId, nid] of Object.entries(idMap)) {
37
+ // EditorJS HTML mention: `data-page-id=\"id\"` (the inner quotes are escaped
38
+ // because the HTML lives inside a JSON string value once serialised).
35
39
  json = json.split(`data-page-id=\\"${oldId}\\"`).join(`data-page-id=\\"${nid}\\"`);
40
+ // Block-doc mention run: an `m` attr (`{"a":{"m":"id"}}`) — structured JSON,
41
+ // so the quotes are plain. Anchored on the exact bundle id (a unique token),
42
+ // so it never rewrites an unrelated value.
43
+ json = json.split(`"m":"${oldId}"`).join(`"m":"${nid}"`);
36
44
  }
37
45
  return JSON.parse(json);
38
46
  };
@@ -1 +1 @@
1
- {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
package/dist/client.d.ts CHANGED
@@ -115,6 +115,79 @@ export interface DataClient {
115
115
  subscribePage(id: string, handlers: PageSubscription): () => void;
116
116
  /** Subscribe to live page-list updates. Returns an unsubscribe fn. */
117
117
  subscribePages(onList: (pages: PageMeta[]) => void): () => void;
118
+ /**
119
+ * Subscribe to the live stream's *reconnect* signal (Collab T7): fires after the SSE
120
+ * stream drops and successfully **reopens** (the OB-132/OB-283 resync signal), so a
121
+ * caller can run a tight catch-up — the relay re-does its state-vector `/sync`
122
+ * handshake, the awareness provider re-announces presence — instead of waiting out the
123
+ * coarser page-*snapshot* resync (≤ snapshot-rate). Trailing-debounced against a
124
+ * flapping connection. It does NOT fire on the FIRST connect (the one-shot handshakes
125
+ * cover that) nor in poll-mode (no live SSE ⇒ convergence stays at snapshot-rate, the
126
+ * existing graceful degrade). Returns an unsubscribe fn; a transport with no live
127
+ * stream (the in-webview client) never fires it.
128
+ */
129
+ subscribeReconnect(onReconnect: () => void): () => void;
130
+ /**
131
+ * Relay one incremental Yjs update for a page to other open editors. `update` is
132
+ * the base64 CRDT bytes; `clientId` is the author's `Y.Doc` id so the author's
133
+ * own echo can be dropped. Ephemeral — never persisted (durability stays with
134
+ * {@link savePage}'s debounced snapshot).
135
+ */
136
+ postPageUpdate(id: string, update: string, clientId: number): Promise<void>;
137
+ /**
138
+ * Subscribe to a page's incremental Yjs updates. `onUpdate` fires with the base64
139
+ * CRDT bytes + the author's `clientId`. Returns an unsubscribe fn.
140
+ */
141
+ subscribePageUpdates(id: string, onUpdate: (update: string, clientId: number) => void): () => void;
142
+ /**
143
+ * Late-joiner handshake: send the local doc's base64 state vector, receive the
144
+ * base64 update carrying exactly the ops this client is missing (or `null` when
145
+ * the server has nothing newer than the snapshot the client already loaded). This
146
+ * is how a client joining mid-session converges to the CURRENT doc.
147
+ */
148
+ syncPageUpdates(id: string, stateVector: string): Promise<string | null>;
149
+ /**
150
+ * Publish this client's presence (cursor / selection) as a base64
151
+ * `y-protocols/awareness` update. **Read-gated** (a viewer appears present), and
152
+ * the server **re-stamps the identity** (name/colour) from the verified principal
153
+ * — what's in the body is never trusted for who-you-are. Ephemeral: never
154
+ * persisted, never in the edit log.
155
+ */
156
+ postPageAwareness(id: string, update: string, clientId: number): Promise<void>;
157
+ /**
158
+ * Subscribe to a page's awareness updates (other clients' presence). `onUpdate`
159
+ * fires with the base64 awareness bytes + the author's `clientId` (so the author
160
+ * drops its own echo). Returns an unsubscribe fn. Ephemeral — not resynced on
161
+ * reconnect; the periodic awareness refresh + the on-connect snapshot recover it.
162
+ */
163
+ subscribePageAwareness(id: string, onUpdate: (update: string, clientId: number) => void): () => void;
164
+ /**
165
+ * Current presence snapshot for a late joiner (Collab T4): the base64 awareness
166
+ * updates of everyone currently present (already identity-stamped), so a client
167
+ * connecting mid-session sees who's here at once rather than waiting out the next
168
+ * awareness refresh. Read-gated. Empty when nobody else is present.
169
+ */
170
+ syncPageAwareness(id: string): Promise<string[]>;
171
+ /**
172
+ * Upload binary `bytes` (e.g. an image's file bytes) to the content-addressed
173
+ * asset store, ref'd to `pageId` — a page the caller can write, whose read-gate
174
+ * the asset inherits so it's immediately reachable. Resolves `{id}`, the
175
+ * SHA-256 content hash; a byte-identical re-upload dedups to the same id. The
176
+ * image block persists only this `id`, never the bytes, so the CRDT stays small.
177
+ */
178
+ putAsset(bytes: Uint8Array, mime: string, pageId: string): Promise<{
179
+ id: string;
180
+ }>;
181
+ /**
182
+ * Fetch an asset's bytes + mime by content-hash `id`, or `null` when it's
183
+ * missing or the caller can read no page that references it (read-gated — an
184
+ * absent and an unreadable asset answer alike, so there's no existence oracle).
185
+ * The image block resolves this to an object URL for `<img src>`.
186
+ */
187
+ getAsset(id: string): Promise<{
188
+ bytes: Uint8Array;
189
+ mime: string;
190
+ } | null>;
118
191
  /** Create a database for a host page. */
119
192
  createDatabase(input: DatabaseInput): Promise<StoredDatabase>;
120
193
  /** Fetch a database by id, or `null` if it does not exist. */
@@ -232,6 +305,36 @@ export interface LiveSourceLike {
232
305
  }) => void): void;
233
306
  close(): void;
234
307
  }
308
+ /**
309
+ * How long to wait for the `EventSource`'s first `open` before deciding the
310
+ * stream is structurally dead and falling back to polling. The *.book.pub
311
+ * forwarding tunnel buffers the never-ending SSE body in release builds and
312
+ * forwards nothing, so a tunneled browser sees neither `open` nor any event;
313
+ * this bound caps how long it stays dark before polling takes over. Loopback
314
+ * (dev) and any future-fixed transport open well inside this window and so
315
+ * never poll. Exported for tests; not re-exported from the package index.
316
+ */
317
+ export declare const LIVE_OPEN_GRACE_MS = 8000;
318
+ /**
319
+ * How often poll-mode re-fetches every open subscription once the SSE stream is
320
+ * deemed dead — the interval within which a tunneled client sees writes.
321
+ */
322
+ export declare const LIVE_POLL_INTERVAL_MS = 4000;
323
+ /**
324
+ * Number of `error`s (with no intervening `open`) that trip poll-mode early,
325
+ * before {@link LIVE_OPEN_GRACE_MS} elapses — e.g. a connection that is refused
326
+ * or fails immediately rather than hanging.
327
+ */
328
+ export declare const LIVE_POLL_AFTER_ERRORS = 3;
329
+ /**
330
+ * Trailing-debounce window for the {@link LiveStream} reconnect signal (Collab T7). A
331
+ * flapping connection can `error`/`open` repeatedly; coalescing the reopens into a single
332
+ * notification — fired once the stream has stayed open this long — keeps a caller's
333
+ * re-handshake from storming the relay. Short relative to a snapshot save, so the
334
+ * post-reconnect catch-up is still far tighter than the snapshot-rate fallback. Exported
335
+ * for tests; not re-exported from the package index.
336
+ */
337
+ export declare const LIVE_RECONNECT_DEBOUNCE_MS = 300;
235
338
  /** Options for swapping {@link HttpDataClient}'s transport (desktop IPC). */
236
339
  export interface HttpDataClientOptions {
237
340
  /** Replacement for the global `fetch` (e.g. tunnel requests over host IPC). */
@@ -302,6 +405,52 @@ export declare class HttpDataClient implements DataClient {
302
405
  compact(): Promise<CompactResult>;
303
406
  subscribePage(id: string, handlers: PageSubscription): () => void;
304
407
  subscribePages(onList: (pages: PageMeta[]) => void): () => void;
408
+ /** Reopen-after-drop reconnect signal (Collab T7) — tight post-reconnect catch-up. */
409
+ subscribeReconnect(onReconnect: () => void): () => void;
410
+ /** Relay one incremental Yjs update (Collab T1); ephemeral, no store write. */
411
+ postPageUpdate(id: string, update: string, clientId: number): Promise<void>;
412
+ subscribePageUpdates(id: string, onUpdate: (update: string, clientId: number) => void): () => void;
413
+ /** Late-joiner sync handshake (Collab T1): state vector in, missing ops out. */
414
+ syncPageUpdates(id: string, stateVector: string): Promise<string | null>;
415
+ /** Publish ephemeral presence (Collab T4); read-gated, identity server-stamped. */
416
+ postPageAwareness(id: string, update: string, clientId: number): Promise<void>;
417
+ subscribePageAwareness(id: string, onUpdate: (update: string, clientId: number) => void): () => void;
418
+ /** Current presence snapshot for a late joiner (Collab T4). */
419
+ syncPageAwareness(id: string): Promise<string[]>;
420
+ /**
421
+ * Upload asset bytes and ref them to `pageId`. The body is base64-JSON
422
+ * (`{data, mime}`) rather than raw binary DELIBERATELY: the desktop IPC bridge
423
+ * (`tauriFetch`) corrupts raw binary / stream request bodies, so base64 keeps
424
+ * the upload byte-exact on BOTH the web-http and desktop-IPC transports.
425
+ */
426
+ putAsset(bytes: Uint8Array, mime: string, pageId: string): Promise<{
427
+ id: string;
428
+ }>;
429
+ /**
430
+ * Fetch an asset by content-hash id, decoding the base64-JSON variant
431
+ * (`?encoding=base64`) — again to stay byte-safe over the desktop IPC bridge,
432
+ * which corrupts raw binary responses. `null` on 404 (missing or read-gated).
433
+ *
434
+ * A5 decision — base64 single-shot over the desktop's `tauriFetch`, NOT the
435
+ * streaming `tauriStreamFetch` (which stays tunnel-only): assets are capped at
436
+ * 10 MiB, and `getAsset` must materialize the FULL bytes anyway (its consumer
437
+ * wraps them in a `Blob`/object-URL), so streaming buys no client-side memory
438
+ * win — it would only spare the server a whole-asset base64 pass. Wiring it would
439
+ * mean threading a second transport through this one `fetchImpl` abstraction for a
440
+ * bounded (~13 MiB transient) payload — cost > benefit. Buffered base64-JSON is
441
+ * the right call at this cap; revisit only if the cap is ever raised materially.
442
+ *
443
+ * `cache: 'no-store'` keeps this byte-exact across all three transports (IPC
444
+ * base64, web, tunnel) — it never relies on the browser HTTP cache, so it holds
445
+ * the object-URL in-app instead. The server's content-addressed `ETag`/304 (A5)
446
+ * still shortcuts a browser's OWN cache-revalidation for any DIRECT `<img src>`
447
+ * fetch of the asset URL (notably through a *.book.pub tunnel, where the browser
448
+ * caches the first response), which is a separate, non-`no-store` code path.
449
+ */
450
+ getAsset(id: string): Promise<{
451
+ bytes: Uint8Array;
452
+ mime: string;
453
+ } | null>;
305
454
  createDatabase(input: DatabaseInput): Promise<StoredDatabase>;
306
455
  getDatabase(id: string): Promise<StoredDatabase | null>;
307
456
  getPageDatabase(pageId: string): Promise<StoredDatabase | null>;