@korajs/store 1.0.0-beta.11 → 1.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +29 -37
  2. package/dist/adapters/better-sqlite3.cjs +90 -4
  3. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  4. package/dist/adapters/better-sqlite3.d.cts +21 -12
  5. package/dist/adapters/better-sqlite3.d.ts +21 -12
  6. package/dist/adapters/better-sqlite3.js +69 -5
  7. package/dist/adapters/better-sqlite3.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +1316 -610
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.cts +29 -3
  11. package/dist/adapters/indexeddb.d.ts +29 -3
  12. package/dist/adapters/indexeddb.js +247 -221
  13. package/dist/adapters/indexeddb.js.map +1 -1
  14. package/dist/adapters/sqlite-wasm-worker.cjs +803 -62
  15. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  16. package/dist/adapters/sqlite-wasm-worker.js +480 -62
  17. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  18. package/dist/adapters/sqlite-wasm.cjs +1032 -344
  19. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  20. package/dist/adapters/sqlite-wasm.d.cts +239 -18
  21. package/dist/adapters/sqlite-wasm.d.ts +239 -18
  22. package/dist/adapters/sqlite-wasm.js +111 -5
  23. package/dist/adapters/sqlite-wasm.js.map +1 -1
  24. package/dist/backup-4NUPDEMB.js +25 -0
  25. package/dist/chunk-32EDKWMI.js +876 -0
  26. package/dist/chunk-32EDKWMI.js.map +1 -0
  27. package/dist/{chunk-CYO3YKFL.js → chunk-5ZRRF6R2.js} +2 -2
  28. package/dist/chunk-A6GFH5IG.js +36 -0
  29. package/dist/chunk-A6GFH5IG.js.map +1 -0
  30. package/dist/chunk-DUKTWVZW.js +175 -0
  31. package/dist/chunk-DUKTWVZW.js.map +1 -0
  32. package/dist/chunk-HZTDBLDB.js +607 -0
  33. package/dist/chunk-HZTDBLDB.js.map +1 -0
  34. package/dist/{chunk-GSJ7EEUC.js → chunk-J5AGXF3D.js} +58 -9
  35. package/dist/chunk-J5AGXF3D.js.map +1 -0
  36. package/dist/chunk-MCQKEHOD.js +112 -0
  37. package/dist/chunk-MCQKEHOD.js.map +1 -0
  38. package/dist/chunk-P4V375FY.js +810 -0
  39. package/dist/chunk-P4V375FY.js.map +1 -0
  40. package/dist/chunk-QDUSJMFV.js +1095 -0
  41. package/dist/chunk-QDUSJMFV.js.map +1 -0
  42. package/dist/chunk-SJP2HTYZ.js +1003 -0
  43. package/dist/chunk-SJP2HTYZ.js.map +1 -0
  44. package/dist/chunk-W5F2LWG6.js +591 -0
  45. package/dist/chunk-W5F2LWG6.js.map +1 -0
  46. package/dist/chunk-Y5LRH7KI.js +5341 -0
  47. package/dist/chunk-Y5LRH7KI.js.map +1 -0
  48. package/dist/{export-audit-ODGQTQIK.js → export-audit-J2QXRHD2.js} +3 -3
  49. package/dist/index.cjs +9354 -4157
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +1064 -108
  52. package/dist/index.d.ts +1064 -108
  53. package/dist/index.js +249 -3092
  54. package/dist/index.js.map +1 -1
  55. package/dist/internal.cjs +580 -264
  56. package/dist/internal.cjs.map +1 -1
  57. package/dist/internal.d.cts +15 -85
  58. package/dist/internal.d.ts +15 -85
  59. package/dist/internal.js +10 -8
  60. package/dist/restore-FR7XIKT2.js +299 -0
  61. package/dist/restore-FR7XIKT2.js.map +1 -0
  62. package/dist/sql-builder-C4u55jKW.d.cts +608 -0
  63. package/dist/sql-builder-CGckdBDL.d.ts +608 -0
  64. package/dist/sqlite-wasm-channel-DVX93Uiq.d.cts +141 -0
  65. package/dist/sqlite-wasm-channel-DVX93Uiq.d.ts +141 -0
  66. package/dist/sqlite-wasm-channel-MLHLKEED.js +12 -0
  67. package/dist/store-IMWKZLWB.js +16 -0
  68. package/dist/store-IMWKZLWB.js.map +1 -0
  69. package/dist/{types-BslDOTk0.d.ts → types-Dh-xyUjP.d.cts} +99 -26
  70. package/dist/{types-BslDOTk0.d.cts → types-Dh-xyUjP.d.ts} +99 -26
  71. package/package.json +3 -2
  72. package/dist/backup-5S7K2Z35.js +0 -14
  73. package/dist/chunk-GSJ7EEUC.js.map +0 -1
  74. package/dist/chunk-HS6DBMST.js +0 -383
  75. package/dist/chunk-HS6DBMST.js.map +0 -1
  76. package/dist/chunk-RJIEKEN4.js +0 -751
  77. package/dist/chunk-RJIEKEN4.js.map +0 -1
  78. package/dist/chunk-TUR7RGEP.js +0 -84
  79. package/dist/chunk-TUR7RGEP.js.map +0 -1
  80. package/dist/chunk-VDBJQ75K.js +0 -78
  81. package/dist/chunk-VDBJQ75K.js.map +0 -1
  82. package/dist/chunk-YT7MRFCP.js +0 -687
  83. package/dist/chunk-YT7MRFCP.js.map +0 -1
  84. package/dist/sqlite-wasm-channel-GGHA2BXU.js +0 -12
  85. package/dist/sqlite-wasm-channel-L6o3bWQK.d.cts +0 -92
  86. package/dist/sqlite-wasm-channel-L6o3bWQK.d.ts +0 -92
  87. package/dist/types-BOO9EPt1.d.ts +0 -170
  88. package/dist/types-C_GDavG-.d.cts +0 -170
  89. /package/dist/{backup-5S7K2Z35.js.map → backup-4NUPDEMB.js.map} +0 -0
  90. /package/dist/{chunk-CYO3YKFL.js.map → chunk-5ZRRF6R2.js.map} +0 -0
  91. /package/dist/{export-audit-ODGQTQIK.js.map → export-audit-J2QXRHD2.js.map} +0 -0
  92. /package/dist/{sqlite-wasm-channel-GGHA2BXU.js.map → sqlite-wasm-channel-MLHLKEED.js.map} +0 -0
