js-bao 0.5.1 → 0.6.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.
@@ -75,6 +75,510 @@ interface ModelOptions {
75
75
  relationships?: Record<string, RelationshipConfig>;
76
76
  }
77
77
 
78
+ /**
79
+ * Shared format-2 ("large document", #2816) primitives.
80
+ *
81
+ * A format-2 document is no longer "one Y.Doc": the authoritative document is
82
+ * a persisted SQLite `records` table on both ends, and the Y.Doc shrinks to a
83
+ * bounded per-epoch **state overlay** of the records/fields touched during the
84
+ * current epoch. This module owns the overlay's on-the-wire shape so the
85
+ * client write points, the client observer, the server projector, the
86
+ * materializer and the snapshot builder all encode and decode it identically.
87
+ *
88
+ * ## Encoding — flat field-level keys (sponsor amendment, 2026-08-21)
89
+ *
90
+ * Each model map holds FLAT entries, not one nested `Y.Map` per record:
91
+ *
92
+ * ```
93
+ * ⟨recordId⟩/⟨field⟩ → value ordinary field (`null` = unset)
94
+ * ⟨recordId⟩/⟨field⟩/⟨member⟩ → true|false stringset member / tombstone (D3)
95
+ * ⟨recordId⟩/_replace → true discard the base row
96
+ * ⟨recordId⟩/_deleted → true|false record tombstone
97
+ * ```
98
+ *
99
+ * The nested layout cannot work under epochs: two clients that both touch a
100
+ * record ABSENT from the current epoch each mint a fresh `Y.Map` at the same
101
+ * model-map key, and Yjs resolves that parent-key conflict as last-writer-wins,
102
+ * silently dropping the loser's fields. With flat keys, concurrency resolves
103
+ * per field key, so a concurrent create-vs-patch merges to the union of fields
104
+ * with LWW only on genuinely colliding fields.
105
+ *
106
+ * The record's LOGICAL overlay content is unchanged from the approved design —
107
+ * consumers group a record's keys back into a fields object plus markers via
108
+ * {@link groupOverlayEntries} and apply the record-level rules in
109
+ * {@link materializeOverlayRecord}.
110
+ */
111
+
112
+ /** Separator between the record-id, field and member segments of an overlay key. */
113
+ declare const OVERLAY_KEY_SEPARATOR = "/";
114
+ /** Marker key: the create discards the base row and writes exactly its fields. */
115
+ declare const OVERLAY_MARKER_REPLACE = "_replace";
116
+ /** Marker key: the record is deleted (a bare Yjs key delete is invisible). */
117
+ declare const OVERLAY_MARKER_DELETED = "_deleted";
118
+ /**
119
+ * Escape one overlay-key segment so the key stays unambiguous.
120
+ *
121
+ * Record ids are ULIDs and always safe, but field and member names are user
122
+ * data: they may contain the separator, the escape character, or start with the
123
+ * `_` that marks a marker key. Escaping is RFC 6901-flavored:
124
+ *
125
+ * - `~` → `~0`
126
+ * - `/` → `~1`
127
+ * - a LEADING `_` → `~2` (so a field literally named `_replace` can never be
128
+ * read back as the `_replace` marker, which is written unescaped)
129
+ */
130
+ declare function encodeOverlaySegment(segment: string): string;
131
+ /** Inverse of {@link encodeOverlaySegment}. */
132
+ declare function decodeOverlaySegment(segment: string): string;
133
+ /** Overlay key for an ordinary field of a record. */
134
+ declare function overlayFieldKey(recordId: string, field: string): string;
135
+ /** Overlay key for one stringset member of a record's field. */
136
+ declare function overlayMemberKey(recordId: string, field: string, member: string): string;
137
+ /** Overlay key for a record-level marker (written unescaped, by construction). */
138
+ declare function overlayMarkerKey(recordId: string, marker: string): string;
139
+ /** The parts an overlay key decodes to, or `null` when it is not an overlay key. */
140
+ interface ParsedOverlayKey {
141
+ recordId: string;
142
+ field?: string;
143
+ member?: string;
144
+ marker?: string;
145
+ }
146
+ /** Decode an overlay key back into its parts; `null` when it is malformed. */
147
+ declare function parseOverlayKey(key: string): ParsedOverlayKey | null;
148
+ /** One record's overlay content, regrouped from its flat keys. */
149
+ interface OverlayRecordEntry {
150
+ id: string;
151
+ /** Touched ordinary fields. A `null` value is an explicit unset (RFC 7396). */
152
+ fields: Record<string, unknown>;
153
+ /** Touched stringset members per field: `member → true` add, `false` tombstone. */
154
+ stringSets: Record<string, Record<string, boolean>>;
155
+ /** The create discarded the base row. */
156
+ replace: boolean;
157
+ /** The record is tombstoned. */
158
+ deleted: boolean;
159
+ }
160
+ /**
161
+ * Regroup a model map's flat overlay entries into one {@link OverlayRecordEntry}
162
+ * per record. Accepts anything iterable of `[key, value]` — a `Y.Map`'s
163
+ * `entries()`, an array of observed changes, or a decoded snapshot.
164
+ */
165
+ declare function groupOverlayEntries(entries: Iterable<readonly [string, unknown]>): Map<string, OverlayRecordEntry>;
166
+ /** Anything that can enumerate a model map's overlay keys. */
167
+ interface OverlayEntrySource {
168
+ entries(): Iterable<readonly [string, unknown]> | IterableIterator<any>;
169
+ }
170
+ /**
171
+ * Read ONE record's complete overlay entry out of a model map.
172
+ *
173
+ * A delta fold — projecting exactly the keys an update touched — is right for
174
+ * a patch, which merges over the stored row. It is wrong for `_replace`: that
175
+ * marker discards the row entirely, so the record has to be rebuilt from every
176
+ * overlay key it currently has, not just the ones the arriving update carried.
177
+ * Concurrency is where the two differ: when one client patches a record while
178
+ * another deletes and re-creates it, Yjs merges their keys per key, and the
179
+ * update carrying `_replace` names only its own — folding it alone would drop
180
+ * the concurrent patch that the overlay itself still holds.
181
+ *
182
+ * Costs a walk of the model map, which is bounded by the epoch rotation
183
+ * threshold. Use {@link completeOverlayEntries} when several records need
184
+ * rebuilding: it pays that walk ONCE for all of them.
185
+ */
186
+ declare function readOverlayRecord(modelMap: OverlayEntrySource, recordId: string): OverlayRecordEntry;
187
+ /**
188
+ * Complete the entries of a delta fold that need the record's whole overlay.
189
+ *
190
+ * Applied by both the server's projector and the client's observer, so the
191
+ * authoritative table and the local merged view fold an update identically.
192
+ *
193
+ * Every create carries `_replace`, so this sits on the hot write path: the
194
+ * model map is walked ONCE per call however many records were replaced. A walk
195
+ * per replaced record would make one bulk create quadratic in the number of
196
+ * records it writes.
197
+ */
198
+ declare function completeOverlayEntries(modelMap: OverlayEntrySource, entries: Map<string, OverlayRecordEntry>): Map<string, OverlayRecordEntry>;
199
+ /**
200
+ * Materialize one record: `base ⊕ overlay`, the operation that turns
201
+ * `S_E ⊕ D_E` into the authoritative `records` row.
202
+ *
203
+ * - a `_deleted` tombstone materializes to `null` (delete the row);
204
+ * - `_replace` discards `base` entirely and writes exactly the overlay's
205
+ * fields, so a delete-then-re-create cannot resurrect base fields the
206
+ * re-created record omitted;
207
+ * - an ordinary field merges RFC 7396-style, with an explicit `null` removing
208
+ * the key;
209
+ * - stringset member keys apply as add/remove deltas over the base members.
210
+ *
211
+ * Applying an overlay is idempotent and order-free at the record level, so a
212
+ * torn read during a snapshot build is corrected by the next overlay.
213
+ */
214
+ declare function materializeOverlayRecord(base: Record<string, unknown> | null | undefined, overlay: OverlayRecordEntry): Record<string, unknown> | null;
215
+ /** A record mutation expressed in overlay terms, before it is encoded to keys. */
216
+ interface OverlayMutation {
217
+ id: string;
218
+ /**
219
+ * `create` writes `_replace` (the id is absent from the merged view) and
220
+ * clears any prior `_deleted`; `patch` touches only the named fields;
221
+ * `delete` writes the `_deleted` tombstone.
222
+ */
223
+ kind: "create" | "patch" | "delete";
224
+ /** Ordinary field values; `null` is an explicit unset. */
225
+ fields?: Record<string, unknown>;
226
+ /** Per-field stringset deltas: `member → true` add, `false` remove (D3). */
227
+ stringSetDeltas?: Record<string, Record<string, boolean>>;
228
+ }
229
+ /**
230
+ * Encode a mutation into the flat overlay entries it writes. Returned as
231
+ * `[key, value]` pairs so callers can apply them to a `Y.Map`, ship them, or
232
+ * assert on them without a Y.Doc.
233
+ */
234
+ declare function encodeOverlayMutation(mutation: OverlayMutation): Array<[string, unknown]>;
235
+ /** Apply {@link encodeOverlayMutation}'s entries to a model map in one place. */
236
+ declare function applyOverlayMutation(modelMap: Y.Map<any>, mutation: OverlayMutation): void;
237
+ /**
238
+ * A composite (`Y.Map` / `Y.Array` / `Y.Text`) value written into a field slot
239
+ * of a format-2 document (D4). Format 2 rejects these instead of normalizing
240
+ * them, which is what lets the overlay encode field values as plain JSON.
241
+ */
242
+ declare class Format2CompositeFieldError extends Error {
243
+ readonly modelName: string;
244
+ readonly recordId: string;
245
+ readonly field: string;
246
+ constructor(modelName: string, recordId: string, field: string);
247
+ }
248
+ /**
249
+ * D4: reject a Yjs composite in a field slot on a format-2 write path. Plain
250
+ * values pass untouched, including stringset arrays and `{$add}`/`{$remove}`
251
+ * delta objects — only Yjs instances are rejected.
252
+ */
253
+ declare function assertNoFormat2CompositeFields(modelName: string, recordId: string, fields: Record<string, unknown> | undefined | null): void;
254
+ /**
255
+ * D3, format-scoped: the tombstone-aware stringset discriminator. A format-2
256
+ * stringset `Y.Map` may carry `false` for a removed member, which the legacy
257
+ * (#625) discriminator deliberately rejects — widening it globally would
258
+ * reclassify legacy all-boolean composites and break the "format 1 unchanged"
259
+ * contract, so legacy paths keep {@link isStringSetYMap} untouched.
260
+ *
261
+ * Safe here because D4 guarantees no composite field values exist on format-2
262
+ * documents.
263
+ */
264
+ declare function isFormat2StringSetYMap(value: unknown): value is Y.Map<any>;
265
+ /** Members of a tombstone-aware stringset map: the `true` entries only. */
266
+ declare function format2StringSetMembers(value: unknown): string[];
267
+
268
+ /**
269
+ * Fold a format-2 epoch overlay into a `records` table (#2816).
270
+ *
271
+ * Shared by BOTH ends: the Durable Object's authoritative table and the
272
+ * client's local merged view fold the overlay with this one implementation, so
273
+ * "what the server says the record is" and "what the client reads back" cannot
274
+ * drift.
275
+ *
276
+ * On a format-2 document the SQLite `records` table is the document — the
277
+ * Y.Doc holds only the current epoch's overlay. Every applied update therefore
278
+ * has to reach `records` in the same transaction boundary as the update-log
279
+ * row, for a document that may be 2GB. That rules out read-modify-write of the
280
+ * record: this module expresses each overlay entry as one merge-upsert whose
281
+ * RFC 7396 semantics SQLite's `json_patch` already implements (an explicit
282
+ * `null` deletes the key), plus incremental `stringset_index` maintenance for
283
+ * the member deltas.
284
+ *
285
+ * The semantics are pinned against the shared JS materializer
286
+ * (`materializeOverlayRecord`), which is what the client's merged view folds
287
+ * with — the two must never drift.
288
+ */
289
+
290
+ /**
291
+ * The slice of Cloudflare's `SqlStorage` this module needs. `exec` returns an
292
+ * iterable cursor of rows; a plain array satisfies it too, which is what the
293
+ * hermetic tests bind.
294
+ */
295
+ interface OverlaySql {
296
+ exec(query: string, ...bindings: unknown[]): Iterable<Record<string, any>>;
297
+ }
298
+ /**
299
+ * Which tables the fold writes to.
300
+ *
301
+ * The Durable Object hosts exactly one document, so it uses the default names.
302
+ * A CLIENT database can hold several connected documents at once, and the
303
+ * merged view of one must never be readable as another's — so the client
304
+ * passes per-document table names (see `format2TableNames`). The STATEMENTS
305
+ * stay this module's single implementation either way; only the table names
306
+ * differ, which is what keeps the two ends from drifting.
307
+ */
308
+ interface OverlayTables {
309
+ records: string;
310
+ stringSetIndex: string;
311
+ }
312
+ /** The Durable Object's tables: one document per DO, so no scoping needed. */
313
+ declare const DEFAULT_OVERLAY_TABLES: OverlayTables;
314
+ /**
315
+ * Project one record's overlay entry into `records` + `stringset_index`.
316
+ *
317
+ * Callers pass the entry for the keys the update TOUCHED, not the record's
318
+ * whole overlay: materialization is a fold, so `row ⊕ delta` over the stored
319
+ * row is the same record as replaying the epoch from its base.
320
+ */
321
+ declare function projectOverlayEntry(sql: OverlaySql, modelName: string, entry: OverlayRecordEntry, tables?: OverlayTables): void;
322
+
323
+ /**
324
+ * The client-side record store for format-2 (large) documents (#2816).
325
+ *
326
+ * A format-2 client no longer reads records out of Y.Maps: the epoch doc holds
327
+ * only what the current epoch touched, so the merged `records` view is the
328
+ * document. The existing `StorageProvider` cannot serve that — it is an async
329
+ * key-value API with no query and no multi-table transaction.
330
+ *
331
+ * This store defines the contract instead:
332
+ *
333
+ * - **reads** come from `records` (folded with the same `projectOverlayEntry`
334
+ * the Durable Object uses, so the two views cannot drift);
335
+ * - **writes** commit the merged row and the `_pending_ops` entry in ONE
336
+ * transaction — the commit barrier a `save()` resolves behind, which is what
337
+ * makes read-your-writes true and makes a crash immediately after the save
338
+ * non-lossy;
339
+ * - **`_pending_ops`** are pruned only at or below the server's `update.ack`
340
+ * high-water mark, never on `ws.send`;
341
+ * - `_epoch` and `_snapshot_load` carry the epoch the client is following and
342
+ * the chunks a cold load has already completed.
343
+ *
344
+ * It is deliberately synchronous over an injected SQL executor: better-sqlite3
345
+ * (Node) drives it directly, and the worker-hosted OPFS engine drives it inside
346
+ * its worker, where a single round-trip commits the whole transaction.
347
+ *
348
+ * ## Document scoping
349
+ *
350
+ * Unlike the Durable Object — one document per DO — a client database can hold
351
+ * several connected documents at once, so every table here is scoped to ONE
352
+ * document: `records`/`stringset_index` get per-document names, and the
353
+ * bookkeeping tables carry a `doc_id` column in their primary key. Without
354
+ * that, two open large documents would read each other's records and one
355
+ * document's `update.ack` would prune the other's unsynced writes.
356
+ */
357
+
358
+ /** A SQL executor that can also run a set of statements as one atomic commit. */
359
+ interface Format2StoreSql extends OverlaySql {
360
+ transaction<T>(fn: () => T): T;
361
+ }
362
+ /** One durable record of a local write, replayed if it is never acked. */
363
+ interface PendingOp {
364
+ seq: number;
365
+ model: string;
366
+ recordId: string;
367
+ op: "create" | "patch" | "delete";
368
+ /** Field names the op touched — the unit amended-D1 conflict detection works on. */
369
+ fields: string[];
370
+ /** Epoch the op was written against, for recency-aware replay. */
371
+ baseEpoch: number;
372
+ /** Server-offset-corrected wall clock at write time. */
373
+ ts: number;
374
+ /**
375
+ * The overlay mutation this op wrote, so the write can be reproduced from
376
+ * the durable log alone — see {@link Format2RecordStore.commitLocalWrite}.
377
+ * `null` only for a row written by a build that did not record it.
378
+ */
379
+ mutation: OverlayMutation | null;
380
+ }
381
+ type PendingOpInput = Omit<PendingOp, "seq" | "mutation"> & {
382
+ seq?: number;
383
+ };
384
+ declare class Format2RecordStore {
385
+ private readonly sql;
386
+ private readonly docId;
387
+ /** This document's merged-view tables (never another document's). */
388
+ private readonly tables;
389
+ /** Models this device holds, or `null` for the whole document. */
390
+ private scope;
391
+ /** Whether {@link scope} has been read from `_epoch` yet. */
392
+ private scopeLoaded;
393
+ constructor(sql: Format2StoreSql, docId: string);
394
+ /** Create the local schema. Safe to call on every open. */
395
+ init(): void;
396
+ /**
397
+ * Record which models this device holds (#2816, Precondition B's fallback).
398
+ *
399
+ * `null` — the normal case — is "the whole document". A list is what a
400
+ * capped cold load leaves behind: the plan
401
+ * {@link planSnapshotHydration} made, persisted so it outlives the load. It
402
+ * has to be durable, because a restart reconnects and starts folding the
403
+ * epoch overlay again, and without the scope that fold would quietly
404
+ * repopulate a skipped model with the handful of records the epoch touched.
405
+ */
406
+ setHydrationScope(models: readonly string[] | null): void;
407
+ /** The models this device holds, or `null` when it holds the document. */
408
+ hydrationScope(): string[] | null;
409
+ /** Whether this device holds `modelName` at all. */
410
+ isHydrated(modelName: string): boolean;
411
+ /** Refuse a model this device does not hold, rather than answering partly. */
412
+ private assertHydrated;
413
+ read(modelName: string, recordId: string): Record<string, any> | null;
414
+ readAll(modelName: string): Array<Record<string, any>>;
415
+ /**
416
+ * The id of the first record whose fields all match — the merged-view answer
417
+ * to `upsertOn` resolution and to an optimistic unique-constraint check. A
418
+ * base-only holder is found here and nowhere in the epoch doc, which is the
419
+ * whole point.
420
+ */
421
+ findIdByFields(modelName: string, fields: string[], values: unknown[]): string | null;
422
+ findIdByField(modelName: string, field: string, value: unknown): string | null;
423
+ /**
424
+ * Every record id a model holds in the merged view, ids only.
425
+ *
426
+ * The derived query tables are rebuilt from this on connect, and a rebuild
427
+ * has no reason to carry each record's whole `_data` through memory on the
428
+ * way.
429
+ */
430
+ recordIds(modelName: string): string[];
431
+ /**
432
+ * One PAGE of a model's record ids, in id order, after `after`.
433
+ *
434
+ * The whole-model list above is a JavaScript array of every id, which on the
435
+ * documents this epic exists for is millions of strings held at once —
436
+ * before a single row has been projected. A keyset page costs the same per
437
+ * row and bounds what is resident, so walking a model is a walk rather than
438
+ * an allocation (#2816).
439
+ */
440
+ recordIdsAfter(modelName: string, after: string | null, limit: number): string[];
441
+ /**
442
+ * Fold an overlay entry that arrived from the server.
443
+ *
444
+ * A model outside this device's hydration scope is DROPPED here rather than
445
+ * folded: the device declined that model because it has no room for it, and
446
+ * a row of it landing anyway would turn "this model is not held here" into
447
+ * "this model holds one record" — a merged view that reads like the document
448
+ * and is not it.
449
+ */
450
+ applyRemote(modelName: string, entry: OverlayRecordEntry): void;
451
+ /**
452
+ * Run `fn` as ONE commit. A catch-up fold of a whole overlay is a single
453
+ * transaction rather than one per record — the difference between one commit
454
+ * and thousands.
455
+ */
456
+ transaction<T>(fn: () => T): T;
457
+ /**
458
+ * Commit a LOCAL write: the merged row, the pending op, and the OVERLAY
459
+ * MUTATION that op stands for all land together, or none of them does.
460
+ * The returned sequence is what an `update.ack` later prunes against.
461
+ *
462
+ * The mutation is stored, not just the field names it touched, because the
463
+ * merged row cannot reproduce it: materializing a row loses the difference
464
+ * between a patch and a `_replace` create, turns an explicit `null` unset
465
+ * into an absent key, and drops stringset member tombstones. Those shapes
466
+ * survive only in the overlay — and the overlay's own persistence is a Yjs
467
+ * update flushed asynchronously AFTER this commit, so a crash in between
468
+ * would otherwise leave a pending op whose write nothing could reconstruct:
469
+ * the client would claim its sequence on the next catch-up frame, be acked
470
+ * for a write it never sent, and drop it.
471
+ */
472
+ commitLocalWrite(modelName: string, mutation: OverlayMutation, pending: PendingOpInput): number;
473
+ /** The next local sequence: one past the highest ever issued. */
474
+ nextSeq(): number;
475
+ /**
476
+ * The highest sequence this client has ever committed locally — what an
477
+ * outbound update frame claims, and what an `update.ack` has to reach before
478
+ * the document has nothing unsynced left.
479
+ */
480
+ highestLocalSeq(): number;
481
+ pendingOps(): PendingOp[];
482
+ /**
483
+ * Prune at or below the server's contiguous-sequence high-water mark.
484
+ * A lower (out-of-order or replayed) ack never lowers the mark and never
485
+ * resurrects a pruned op.
486
+ */
487
+ prunePendingOps(maxContiguousSeq: number): void;
488
+ ackedSeq(): number;
489
+ epoch(): number;
490
+ setEpoch(epoch: number): void;
491
+ lastSyncAt(): number | null;
492
+ setLastSyncAt(at: number): void;
493
+ /**
494
+ * The offline write window this document is held to (#2816, behavior 18),
495
+ * as the server last reported it — the deployment default until one has.
496
+ */
497
+ offlineWindowDays(): number;
498
+ /**
499
+ * Record that this client was in touch with the server, and (when the
500
+ * server said so) the window it is held to.
501
+ *
502
+ * Persisted together because the session that enforces the window is the one
503
+ * that cannot ask for it: a client that starts offline reads both back out
504
+ * of its own database.
505
+ */
506
+ noteSync(at: number, windowDays?: number | null): void;
507
+ /**
508
+ * Record the window without claiming a sync (#2816, behavior 18).
509
+ *
510
+ * The handshake carries the window whatever else it says, including to a
511
+ * client that is behind by whole epochs and cannot act on the rest of the
512
+ * frame. Taking the number is right there; moving the mark it is measured
513
+ * FROM is not — a frame is proof of contact, not proof that this client's
514
+ * merged view is the room's state, and a document past its window would
515
+ * become writable again on the strength of a handshake whose catch-up then
516
+ * failed.
517
+ */
518
+ noteOfflineWindow(windowDays: number | null | undefined): void;
519
+ /**
520
+ * How far this client's clock is behind the server's, in milliseconds
521
+ * (#2816, behavior 17).
522
+ *
523
+ * Zero until one has been measured — which is why {@link clockOffsetKnown}
524
+ * exists separately: a measured zero and never having measured are the same
525
+ * number and very different facts, and the replay resolver refuses to call a
526
+ * write "clearly older" on the second.
527
+ */
528
+ clockOffset(): number;
529
+ /** Whether the offset above was ever measured against a server. */
530
+ clockOffsetKnown(): boolean;
531
+ /** Record the offset measured against server time while connected. */
532
+ noteClockOffset(offsetMs: number): void;
533
+ /** Now, on the server's clock as far as this client can tell. */
534
+ correctedNow(now?: number): number;
535
+ /**
536
+ * Forget specific pending ops (#2816, behavior 17).
537
+ *
538
+ * The one caller is a replay whose recency resolution DROPPED them: the
539
+ * online write won, so the op must never be replayed — and leaving it in the
540
+ * log would have the next epoch move carry it anyway, and would report the
541
+ * document as holding unsynced changes for good. The merged row it wrote
542
+ * stays: it is what the client is showing, and the next update from the
543
+ * server corrects it.
544
+ */
545
+ forgetPendingOps(seqs: number[]): void;
546
+ /**
547
+ * Rewrite pending ops that survived a replay resolution only in PART
548
+ * (#2816, behavior 17).
549
+ *
550
+ * The same caller as {@link forgetPendingOps}, for the ops it narrowed
551
+ * rather than dropped: a patch that touched two fields, one of which an
552
+ * online write clearly beat. The row must be rewritten to the surviving
553
+ * delta — its field list AND the mutation it stands for — because the
554
+ * mutation is what the restore path puts back on the overlay after a crash,
555
+ * and putting the whole one back would resurrect exactly the field this
556
+ * resolution decided against.
557
+ */
558
+ narrowPendingOps(ops: Array<Pick<PendingOp, "seq" | "fields" | "mutation">>): void;
559
+ completedChunks(buildId: string): string[];
560
+ markChunkComplete(buildId: string, chunkKey: string): void;
561
+ /**
562
+ * Throw away a merged view that cannot be repaired (#2816, behavior 12).
563
+ *
564
+ * The one caller is a client whose sealed-overlay chain was refused: the
565
+ * rows it holds are the state of an epoch the room has left behind, and no
566
+ * available artifact turns them into the current state. It reloads from a
567
+ * base snapshot instead — and a snapshot is a set of rows, not a diff, so
568
+ * loading it over these would leave behind every record deleted since. The
569
+ * chunk marks go with them: a mark whose rows are gone is exactly what makes
570
+ * a resumed load skip a chunk and finish incomplete.
571
+ *
572
+ * What survives is what this client owes the server. Unacknowledged writes
573
+ * are replayed onto the epoch it lands on, and the acked high-water mark
574
+ * keeps their sequences from colliding with fresh ones — dropping either
575
+ * would lose writes the app has already been told were saved.
576
+ */
577
+ discardMergedView(): void;
578
+ private rows;
579
+ private scalar;
580
+ }
581
+
78
582
  interface ITransactionalDatabaseOperations {
79
583
  insert(modelName: string, data: any): Promise<void>;
80
584
  delete(modelName: string, id: string): Promise<void>;
@@ -130,6 +634,32 @@ declare abstract class DatabaseEngine {
130
634
  */
131
635
  deleteByDocumentId(_modelName: string, _docId: string): Promise<void>;
132
636
  getTableName(_modelName: string): string;
637
+ /**
638
+ * A SYNCHRONOUS SQL executor over this engine's database, used by the
639
+ * large-document (format 2) record store. That store commits a merged
640
+ * record row and its pending-operation entry as one transaction — the
641
+ * barrier a `save()` resolves behind — which the async `query()` API cannot
642
+ * express. Engines whose underlying SQLite is synchronous (better-sqlite3,
643
+ * SQL.js) provide it; anything else leaves it undefined and cannot host a
644
+ * large document.
645
+ */
646
+ createFormat2Sql?: () => Format2StoreSql;
647
+ /**
648
+ * Whether what {@link createFormat2Sql} writes survives this process.
649
+ *
650
+ * On a large document the merged record view IS the document: the Y.Doc
651
+ * holds one epoch's overlay, and everything else — including a record a
652
+ * `save()` has already resolved, and the pending write that still owes it to
653
+ * the server — lives only in this store. An engine whose database is in
654
+ * memory therefore cannot host one: a reload does not cost a cache, it costs
655
+ * the document and the writes nobody has acknowledged yet.
656
+ *
657
+ * The platform is asked separately about its storage quota; this answers the
658
+ * half only the engine knows. An engine that does not implement it is taken
659
+ * at the platform's word, which is what keeps hosts with storage of their
660
+ * own — a worker handed a file store, a test — working unchanged.
661
+ */
662
+ format2StorageIsDurable?: () => boolean;
133
663
  withTransaction<T>(_callback: (operations: ITransactionalDatabaseOperations) => Promise<T>): Promise<T>;
134
664
  }
135
665
 
@@ -981,6 +1511,18 @@ interface HookResult {
981
1511
  allow: boolean;
982
1512
  /** Reason for denial (returned to client as error message) */
983
1513
  reason?: string;
1514
+ /**
1515
+ * Names of fields the hook wrote onto `ctx.data` that the DO should echo
1516
+ * back to the caller in the write response's `appliedFields`. A `beforeSave`
1517
+ * hook can compute and set fields on `ctx.data` (e.g. app-level triggers that
1518
+ * derive a value from the submitted row); those values are persisted but are
1519
+ * invisible to a caller that rebuilds a change event from its original
1520
+ * submission inputs. Reporting the names here lets the DO read the final
1521
+ * post-hook values back and thread them into `appliedFields` so the caller
1522
+ * can propagate them (#1476). The hook contract stays generic — it names the
1523
+ * fields it applied and does not need to know how the caller uses them.
1524
+ */
1525
+ serverAppliedFieldNames?: string[];
984
1526
  }
985
1527
  /** Result from beforeQuery hooks — can inject additional filters */
986
1528
  interface BeforeQueryResult extends HookResult {
@@ -1107,6 +1649,20 @@ interface QueryResponse {
1107
1649
  interface SaveResponse {
1108
1650
  success: boolean;
1109
1651
  id: string;
1652
+ /**
1653
+ * Server-applied field values the DO wrote AFTER the caller's submission
1654
+ * inputs were assembled, read back from the final post-hook `data`
1655
+ * (#1476, consolidating the #1462 `stamps` channel). This is the single
1656
+ * "server-applied fields" channel: it carries both the applied `autoStamps`
1657
+ * timestamps and any fields a `beforeSave` hook reported via
1658
+ * `serverAppliedFieldNames` (e.g. app-level trigger-computed values). Present
1659
+ * only when at least one such field ended up with a value. Lets the Worker
1660
+ * thread these values into the change-event `data` it broadcasts to
1661
+ * subscribers — otherwise a `select`-listed stamped or trigger-computed field
1662
+ * never reaches the frame, because the frame is rebuilt from the submission
1663
+ * inputs (which predate the DO-side write).
1664
+ */
1665
+ appliedFields?: Record<string, any>;
1110
1666
  }
1111
1667
  interface DeleteResponse {
1112
1668
  success: boolean;
@@ -1189,6 +1745,8 @@ interface PatchRequest {
1189
1745
  interface PatchResponse {
1190
1746
  success: boolean;
1191
1747
  id: string;
1748
+ /** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
1749
+ appliedFields?: Record<string, any>;
1192
1750
  }
1193
1751
  interface BatchOperation {
1194
1752
  op: "save" | "patch" | "delete" | "increment" | "addToSet" | "removeFromSet";
@@ -1216,12 +1774,46 @@ interface BatchOperation {
1216
1774
  }
1217
1775
  interface BatchRequest {
1218
1776
  operations: BatchOperation[];
1777
+ /**
1778
+ * All-or-nothing execution (#1478). When `true`, the FIRST operation that
1779
+ * fails inside the transaction — hook denial, condition / `ifNotExists` /
1780
+ * unique violation, record-not-found, or upsert validation error — aborts
1781
+ * the whole batch: every write is rolled back and the handler returns an
1782
+ * error naming the failed step with its natural HTTP status (see
1783
+ * `BatchOperationResult.status`). When `false` or absent (the default), each
1784
+ * operation's outcome is recorded independently and successful siblings
1785
+ * commit — the partial-success behavior import-bulk and applyToQuery rely on.
1786
+ */
1787
+ atomic?: boolean;
1219
1788
  }
1789
+ /**
1790
+ * Stable, machine-readable failure tag on a failed `BatchOperationResult`
1791
+ * (#1478). Derived at each failure site — never inferred from the `error`
1792
+ * string. `#1389` (conditional writes + fencing-token enforcement) surfaces its
1793
+ * condition / stale-token rejections through this same channel.
1794
+ */
1795
+ type BatchFailureCode = "NOT_FOUND" | "HOOK_DENIED" | "CONDITION_NOT_MET" | "ALREADY_EXISTS" | "UNIQUE_VIOLATION" | "VALIDATION_ERROR";
1220
1796
  interface BatchOperationResult {
1221
1797
  success: boolean;
1222
1798
  id: string;
1223
1799
  error?: string;
1800
+ /**
1801
+ * Structured failure metadata (#1478), present only on a failed result. Both
1802
+ * are derived at the failure site, not parsed back out of `error`:
1803
+ * - `status` — the natural HTTP status for the cause (404 not found,
1804
+ * 403 hook denial, 409 condition / `ifNotExists` / unique conflict,
1805
+ * 400 upsert validation).
1806
+ * - `code` — a stable machine-readable tag (see `BatchFailureCode`).
1807
+ *
1808
+ * In atomic mode the handler reads the first failure's `status`/`code` to
1809
+ * build the error response, so an increment-on-missing op keeps its 404 and a
1810
+ * hook denial keeps its 403 instead of collapsing to a uniform 409.
1811
+ */
1812
+ status?: number;
1813
+ code?: BatchFailureCode;
1224
1814
  values?: Record<string, number>;
1815
+ /** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
1816
+ appliedFields?: Record<string, any>;
1225
1817
  }
1226
1818
  interface BatchResponse {
1227
1819
  results: BatchOperationResult[];
@@ -1453,6 +2045,33 @@ declare function createDatabaseDO(config?: DatabaseDOConfig): {
1453
2045
  */
1454
2046
  declare const createDocumentDO: typeof createDatabaseDO;
1455
2047
 
2048
+ /**
2049
+ * Decoding for rows read out of the schemaless `records` table.
2050
+ *
2051
+ * A `SELECT` built by `JsonQueryTranslator` comes back in one of two shapes:
2052
+ * the whole record (`_id`, `_type`, `_data`), or — under an inclusion
2053
+ * projection — one column per projected field. Both shapes are turned back
2054
+ * into a record object here, so every reader of the store (the DatabaseDO's
2055
+ * query/get paths, the per-document SQLite projection) hands callers the same
2056
+ * values for the same stored record.
2057
+ */
2058
+ /**
2059
+ * Column-alias prefix for a projected field whose value is selected as JSON
2060
+ * text. The prefix marks the column as still-encoded so
2061
+ * {@link parseRecordRow} knows to decode it; field names cannot start with
2062
+ * `_` (the write path rejects them as reserved), so the prefix can never
2063
+ * collide with a real field.
2064
+ */
2065
+ declare const PROJECTED_JSON_ALIAS_PREFIX = "_json_";
2066
+ /** Column alias under which a projected field's JSON text is selected. */
2067
+ declare function projectedJsonAlias(fieldName: string): string;
2068
+ /**
2069
+ * Decode one row of a `records` SELECT into a record object: `_id`/`_type`
2070
+ * become `id`/`type`, `_data` is parsed when present, and any projected
2071
+ * column is decoded back to its stored value.
2072
+ */
2073
+ declare function parseRecordRow(row: Record<string, any>): Record<string, any>;
2074
+
1456
2075
  /**
1457
2076
  * Cloudflare Worker Router Template
1458
2077
  *
@@ -1558,6 +2177,17 @@ interface DiscoveredSchema {
1558
2177
  * just a Y.Doc.
1559
2178
  */
1560
2179
  declare function discoverSchema(yDoc: Y.Doc): DiscoveredSchema;
2180
+ /**
2181
+ * The schema ONE model DECLARES, read from its `_meta_<model>` map alone.
2182
+ *
2183
+ * `discoverSchema` walks every top-level map, and falls back to inferring a
2184
+ * schema from record data for models without metadata. A caller that only
2185
+ * needs one model's declaration — a format-2 document's projector deciding
2186
+ * which fields are unique or indexed (#2816), on a write path that runs for
2187
+ * every update — pays neither: the meta map is small and epoch-independent,
2188
+ * and a model that declares nothing simply has no declaration.
2189
+ */
2190
+ declare function discoverDeclaredModel(yDoc: Y.Doc, modelName: string): DiscoveredModel | undefined;
1561
2191
  /**
1562
2192
  * List model names that have data in the YDoc (non-`_` prefixed top-level maps).
1563
2193
  */
@@ -1677,6 +2307,176 @@ declare class DocumentQueryTranslator {
1677
2307
  private buildDocumentClause;
1678
2308
  }
1679
2309
 
2310
+ interface RegisteredModelInfo {
2311
+ class: typeof BaseModel;
2312
+ options: ModelOptions;
2313
+ fields: Map<string, FieldOptions>;
2314
+ }
2315
+ declare class ModelRegistry {
2316
+ private static instance;
2317
+ private models;
2318
+ private modelOptions;
2319
+ private activeSessionModels;
2320
+ private dbEngine;
2321
+ constructor();
2322
+ static getInstance(): ModelRegistry;
2323
+ /**
2324
+ * A registry for ONE `initJsBao` instance (#3039).
2325
+ *
2326
+ * The global singleton stays the registration target for
2327
+ * `attachAndRegisterModel` / `createModelClass`; this is the per-instance
2328
+ * view the ORM actually resolves schemas through, so two clients that
2329
+ * register different models under the same name no longer overwrite each
2330
+ * other's include translation and relationship validation.
2331
+ *
2332
+ * With explicit `models`, only those classes are in it. With `models`
2333
+ * undefined the legacy session semantics are kept exactly: the global
2334
+ * registry is SNAPSHOT at init time, so a model registered afterwards is not
2335
+ * silently adopted by an already-running instance.
2336
+ */
2337
+ static createInstanceRegistry(modelClasses?: (typeof BaseModel)[]): ModelRegistry;
2338
+ registerModel(modelClass: any, options: ModelOptions, fields: Map<string, FieldOptions>): void;
2339
+ getModelClass(name: string): typeof BaseModel | undefined;
2340
+ getModelOptions(name: string): ModelOptions | undefined;
2341
+ getModelInfo(modelName: string): RegisteredModelInfo | undefined;
2342
+ getAllRegisteredModelsInfo(): RegisteredModelInfo[];
2343
+ private getModelNameFromClass;
2344
+ setExplicitModelsForSession(modelClasses?: (typeof BaseModel)[]): void;
2345
+ initializeAll(yDoc: Y.Doc, dbEngine: DatabaseEngine): Promise<void>;
2346
+ initializeAllForDocument(yDoc: Y.Doc, dbEngine: DatabaseEngine, docId: string, permissionHint: DocumentPermissionHint,
2347
+ /** #2816 — `2` prepares the tables without reading the Y.Map as records. */
2348
+ documentFormat?: 1 | 2): Promise<void>;
2349
+ removeDocumentData(docId: string, dbEngine: DatabaseEngine): Promise<void>;
2350
+ initializeRelationships(): Promise<void>;
2351
+ private addPrototypeMethod;
2352
+ clearSessionState(): void;
2353
+ getActiveModels(): Map<string, typeof BaseModel>;
2354
+ private validateSessionModels;
2355
+ }
2356
+
2357
+ /**
2358
+ * The binding state one ORM instance owns (#3039).
2359
+ *
2360
+ * Everything here used to live as `BaseModel` statics, so two `initJsBao`
2361
+ * instances in one process shared one engine, one set of connected documents,
2362
+ * one set of `subscribe()` listeners and one default-document mapping. The
2363
+ * static surface survives as accessors that resolve the acting class's
2364
+ * context; the state itself lives here, one per instance.
2365
+ *
2366
+ * Deliberately imports NOTHING at runtime: `BaseModel` reaches for this module
2367
+ * during its own evaluation, so a value import of `ModelRegistry` (which
2368
+ * imports `BaseModel`) would be a cycle that bites at module-init time.
2369
+ */
2370
+ declare class OrmContext {
2371
+ /** Declared before the field that reads it: static initialization is ordered. */
2372
+ private static instanceCounter;
2373
+ /**
2374
+ * Which instance this is, for diagnostics. A process holding several clients
2375
+ * needs a way to say WHICH one a warning is about, so every context takes the
2376
+ * next number as it is created and keeps it for its lifetime.
2377
+ */
2378
+ readonly instanceId: number;
2379
+ /** The engine this instance's models read and write through. */
2380
+ dbEngine: DatabaseEngine | null;
2381
+ connectedDocuments: Map<string, {
2382
+ yDoc: Y.Doc;
2383
+ permissionHint: DocumentPermissionHint;
2384
+ }>;
2385
+ /** `docId_modelName` → the Y.Map that document holds for that model. */
2386
+ documentYMaps: Map<string, Y.Map<any>>;
2387
+ /** `subscribe()` callbacks, by model name. */
2388
+ listenersMap: Map<string, Set<() => void>>;
2389
+ modelNameToDefaultDocId: Map<string, string>;
2390
+ globalDefaultDocId: string | undefined;
2391
+ defaultDocChangedListeners: Set<(payload: {
2392
+ previous?: string;
2393
+ current?: string;
2394
+ }) => void>;
2395
+ modelDocMappingChangedListeners: Set<(payload: {
2396
+ modelName: string;
2397
+ previous?: string;
2398
+ current?: string;
2399
+ }) => void>;
2400
+ /** Format-2 ("large document", #2816) record stores by document id. */
2401
+ format2Documents: Map<string, Format2RecordStore>;
2402
+ /** Format-2 epoch moves in flight (#2816). */
2403
+ format2Transitions: Map<string, Promise<void>>;
2404
+ /**
2405
+ * Nested-Y.Map stringset fields this context has already observed. Per
2406
+ * context, not global: a Y.Doc reconnected to a fresh instance has to be
2407
+ * observed again by that instance, and a global guard would silently
2408
+ * suppress it.
2409
+ */
2410
+ observedStringSetMaps: WeakSet<Y.Map<any>>;
2411
+ /**
2412
+ * The registry this instance's schema lookups use. `null` on the standing
2413
+ * default context, where callers fall back to the global singleton.
2414
+ */
2415
+ modelRegistry: ModelRegistry | null;
2416
+ /** Model classes routed to this context. */
2417
+ boundModels: Set<any>;
2418
+ /** True once {@link destroy} has run; bound classes keep pointing here. */
2419
+ destroyed: boolean;
2420
+ /** Yjs `unobserve` thunks by owning document id. */
2421
+ private observerDisposers;
2422
+ /** Teardown the owner (the document manager) registers for `destroy()`. */
2423
+ private teardownHooks;
2424
+ private static readonly NO_DOCUMENT_KEY;
2425
+ /**
2426
+ * Record how to remove an observer this context installed. Every `observe()`
2427
+ * in the ORM pairs with one of these: without it a torn-down instance's
2428
+ * callbacks keep firing on a Y.Doc that outlives it.
2429
+ */
2430
+ registerObserverDisposer(docId: string | null | undefined, dispose: () => void): void;
2431
+ /** Remove every observer this context installed for `docId`. */
2432
+ disposeObserversForDocument(docId: string): void;
2433
+ private disposeAllObservers;
2434
+ /** Run `hook` as part of `destroy()` (the format-2 binding registers here). */
2435
+ onDestroy(hook: () => void | Promise<void>): void;
2436
+ /** The record store for `docId`, or `null` when the document is format 1. */
2437
+ getFormat2Store(docId: string | null | undefined): Format2RecordStore | null;
2438
+ /**
2439
+ * The epoch move a write to `docId` has to wait out (#2816, behavior 10), or
2440
+ * `null` when none is in flight.
2441
+ */
2442
+ format2WritesHeld(docId: string | null | undefined): Promise<void> | null;
2443
+ /** Run an epoch move with `docId`'s local writes held back (#2816). */
2444
+ holdFormat2Writes<T>(docId: string, run: () => Promise<T>): Promise<T>;
2445
+ /** Notify `subscribe()` callbacks registered on this context for `modelName`. */
2446
+ notifyListeners(modelName: string, log?: (message: string) => void, onError?: (error: unknown) => void): void;
2447
+ setModelDefaultDocumentId(modelName: string, docId: string): void;
2448
+ removeModelDefaultDocumentId(modelName: string): void;
2449
+ clearModelDefaultDocumentIds(): void;
2450
+ setGlobalDefaultDocumentId(docId: string): void;
2451
+ clearGlobalDefaultDocumentId(): void;
2452
+ getModelDefaultDocumentMapping(): Record<string, string>;
2453
+ getDocumentIdForModel(modelName: string): string | undefined;
2454
+ getGlobalDefaultDocumentId(): string | undefined;
2455
+ onDefaultDocChanged(listener: (payload: {
2456
+ previous?: string;
2457
+ current?: string;
2458
+ }) => void): () => void;
2459
+ onModelDocMappingChanged(listener: (payload: {
2460
+ modelName: string;
2461
+ previous?: string;
2462
+ current?: string;
2463
+ }) => void): () => void;
2464
+ /** Drop the model→document defaults that point at a disconnected document. */
2465
+ clearMappingsForDocId(docId: string): void;
2466
+ private announceDefaultDoc;
2467
+ private announceModelDocMapping;
2468
+ /**
2469
+ * Tear this instance down: observers off, engine destroyed, state cleared.
2470
+ *
2471
+ * Deliberately does NOT unbind its model classes. They keep resolving this
2472
+ * context, whose engine is now null, so an operation through them throws the
2473
+ * ordinary not-initialized error instead of silently landing on whatever
2474
+ * other instance happens to be live (#3039, behavior 7). Only a later
2475
+ * `initJsBao` may rebind a class whose context is destroyed.
2476
+ */
2477
+ destroy(): Promise<void>;
2478
+ }
2479
+
1680
2480
  interface StringSetMembership {
1681
2481
  field: string;
1682
2482
  contains: string;
@@ -1721,19 +2521,59 @@ declare enum LogLevel {
1721
2521
  }
1722
2522
  declare class BaseModel implements StringSetChangeTracker {
1723
2523
  static modelName?: string;
1724
- private static listenersMap;
1725
- private static modelNameToDefaultDocId;
1726
- private static globalDefaultDocId;
1727
- private static defaultDocChangedListeners;
1728
- private static modelDocMappingChangedListeners;
2524
+ /**
2525
+ * The ORM instance this class is bound to (#3039), set by `initJsBao`.
2526
+ *
2527
+ * Read through the static prototype chain by {@link resolveOrmContext}, so a
2528
+ * subclass inherits its parent's binding. Never reassigned by `destroy()`:
2529
+ * a destroyed instance's classes keep pointing at their tombstone rather
2530
+ * than falling back to a surviving instance.
2531
+ */
2532
+ static _ormContext?: OrmContext;
2533
+ /**
2534
+ * Every field below is per-ORM-instance state (#3039). The accessors keep
2535
+ * the static surface consumers (and this file's own call sites) already use
2536
+ * while the state itself lives on the acting class's {@link OrmContext}.
2537
+ */
2538
+ protected static get listenersMap(): Map<string, Set<() => void>>;
2539
+ protected static set listenersMap(value: Map<string, Set<() => void>>);
2540
+ protected static get modelNameToDefaultDocId(): Map<string, string>;
2541
+ protected static set modelNameToDefaultDocId(value: Map<string, string>);
2542
+ protected static get globalDefaultDocId(): string | undefined;
2543
+ protected static set globalDefaultDocId(value: string | undefined);
2544
+ protected static get defaultDocChangedListeners(): Set<(payload: {
2545
+ previous?: string;
2546
+ current?: string;
2547
+ }) => void>;
2548
+ protected static get modelDocMappingChangedListeners(): Set<(payload: {
2549
+ modelName: string;
2550
+ previous?: string;
2551
+ current?: string;
2552
+ }) => void>;
1729
2553
  private static readonly DEFAULT_LEGACY_DOC_ID;
1730
- protected static dbInstance: DatabaseEngine | null;
1731
- protected static connectedDocuments: Map<string, {
2554
+ protected static get dbInstance(): DatabaseEngine | null;
2555
+ protected static set dbInstance(value: DatabaseEngine | null);
2556
+ protected static get connectedDocuments(): Map<string, {
1732
2557
  yDoc: Y.Doc;
1733
2558
  permissionHint: DocumentPermissionHint;
1734
2559
  }>;
1735
- protected static documentYMaps: Map<string, Y.Map<any>>;
1736
- private static _observedStringSetMaps;
2560
+ protected static set connectedDocuments(value: Map<string, {
2561
+ yDoc: Y.Doc;
2562
+ permissionHint: DocumentPermissionHint;
2563
+ }>);
2564
+ protected static get documentYMaps(): Map<string, Y.Map<any>>;
2565
+ protected static set documentYMaps(value: Map<string, Y.Map<any>>);
2566
+ /**
2567
+ * Format-2 ("large document", #2816) documents by id, each with the record
2568
+ * store that holds its merged view. Presence in this map IS format-2-ness on
2569
+ * the client: a document without an entry takes the legacy path unchanged.
2570
+ */
2571
+ private static get format2Documents();
2572
+ /**
2573
+ * Large documents whose epoch overlay is being replaced right now (#2816,
2574
+ * behavior 10), each with the promise that settles when the move is done.
2575
+ */
2576
+ protected static get format2Transitions(): Map<string, Promise<void>>;
1737
2577
  private _localChanges;
1738
2578
  private _isDirty;
1739
2579
  private _isLoadingFromYjs;
@@ -1750,6 +2590,53 @@ declare class BaseModel implements StringSetChangeTracker {
1750
2590
  type: string;
1751
2591
  static setLogLevel(level: LogLevel): void;
1752
2592
  static getLogLevel(): LogLevel;
2593
+ /**
2594
+ * Mark a connected document as format 2 and bind the record store that holds
2595
+ * its merged view. Called by `connectDocument(..., { documentFormat: 2 })`.
2596
+ */
2597
+ static registerFormat2Document(docId: string, store: Format2RecordStore): void;
2598
+ static unregisterFormat2Document(docId: string): void;
2599
+ /**
2600
+ * Drop every format-2 binding. Called when the whole runtime is reset: the
2601
+ * stores run over the engine's SQL executor, so one left behind would answer
2602
+ * reads out of a destroyed database.
2603
+ */
2604
+ static clearFormat2Documents(): void;
2605
+ /** The record store for `docId`, or `null` when the document is format 1. */
2606
+ static getFormat2Store(docId: string | null | undefined): Format2RecordStore | null;
2607
+ static isFormat2Document(docId: string | null | undefined): boolean;
2608
+ /**
2609
+ * Run an epoch move with `docId`'s local writes held back (#2816,
2610
+ * behavior 10).
2611
+ *
2612
+ * A move replaces the document's overlay: the sealed Y.Doc is destroyed once
2613
+ * the fresh one is bound. A write that resolved its target document before
2614
+ * the move and lands its overlay entry after would land it in the doomed
2615
+ * doc — and it could never be recovered, because `_pending_ops` records
2616
+ * which fields a write touched, not the values it wrote. A write that starts
2617
+ * during the move therefore waits here and resolves the document again on
2618
+ * the other side. (Concurrent moves are serialized for the same reason.)
2619
+ */
2620
+ static holdFormat2Writes<T>(docId: string, run: () => Promise<T>): Promise<T>;
2621
+ /**
2622
+ * The move a write to `docId` has to wait out, or `null` when none is in
2623
+ * flight — which is every write on every document but the one being rotated.
2624
+ */
2625
+ static format2WritesHeld(docId: string | null | undefined): Promise<void> | null;
2626
+ /**
2627
+ * The record store, Y.Doc and model map a format-2 write should land on,
2628
+ * having waited out any epoch move in flight (#2816, behavior 10).
2629
+ *
2630
+ * Resolves to the callers' own values in the overwhelmingly common case that
2631
+ * no move is running, so an ordinary write pays one map lookup. When a move
2632
+ * IS running, the write waits for it and the freshly bound overlay — and its
2633
+ * rebuilt store — are what it lands on.
2634
+ */
2635
+ static resolveFormat2Target(docId: string, modelName: string, store: Format2RecordStore, yDoc: Y.Doc, modelYMap: Y.Map<any>): Promise<{
2636
+ store: Format2RecordStore;
2637
+ yDoc: Y.Doc;
2638
+ modelYMap: Y.Map<any>;
2639
+ }>;
1753
2640
  static setModelDefaultDocumentId(modelName: string, docId: string): void;
1754
2641
  static removeModelDefaultDocumentId(modelName: string): void;
1755
2642
  static clearModelDefaultDocumentIds(): void;
@@ -1799,10 +2686,24 @@ declare class BaseModel implements StringSetChangeTracker {
1799
2686
  hasFieldChanged(fieldKey: string): boolean;
1800
2687
  markStringSetChange(fieldName: string, operation: "add" | "remove" | "clear", value?: string): void;
1801
2688
  private getStringSetCurrentValues;
2689
+ /** The schema default for a field, or `undefined` when it has none. */
2690
+ private schemaDefaultFor;
1802
2691
  private getStringSetFromYjs;
1803
2692
  private getOrCreateStringSet;
1804
2693
  static initialize(_yDoc: Y.Doc, _db: DatabaseEngine): Promise<void>;
1805
- static initializeForDocument(yDoc: Y.Doc, db: DatabaseEngine, docId: string, permissionHint: DocumentPermissionHint): Promise<void>;
2694
+ /**
2695
+ * Initialize this model for a connected document.
2696
+ *
2697
+ * `documentFormat` (#2816) decides where the records come from. On a legacy
2698
+ * document the Y.Map IS the records, so this loads them into the query
2699
+ * tables and observes the map. On a format-2 document the Y.Map is an epoch
2700
+ * OVERLAY of flat `recordId/field` entries — walking it as records would
2701
+ * read a scalar (or a `null` unset) as a record and mint junk rows, and
2702
+ * observing it would run every overlay change through the legacy
2703
+ * record-shaped path. There the tables are prepared here and hydrated and
2704
+ * observed by `Format2DocumentBinding` from the merged view instead.
2705
+ */
2706
+ static initializeForDocument(yDoc: Y.Doc, db: DatabaseEngine, docId: string, permissionHint: DocumentPermissionHint, documentFormat?: 1 | 2): Promise<void>;
1806
2707
  static cleanupDocumentData(docId: string): Promise<void>;
1807
2708
  static subscribe(callback: () => void): () => void;
1808
2709
  protected static notifyListeners(): void;
@@ -1837,9 +2738,88 @@ declare class BaseModel implements StringSetChangeTracker {
1837
2738
  protected static _buildKeyFromValues(fields: string[], keyValues: any[], modelName: string, constraintName: string): string | null;
1838
2739
  protected _buildUniqueKey(fields: string[], data: Record<string, any>, modelName: string, constraintName: string): string | null;
1839
2740
  save(options?: SaveOptions): Promise<void>;
2741
+ /**
2742
+ * Save onto a format-2 (large) document (#2816).
2743
+ *
2744
+ * The record is NOT written as a nested `Y.Map`: the epoch doc carries flat
2745
+ * field-level overlay entries, and the merged `records` view is what reads
2746
+ * come from. Existence, `upsertOn` and uniqueness are therefore resolved
2747
+ * against that view, not against the doc, which is what makes a base-only
2748
+ * record patchable instead of silently re-created.
2749
+ *
2750
+ * The merged row and the `_pending_ops` entry commit TOGETHER and BEFORE the
2751
+ * overlay lands in the Y.Doc: that ordering is the commit barrier a resolved
2752
+ * `save()` stands behind, and it keeps a crash in between recoverable (the
2753
+ * durable pending op replays the write) rather than divergent.
2754
+ */
2755
+ private saveFormat2;
2756
+ /**
2757
+ * Mirror one merged-view row into the engine's per-model query tables, so
2758
+ * `query()` / `aggregate()` answer from the same state `find()` does. The
2759
+ * merged view remains the source; this is its derived query projection, and
2760
+ * it is awaited so a resolved save is immediately queryable.
2761
+ */
2762
+ /**
2763
+ * Project one merged-view row into the engine's per-model query tables, so
2764
+ * `query()` / `aggregate()` answer from the same state `find()` does. The
2765
+ * merged view stays the source; this is its derived query projection.
2766
+ *
2767
+ * Called (awaited) by the local write path — which is what makes a resolved
2768
+ * save immediately queryable — and enqueued by the overlay observer for
2769
+ * updates that arrive from the sync layer.
2770
+ */
2771
+ static projectFormat2Record(docId: string, recordId: string,
2772
+ /**
2773
+ * `notify: false` withholds the listener notification, for a caller
2774
+ * projecting many records at once (#2816): hydrating a model would
2775
+ * otherwise tell every subscriber once per record, which on a large
2776
+ * document is a re-render per row of the document.
2777
+ */
2778
+ options?: {
2779
+ notify?: boolean;
2780
+ }): Promise<void>;
2781
+ /**
2782
+ * Remove a PAGE of records from the query tables, without consulting the
2783
+ * merged view (#2816).
2784
+ *
2785
+ * The discard path: the rows are erased BEFORE the merged view is thrown
2786
+ * away, because the view is the only place that still names them. Projecting
2787
+ * would re-insert them — the record is still there at that point.
2788
+ */
2789
+ static deleteFormat2QueryRows(recordIds: readonly string[]): Promise<void>;
2790
+ /**
2791
+ * Project a PAGE of merged-view rows into the query tables (#2816).
2792
+ *
2793
+ * The hydration path: one notification for the page rather than one per
2794
+ * record, because a model being hydrated has no intermediate states worth
2795
+ * telling anyone about — and on a large document the per-record version is a
2796
+ * subscriber callback per row of the document.
2797
+ */
2798
+ static projectFormat2Records(docId: string, recordIds: readonly string[]): Promise<void>;
1840
2799
  delete(): Promise<void>;
2800
+ /**
2801
+ * Delete from a format-2 (large) document (#2816): a `_deleted` tombstone in
2802
+ * the overlay plus the merged-view removal, committed with its pending op
2803
+ * before the overlay lands, exactly as `saveFormat2` orders its write.
2804
+ */
2805
+ private deleteFormat2;
1841
2806
  protected getCurrentJSState(): Record<string, any>;
1842
2807
  protected toJSON(): Record<string, any>;
2808
+ /**
2809
+ * Refuse a read through a class whose ORM instance has been destroyed
2810
+ * (#3039, the tombstone).
2811
+ *
2812
+ * `query()`, `count()` and `aggregate()` already refuse: they read the
2813
+ * engine, which a destroyed instance has nulled. The readers that walk the
2814
+ * connected documents instead — `find`, `findAll`, `findByUnique` — would
2815
+ * walk maps `destroy()` emptied and report absence, which a caller cannot
2816
+ * tell apart from a database that genuinely holds no such record. So they
2817
+ * say so instead.
2818
+ *
2819
+ * Only a DESTROYED context is refused. A class that was never bound still
2820
+ * reads as empty before any `initJsBao`, exactly as it always has.
2821
+ */
2822
+ private static assertInstanceAlive;
1843
2823
  static find<T extends BaseModel>(this: new (...args: any[]) => T, id: string): Promise<T | null>;
1844
2824
  /**
1845
2825
  * Document-style query API - returns paginated results
@@ -1919,6 +2899,27 @@ declare class BaseModel implements StringSetChangeTracker {
1919
2899
  */
1920
2900
  static count(this: new (...args: any[]) => any, filter?: DocumentFilter, options?: Pick<QueryOptions, "documents">): Promise<number>;
1921
2901
  static findAll<T extends BaseModel>(this: new (...args: any[]) => T): Promise<T[]>;
2902
+ /**
2903
+ * Resolve a record by its unique-constraint field values via an engine query.
2904
+ * Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
2905
+ * entry for a key.
2906
+ *
2907
+ * The index map is a cache over the SQLite projection, not the source of
2908
+ * truth: every record is loaded into the projection on `initializeForDocument`
2909
+ * regardless of index presence, but a record written by a client predating
2910
+ * index maintenance (legacy data) is missing from the index. Such a record
2911
+ * silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
2912
+ * check. A query over the constraint fields finds it reliably.
2913
+ *
2914
+ * Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
2915
+ * the document that holds it, via `queryOne`) or `null`. Callers decide
2916
+ * whether to heal the missing index entry (write paths) and do their own
2917
+ * logging — this method never mutates the index, so read paths stay
2918
+ * read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
2919
+ * constraint field means the record does not participate in the constraint,
2920
+ * so no query runs.
2921
+ */
2922
+ private static _resolveByUniqueFallback;
1922
2923
  static findByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, value: any | any[]): Promise<T | null>;
1923
2924
  static upsertByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, uniqueLookupValue: any | any[], dataToUpsert: Partial<Omit<InstanceType<new (...args: any[]) => T>, keyof BaseModel | "toJSON">> & {
1924
2925
  id?: string;
@@ -1937,14 +2938,14 @@ declare class BaseModel implements StringSetChangeTracker {
1937
2938
  * member arrives via Y.applyUpdate, or a local per-member set/delete).
1938
2939
  * Idempotent — re-calling on the same Y.Map is a no-op.
1939
2940
  */
1940
- protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
2941
+ protected static observeStringSetMapOnce(nestedMap: Y.Map<any>, docId?: string | null): void;
1941
2942
  /**
1942
2943
  * Walk the schema's stringset fields on `recordYMap` and observe any nested
1943
2944
  * Y.Map values. Called from observer setup and from the parent observer body
1944
2945
  * so newly-arrived stringset Y.Maps (local migration or remote create) get
1945
2946
  * an observer too.
1946
2947
  */
1947
- protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
2948
+ protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any, docId?: string | null): void;
1948
2949
  /**
1949
2950
  * Sets up deep observation on a nested YMap to sync field-level changes to the database
1950
2951
  */
@@ -2067,4 +3068,185 @@ declare function deterministicIdentifierHash(value: string): string;
2067
3068
  */
2068
3069
  declare function buildSafeAlias(prefix: string, rawDescriptor: string): string;
2069
3070
 
2070
- export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation$1 as AggregationOperation, type AggregationOptions$1 as AggregationOptions, type AggregationResult$1 as AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, type CountRequest, type CountResponse, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DocumentDOConfig, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type Env, type ErrorResponse, type GroupByField$1 as GroupByField, type HookContext, type HookResult, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type PatchRequest, type PatchResponse, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SqlStorage, type SqlStorageCursor, type StringSetMembership$1 as StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, createDatabaseDO, createDocumentDO, deterministicIdentifierHash, discoverModelNames, discoverSchema, handleRequest, inferFieldType, loadSchemaFromTomlString, registerFunctionDefault, schemaToToml, syncInferredMeta, syncModelMeta };
3071
+ /**
3072
+ * The on-the-wire shape of a snapshot chunk (#2816, Phase 4).
3073
+ *
3074
+ * Shared by BOTH ends, like the overlay fold: the Durable Object writes these
3075
+ * bytes and the client materializes them, so one implementation encodes,
3076
+ * verifies and decodes them. A snapshot that meant something slightly
3077
+ * different on the two ends would put records into a client's authoritative
3078
+ * table that the server never wrote — silently, because on a format-2 document
3079
+ * there is no whole-document comparison left to catch it.
3080
+ *
3081
+ * ## Why chunks, and why they are verified
3082
+ *
3083
+ * A 2GB document cannot be one object: the load has to survive a dropped
3084
+ * connection, report progress, and let a model answer queries before the rest
3085
+ * has arrived. So a snapshot is a set of per-model gzip ndjson chunks plus a
3086
+ * manifest naming each one's row count and digest, and a chunk is checked
3087
+ * against its entry BEFORE it is decoded — a mismatch is a typed refusal the
3088
+ * loader retries on, cheaper than inflating bytes that are already wrong.
3089
+ *
3090
+ * ## The line format
3091
+ *
3092
+ * One `⟨id⟩\t⟨_data JSON verbatim⟩` line per record. Verbatim matters: the
3093
+ * data comes out of SQLite's `json_patch` and goes straight back into the
3094
+ * other end's `records` table, so re-serializing it here would be a chance to
3095
+ * round a number the server stored exactly. Compact JSON has no raw newline or
3096
+ * tab in it, and a row that somehow held one is refused rather than written —
3097
+ * it would come back as two records, or as one with its tail lost.
3098
+ */
3099
+ /** One row of the authoritative `records` table. */
3100
+ interface SnapshotRow {
3101
+ _type: string;
3102
+ _id: string;
3103
+ _data: string;
3104
+ }
3105
+ /** What the manifest says about one chunk: enough to verify it standalone. */
3106
+ interface SnapshotChunkEntry {
3107
+ /** R2 key, inside the document's own `{appId}/{docId}/` prefix. */
3108
+ key: string;
3109
+ /** The model whose records this chunk holds. */
3110
+ model: string;
3111
+ /** Stored (compressed) length, so a Range loader knows what it is owed. */
3112
+ bytes: number;
3113
+ /**
3114
+ * Uncompressed ndjson length.
3115
+ *
3116
+ * What the rows actually cost once they are somewhere other than a gzip
3117
+ * stream — which is the number a device deciding whether it can hold this
3118
+ * document needs. Compressed length is not a proxy for it: highly
3119
+ * compressible JSON is exactly the shape these chunks hold. Optional
3120
+ * because a manifest written before it was recorded carries none.
3121
+ */
3122
+ rawBytes?: number;
3123
+ rows: number;
3124
+ firstId: string;
3125
+ lastId: string;
3126
+ /** Hex SHA-256 of the STORED bytes — checkable before decompressing. */
3127
+ sha256: string;
3128
+ }
3129
+ /**
3130
+ * What one build wrote: the chunks, and everything a loader needs before the
3131
+ * first of them lands.
3132
+ *
3133
+ * `epoch` is the epoch this snapshot is a BASE for — the state as of that
3134
+ * epoch's start, give or take the torn read the overlays after it repair. The
3135
+ * schema (D5) travels with it so a client can create a model's tables without
3136
+ * a document to discover them from.
3137
+ */
3138
+ interface SnapshotManifest {
3139
+ version: 2;
3140
+ epoch: number;
3141
+ buildId: string;
3142
+ createdAt: number;
3143
+ schema: Record<string, unknown>;
3144
+ chunks: SnapshotChunkEntry[];
3145
+ totals: {
3146
+ rows: number;
3147
+ bytes: number;
3148
+ };
3149
+ }
3150
+ /** A chunk's bytes and the manifest entry that describes them. */
3151
+ interface EncodedSnapshotChunk {
3152
+ body: Uint8Array;
3153
+ entry: SnapshotChunkEntry;
3154
+ }
3155
+ /** A chunk that is not the one its manifest entry describes. */
3156
+ declare class SnapshotChunkIntegrityError extends Error {
3157
+ readonly key: string;
3158
+ constructor(key: string, reason: string);
3159
+ }
3160
+ /** A record that cannot be written as one ndjson line. */
3161
+ declare class SnapshotRowFormatError extends Error {
3162
+ constructor(model: string, id: string);
3163
+ }
3164
+ /**
3165
+ * Fields a model declares as stringsets, out of the manifest's schema (D5).
3166
+ *
3167
+ * The manifest carries the schema so a chunk can be materialized before any
3168
+ * document has been discovered from — which is what lets a cold client, and a
3169
+ * server installing an imported artifact, create a model's tables without
3170
+ * replaying anything.
3171
+ */
3172
+ declare function snapshotStringSetFields(manifest: Pick<SnapshotManifest, "schema">, model: string): Set<string>;
3173
+ /**
3174
+ * What a model declares, out of the manifest's schema (D5).
3175
+ *
3176
+ * A chain carries only what its epochs changed, so for a model no overlay in
3177
+ * it touched the manifest is the ONLY record of what that model declares —
3178
+ * its stringset fields, but also its unique constraints and its query indexes,
3179
+ * which the document installing the chain has to rebuild as the rows land.
3180
+ */
3181
+ declare function snapshotDeclaredSchema(manifest: Pick<SnapshotManifest, "schema">, model: string): {
3182
+ stringSetFields: string[];
3183
+ uniqueConstraints: Array<{
3184
+ name: string;
3185
+ fields: string[];
3186
+ }>;
3187
+ indexedFields: Array<{
3188
+ field: string;
3189
+ type: string;
3190
+ unique: boolean;
3191
+ }>;
3192
+ /**
3193
+ * The model's declaration verbatim, when the exporting document had one.
3194
+ *
3195
+ * The three lists are what the STORE acts on; this is what a WRITE acts on
3196
+ * (coercion, defaults, required fields, the contract a strict write is
3197
+ * checked against). An imported document's overlays carry `_meta_` only for
3198
+ * the epochs that changed it, so for every other model this is the only
3199
+ * record of what it declares (#2816).
3200
+ */
3201
+ declaration?: {
3202
+ fields: Record<string, any>;
3203
+ constraints: Record<string, any>;
3204
+ };
3205
+ };
3206
+ /**
3207
+ * One snapshot row as an overlay entry that REPLACES whatever is there.
3208
+ *
3209
+ * A snapshot row is the whole record, not a patch, so it carries `_replace` —
3210
+ * which is also what clears any stringset members a half-loaded earlier
3211
+ * attempt left behind. Shared by the client's cold load and the server's
3212
+ * install of an imported chain: a row must not come to mean two things.
3213
+ */
3214
+ declare function snapshotRowAsOverlayEntry(id: string, data: Record<string, any>, stringSetFields: ReadonlySet<string>): SnapshotOverlayEntry;
3215
+ /**
3216
+ * The shape {@link snapshotRowAsOverlayEntry} produces — structurally the
3217
+ * `OverlayRecordEntry` of `largeDocuments.ts`, restated here so the chunk
3218
+ * format does not depend on the overlay module.
3219
+ */
3220
+ interface SnapshotOverlayEntry {
3221
+ id: string;
3222
+ fields: Record<string, unknown>;
3223
+ stringSets: Record<string, Record<string, boolean>>;
3224
+ replace: boolean;
3225
+ deleted: boolean;
3226
+ }
3227
+ /** The uncompressed ndjson body of a chunk. Exported for the loader's tests. */
3228
+ declare function snapshotChunkLines(rows: readonly SnapshotRow[]): string;
3229
+ /**
3230
+ * Encode one chunk: the rows as gzip ndjson, plus the entry that describes it.
3231
+ *
3232
+ * The digest covers the STORED bytes so a loader can check what it downloaded
3233
+ * without decompressing it first — a corrupted chunk is refused before it can
3234
+ * cost the client the memory of inflating it.
3235
+ */
3236
+ declare function encodeSnapshotChunk(input: {
3237
+ key: string;
3238
+ model: string;
3239
+ rows: readonly SnapshotRow[];
3240
+ }): Promise<EncodedSnapshotChunk>;
3241
+ /**
3242
+ * Read a chunk back, verified against its manifest entry when one is offered.
3243
+ *
3244
+ * The builder round-trips its own bytes and has nothing to verify against; the
3245
+ * loader always passes the entry, which is what makes a truncated or swapped
3246
+ * chunk a typed refusal rather than records the server never wrote.
3247
+ */
3248
+ declare function readSnapshotChunk(body: Uint8Array, descriptor: SnapshotChunkEntry | {
3249
+ model: string;
3250
+ }): Promise<SnapshotRow[]>;
3251
+
3252
+ export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation$1 as AggregationOperation, type AggregationOptions$1 as AggregationOptions, type AggregationResult$1 as AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DocumentDOConfig, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, Format2CompositeFieldError, type GroupByField$1 as GroupByField, type HookContext, type HookResult, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotManifest, type SnapshotOverlayEntry, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership$1 as StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, completeOverlayEntries, createDatabaseDO, createDocumentDO, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, format2StringSetMembers, groupOverlayEntries, handleRequest, inferFieldType, isFormat2StringSetYMap, loadSchemaFromTomlString, materializeOverlayRecord, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, projectOverlayEntry, projectedJsonAlias, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, schemaToToml, snapshotChunkLines, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, syncInferredMeta, syncModelMeta };