@korajs/store 1.0.0-beta.12 → 1.0.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -37
- package/dist/adapters/better-sqlite3.cjs +90 -4
- package/dist/adapters/better-sqlite3.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.d.cts +21 -12
- package/dist/adapters/better-sqlite3.d.ts +21 -12
- package/dist/adapters/better-sqlite3.js +69 -5
- package/dist/adapters/better-sqlite3.js.map +1 -1
- package/dist/adapters/indexeddb.cjs +1316 -610
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +29 -3
- package/dist/adapters/indexeddb.d.ts +29 -3
- package/dist/adapters/indexeddb.js +247 -221
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs +856 -63
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +529 -63
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +1032 -344
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +239 -18
- package/dist/adapters/sqlite-wasm.d.ts +239 -18
- package/dist/adapters/sqlite-wasm.js +111 -5
- package/dist/adapters/sqlite-wasm.js.map +1 -1
- package/dist/backup-IHKVMUAM.js +25 -0
- package/dist/chunk-5AQSGMJV.js +5471 -0
- package/dist/chunk-5AQSGMJV.js.map +1 -0
- package/dist/chunk-5JMGMPUE.js +591 -0
- package/dist/chunk-5JMGMPUE.js.map +1 -0
- package/dist/{chunk-CYO3YKFL.js → chunk-5ZRRF6R2.js} +2 -2
- package/dist/chunk-A6GFH5IG.js +36 -0
- package/dist/chunk-A6GFH5IG.js.map +1 -0
- package/dist/chunk-BILSA6XZ.js +608 -0
- package/dist/chunk-BILSA6XZ.js.map +1 -0
- package/dist/chunk-BQSIL6TT.js +1095 -0
- package/dist/chunk-BQSIL6TT.js.map +1 -0
- package/dist/{chunk-GSJ7EEUC.js → chunk-J5AGXF3D.js} +58 -9
- package/dist/chunk-J5AGXF3D.js.map +1 -0
- package/dist/chunk-KK3GBONL.js +112 -0
- package/dist/chunk-KK3GBONL.js.map +1 -0
- package/dist/chunk-KTVNANUH.js +876 -0
- package/dist/chunk-KTVNANUH.js.map +1 -0
- package/dist/chunk-QW4UU7NG.js +1003 -0
- package/dist/chunk-QW4UU7NG.js.map +1 -0
- package/dist/chunk-SRHDJF5B.js +186 -0
- package/dist/chunk-SRHDJF5B.js.map +1 -0
- package/dist/chunk-UC2LGFPV.js +810 -0
- package/dist/chunk-UC2LGFPV.js.map +1 -0
- package/dist/{export-audit-ODGQTQIK.js → export-audit-J2QXRHD2.js} +3 -3
- package/dist/index.cjs +9533 -4246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1143 -110
- package/dist/index.d.ts +1143 -110
- package/dist/index.js +298 -3191
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +581 -264
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +15 -85
- package/dist/internal.d.ts +15 -85
- package/dist/internal.js +10 -8
- package/dist/restore-MNDPR557.js +299 -0
- package/dist/restore-MNDPR557.js.map +1 -0
- package/dist/sql-builder-D0tC3sv9.d.cts +615 -0
- package/dist/sql-builder-D5UMzHEo.d.ts +615 -0
- package/dist/sqlite-wasm-channel-DVX93Uiq.d.cts +141 -0
- package/dist/sqlite-wasm-channel-DVX93Uiq.d.ts +141 -0
- package/dist/sqlite-wasm-channel-QCCNBYCU.js +12 -0
- package/dist/store-HNGX4KTT.js +16 -0
- package/dist/store-HNGX4KTT.js.map +1 -0
- package/dist/{types-BslDOTk0.d.ts → types-Dh-xyUjP.d.cts} +99 -26
- package/dist/{types-BslDOTk0.d.cts → types-Dh-xyUjP.d.ts} +99 -26
- package/package.json +3 -2
- package/dist/backup-5S7K2Z35.js +0 -14
- package/dist/chunk-GSJ7EEUC.js.map +0 -1
- package/dist/chunk-HS6DBMST.js +0 -383
- package/dist/chunk-HS6DBMST.js.map +0 -1
- package/dist/chunk-RJIEKEN4.js +0 -751
- package/dist/chunk-RJIEKEN4.js.map +0 -1
- package/dist/chunk-TUR7RGEP.js +0 -84
- package/dist/chunk-TUR7RGEP.js.map +0 -1
- package/dist/chunk-VDBJQ75K.js +0 -78
- package/dist/chunk-VDBJQ75K.js.map +0 -1
- package/dist/chunk-YT7MRFCP.js +0 -687
- package/dist/chunk-YT7MRFCP.js.map +0 -1
- package/dist/sqlite-wasm-channel-GGHA2BXU.js +0 -12
- package/dist/sqlite-wasm-channel-L6o3bWQK.d.cts +0 -92
- package/dist/sqlite-wasm-channel-L6o3bWQK.d.ts +0 -92
- package/dist/types-BOO9EPt1.d.ts +0 -170
- package/dist/types-C_GDavG-.d.cts +0 -170
- /package/dist/{backup-5S7K2Z35.js.map → backup-IHKVMUAM.js.map} +0 -0
- /package/dist/{chunk-CYO3YKFL.js.map → chunk-5ZRRF6R2.js.map} +0 -0
- /package/dist/{export-audit-ODGQTQIK.js.map → export-audit-J2QXRHD2.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-GGHA2BXU.js.map → sqlite-wasm-channel-QCCNBYCU.js.map} +0 -0
|
@@ -0,0 +1,615 @@
|
|
|
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
|
+
* Throws when local writes are not allowed right now: the store's pinned node
|
|
295
|
+
* belongs to another user than the one signed in (F9). Called before every local
|
|
296
|
+
* operation is built.
|
|
297
|
+
*/
|
|
298
|
+
readonly assertLocalWriteAllowed?: () => void;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Causal bookkeeping for the operations built inside ONE storage transaction.
|
|
302
|
+
*
|
|
303
|
+
* Dependencies are computed from the committed causal heads (the shared
|
|
304
|
+
* {@link CausalTracker}) overlaid with the operations already built in this
|
|
305
|
+
* transaction. Heads are published to the shared tracker only after the
|
|
306
|
+
* transaction commits, so a rolled-back write never leaves a dependency on an
|
|
307
|
+
* operation that does not exist.
|
|
308
|
+
*/
|
|
309
|
+
declare class CausalScope {
|
|
310
|
+
private readonly tracker;
|
|
311
|
+
/** When true, every operation also depends on the previous one in this scope. */
|
|
312
|
+
private readonly chainOperations;
|
|
313
|
+
private readonly lastByCollection;
|
|
314
|
+
private lastInScope;
|
|
315
|
+
private readonly recorded;
|
|
316
|
+
constructor(tracker: CausalTracker | null,
|
|
317
|
+
/** When true, every operation also depends on the previous one in this scope. */
|
|
318
|
+
chainOperations: boolean);
|
|
319
|
+
/**
|
|
320
|
+
* Direct causal parents for the next operation in `collection`.
|
|
321
|
+
*
|
|
322
|
+
* @param collection - Collection of the operation being built
|
|
323
|
+
* @param extra - Explicit parents (e.g. the delete that caused a cascade)
|
|
324
|
+
*/
|
|
325
|
+
depsFor(collection: string, extra?: readonly string[]): string[];
|
|
326
|
+
/** Record an operation built in this scope. */
|
|
327
|
+
record(collection: string, operationId: string): void;
|
|
328
|
+
/** Publish this scope's heads to the shared tracker. Call only after commit. */
|
|
329
|
+
publish(): void;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* The sequence-number block of ONE write transaction.
|
|
333
|
+
*
|
|
334
|
+
* The first number is reserved with one `UPSERT ... RETURNING` on
|
|
335
|
+
* `_kora_version_vector`, inside the transaction; later numbers continue the block
|
|
336
|
+
* in memory, and {@link SequenceBlock.close} persists the block's end with one
|
|
337
|
+
* write before the transaction commits. Write transactions are serialized and no
|
|
338
|
+
* code path allocates numbers outside one, so the block is contiguous and never
|
|
339
|
+
* shared; the UNIQUE `(node_id, sequence_number)` index on every op table is the
|
|
340
|
+
* backstop. A rollback discards the reservation with everything else.
|
|
341
|
+
*/
|
|
342
|
+
declare class SequenceBlock {
|
|
343
|
+
private readonly tx;
|
|
344
|
+
private readonly nodeId;
|
|
345
|
+
private last;
|
|
346
|
+
private persisted;
|
|
347
|
+
constructor(tx: Transaction, nodeId: string);
|
|
348
|
+
/** The next number of the block. */
|
|
349
|
+
take(): Promise<number>;
|
|
350
|
+
/** Persist the end of the block. Must run inside the transaction, before commit. */
|
|
351
|
+
close(): Promise<void>;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* The per-transaction state of a local write: the transaction handle, its
|
|
355
|
+
* sequence block and its causal scope. Every operation built in one transaction
|
|
356
|
+
* takes the next number of the same contiguous block.
|
|
357
|
+
*/
|
|
358
|
+
interface WriteScope {
|
|
359
|
+
readonly tx: Transaction;
|
|
360
|
+
readonly sequence: SequenceBlock;
|
|
361
|
+
readonly causal: CausalScope;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* A resolved relation reference: describes a relation that points TO a given collection.
|
|
366
|
+
* When a record in `targetCollection` is deleted, we must handle records in
|
|
367
|
+
* `sourceCollection` that reference it via `foreignKeyField`.
|
|
368
|
+
*/
|
|
369
|
+
interface IncomingRelation {
|
|
370
|
+
/** Name of the relation in the schema */
|
|
371
|
+
relationName: string;
|
|
372
|
+
/** Collection that holds the foreign key (the "from" side) */
|
|
373
|
+
sourceCollection: string;
|
|
374
|
+
/** Field in the source collection that references the target */
|
|
375
|
+
foreignKeyField: string;
|
|
376
|
+
/** What to do when the referenced record is deleted */
|
|
377
|
+
onDelete: OnDeleteAction;
|
|
378
|
+
/** The full relation definition */
|
|
379
|
+
relation: RelationDefinition;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Configuration for the RelationEnforcer.
|
|
384
|
+
*/
|
|
385
|
+
interface RelationEnforcerConfig {
|
|
386
|
+
schema: SchemaDefinition;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Result of enforcing referential integrity on a delete operation.
|
|
390
|
+
* Contains all additional operations that were created as side effects
|
|
391
|
+
* (cascaded deletes and set-null updates).
|
|
392
|
+
*/
|
|
393
|
+
interface EnforcementResult {
|
|
394
|
+
/** Additional operations created by cascading deletes and set-null updates */
|
|
395
|
+
operations: Operation[];
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Enforces referential integrity constraints during local delete operations.
|
|
399
|
+
*
|
|
400
|
+
* When a record is deleted, this enforcer checks all relations that reference
|
|
401
|
+
* the deleted record's collection and applies the appropriate onDelete policy:
|
|
402
|
+
*
|
|
403
|
+
* - **cascade**: Recursively deletes all referencing records
|
|
404
|
+
* - **set-null**: Sets the foreign key to null on all referencing records
|
|
405
|
+
* - **restrict**: Throws a ReferentialIntegrityError if any references exist
|
|
406
|
+
* - **no-action**: Does nothing (the foreign key is left dangling)
|
|
407
|
+
*
|
|
408
|
+
* The enforcer runs inside the delete's write transaction and writes every side
|
|
409
|
+
* effect through the single local write path (`writeDeleteInTx` /
|
|
410
|
+
* `writeUpdateInTx`), so cascaded operations reserve their sequence numbers in
|
|
411
|
+
* the same contiguous block as the parent and carry per-field version stamps
|
|
412
|
+
* (STORE-2, STORE-3). Every generated operation depends causally on the delete
|
|
413
|
+
* that caused it.
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```typescript
|
|
417
|
+
* const enforcer = new RelationEnforcer({ schema })
|
|
418
|
+
* // inside a write transaction, after building the parent delete operation:
|
|
419
|
+
* const result = await enforcer.enforceDelete('projects', 'proj-1', scope, env, deleteOp.id)
|
|
420
|
+
* // result.operations contains any cascaded delete/update ops
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
declare class RelationEnforcer {
|
|
424
|
+
private readonly lookup;
|
|
425
|
+
constructor(config: RelationEnforcerConfig);
|
|
426
|
+
/**
|
|
427
|
+
* Enforce referential integrity for a record being deleted.
|
|
428
|
+
*
|
|
429
|
+
* @param collection - The collection the deleted record belongs to
|
|
430
|
+
* @param recordId - The ID of the deleted record
|
|
431
|
+
* @param scope - The active write scope (transaction + causal scope)
|
|
432
|
+
* @param env - The write environment of the delete
|
|
433
|
+
* @param parentOpId - Id of the delete operation; every side effect depends on it
|
|
434
|
+
* @returns All additional operations created as side effects
|
|
435
|
+
* @throws {ReferentialIntegrityError} If a 'restrict' policy is violated
|
|
436
|
+
*/
|
|
437
|
+
enforceDelete(collection: string, recordId: string, scope: WriteScope, env: WriteEnv, parentOpId: string): Promise<EnforcementResult>;
|
|
438
|
+
/**
|
|
439
|
+
* Enforce a single relation's onDelete policy.
|
|
440
|
+
*/
|
|
441
|
+
private enforceRelation;
|
|
442
|
+
/**
|
|
443
|
+
* Cascade: delete every live record that references the deleted record. Each
|
|
444
|
+
* cascaded delete runs the full delete path, so it recurses into its own
|
|
445
|
+
* referencing records.
|
|
446
|
+
*/
|
|
447
|
+
private enforceCascade;
|
|
448
|
+
/**
|
|
449
|
+
* Set-null: set the foreign key to null on every live referencing record.
|
|
450
|
+
*/
|
|
451
|
+
private enforceSetNull;
|
|
452
|
+
/**
|
|
453
|
+
* Restrict: refuse the delete if any referencing records exist.
|
|
454
|
+
*/
|
|
455
|
+
private enforceRestrict;
|
|
456
|
+
/** Ids of live records referencing `deletedRecordId`, in id order (deterministic). */
|
|
457
|
+
private findReferencing;
|
|
458
|
+
/**
|
|
459
|
+
* Get the relation lookup map for external use (e.g., by the merge engine).
|
|
460
|
+
*/
|
|
461
|
+
getRelationLookup(): Map<string, IncomingRelation[]>;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Callback invoked after a mutation so the Store can notify subscriptions.
|
|
466
|
+
*/
|
|
467
|
+
type MutationCallback = (collection: string, operation: Operation) => void;
|
|
468
|
+
/**
|
|
469
|
+
* Collection provides CRUD operations on a single schema collection.
|
|
470
|
+
* Delegates to {@link LocalMutationHandler} when configured (unified apply pipeline),
|
|
471
|
+
* otherwise uses shared mutation executors directly.
|
|
472
|
+
*/
|
|
473
|
+
declare class Collection {
|
|
474
|
+
private readonly name;
|
|
475
|
+
private readonly definition;
|
|
476
|
+
private readonly schema;
|
|
477
|
+
private readonly adapter;
|
|
478
|
+
private clock;
|
|
479
|
+
private nodeId;
|
|
480
|
+
private readonly onMutation;
|
|
481
|
+
private relationEnforcer;
|
|
482
|
+
private mutationHandler;
|
|
483
|
+
private causalTracker;
|
|
484
|
+
private readonly secretKeyProvider?;
|
|
485
|
+
private readonly onStorageError?;
|
|
486
|
+
private readonly fold?;
|
|
487
|
+
private readonly maxOperationBytes?;
|
|
488
|
+
private readonly assertLocalWriteAllowed?;
|
|
489
|
+
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, assertLocalWriteAllowed?: (() => void) | undefined);
|
|
490
|
+
/**
|
|
491
|
+
* Point this collection at the store's new device identity after a node-id
|
|
492
|
+
* rotation (RT-21). Internal: called by the owning Store only.
|
|
493
|
+
*
|
|
494
|
+
* @internal
|
|
495
|
+
*/
|
|
496
|
+
rebindNode(clock: HybridLogicalClock, nodeId: string, relationEnforcer: RelationEnforcer | null, causalTracker: CausalTracker | null): void;
|
|
497
|
+
private mutationContext;
|
|
498
|
+
insert(data: Record<string, unknown>): Promise<CollectionRecord>;
|
|
499
|
+
findById(id: string): Promise<CollectionRecord | null>;
|
|
500
|
+
update(id: string, data: Record<string, unknown>): Promise<CollectionRecord>;
|
|
501
|
+
delete(id: string): Promise<void>;
|
|
502
|
+
getName(): string;
|
|
503
|
+
getDefinition(): CollectionDefinition;
|
|
504
|
+
/** Replace the mutation handler (e.g. after ApplyPipeline is wired in createApp). */
|
|
505
|
+
setMutationHandler(handler: LocalMutationHandler | null): void;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Shared context for executing local collection mutations.
|
|
510
|
+
*
|
|
511
|
+
* Extends the {@link WriteEnv} of the single local write path with the target
|
|
512
|
+
* collection and the post-commit plumbing. There is deliberately no way to
|
|
513
|
+
* allocate a sequence number outside a write transaction: every local operation
|
|
514
|
+
* reserves its number inside the transaction that persists it (W6).
|
|
515
|
+
*/
|
|
516
|
+
interface LocalMutationContext extends WriteEnv {
|
|
517
|
+
readonly collection: string;
|
|
518
|
+
readonly definition: CollectionDefinition;
|
|
519
|
+
readonly adapter: StorageAdapter;
|
|
520
|
+
/** Called once per committed operation, after the storage transaction commits. */
|
|
521
|
+
readonly onMutation: MutationCallback;
|
|
522
|
+
readonly causalTracker: CausalTracker | null;
|
|
523
|
+
/** Additional parent op ids (e.g. referential cascade from a delete). */
|
|
524
|
+
readonly extraCausalDeps?: string[];
|
|
525
|
+
/**
|
|
526
|
+
* Called when a write transaction fails, before the error propagates (the
|
|
527
|
+
* store maps out-of-space errors to `store:quota-exceeded`).
|
|
528
|
+
*/
|
|
529
|
+
readonly onStorageError?: (error: unknown) => void;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Result of building a SQL query: the parameterized SQL string and its bound values.
|
|
534
|
+
*/
|
|
535
|
+
interface SqlQuery {
|
|
536
|
+
sql: string;
|
|
537
|
+
params: unknown[];
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Build a SELECT query from a QueryDescriptor.
|
|
541
|
+
* Automatically adds `WHERE _deleted = 0` to exclude soft-deleted records.
|
|
542
|
+
*
|
|
543
|
+
* @param descriptor - The query descriptor
|
|
544
|
+
* @param fields - The field descriptors from the collection schema
|
|
545
|
+
* @returns A parameterized SQL query
|
|
546
|
+
*/
|
|
547
|
+
declare function buildSelectQuery(descriptor: QueryDescriptor, fields: Record<string, FieldDescriptor>): SqlQuery;
|
|
548
|
+
/**
|
|
549
|
+
* Build a COUNT query from a QueryDescriptor.
|
|
550
|
+
* Automatically adds `WHERE _deleted = 0`.
|
|
551
|
+
*
|
|
552
|
+
* @param descriptor - The query descriptor
|
|
553
|
+
* @param fields - The field descriptors from the collection schema
|
|
554
|
+
* @returns A parameterized SQL query that returns { count: number }
|
|
555
|
+
*/
|
|
556
|
+
declare function buildCountQuery(descriptor: QueryDescriptor, fields: Record<string, FieldDescriptor>): SqlQuery;
|
|
557
|
+
/**
|
|
558
|
+
* Build an INSERT query for a collection record.
|
|
559
|
+
*
|
|
560
|
+
* @param collection - The collection name
|
|
561
|
+
* @param record - The record data (already serialized with id, _created_at, _updated_at)
|
|
562
|
+
* @returns A parameterized SQL query
|
|
563
|
+
*/
|
|
564
|
+
declare function buildInsertQuery(collection: string, record: Record<string, unknown>): SqlQuery;
|
|
565
|
+
/**
|
|
566
|
+
* Build an UPDATE query for a collection record.
|
|
567
|
+
*
|
|
568
|
+
* @param collection - The collection name
|
|
569
|
+
* @param id - The record ID
|
|
570
|
+
* @param changes - The fields to update (already serialized)
|
|
571
|
+
* @returns A parameterized SQL query
|
|
572
|
+
*/
|
|
573
|
+
declare function buildUpdateQuery(collection: string, id: string, changes: Record<string, unknown>): SqlQuery;
|
|
574
|
+
/**
|
|
575
|
+
* Build a soft-delete query (SET _deleted = 1).
|
|
576
|
+
*
|
|
577
|
+
* @param collection - The collection name
|
|
578
|
+
* @param id - The record ID
|
|
579
|
+
* @param updatedAt - The timestamp to set on _updated_at
|
|
580
|
+
* @returns A parameterized SQL query
|
|
581
|
+
*/
|
|
582
|
+
declare function buildSoftDeleteQuery(collection: string, id: string, updatedAt: number, version?: string): SqlQuery;
|
|
583
|
+
/**
|
|
584
|
+
* Build an UPDATE that applies only when the row is missing or older than `remoteVersion`
|
|
585
|
+
* (serialized HLC). Prevents stale remote sync from overwriting newer local materialized state.
|
|
586
|
+
*/
|
|
587
|
+
declare function buildLwwUpdateQuery(collection: string, id: string, changes: Record<string, unknown>, remoteVersion: string): SqlQuery;
|
|
588
|
+
/**
|
|
589
|
+
* Build a soft-delete that applies only when the row is older than `remoteVersion`.
|
|
590
|
+
*/
|
|
591
|
+
declare function buildLwwSoftDeleteQuery(collection: string, id: string, updatedAt: number, version: string): SqlQuery;
|
|
592
|
+
/**
|
|
593
|
+
* Build a WHERE clause from conditions, validating field names against the schema.
|
|
594
|
+
*
|
|
595
|
+
* @param where - The where conditions
|
|
596
|
+
* @param fields - The field descriptors from the collection schema
|
|
597
|
+
* @returns The SQL WHERE clause string and params, or null if no conditions
|
|
598
|
+
*/
|
|
599
|
+
declare function buildWhereClause(where: Record<string, unknown>, fields: Record<string, FieldDescriptor>): SqlQuery | null;
|
|
600
|
+
/**
|
|
601
|
+
* Record metadata exposed on every record under a camelCase name and backed by a
|
|
602
|
+
* system column (STORE-11): `where({ updatedAt: { $gt: t } })` and
|
|
603
|
+
* `orderBy('createdAt', 'desc')` filter and sort on these columns. A schema field
|
|
604
|
+
* declared with the same name wins and its own column is used instead.
|
|
605
|
+
*/
|
|
606
|
+
declare const VIRTUAL_TIMESTAMP_FIELDS: Readonly<{
|
|
607
|
+
readonly createdAt: "_created_at";
|
|
608
|
+
readonly updatedAt: "_updated_at";
|
|
609
|
+
}>;
|
|
610
|
+
/** Name of a virtual record-metadata field usable in `where` and `orderBy`. */
|
|
611
|
+
type VirtualTimestampField = keyof typeof VIRTUAL_TIMESTAMP_FIELDS;
|
|
612
|
+
/** Every virtual record-metadata field name, for typing `where` / `orderBy` keys. */
|
|
613
|
+
declare const VIRTUAL_TIMESTAMP_FIELD_NAMES: readonly VirtualTimestampField[];
|
|
614
|
+
|
|
615
|
+
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 };
|