@@ -0,0 +1,608 @@
1
+ import { SchemaDefinition, OperationTransform, FoldOptions, Operation, MergeTrace, FoldState, CausalTracker, HybridLogicalClock, SecretKeyProvider, OnDeleteAction, RelationDefinition, CollectionDefinition, FieldDescriptor } from '@korajs/core';
2
+ import { T as Transaction, S as StorageAdapter, L as LocalMutationHandler, C as CollectionRecord, Q as QueryDescriptor } from './types-Dh-xyUjP.js';
3
+
4
+ /**
5
+ * Merges richtext Yjs updates for the fold (core has no Yjs dependency): applies
6
+ * every update to one fresh document and encodes its state. The encoding depends
7
+ * only on the document's content, not on how it was split into updates, so every
8
+ * replica materializes byte-identical richtext.
9
+ */
10
+ declare function mergeYjsUpdates(updates: Uint8Array[]): Uint8Array;
11
+ /** Which fields of a row to (re)write from the fold state. */
12
+ type MaterializeFields = 'all' | ReadonlySet<string>;
13
+ /** Result of merging one operation into its record. */
14
+ interface FoldApplyOutcome {
15
+ /** The merge changed the record's state. */
16
+ changed: boolean;
17
+ /** Merge traces (MergeTrace form), for `merge:*` events. */
18
+ traces: MergeTrace[];
19
+ }
20
+ /**
21
+ * The client side of the W7 fold: every write to a materialized row — local
22
+ * writes, remote operations, scope entries, rejection re-merges, re-materialization
23
+ * — goes through this class, inside the write transaction that appends the
24
+ * operation ("append, then merge"). A row is always the materialization of its
25
+ * record's fold state.
26
+ */
27
+ declare class RecordFolder {
28
+ private readonly schema;
29
+ private readonly transforms;
30
+ private authoritative;
31
+ constructor(schema: SchemaDefinition, transforms?: readonly OperationTransform[]);
32
+ /** The schema transforms every merge applies (transforms at fold time, RT-84). */
33
+ getOperationTransforms(): readonly OperationTransform[];
34
+ /**
35
+ * The fields an operation's merge can change: its own data's keys, or every field
36
+ * when the fold reads it through a schema transform (whose view may name others).
37
+ */
38
+ private touchedFields;
39
+ /** Node ids whose writes win `merge('server-authoritative')` fields. */
40
+ getAuthoritativeNodeIds(): ReadonlySet<string>;
41
+ /** @internal Set by the store from persisted meta / the sync handshake. */
42
+ setAuthoritativeNodeIds(ids: Iterable<string>): void;
43
+ /** Fold options every merge of this database uses. */
44
+ options(traces?: FoldOptions['traces']): FoldOptions;
45
+ /** Whether the schema has a `merge('server-authoritative')` field in `collection`. */
46
+ hasAuthoritativeFields(collection: string): boolean;
47
+ /**
48
+ * Merge one operation (already appended to the log) into its record and
49
+ * re-materialize the row.
50
+ *
51
+ * @param tx - The write transaction that appended the operation
52
+ * @param op - The operation
53
+ * @param mode - 'remote' emits merge traces; 'local' does not (a local write is
54
+ * computed from the row it updates, so it is never concurrent with it)
55
+ */
56
+ applyInTx(tx: Transaction, op: Operation, mode: 'local' | 'remote'): Promise<FoldApplyOutcome>;
57
+ /**
58
+ * Fold a record from its base state and its log, leaving out operations the
59
+ * server terminally rejected, and re-materialize its row (all fields).
60
+ *
61
+ * @returns The new state (null when nothing of the record remains)
62
+ */
63
+ refoldInTx(tx: Transaction, collection: string, recordId: string, options?: {
64
+ clearRetraction?: boolean;
65
+ }): Promise<FoldState | null>;
66
+ /**
67
+ * `join(base, snapshot, fold(log - terminally rejected, provisional effects))`, or
68
+ * null when none exists. Base and snapshot are adapted to the current fold plan
69
+ * (RT-63): a field whose kind changed is rebuilt from its materialized value.
70
+ */
71
+ foldFromLog(tx: Transaction, collection: string, recordId: string): Promise<FoldState | null>;
72
+ /** The record's operations (log + retained sequence-conflict rows). */
73
+ loadRecordOperations(tx: Transaction, collection: string, recordId: string): Promise<Operation[]>;
74
+ /**
75
+ * Which of `ids` the fold leaves out: operations with a terminal-rejection marker
76
+ * from the server (or quarantined by a scope retraction, which the server never
77
+ * receives). Writes the user chose to discard from a held node are NOT left out:
78
+ * discarding stops the upload, it is not a rollback.
79
+ */
80
+ loadTerminalRejections(tx: Transaction, ids: readonly string[]): Promise<Set<string>>;
81
+ /**
82
+ * The record's stored state, or null (none, another format version, or a field
83
+ * whose fold kind the schema has changed since: the caller re-folds, RT-63).
84
+ */
85
+ loadState(tx: Transaction, collection: string, recordId: string): Promise<FoldState | null>;
86
+ /** The stored state as it is, even when the plan changed (a snapshot seed). */
87
+ loadStoredState(tx: Transaction, collection: string, recordId: string): Promise<FoldState | null>;
88
+ /** The record's compacted base, adapted to the current fold plan (RT-63). */
89
+ loadBase(tx: Transaction, collection: string, recordId: string): Promise<FoldState | null>;
90
+ /** The record's row snapshot (RT-68), adapted to the current fold plan. */
91
+ loadSnapshot(tx: Transaction, collection: string, recordId: string): Promise<FoldState | null>;
92
+ /** Store `snapshot` as the record's row snapshot (joined with an existing one). */
93
+ saveSnapshot(tx: Transaction, snapshot: FoldState): Promise<void>;
94
+ /**
95
+ * A scope entry carrying the server's fold state (RT-29) replaces the record's row
96
+ * snapshot (RT-68): the server's state is built from every operation of the record
97
+ * the device was missing, so with the device's own log it is the exact state.
98
+ * Only a carried state this schema can read and join counts (otherwise the entry
99
+ * merges by its data and the snapshot stays).
100
+ *
101
+ * @returns Whether a snapshot was dropped
102
+ */
103
+ private dropSnapshotForCarriedState;
104
+ /** Records that still fold against a row snapshot (approximate, RT-68). */
105
+ listSnapshotRecords(tx: Transaction): Promise<Array<{
106
+ collection: string;
107
+ recordId: string;
108
+ }>>;
109
+ /**
110
+ * After a full resync, a record whose log holds its insert again no longer needs
111
+ * its row snapshot: drop it and re-fold the record from base and log (RT-68).
112
+ *
113
+ * @returns Whether the snapshot was dropped
114
+ */
115
+ settleSnapshotInTx(tx: Transaction, collection: string, recordId: string): Promise<boolean>;
116
+ /**
117
+ * Fold a local-only provisional side effect (RT-69) into its record: stored apart
118
+ * from the log (never sequenced, never uploaded) and kept until the real copy of
119
+ * the effect arrives or the delivery stream catches up.
120
+ *
121
+ * @param parentId - The remote operation that caused it (the parent delete)
122
+ */
123
+ applyProvisionalInTx(tx: Transaction, op: Operation, parentId: string): Promise<void>;
124
+ /**
125
+ * Remove provisional effects of a record (those caused by one of `parentIds`, or
126
+ * all of them when null). The caller re-folds the record.
127
+ *
128
+ * @returns How many were removed
129
+ */
130
+ deleteProvisional(tx: Transaction, collection: string, recordId: string, parentIds: readonly string[] | null): Promise<number>;
131
+ /** Records with provisional effects. */
132
+ listProvisionalRecords(tx: Transaction): Promise<Array<{
133
+ collection: string;
134
+ recordId: string;
135
+ }>>;
136
+ private loadProvisional;
137
+ saveState(tx: Transaction, state: FoldState): Promise<void>;
138
+ saveBase(tx: Transaction, state: FoldState): Promise<void>;
139
+ /** Join `addition` into the record's base state. */
140
+ joinIntoBase(tx: Transaction, addition: FoldState): Promise<void>;
141
+ /**
142
+ * Write the row of a record from its fold state.
143
+ *
144
+ * - No insert merged: no row (an existing one is hidden).
145
+ * - Otherwise the row holds the fold's values (also while deleted: a tombstone
146
+ * keeps its last values), `_deleted` from the record's liveness, and versions
147
+ * from the state: `_version` / `_updated_at` = newest operation, `_created_at` =
148
+ * creation, `_field_versions` = each field's newest write.
149
+ * - Only `fields` are written to an existing row; columns the fold never wrote
150
+ * (a column a schema migration added with a default, a local-only backfill)
151
+ * keep their stored value.
152
+ * - A row hidden by a scope retraction stays hidden unless `clearRetraction`
153
+ * (a scope-entry insert) is set.
154
+ */
155
+ materializeRow(tx: Transaction, state: FoldState, fields: MaterializeFields, options: {
156
+ clearRetraction: boolean;
157
+ absent?: boolean;
158
+ }): Promise<void>;
159
+ /**
160
+ * Load the record's state; rebuild it from base + log when it is missing or in
161
+ * another format version. `rebuilt` tells the caller to rewrite the whole row.
162
+ */
163
+ private loadOrRebuild;
164
+ private hasStateRow;
165
+ private readStateRow;
166
+ private toMergeTraces;
167
+ }
168
+
169
+ /**
170
+ * How a local node's {@link LocalNodeRecord.principal} was learned (RT-50):
171
+ *
172
+ * - `fresh`: the node had no writes when it was bound to the signed-in user, so every
173
+ * write under it was made by that user.
174
+ * - `server`: a sync handshake as the node was accepted for that user (the server's node
175
+ * claims are authoritative).
176
+ * - `app`: the app assigned a held node to the signed-in user
177
+ * (`app.sync.assignHeld`). A guess: cleared if the server refuses the node for them.
178
+ */
179
+ type LocalNodeBinding = 'fresh' | 'server' | 'app';
180
+ /** A terminal rejection to record (RT-36). */
181
+ interface TerminalRejection {
182
+ operationId: string;
183
+ /** Authoring node and sequence, when known (null for rows seeded from the old list). */
184
+ nodeId: string | null;
185
+ sequenceNumber: number | null;
186
+ code: string;
187
+ rejectedAt: number;
188
+ }
189
+ /** A node id this database authored operations under (RT-38, RT-40). */
190
+ interface LocalNodeRecord {
191
+ nodeId: string;
192
+ /** Wall-clock time (ms) the node was registered. Display only. */
193
+ createdAt: number;
194
+ /** A sync handshake as this node was accepted at least once. */
195
+ accepted: boolean;
196
+ /**
197
+ * The server refused this node (`NODE_ID_CLAIMED`) after it had been accepted: it
198
+ * belongs to a principal that is not signed in. Its unsynced operations are held for
199
+ * that principal, never uploaded under another one.
200
+ */
201
+ held: boolean;
202
+ /** {@link ACCEPTED_CYCLE_META_KEY} value when the server last refused it, or null. */
203
+ refusedCycle: number | null;
204
+ /**
205
+ * The signed-in user the node's writes belong to (RT-42), or null when the app never
206
+ * told the store who was signed in while it was in use. A node bound to one user is
207
+ * never used for, uploaded under, or adopted by another user's session.
208
+ */
209
+ principal: string | null;
210
+ /**
211
+ * How {@link principal} was learned (RT-50), or null when unbound, or bound by a release
212
+ * that did not record it (treated as a guess).
213
+ */
214
+ binding: LocalNodeBinding | null;
215
+ /**
216
+ * Users the server refused this UNBOUND node for (`NODE_ID_CLAIMED`, RT-50): it is
217
+ * not theirs, so it is never tried again on their sessions. An unbound node that was
218
+ * accepted before belongs to whichever user the server accepts it for.
219
+ */
220
+ refusedPrincipals: string[];
221
+ }
222
+ /** A parked adoption (RT-46): the node made no upload progress in its last session. */
223
+ interface ParkedAdoption {
224
+ /** {@link AdoptionSchedule.progress} when it was parked: retried once it moves. */
225
+ progressMark: number;
226
+ /** Wall-clock time (ms) after which it is retried anyway. Scheduling only. */
227
+ untilMs: number;
228
+ /** Consecutive parks (backoff exponent). */
229
+ count: number;
230
+ /** Wall-clock time (ms) it was parked. Scheduling only. */
231
+ parkedAtMs: number;
232
+ }
233
+ /**
234
+ * Which local nodes' adoptions are parked (RT-46), and a counter of upload progress on
235
+ * this database: a parked node is retried once anything else uploaded (its deferral may
236
+ * have depended on it) or after its backoff.
237
+ */
238
+ interface AdoptionSchedule {
239
+ progress: number;
240
+ parked: Record<string, ParkedAdoption>;
241
+ }
242
+ /** Read access to a database (an open adapter, or a reader handed to a delete check). */
243
+ interface DatabaseQuery {
244
+ query<T>(sql: string, params?: unknown[]): Promise<T[]>;
245
+ }
246
+ /**
247
+ * Whether any local node has operations above its contiguous acknowledged prefix that
248
+ * the server did not refuse for good (RT-41): the authoritative "unsynced" (W3), which
249
+ * the outbound queue only approximates. A local node is one in the registry, the
250
+ * database's node id, or the node of the legacy prefix key; a node with no recorded
251
+ * prefix counts from 0. Works on a database no store has opened in this release.
252
+ */
253
+ declare function hasUnsyncedOwnOperations(db: DatabaseQuery): Promise<boolean>;
254
+
255
+ /**
256
+ * Called inside the write transaction for every local delete, after the delete
257
+ * operation is built and before referential side effects run. Lets the unified
258
+ * apply pipeline run its referential checks (and emit their traces) against the
259
+ * rows the transaction sees. Throwing aborts the whole write.
260
+ */
261
+ type LocalDeleteHook = (operation: Operation, tx: Transaction) => Promise<void>;
262
+ /**
263
+ * Everything the single local write path needs to build and persist an
264
+ * operation inside a storage transaction. One environment serves single-record
265
+ * writes, `app.transaction` commits and referential cascades, so every local
266
+ * operation is built the same way (W6).
267
+ */
268
+ interface WriteEnv {
269
+ readonly schema: SchemaDefinition;
270
+ readonly clock: HybridLogicalClock;
271
+ readonly nodeId: string;
272
+ readonly relationEnforcer: RelationEnforcer | null;
273
+ readonly secretKeyProvider?: SecretKeyProvider;
274
+ /** Shared by every operation of an `app.transaction` / `app.mutation` commit. */
275
+ readonly transactionId?: string;
276
+ /** Human-readable mutation name propagated to every operation (DevTools). */
277
+ readonly mutationName?: string;
278
+ /** See {@link LocalDeleteHook}. */
279
+ readonly beforeLocalDelete?: LocalDeleteHook;
280
+ /**
281
+ * The W7 record fold. When set, a local write appends its operation and then
282
+ * merges it into the record's fold state, which re-materializes the row (the
283
+ * same path remote operations take). Absent only under
284
+ * `experimental.legacyMerge` (and while a pre-W7 database is migrated, before its
285
+ * re-materialization): the row is then written directly, as in beta.12.
286
+ */
287
+ readonly fold?: RecordFolder;
288
+ /**
289
+ * Largest operation a local write may produce (RT-86), the server's
290
+ * `maxOperationBytes`. Default `DEFAULT_MAX_OPERATION_BYTES` (256 KiB).
291
+ */
292
+ readonly maxOperationBytes?: number;
293
+ }
294
+ /**
295
+ * Causal bookkeeping for the operations built inside ONE storage transaction.
296
+ *
297
+ * Dependencies are computed from the committed causal heads (the shared
298
+ * {@link CausalTracker}) overlaid with the operations already built in this
299
+ * transaction. Heads are published to the shared tracker only after the
300
+ * transaction commits, so a rolled-back write never leaves a dependency on an
301
+ * operation that does not exist.
302
+ */
303
+ declare class CausalScope {
304
+ private readonly tracker;
305
+ /** When true, every operation also depends on the previous one in this scope. */
306
+ private readonly chainOperations;
307
+ private readonly lastByCollection;
308
+ private lastInScope;
309
+ private readonly recorded;
310
+ constructor(tracker: CausalTracker | null,
311
+ /** When true, every operation also depends on the previous one in this scope. */
312
+ chainOperations: boolean);
313
+ /**
314
+ * Direct causal parents for the next operation in `collection`.
315
+ *
316
+ * @param collection - Collection of the operation being built
317
+ * @param extra - Explicit parents (e.g. the delete that caused a cascade)
318
+ */
319
+ depsFor(collection: string, extra?: readonly string[]): string[];
320
+ /** Record an operation built in this scope. */
321
+ record(collection: string, operationId: string): void;
322
+ /** Publish this scope's heads to the shared tracker. Call only after commit. */
323
+ publish(): void;
324
+ }
325
+ /**
326
+ * The sequence-number block of ONE write transaction.
327
+ *
328
+ * The first number is reserved with one `UPSERT ... RETURNING` on
329
+ * `_kora_version_vector`, inside the transaction; later numbers continue the block
330
+ * in memory, and {@link SequenceBlock.close} persists the block's end with one
331
+ * write before the transaction commits. Write transactions are serialized and no
332
+ * code path allocates numbers outside one, so the block is contiguous and never
333
+ * shared; the UNIQUE `(node_id, sequence_number)` index on every op table is the
334
+ * backstop. A rollback discards the reservation with everything else.
335
+ */
336
+ declare class SequenceBlock {
337
+ private readonly tx;
338
+ private readonly nodeId;
339
+ private last;
340
+ private persisted;
341
+ constructor(tx: Transaction, nodeId: string);
342
+ /** The next number of the block. */
343
+ take(): Promise<number>;
344
+ /** Persist the end of the block. Must run inside the transaction, before commit. */
345
+ close(): Promise<void>;
346
+ }
347
+ /**
348
+ * The per-transaction state of a local write: the transaction handle, its
349
+ * sequence block and its causal scope. Every operation built in one transaction
350
+ * takes the next number of the same contiguous block.
351
+ */
352
+ interface WriteScope {
353
+ readonly tx: Transaction;
354
+ readonly sequence: SequenceBlock;
355
+ readonly causal: CausalScope;
356
+ }
357
+
358
+ /**
359
+ * A resolved relation reference: describes a relation that points TO a given collection.
360
+ * When a record in `targetCollection` is deleted, we must handle records in
361
+ * `sourceCollection` that reference it via `foreignKeyField`.
362
+ */
363
+ interface IncomingRelation {
364
+ /** Name of the relation in the schema */
365
+ relationName: string;
366
+ /** Collection that holds the foreign key (the "from" side) */
367
+ sourceCollection: string;
368
+ /** Field in the source collection that references the target */
369
+ foreignKeyField: string;
370
+ /** What to do when the referenced record is deleted */
371
+ onDelete: OnDeleteAction;
372
+ /** The full relation definition */
373
+ relation: RelationDefinition;
374
+ }
375
+
376
+ /**
377
+ * Configuration for the RelationEnforcer.
378
+ */
379
+ interface RelationEnforcerConfig {
380
+ schema: SchemaDefinition;
381
+ }
382
+ /**
383
+ * Result of enforcing referential integrity on a delete operation.
384
+ * Contains all additional operations that were created as side effects
385
+ * (cascaded deletes and set-null updates).
386
+ */
387
+ interface EnforcementResult {
388
+ /** Additional operations created by cascading deletes and set-null updates */
389
+ operations: Operation[];
390
+ }
391
+ /**
392
+ * Enforces referential integrity constraints during local delete operations.
393
+ *
394
+ * When a record is deleted, this enforcer checks all relations that reference
395
+ * the deleted record's collection and applies the appropriate onDelete policy:
396
+ *
397
+ * - **cascade**: Recursively deletes all referencing records
398
+ * - **set-null**: Sets the foreign key to null on all referencing records
399
+ * - **restrict**: Throws a ReferentialIntegrityError if any references exist
400
+ * - **no-action**: Does nothing (the foreign key is left dangling)
401
+ *
402
+ * The enforcer runs inside the delete's write transaction and writes every side
403
+ * effect through the single local write path (`writeDeleteInTx` /
404
+ * `writeUpdateInTx`), so cascaded operations reserve their sequence numbers in
405
+ * the same contiguous block as the parent and carry per-field version stamps
406
+ * (STORE-2, STORE-3). Every generated operation depends causally on the delete
407
+ * that caused it.
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * const enforcer = new RelationEnforcer({ schema })
412
+ * // inside a write transaction, after building the parent delete operation:
413
+ * const result = await enforcer.enforceDelete('projects', 'proj-1', scope, env, deleteOp.id)
414
+ * // result.operations contains any cascaded delete/update ops
415
+ * ```
416
+ */
417
+ declare class RelationEnforcer {
418
+ private readonly lookup;
419
+ constructor(config: RelationEnforcerConfig);
420
+ /**
421
+ * Enforce referential integrity for a record being deleted.
422
+ *
423
+ * @param collection - The collection the deleted record belongs to
424
+ * @param recordId - The ID of the deleted record
425
+ * @param scope - The active write scope (transaction + causal scope)
426
+ * @param env - The write environment of the delete
427
+ * @param parentOpId - Id of the delete operation; every side effect depends on it
428
+ * @returns All additional operations created as side effects
429
+ * @throws {ReferentialIntegrityError} If a 'restrict' policy is violated
430
+ */
431
+ enforceDelete(collection: string, recordId: string, scope: WriteScope, env: WriteEnv, parentOpId: string): Promise<EnforcementResult>;
432
+ /**
433
+ * Enforce a single relation's onDelete policy.
434
+ */
435
+ private enforceRelation;
436
+ /**
437
+ * Cascade: delete every live record that references the deleted record. Each
438
+ * cascaded delete runs the full delete path, so it recurses into its own
439
+ * referencing records.
440
+ */
441
+ private enforceCascade;
442
+ /**
443
+ * Set-null: set the foreign key to null on every live referencing record.
444
+ */
445
+ private enforceSetNull;
446
+ /**
447
+ * Restrict: refuse the delete if any referencing records exist.
448
+ */
449
+ private enforceRestrict;
450
+ /** Ids of live records referencing `deletedRecordId`, in id order (deterministic). */
451
+ private findReferencing;
452
+ /**
453
+ * Get the relation lookup map for external use (e.g., by the merge engine).
454
+ */
455
+ getRelationLookup(): Map<string, IncomingRelation[]>;
456
+ }
457
+
458
+ /**
459
+ * Callback invoked after a mutation so the Store can notify subscriptions.
460
+ */
461
+ type MutationCallback = (collection: string, operation: Operation) => void;
462
+ /**
463
+ * Collection provides CRUD operations on a single schema collection.
464
+ * Delegates to {@link LocalMutationHandler} when configured (unified apply pipeline),
465
+ * otherwise uses shared mutation executors directly.
466
+ */
467
+ declare class Collection {
468
+ private readonly name;
469
+ private readonly definition;
470
+ private readonly schema;
471
+ private readonly adapter;
472
+ private clock;
473
+ private nodeId;
474
+ private readonly onMutation;
475
+ private relationEnforcer;
476
+ private mutationHandler;
477
+ private causalTracker;
478
+ private readonly secretKeyProvider?;
479
+ private readonly onStorageError?;
480
+ private readonly fold?;
481
+ private readonly maxOperationBytes?;
482
+ constructor(name: string, definition: CollectionDefinition, schema: SchemaDefinition, adapter: StorageAdapter, clock: HybridLogicalClock, nodeId: string, onMutation: MutationCallback, relationEnforcer: RelationEnforcer | null, mutationHandler: LocalMutationHandler | null, causalTracker: CausalTracker | null, secretKeyProvider?: SecretKeyProvider | undefined, onStorageError?: ((error: unknown) => void) | undefined, fold?: (() => RecordFolder | undefined) | undefined, maxOperationBytes?: number | undefined);
483
+ /**
484
+ * Point this collection at the store's new device identity after a node-id
485
+ * rotation (RT-21). Internal: called by the owning Store only.
486
+ *
487
+ * @internal
488
+ */
489
+ rebindNode(clock: HybridLogicalClock, nodeId: string, relationEnforcer: RelationEnforcer | null, causalTracker: CausalTracker | null): void;
490
+ private mutationContext;
491
+ insert(data: Record<string, unknown>): Promise<CollectionRecord>;
492
+ findById(id: string): Promise<CollectionRecord | null>;
493
+ update(id: string, data: Record<string, unknown>): Promise<CollectionRecord>;
494
+ delete(id: string): Promise<void>;
495
+ getName(): string;
496
+ getDefinition(): CollectionDefinition;
497
+ /** Replace the mutation handler (e.g. after ApplyPipeline is wired in createApp). */
498
+ setMutationHandler(handler: LocalMutationHandler | null): void;
499
+ }
500
+
501
+ /**
502
+ * Shared context for executing local collection mutations.
503
+ *
504
+ * Extends the {@link WriteEnv} of the single local write path with the target
505
+ * collection and the post-commit plumbing. There is deliberately no way to
506
+ * allocate a sequence number outside a write transaction: every local operation
507
+ * reserves its number inside the transaction that persists it (W6).
508
+ */
509
+ interface LocalMutationContext extends WriteEnv {
510
+ readonly collection: string;
511
+ readonly definition: CollectionDefinition;
512
+ readonly adapter: StorageAdapter;
513
+ /** Called once per committed operation, after the storage transaction commits. */
514
+ readonly onMutation: MutationCallback;
515
+ readonly causalTracker: CausalTracker | null;
516
+ /** Additional parent op ids (e.g. referential cascade from a delete). */
517
+ readonly extraCausalDeps?: string[];
518
+ /**
519
+ * Called when a write transaction fails, before the error propagates (the
520
+ * store maps out-of-space errors to `store:quota-exceeded`).
521
+ */
522
+ readonly onStorageError?: (error: unknown) => void;
523
+ }
524
+
525
+ /**
526
+ * Result of building a SQL query: the parameterized SQL string and its bound values.
527
+ */
528
+ interface SqlQuery {
529
+ sql: string;
530
+ params: unknown[];
531
+ }
532
+ /**
533
+ * Build a SELECT query from a QueryDescriptor.
534
+ * Automatically adds `WHERE _deleted = 0` to exclude soft-deleted records.
535
+ *
536
+ * @param descriptor - The query descriptor
537
+ * @param fields - The field descriptors from the collection schema
538
+ * @returns A parameterized SQL query
539
+ */
540
+ declare function buildSelectQuery(descriptor: QueryDescriptor, fields: Record<string, FieldDescriptor>): SqlQuery;
541
+ /**
542
+ * Build a COUNT query from a QueryDescriptor.
543
+ * Automatically adds `WHERE _deleted = 0`.
544
+ *
545
+ * @param descriptor - The query descriptor
546
+ * @param fields - The field descriptors from the collection schema
547
+ * @returns A parameterized SQL query that returns { count: number }
548
+ */
549
+ declare function buildCountQuery(descriptor: QueryDescriptor, fields: Record<string, FieldDescriptor>): SqlQuery;
550
+ /**
551
+ * Build an INSERT query for a collection record.
552
+ *
553
+ * @param collection - The collection name
554
+ * @param record - The record data (already serialized with id, _created_at, _updated_at)
555
+ * @returns A parameterized SQL query
556
+ */
557
+ declare function buildInsertQuery(collection: string, record: Record<string, unknown>): SqlQuery;
558
+ /**
559
+ * Build an UPDATE query for a collection record.
560
+ *
561
+ * @param collection - The collection name
562
+ * @param id - The record ID
563
+ * @param changes - The fields to update (already serialized)
564
+ * @returns A parameterized SQL query
565
+ */
566
+ declare function buildUpdateQuery(collection: string, id: string, changes: Record<string, unknown>): SqlQuery;
567
+ /**
568
+ * Build a soft-delete query (SET _deleted = 1).
569
+ *
570
+ * @param collection - The collection name
571
+ * @param id - The record ID
572
+ * @param updatedAt - The timestamp to set on _updated_at
573
+ * @returns A parameterized SQL query
574
+ */
575
+ declare function buildSoftDeleteQuery(collection: string, id: string, updatedAt: number, version?: string): SqlQuery;
576
+ /**
577
+ * Build an UPDATE that applies only when the row is missing or older than `remoteVersion`
578
+ * (serialized HLC). Prevents stale remote sync from overwriting newer local materialized state.
579
+ */
580
+ declare function buildLwwUpdateQuery(collection: string, id: string, changes: Record<string, unknown>, remoteVersion: string): SqlQuery;
581
+ /**
582
+ * Build a soft-delete that applies only when the row is older than `remoteVersion`.
583
+ */
584
+ declare function buildLwwSoftDeleteQuery(collection: string, id: string, updatedAt: number, version: string): SqlQuery;
585
+ /**
586
+ * Build a WHERE clause from conditions, validating field names against the schema.
587
+ *
588
+ * @param where - The where conditions
589
+ * @param fields - The field descriptors from the collection schema
590
+ * @returns The SQL WHERE clause string and params, or null if no conditions
591
+ */
592
+ declare function buildWhereClause(where: Record<string, unknown>, fields: Record<string, FieldDescriptor>): SqlQuery | null;
593
+ /**
594
+ * Record metadata exposed on every record under a camelCase name and backed by a
595
+ * system column (STORE-11): `where({ updatedAt: { $gt: t } })` and
596
+ * `orderBy('createdAt', 'desc')` filter and sort on these columns. A schema field
597
+ * declared with the same name wins and its own column is used instead.
598
+ */
599
+ declare const VIRTUAL_TIMESTAMP_FIELDS: Readonly<{
600
+ readonly createdAt: "_created_at";
601
+ readonly updatedAt: "_updated_at";
602
+ }>;
603
+ /** Name of a virtual record-metadata field usable in `where` and `orderBy`. */
604
+ type VirtualTimestampField = keyof typeof VIRTUAL_TIMESTAMP_FIELDS;
605
+ /** Every virtual record-metadata field name, for typing `where` / `orderBy` keys. */
606
+ declare const VIRTUAL_TIMESTAMP_FIELD_NAMES: readonly VirtualTimestampField[];
607
+
608
+ export { type AdoptionSchedule as A, Collection as C, type DatabaseQuery as D, type LocalMutationContext as L, type ParkedAdoption as P, RelationEnforcer as R, type SqlQuery as S, type TerminalRejection as T, VIRTUAL_TIMESTAMP_FIELDS as V, buildInsertQuery as a, buildCountQuery as b, buildLwwSoftDeleteQuery as c, buildLwwUpdateQuery as d, buildSelectQuery as e, buildSoftDeleteQuery as f, buildUpdateQuery as g, buildWhereClause as h, hasUnsyncedOwnOperations as i, type LocalDeleteHook as j, RecordFolder as k, type LocalNodeRecord as l, VIRTUAL_TIMESTAMP_FIELD_NAMES as m, type VirtualTimestampField as n, mergeYjsUpdates as o };