@korajs/store 0.4.0 → 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.
- package/dist/adapters/better-sqlite3.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/indexeddb.cjs +410 -17
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +23 -5
- package/dist/adapters/indexeddb.d.ts +23 -5
- package/dist/adapters/indexeddb.js +163 -18
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-shared-host.cjs +68 -0
- package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -0
- package/dist/adapters/sqlite-wasm-shared-host.d.cts +2 -0
- package/dist/adapters/sqlite-wasm-shared-host.d.ts +2 -0
- package/dist/adapters/sqlite-wasm-shared-host.js +66 -0
- package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -0
- package/dist/adapters/sqlite-wasm-worker.cjs +34 -5
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +34 -5
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +249 -2
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +15 -3
- package/dist/adapters/sqlite-wasm.d.ts +15 -3
- package/dist/adapters/sqlite-wasm.js +2 -2
- package/dist/audit-trace-store-AM4EWQNZ.js +15 -0
- package/dist/backup-JN3LB4YT.js +14 -0
- package/dist/backup-JN3LB4YT.js.map +1 -0
- package/dist/chunk-56LL4XP6.js +205 -0
- package/dist/chunk-56LL4XP6.js.map +1 -0
- package/dist/chunk-6AHYGQQT.js +425 -0
- package/dist/chunk-6AHYGQQT.js.map +1 -0
- package/dist/chunk-7BPETY5Y.js +570 -0
- package/dist/chunk-7BPETY5Y.js.map +1 -0
- package/dist/chunk-AGN3CIPB.js +188 -0
- package/dist/chunk-AGN3CIPB.js.map +1 -0
- package/dist/chunk-EZSTD2XC.js +380 -0
- package/dist/chunk-EZSTD2XC.js.map +1 -0
- package/dist/chunk-MYSJN3DF.js +117 -0
- package/dist/chunk-MYSJN3DF.js.map +1 -0
- package/dist/{chunk-DXKLAQ6P.js → chunk-XO4LMPAH.js} +1 -1
- package/dist/chunk-XO4LMPAH.js.map +1 -0
- package/dist/export-audit-5GV6VRDZ.js +15 -0
- package/dist/export-audit-5GV6VRDZ.js.map +1 -0
- package/dist/index.cjs +2662 -577
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +565 -187
- package/dist/index.d.ts +565 -187
- package/dist/index.js +1381 -739
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +789 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +154 -0
- package/dist/internal.d.ts +154 -0
- package/dist/internal.js +48 -0
- package/dist/internal.js.map +1 -0
- package/dist/{sqlite-wasm-channel-46AOWNPM.js → sqlite-wasm-channel-HDDW6UBH.js} +2 -2
- package/dist/sqlite-wasm-channel-HDDW6UBH.js.map +1 -0
- package/dist/{sqlite-wasm-channel-Lakjuk2E.d.cts → sqlite-wasm-channel-I9y_jObg.d.cts} +1 -0
- package/dist/{sqlite-wasm-channel-Lakjuk2E.d.ts → sqlite-wasm-channel-I9y_jObg.d.ts} +1 -0
- package/dist/types-BMyHmwvn.d.ts +163 -0
- package/dist/types-CTr00g_d.d.cts +163 -0
- package/dist/types-DepnRDk_.d.cts +212 -0
- package/dist/types-DepnRDk_.d.ts +212 -0
- package/package.json +23 -2
- package/dist/chunk-DXKLAQ6P.js.map +0 -1
- package/dist/chunk-ZP5AXQ3Z.js +0 -179
- package/dist/chunk-ZP5AXQ3Z.js.map +0 -1
- package/dist/types-DF-KDSK1.d.cts +0 -106
- package/dist/types-DF-KDSK1.d.ts +0 -106
- /package/dist/{sqlite-wasm-channel-46AOWNPM.js.map → audit-trace-store-AM4EWQNZ.js.map} +0 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deserializeOperationWithCollection
|
|
3
|
+
} from "./chunk-56LL4XP6.js";
|
|
4
|
+
|
|
5
|
+
// src/backup/backup.ts
|
|
6
|
+
import "@korajs/core";
|
|
7
|
+
function encodeSection(name, content) {
|
|
8
|
+
const nameBytes = new TextEncoder().encode(name);
|
|
9
|
+
const header = new Uint8Array(8);
|
|
10
|
+
const dv = new DataView(header.buffer);
|
|
11
|
+
dv.setUint32(0, nameBytes.length, true);
|
|
12
|
+
dv.setUint32(4, content.length, true);
|
|
13
|
+
const result = new Uint8Array(header.length + nameBytes.length + content.length);
|
|
14
|
+
result.set(header, 0);
|
|
15
|
+
result.set(nameBytes, 8);
|
|
16
|
+
result.set(content, 8 + nameBytes.length);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
function encodeJsonSection(name, data) {
|
|
20
|
+
const json = JSON.stringify(data);
|
|
21
|
+
return encodeSection(name, new TextEncoder().encode(json));
|
|
22
|
+
}
|
|
23
|
+
function readUint32(view, offset) {
|
|
24
|
+
return view.getUint32(offset, true);
|
|
25
|
+
}
|
|
26
|
+
function parseSections(data) {
|
|
27
|
+
const sections = [];
|
|
28
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
29
|
+
let offset = 0;
|
|
30
|
+
while (offset + 8 <= data.byteLength) {
|
|
31
|
+
const nameLen = readUint32(dv, offset);
|
|
32
|
+
const contentLen = readUint32(dv, offset + 4);
|
|
33
|
+
offset += 8;
|
|
34
|
+
if (offset + nameLen + contentLen > data.byteLength) break;
|
|
35
|
+
const name = new TextDecoder().decode(data.slice(offset, offset + nameLen));
|
|
36
|
+
offset += nameLen;
|
|
37
|
+
const content = data.slice(offset, offset + contentLen);
|
|
38
|
+
offset += contentLen;
|
|
39
|
+
sections.push({ name, content });
|
|
40
|
+
}
|
|
41
|
+
return sections;
|
|
42
|
+
}
|
|
43
|
+
function findSection(sections, name) {
|
|
44
|
+
for (const s of sections) {
|
|
45
|
+
if (s.name === name) return s.content;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function parseJsonSection(sections, name) {
|
|
50
|
+
const content = findSection(sections, name);
|
|
51
|
+
if (!content) return null;
|
|
52
|
+
return JSON.parse(new TextDecoder().decode(content));
|
|
53
|
+
}
|
|
54
|
+
var BACKUP_VERSION = 1;
|
|
55
|
+
async function exportBackup(adapter, schema, nodeId, schemaVersion, options) {
|
|
56
|
+
const onProgress = options?.onProgress ?? (() => {
|
|
57
|
+
});
|
|
58
|
+
const includeRecords = options?.includeRecords ?? true;
|
|
59
|
+
const collectionFilter = options?.collections;
|
|
60
|
+
const collections = collectionFilter ? Object.keys(schema.collections).filter((c) => collectionFilter.includes(c)) : Object.keys(schema.collections);
|
|
61
|
+
onProgress({ phase: "reading", progress: 0, message: "Reading version vector" });
|
|
62
|
+
const versionVector = await readVersionVector(adapter);
|
|
63
|
+
onProgress({ phase: "reading", progress: 0.1, message: "Reading metadata" });
|
|
64
|
+
const meta = await readMeta(adapter);
|
|
65
|
+
onProgress({ phase: "reading", progress: 0.2, message: "Reading operations" });
|
|
66
|
+
const operations = await readAllOperations(adapter, schema);
|
|
67
|
+
onProgress({ phase: "reading", progress: 0.5, message: "Reading records" });
|
|
68
|
+
const sections = [];
|
|
69
|
+
let allContentForChecksum = new Uint8Array(0);
|
|
70
|
+
const addSection = (name, data) => {
|
|
71
|
+
sections.push(data);
|
|
72
|
+
const newLen = allContentForChecksum.length + data.length;
|
|
73
|
+
const combined = new Uint8Array(newLen);
|
|
74
|
+
combined.set(allContentForChecksum, 0);
|
|
75
|
+
combined.set(data, allContentForChecksum.length);
|
|
76
|
+
allContentForChecksum = combined;
|
|
77
|
+
};
|
|
78
|
+
const vvObj = {};
|
|
79
|
+
for (const [nid, seq] of versionVector) {
|
|
80
|
+
vvObj[nid] = seq;
|
|
81
|
+
}
|
|
82
|
+
addSection("version_vector", encodeJsonSection("version_vector", vvObj));
|
|
83
|
+
const metaObj = {};
|
|
84
|
+
for (const row of meta) {
|
|
85
|
+
metaObj[row.key] = row.value;
|
|
86
|
+
}
|
|
87
|
+
addSection("meta", encodeJsonSection("meta", metaObj));
|
|
88
|
+
const opLines = `${operations.map((op) => JSON.stringify(op)).join("\n")}
|
|
89
|
+
`;
|
|
90
|
+
addSection("operations", encodeSection("operations", new TextEncoder().encode(opLines)));
|
|
91
|
+
let totalRecords = 0;
|
|
92
|
+
if (includeRecords) {
|
|
93
|
+
for (const col of collections) {
|
|
94
|
+
const records = await readCollectionRecords(adapter, col);
|
|
95
|
+
if (records.length > 0) {
|
|
96
|
+
const lines = `${records.map((r) => JSON.stringify(r)).join("\n")}
|
|
97
|
+
`;
|
|
98
|
+
const sectionName = `records:${col}`;
|
|
99
|
+
addSection(sectionName, encodeSection(sectionName, new TextEncoder().encode(lines)));
|
|
100
|
+
totalRecords += records.length;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
onProgress({ phase: "reading", progress: 0.9, message: "Computing checksum" });
|
|
105
|
+
const checksumHex = await computeSha256(allContentForChecksum);
|
|
106
|
+
onProgress({ phase: "writing", progress: 1, message: "Finalizing" });
|
|
107
|
+
const manifest = {
|
|
108
|
+
version: BACKUP_VERSION,
|
|
109
|
+
createdAt: Date.now(),
|
|
110
|
+
nodeId,
|
|
111
|
+
schemaVersion,
|
|
112
|
+
operationCount: operations.length,
|
|
113
|
+
collections,
|
|
114
|
+
includesRecords: includeRecords,
|
|
115
|
+
checksum: checksumHex
|
|
116
|
+
};
|
|
117
|
+
const header = new Uint8Array(0);
|
|
118
|
+
const manifestSection = encodeJsonSection("manifest", manifest);
|
|
119
|
+
const checksumSection = encodeSection("checksum", new TextEncoder().encode(checksumHex));
|
|
120
|
+
const totalLen = manifestSection.length + allContentForChecksum.length + checksumSection.length;
|
|
121
|
+
const result = new Uint8Array(totalLen);
|
|
122
|
+
let pos = 0;
|
|
123
|
+
result.set(manifestSection, pos);
|
|
124
|
+
pos += manifestSection.length;
|
|
125
|
+
result.set(allContentForChecksum, pos);
|
|
126
|
+
pos += allContentForChecksum.length;
|
|
127
|
+
result.set(checksumSection, pos);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function readBackupManifest(data) {
|
|
131
|
+
const sections = parseSections(data);
|
|
132
|
+
const manifest = parseJsonSection(sections, "manifest");
|
|
133
|
+
if (!manifest) {
|
|
134
|
+
throw new Error("Invalid backup: manifest section not found");
|
|
135
|
+
}
|
|
136
|
+
return manifest;
|
|
137
|
+
}
|
|
138
|
+
async function verifyBackupChecksum(data) {
|
|
139
|
+
const sections = parseSections(data);
|
|
140
|
+
const manifest = parseJsonSection(sections, "manifest");
|
|
141
|
+
if (!manifest) return false;
|
|
142
|
+
const storedChecksum = manifest.checksum;
|
|
143
|
+
if (!storedChecksum) return false;
|
|
144
|
+
const manifestSection = findSection(sections, "manifest");
|
|
145
|
+
if (!manifestSection) return false;
|
|
146
|
+
const checksumSection = findSection(sections, "checksum");
|
|
147
|
+
const checksumBytes = checksumSection ? new TextEncoder().encode(manifest.checksum) : new Uint8Array(0);
|
|
148
|
+
const contentSections = sections.filter((s) => s.name !== "manifest" && s.name !== "checksum");
|
|
149
|
+
let contentForHash = new Uint8Array(0);
|
|
150
|
+
for (const section of contentSections) {
|
|
151
|
+
const sectionBytes = encodeSection(section.name, section.content);
|
|
152
|
+
const newLen = contentForHash.length + sectionBytes.length;
|
|
153
|
+
const combined = new Uint8Array(newLen);
|
|
154
|
+
combined.set(contentForHash, 0);
|
|
155
|
+
combined.set(sectionBytes, contentForHash.length);
|
|
156
|
+
contentForHash = combined;
|
|
157
|
+
}
|
|
158
|
+
const computed = await computeSha256(contentForHash);
|
|
159
|
+
return computed === storedChecksum;
|
|
160
|
+
}
|
|
161
|
+
async function restoreBackup(adapter, schema, data, options) {
|
|
162
|
+
const startTime = Date.now();
|
|
163
|
+
const onProgress = options?.onProgress ?? (() => {
|
|
164
|
+
});
|
|
165
|
+
const collectionFilter = options?.collections;
|
|
166
|
+
onProgress({ phase: "verifying", progress: 0, message: "Parsing backup" });
|
|
167
|
+
const sections = parseSections(data);
|
|
168
|
+
const manifest = parseJsonSection(sections, "manifest");
|
|
169
|
+
if (!manifest) {
|
|
170
|
+
return {
|
|
171
|
+
operationsRestored: 0,
|
|
172
|
+
recordsRestored: 0,
|
|
173
|
+
success: false,
|
|
174
|
+
error: "Invalid backup: manifest section not found",
|
|
175
|
+
duration: Date.now() - startTime
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (manifest.version !== BACKUP_VERSION) {
|
|
179
|
+
return {
|
|
180
|
+
operationsRestored: 0,
|
|
181
|
+
recordsRestored: 0,
|
|
182
|
+
success: false,
|
|
183
|
+
error: `Unsupported backup version: ${manifest.version}`,
|
|
184
|
+
duration: Date.now() - startTime
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
onProgress({ phase: "verifying", progress: 0.2, message: "Verifying checksum" });
|
|
188
|
+
const valid = await verifyBackupChecksum(data);
|
|
189
|
+
if (!valid) {
|
|
190
|
+
return {
|
|
191
|
+
operationsRestored: 0,
|
|
192
|
+
recordsRestored: 0,
|
|
193
|
+
success: false,
|
|
194
|
+
error: "Backup checksum mismatch: data may be corrupted",
|
|
195
|
+
duration: Date.now() - startTime
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
onProgress({ phase: "restoring", progress: 0.3, message: "Loading operations" });
|
|
199
|
+
const opsContent = findSection(sections, "operations");
|
|
200
|
+
let operations = [];
|
|
201
|
+
if (opsContent) {
|
|
202
|
+
const text = new TextDecoder().decode(opsContent);
|
|
203
|
+
const lines = text.trim().split("\n").filter((l) => l.length > 0);
|
|
204
|
+
operations = lines.map((line) => JSON.parse(line));
|
|
205
|
+
}
|
|
206
|
+
if (collectionFilter) {
|
|
207
|
+
operations = operations.filter((op) => collectionFilter.includes(op.collection));
|
|
208
|
+
}
|
|
209
|
+
const vvData = parseJsonSection(sections, "version_vector");
|
|
210
|
+
const metaData = parseJsonSection(sections, "meta");
|
|
211
|
+
onProgress({ phase: "restoring", progress: 0.4, message: "Applying operations" });
|
|
212
|
+
if (options?.merge) {
|
|
213
|
+
await adapter.transaction(async (tx) => {
|
|
214
|
+
if (vvData) {
|
|
215
|
+
for (const [nid, seq] of Object.entries(vvData)) {
|
|
216
|
+
await tx.execute(
|
|
217
|
+
"INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
|
|
218
|
+
[nid, seq]
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (metaData) {
|
|
223
|
+
for (const [key, value] of Object.entries(metaData)) {
|
|
224
|
+
await tx.execute("INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)", [
|
|
225
|
+
key,
|
|
226
|
+
value
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const op of operations) {
|
|
231
|
+
const collection = op.collection;
|
|
232
|
+
const opRow = {
|
|
233
|
+
id: op.id,
|
|
234
|
+
node_id: op.nodeId,
|
|
235
|
+
type: op.type,
|
|
236
|
+
record_id: op.recordId,
|
|
237
|
+
data: op.data !== null ? JSON.stringify(op.data) : null,
|
|
238
|
+
previous_data: op.previousData !== null ? JSON.stringify(op.previousData) : null,
|
|
239
|
+
timestamp: JSON.stringify(op.timestamp),
|
|
240
|
+
sequence_number: op.sequenceNumber,
|
|
241
|
+
causal_deps: JSON.stringify(op.causalDeps),
|
|
242
|
+
schema_version: op.schemaVersion
|
|
243
|
+
};
|
|
244
|
+
await tx.execute(
|
|
245
|
+
`INSERT OR IGNORE INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
246
|
+
[
|
|
247
|
+
opRow.id,
|
|
248
|
+
opRow.node_id,
|
|
249
|
+
opRow.type,
|
|
250
|
+
opRow.record_id,
|
|
251
|
+
opRow.data,
|
|
252
|
+
opRow.previous_data,
|
|
253
|
+
opRow.timestamp,
|
|
254
|
+
opRow.sequence_number,
|
|
255
|
+
opRow.causal_deps,
|
|
256
|
+
opRow.schema_version
|
|
257
|
+
]
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
} else {
|
|
262
|
+
await adapter.transaction(async (tx) => {
|
|
263
|
+
for (const collection of Object.keys(schema.collections)) {
|
|
264
|
+
await tx.execute(`DELETE FROM _kora_ops_${collection}`);
|
|
265
|
+
await tx.execute(`DELETE FROM ${collection}`);
|
|
266
|
+
}
|
|
267
|
+
await tx.execute("DELETE FROM _kora_version_vector");
|
|
268
|
+
await tx.execute("DELETE FROM _kora_meta");
|
|
269
|
+
if (vvData) {
|
|
270
|
+
for (const [nid, seq] of Object.entries(vvData)) {
|
|
271
|
+
await tx.execute(
|
|
272
|
+
"INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)",
|
|
273
|
+
[nid, seq]
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (metaData) {
|
|
278
|
+
for (const [key, value] of Object.entries(metaData)) {
|
|
279
|
+
await tx.execute("INSERT INTO _kora_meta (key, value) VALUES (?, ?)", [key, value]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
for (const op of operations) {
|
|
283
|
+
const collection = op.collection;
|
|
284
|
+
await tx.execute(
|
|
285
|
+
`INSERT INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
286
|
+
[
|
|
287
|
+
op.id,
|
|
288
|
+
op.nodeId,
|
|
289
|
+
op.type,
|
|
290
|
+
op.recordId,
|
|
291
|
+
op.data !== null ? JSON.stringify(op.data) : null,
|
|
292
|
+
op.previousData !== null ? JSON.stringify(op.previousData) : null,
|
|
293
|
+
JSON.stringify(op.timestamp),
|
|
294
|
+
op.sequenceNumber,
|
|
295
|
+
JSON.stringify(op.causalDeps),
|
|
296
|
+
op.schemaVersion
|
|
297
|
+
]
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
onProgress({ phase: "restoring", progress: 0.8, message: "Restoring records" });
|
|
303
|
+
let recordsRestored = 0;
|
|
304
|
+
if (manifest.includesRecords) {
|
|
305
|
+
for (const col of manifest.collections) {
|
|
306
|
+
if (collectionFilter && !collectionFilter.includes(col)) continue;
|
|
307
|
+
const sectionContent = findSection(sections, `records:${col}`);
|
|
308
|
+
if (!sectionContent) continue;
|
|
309
|
+
const text = new TextDecoder().decode(sectionContent);
|
|
310
|
+
const lines = text.trim().split("\n").filter((l) => l.length > 0);
|
|
311
|
+
const records = lines.map((line) => JSON.parse(line));
|
|
312
|
+
if (records.length === 0) continue;
|
|
313
|
+
await adapter.transaction(async (tx) => {
|
|
314
|
+
for (const record of records) {
|
|
315
|
+
const keys = Object.keys(record);
|
|
316
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
317
|
+
const values = keys.map((k) => {
|
|
318
|
+
const v = record[k];
|
|
319
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
320
|
+
if (Array.isArray(v) || typeof v === "object" && v !== null) return JSON.stringify(v);
|
|
321
|
+
return v;
|
|
322
|
+
});
|
|
323
|
+
await tx.execute(
|
|
324
|
+
`INSERT OR REPLACE INTO ${col} (${keys.join(", ")}) VALUES (${placeholders})`,
|
|
325
|
+
values
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
recordsRestored += records.length;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
onProgress({ phase: "restoring", progress: 1, message: "Done" });
|
|
333
|
+
return {
|
|
334
|
+
operationsRestored: operations.length,
|
|
335
|
+
recordsRestored,
|
|
336
|
+
success: true,
|
|
337
|
+
duration: Date.now() - startTime
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function readVersionVector(adapter) {
|
|
341
|
+
const rows = await adapter.query(
|
|
342
|
+
"SELECT node_id, sequence_number FROM _kora_version_vector"
|
|
343
|
+
);
|
|
344
|
+
const vv = /* @__PURE__ */ new Map();
|
|
345
|
+
for (const row of rows) {
|
|
346
|
+
vv.set(row.node_id, row.sequence_number);
|
|
347
|
+
}
|
|
348
|
+
return vv;
|
|
349
|
+
}
|
|
350
|
+
async function readMeta(adapter) {
|
|
351
|
+
return adapter.query("SELECT key, value FROM _kora_meta");
|
|
352
|
+
}
|
|
353
|
+
async function readAllOperations(adapter, schema) {
|
|
354
|
+
const allOps = [];
|
|
355
|
+
for (const collectionName of Object.keys(schema.collections)) {
|
|
356
|
+
const rows = await adapter.query(
|
|
357
|
+
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
358
|
+
);
|
|
359
|
+
for (const row of rows) {
|
|
360
|
+
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return allOps;
|
|
364
|
+
}
|
|
365
|
+
async function readCollectionRecords(adapter, collection) {
|
|
366
|
+
return adapter.query(`SELECT * FROM ${collection} WHERE _deleted = 0`);
|
|
367
|
+
}
|
|
368
|
+
async function computeSha256(data) {
|
|
369
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
370
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
371
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export {
|
|
375
|
+
exportBackup,
|
|
376
|
+
readBackupManifest,
|
|
377
|
+
verifyBackupChecksum,
|
|
378
|
+
restoreBackup
|
|
379
|
+
};
|
|
380
|
+
//# sourceMappingURL=chunk-EZSTD2XC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/backup/backup.ts"],"sourcesContent":["import { generateUUIDv7 } from '@korajs/core'\nimport type { Operation, SchemaDefinition, VersionVector } from '@korajs/core'\nimport { deserializeOperationWithCollection } from '../serialization/serializer'\nimport type { OperationRow, StorageAdapter } from '../types'\nimport type {\n\tBackupManifest,\n\tBackupOptions,\n\tBackupProgress,\n\tRestoreOptions,\n\tRestoreResult,\n} from './types'\n\n// ── Section I/O helpers ──────────────────────────────────────────────────────\n\nfunction encodeSection(name: string, content: Uint8Array): Uint8Array {\n\tconst nameBytes = new TextEncoder().encode(name)\n\tconst header = new Uint8Array(8)\n\tconst dv = new DataView(header.buffer)\n\tdv.setUint32(0, nameBytes.length, true)\n\tdv.setUint32(4, content.length, true)\n\n\tconst result = new Uint8Array(header.length + nameBytes.length + content.length)\n\tresult.set(header, 0)\n\tresult.set(nameBytes, 8)\n\tresult.set(content, 8 + nameBytes.length)\n\treturn result\n}\n\nfunction encodeJsonSection(name: string, data: unknown): Uint8Array {\n\tconst json = JSON.stringify(data)\n\treturn encodeSection(name, new TextEncoder().encode(json))\n}\n\nfunction encodeNdjsonSection(name: string, records: unknown[]): Uint8Array {\n\tconst lines = `${records.map((r) => JSON.stringify(r)).join('\\n')}\\n`\n\treturn encodeSection(name, new TextEncoder().encode(lines))\n}\n\nfunction readUint32(view: DataView, offset: number): number {\n\treturn view.getUint32(offset, true)\n}\n\ninterface Section {\n\tname: string\n\tcontent: Uint8Array\n}\n\nfunction parseSections(data: Uint8Array): Section[] {\n\tconst sections: Section[] = []\n\tconst dv = new DataView(data.buffer, data.byteOffset, data.byteLength)\n\tlet offset = 0\n\n\twhile (offset + 8 <= data.byteLength) {\n\t\tconst nameLen = readUint32(dv, offset)\n\t\tconst contentLen = readUint32(dv, offset + 4)\n\t\toffset += 8\n\n\t\tif (offset + nameLen + contentLen > data.byteLength) break\n\n\t\tconst name = new TextDecoder().decode(data.slice(offset, offset + nameLen))\n\t\toffset += nameLen\n\n\t\tconst content = data.slice(offset, offset + contentLen)\n\t\toffset += contentLen\n\n\t\tsections.push({ name, content })\n\t}\n\n\treturn sections\n}\n\nfunction findSection(sections: Section[], name: string): Uint8Array | null {\n\tfor (const s of sections) {\n\t\tif (s.name === name) return s.content\n\t}\n\treturn null\n}\n\nfunction parseJsonSection<T>(sections: Section[], name: string): T | null {\n\tconst content = findSection(sections, name)\n\tif (!content) return null\n\treturn JSON.parse(new TextDecoder().decode(content)) as T\n}\n\n/**\n * Export format version. Bump on breaking changes.\n */\nconst BACKUP_VERSION = 1\n\n/**\n * Export a backup from a store.\n *\n * Reads all data via the adapter and produces a portable binary backup.\n *\n * @param adapter - The storage adapter\n * @param schema - The schema definition\n * @param nodeId - Current node ID\n * @param schemaVersion - Schema version\n * @param options - Backup options\n * @returns Backup as a Uint8Array\n */\nexport async function exportBackup(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n\tnodeId: string,\n\tschemaVersion: number,\n\toptions?: BackupOptions,\n): Promise<Uint8Array> {\n\tconst onProgress = options?.onProgress ?? (() => {})\n\tconst includeRecords = options?.includeRecords ?? true\n\tconst collectionFilter = options?.collections\n\tconst collections = collectionFilter\n\t\t? Object.keys(schema.collections).filter((c) => collectionFilter.includes(c))\n\t\t: Object.keys(schema.collections)\n\n\tonProgress({ phase: 'reading', progress: 0, message: 'Reading version vector' })\n\tconst versionVector = await readVersionVector(adapter)\n\tonProgress({ phase: 'reading', progress: 0.1, message: 'Reading metadata' })\n\tconst meta = await readMeta(adapter)\n\tonProgress({ phase: 'reading', progress: 0.2, message: 'Reading operations' })\n\tconst operations = await readAllOperations(adapter, schema)\n\tonProgress({ phase: 'reading', progress: 0.5, message: 'Reading records' })\n\n\tconst sections: Uint8Array[] = []\n\tlet allContentForChecksum = new Uint8Array(0)\n\n\t// Helper: add a section and update running checksum content\n\tconst addSection = (name: string, data: Uint8Array) => {\n\t\tsections.push(data)\n\t\tconst newLen = allContentForChecksum.length + data.length\n\t\tconst combined = new Uint8Array(newLen)\n\t\tcombined.set(allContentForChecksum, 0)\n\t\tcombined.set(data, allContentForChecksum.length)\n\t\tallContentForChecksum = combined\n\t}\n\n\t// Version vector section\n\tconst vvObj: Record<string, number> = {}\n\tfor (const [nid, seq] of versionVector) {\n\t\tvvObj[nid] = seq\n\t}\n\taddSection('version_vector', encodeJsonSection('version_vector', vvObj))\n\n\t// Meta section\n\tconst metaObj: Record<string, string> = {}\n\tfor (const row of meta) {\n\t\tmetaObj[row.key] = row.value\n\t}\n\taddSection('meta', encodeJsonSection('meta', metaObj))\n\n\t// Operations section\n\tconst opLines = `${operations.map((op) => JSON.stringify(op)).join('\\n')}\\n`\n\taddSection('operations', encodeSection('operations', new TextEncoder().encode(opLines)))\n\n\t// Records sections (optional)\n\tlet totalRecords = 0\n\tif (includeRecords) {\n\t\tfor (const col of collections) {\n\t\t\tconst records = await readCollectionRecords(adapter, col)\n\t\t\tif (records.length > 0) {\n\t\t\t\tconst lines = `${records.map((r) => JSON.stringify(r)).join('\\n')}\\n`\n\t\t\t\tconst sectionName = `records:${col}`\n\t\t\t\taddSection(sectionName, encodeSection(sectionName, new TextEncoder().encode(lines)))\n\t\t\t\ttotalRecords += records.length\n\t\t\t}\n\t\t}\n\t}\n\tonProgress({ phase: 'reading', progress: 0.9, message: 'Computing checksum' })\n\n\t// Compute SHA-256 checksum over all content sections\n\tconst checksumHex = await computeSha256(allContentForChecksum)\n\tonProgress({ phase: 'writing', progress: 1, message: 'Finalizing' })\n\n\t// Build manifest\n\tconst manifest: BackupManifest = {\n\t\tversion: BACKUP_VERSION,\n\t\tcreatedAt: Date.now(),\n\t\tnodeId,\n\t\tschemaVersion,\n\t\toperationCount: operations.length,\n\t\tcollections,\n\t\tincludesRecords: includeRecords,\n\t\tchecksum: checksumHex,\n\t}\n\n\t// Final output: header + manifest + all content sections + checksum section\n\tconst header = new Uint8Array(0)\n\n\t// Merge manifest + all sections + checksum section\n\tconst manifestSection = encodeJsonSection('manifest', manifest)\n\tconst checksumSection = encodeSection('checksum', new TextEncoder().encode(checksumHex))\n\n\tconst totalLen = manifestSection.length + allContentForChecksum.length + checksumSection.length\n\tconst result = new Uint8Array(totalLen)\n\tlet pos = 0\n\tresult.set(manifestSection, pos)\n\tpos += manifestSection.length\n\tresult.set(allContentForChecksum, pos)\n\tpos += allContentForChecksum.length\n\tresult.set(checksumSection, pos)\n\n\treturn result\n}\n\n/**\n * Read backup manifest without loading the entire backup.\n * Parses only the first section (which is always the manifest).\n *\n * @param data - The raw backup data\n * @returns The backup manifest\n */\nexport function readBackupManifest(data: Uint8Array): BackupManifest {\n\tconst sections = parseSections(data)\n\tconst manifest = parseJsonSection<BackupManifest>(sections, 'manifest')\n\tif (!manifest) {\n\t\tthrow new Error('Invalid backup: manifest section not found')\n\t}\n\treturn manifest\n}\n\n/**\n * Validate the backup checksum.\n *\n * @param data - The raw backup data\n * @returns True if the checksum is valid\n */\nexport async function verifyBackupChecksum(data: Uint8Array): Promise<boolean> {\n\tconst sections = parseSections(data)\n\tconst manifest = parseJsonSection<BackupManifest>(sections, 'manifest')\n\tif (!manifest) return false\n\n\tconst storedChecksum = manifest.checksum\n\tif (!storedChecksum) return false\n\n\t// Recompute checksum over content sections (everything after manifest, before checksum)\n\tconst manifestSection = findSection(sections, 'manifest')\n\tif (!manifestSection) return false\n\n\t// Find the checksum section\n\tconst checksumSection = findSection(sections, 'checksum')\n\n\t// The content spans from end of manifest section to start of checksum section\n\tconst checksumBytes = checksumSection\n\t\t? new TextEncoder().encode(manifest.checksum)\n\t\t: new Uint8Array(0)\n\n\t// Rebuild all content sections and hash them\n\tconst contentSections = sections.filter((s) => s.name !== 'manifest' && s.name !== 'checksum')\n\n\tlet contentForHash = new Uint8Array(0)\n\tfor (const section of contentSections) {\n\t\tconst sectionBytes = encodeSection(section.name, section.content)\n\t\tconst newLen = contentForHash.length + sectionBytes.length\n\t\tconst combined = new Uint8Array(newLen)\n\t\tcombined.set(contentForHash, 0)\n\t\tcombined.set(sectionBytes, contentForHash.length)\n\t\tcontentForHash = combined\n\t}\n\n\tconst computed = await computeSha256(contentForHash)\n\treturn computed === storedChecksum\n}\n\n/**\n * Restore a backup into a store.\n *\n * @param adapter - The storage adapter\n * @param schema - The schema definition\n * @param data - The backup data\n * @param options - Restore options\n * @returns Result of the restore operation\n */\nexport async function restoreBackup(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n\tdata: Uint8Array,\n\toptions?: RestoreOptions,\n): Promise<RestoreResult> {\n\tconst startTime = Date.now()\n\tconst onProgress = options?.onProgress ?? (() => {})\n\tconst collectionFilter = options?.collections\n\n\tonProgress({ phase: 'verifying', progress: 0, message: 'Parsing backup' })\n\n\tconst sections = parseSections(data)\n\tconst manifest = parseJsonSection<BackupManifest>(sections, 'manifest')\n\tif (!manifest) {\n\t\treturn {\n\t\t\toperationsRestored: 0,\n\t\t\trecordsRestored: 0,\n\t\t\tsuccess: false,\n\t\t\terror: 'Invalid backup: manifest section not found',\n\t\t\tduration: Date.now() - startTime,\n\t\t}\n\t}\n\n\t// Validate format version\n\tif (manifest.version !== BACKUP_VERSION) {\n\t\treturn {\n\t\t\toperationsRestored: 0,\n\t\t\trecordsRestored: 0,\n\t\t\tsuccess: false,\n\t\t\terror: `Unsupported backup version: ${manifest.version}`,\n\t\t\tduration: Date.now() - startTime,\n\t\t}\n\t}\n\n\tonProgress({ phase: 'verifying', progress: 0.2, message: 'Verifying checksum' })\n\n\t// Verify checksum\n\tconst valid = await verifyBackupChecksum(data)\n\tif (!valid) {\n\t\treturn {\n\t\t\toperationsRestored: 0,\n\t\t\trecordsRestored: 0,\n\t\t\tsuccess: false,\n\t\t\terror: 'Backup checksum mismatch: data may be corrupted',\n\t\t\tduration: Date.now() - startTime,\n\t\t}\n\t}\n\n\tonProgress({ phase: 'restoring', progress: 0.3, message: 'Loading operations' })\n\n\t// Parse operations\n\tconst opsContent = findSection(sections, 'operations')\n\tlet operations: Operation[] = []\n\tif (opsContent) {\n\t\tconst text = new TextDecoder().decode(opsContent)\n\t\tconst lines = text\n\t\t\t.trim()\n\t\t\t.split('\\n')\n\t\t\t.filter((l) => l.length > 0)\n\t\toperations = lines.map((line) => JSON.parse(line) as Operation)\n\t}\n\n\t// Filter collections if requested\n\tif (collectionFilter) {\n\t\toperations = operations.filter((op) => collectionFilter.includes(op.collection))\n\t}\n\n\t// Load version vector\n\tconst vvData = parseJsonSection<Record<string, number>>(sections, 'version_vector')\n\n\t// Load meta\n\tconst metaData = parseJsonSection<Record<string, string>>(sections, 'meta')\n\n\tonProgress({ phase: 'restoring', progress: 0.4, message: 'Applying operations' })\n\n\tif (options?.merge) {\n\t\t// Merge mode: replay operations through applyRemoteOperation.\n\t\t// This won't work directly since adapter doesn't have applyRemoteOperation.\n\t\t// Instead, we import the raw data into the adapter.\n\t\tawait adapter.transaction(async (tx) => {\n\t\t\t// Import version vector\n\t\t\tif (vvData) {\n\t\t\t\tfor (const [nid, seq] of Object.entries(vvData)) {\n\t\t\t\t\tawait tx.execute(\n\t\t\t\t\t\t'INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\t\t\t[nid, seq],\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import meta\n\t\t\tif (metaData) {\n\t\t\t\tfor (const [key, value] of Object.entries(metaData)) {\n\t\t\t\t\tawait tx.execute('INSERT OR REPLACE INTO _kora_meta (key, value) VALUES (?, ?)', [\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import operations into _kora_ops_ tables\n\t\t\tfor (const op of operations) {\n\t\t\t\tconst collection = op.collection\n\t\t\t\tconst opRow = {\n\t\t\t\t\tid: op.id,\n\t\t\t\t\tnode_id: op.nodeId,\n\t\t\t\t\ttype: op.type,\n\t\t\t\t\trecord_id: op.recordId,\n\t\t\t\t\tdata: op.data !== null ? JSON.stringify(op.data) : null,\n\t\t\t\t\tprevious_data: op.previousData !== null ? JSON.stringify(op.previousData) : null,\n\t\t\t\t\ttimestamp: JSON.stringify(op.timestamp),\n\t\t\t\t\tsequence_number: op.sequenceNumber,\n\t\t\t\t\tcausal_deps: JSON.stringify(op.causalDeps),\n\t\t\t\t\tschema_version: op.schemaVersion,\n\t\t\t\t}\n\n\t\t\t\tawait tx.execute(\n\t\t\t\t\t`INSERT OR IGNORE INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t\t\t[\n\t\t\t\t\t\topRow.id,\n\t\t\t\t\t\topRow.node_id,\n\t\t\t\t\t\topRow.type,\n\t\t\t\t\t\topRow.record_id,\n\t\t\t\t\t\topRow.data,\n\t\t\t\t\t\topRow.previous_data,\n\t\t\t\t\t\topRow.timestamp,\n\t\t\t\t\t\topRow.sequence_number,\n\t\t\t\t\t\topRow.causal_deps,\n\t\t\t\t\t\topRow.schema_version,\n\t\t\t\t\t],\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t} else {\n\t\t// Replace mode: clear existing data and bulk import\n\t\tawait adapter.transaction(async (tx) => {\n\t\t\t// Clear existing data\n\t\t\tfor (const collection of Object.keys(schema.collections)) {\n\t\t\t\tawait tx.execute(`DELETE FROM _kora_ops_${collection}`)\n\t\t\t\tawait tx.execute(`DELETE FROM ${collection}`)\n\t\t\t}\n\t\t\tawait tx.execute('DELETE FROM _kora_version_vector')\n\t\t\tawait tx.execute('DELETE FROM _kora_meta')\n\n\t\t\t// Import version vector\n\t\t\tif (vvData) {\n\t\t\t\tfor (const [nid, seq] of Object.entries(vvData)) {\n\t\t\t\t\tawait tx.execute(\n\t\t\t\t\t\t'INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\t\t\t[nid, seq],\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import meta\n\t\t\tif (metaData) {\n\t\t\t\tfor (const [key, value] of Object.entries(metaData)) {\n\t\t\t\t\tawait tx.execute('INSERT INTO _kora_meta (key, value) VALUES (?, ?)', [key, value])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Import operations\n\t\t\tfor (const op of operations) {\n\t\t\t\tconst collection = op.collection\n\t\t\t\tawait tx.execute(\n\t\t\t\t\t`INSERT INTO _kora_ops_${collection} (id, node_id, type, record_id, data, previous_data, timestamp, sequence_number, causal_deps, schema_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t\t\t\t[\n\t\t\t\t\t\top.id,\n\t\t\t\t\t\top.nodeId,\n\t\t\t\t\t\top.type,\n\t\t\t\t\t\top.recordId,\n\t\t\t\t\t\top.data !== null ? JSON.stringify(op.data) : null,\n\t\t\t\t\t\top.previousData !== null ? JSON.stringify(op.previousData) : null,\n\t\t\t\t\t\tJSON.stringify(op.timestamp),\n\t\t\t\t\t\top.sequenceNumber,\n\t\t\t\t\t\tJSON.stringify(op.causalDeps),\n\t\t\t\t\t\top.schemaVersion,\n\t\t\t\t\t],\n\t\t\t\t)\n\t\t\t}\n\t\t})\n\t}\n\n\tonProgress({ phase: 'restoring', progress: 0.8, message: 'Restoring records' })\n\n\t// Restore records sections (optional)\n\tlet recordsRestored = 0\n\tif (manifest.includesRecords) {\n\t\tfor (const col of manifest.collections) {\n\t\t\tif (collectionFilter && !collectionFilter.includes(col)) continue\n\n\t\t\tconst sectionContent = findSection(sections, `records:${col}`)\n\t\t\tif (!sectionContent) continue\n\n\t\t\tconst text = new TextDecoder().decode(sectionContent)\n\t\t\tconst lines = text\n\t\t\t\t.trim()\n\t\t\t\t.split('\\n')\n\t\t\t\t.filter((l) => l.length > 0)\n\t\t\tconst records = lines.map((line) => JSON.parse(line) as Record<string, unknown>)\n\n\t\t\tif (records.length === 0) continue\n\n\t\t\tawait adapter.transaction(async (tx) => {\n\t\t\t\tfor (const record of records) {\n\t\t\t\t\tconst keys = Object.keys(record)\n\t\t\t\t\tconst placeholders = keys.map(() => '?').join(', ')\n\t\t\t\t\tconst values = keys.map((k) => {\n\t\t\t\t\t\tconst v = record[k]\n\t\t\t\t\t\tif (typeof v === 'boolean') return v ? 1 : 0\n\t\t\t\t\t\tif (Array.isArray(v) || (typeof v === 'object' && v !== null)) return JSON.stringify(v)\n\t\t\t\t\t\treturn v\n\t\t\t\t\t})\n\n\t\t\t\t\tawait tx.execute(\n\t\t\t\t\t\t`INSERT OR REPLACE INTO ${col} (${keys.join(', ')}) VALUES (${placeholders})`,\n\t\t\t\t\t\tvalues,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\trecordsRestored += records.length\n\t\t}\n\t}\n\n\tonProgress({ phase: 'restoring', progress: 1, message: 'Done' })\n\n\treturn {\n\t\toperationsRestored: operations.length,\n\t\trecordsRestored,\n\t\tsuccess: true,\n\t\tduration: Date.now() - startTime,\n\t}\n}\n\n// ── Reading helpers ──────────────────────────────────────────────────────────\n\nasync function readVersionVector(adapter: StorageAdapter): Promise<Map<string, number>> {\n\tconst rows = await adapter.query<{ node_id: string; sequence_number: number }>(\n\t\t'SELECT node_id, sequence_number FROM _kora_version_vector',\n\t)\n\tconst vv = new Map<string, number>()\n\tfor (const row of rows) {\n\t\tvv.set(row.node_id, row.sequence_number)\n\t}\n\treturn vv\n}\n\ninterface MetaRow {\n\tkey: string\n\tvalue: string\n}\n\nasync function readMeta(adapter: StorageAdapter): Promise<MetaRow[]> {\n\treturn adapter.query<MetaRow>('SELECT key, value FROM _kora_meta')\n}\n\nasync function readAllOperations(\n\tadapter: StorageAdapter,\n\tschema: SchemaDefinition,\n): Promise<Operation[]> {\n\tconst allOps: Operation[] = []\n\n\tfor (const collectionName of Object.keys(schema.collections)) {\n\t\tconst rows = await adapter.query<OperationRow>(\n\t\t\t`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`,\n\t\t)\n\n\t\tfor (const row of rows) {\n\t\t\tallOps.push(deserializeOperationWithCollection(row, collectionName))\n\t\t}\n\t}\n\n\treturn allOps\n}\n\nasync function readCollectionRecords(\n\tadapter: StorageAdapter,\n\tcollection: string,\n): Promise<Record<string, unknown>[]> {\n\treturn adapter.query<Record<string, unknown>>(`SELECT * FROM ${collection} WHERE _deleted = 0`)\n}\n\n// ── Crypto helpers ───────────────────────────────────────────────────────────\n\nasync function computeSha256(data: Uint8Array<ArrayBuffer>): Promise<string> {\n\tconst hashBuffer = await crypto.subtle.digest('SHA-256', data)\n\tconst hashArray = Array.from(new Uint8Array(hashBuffer))\n\treturn hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')\n}\n"],"mappings":";;;;;AAAA,OAA+B;AAc/B,SAAS,cAAc,MAAc,SAAiC;AACrE,QAAM,YAAY,IAAI,YAAY,EAAE,OAAO,IAAI;AAC/C,QAAM,SAAS,IAAI,WAAW,CAAC;AAC/B,QAAM,KAAK,IAAI,SAAS,OAAO,MAAM;AACrC,KAAG,UAAU,GAAG,UAAU,QAAQ,IAAI;AACtC,KAAG,UAAU,GAAG,QAAQ,QAAQ,IAAI;AAEpC,QAAM,SAAS,IAAI,WAAW,OAAO,SAAS,UAAU,SAAS,QAAQ,MAAM;AAC/E,SAAO,IAAI,QAAQ,CAAC;AACpB,SAAO,IAAI,WAAW,CAAC;AACvB,SAAO,IAAI,SAAS,IAAI,UAAU,MAAM;AACxC,SAAO;AACR;AAEA,SAAS,kBAAkB,MAAc,MAA2B;AACnE,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,SAAO,cAAc,MAAM,IAAI,YAAY,EAAE,OAAO,IAAI,CAAC;AAC1D;AAOA,SAAS,WAAW,MAAgB,QAAwB;AAC3D,SAAO,KAAK,UAAU,QAAQ,IAAI;AACnC;AAOA,SAAS,cAAc,MAA6B;AACnD,QAAM,WAAsB,CAAC;AAC7B,QAAM,KAAK,IAAI,SAAS,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AACrE,MAAI,SAAS;AAEb,SAAO,SAAS,KAAK,KAAK,YAAY;AACrC,UAAM,UAAU,WAAW,IAAI,MAAM;AACrC,UAAM,aAAa,WAAW,IAAI,SAAS,CAAC;AAC5C,cAAU;AAEV,QAAI,SAAS,UAAU,aAAa,KAAK,WAAY;AAErD,UAAM,OAAO,IAAI,YAAY,EAAE,OAAO,KAAK,MAAM,QAAQ,SAAS,OAAO,CAAC;AAC1E,cAAU;AAEV,UAAM,UAAU,KAAK,MAAM,QAAQ,SAAS,UAAU;AACtD,cAAU;AAEV,aAAS,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,EAChC;AAEA,SAAO;AACR;AAEA,SAAS,YAAY,UAAqB,MAAiC;AAC1E,aAAW,KAAK,UAAU;AACzB,QAAI,EAAE,SAAS,KAAM,QAAO,EAAE;AAAA,EAC/B;AACA,SAAO;AACR;AAEA,SAAS,iBAAoB,UAAqB,MAAwB;AACzE,QAAM,UAAU,YAAY,UAAU,IAAI;AAC1C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,OAAO,CAAC;AACpD;AAKA,IAAM,iBAAiB;AAcvB,eAAsB,aACrB,SACA,QACA,QACA,eACA,SACsB;AACtB,QAAM,aAAa,SAAS,eAAe,MAAM;AAAA,EAAC;AAClD,QAAM,iBAAiB,SAAS,kBAAkB;AAClD,QAAM,mBAAmB,SAAS;AAClC,QAAM,cAAc,mBACjB,OAAO,KAAK,OAAO,WAAW,EAAE,OAAO,CAAC,MAAM,iBAAiB,SAAS,CAAC,CAAC,IAC1E,OAAO,KAAK,OAAO,WAAW;AAEjC,aAAW,EAAE,OAAO,WAAW,UAAU,GAAG,SAAS,yBAAyB,CAAC;AAC/E,QAAM,gBAAgB,MAAM,kBAAkB,OAAO;AACrD,aAAW,EAAE,OAAO,WAAW,UAAU,KAAK,SAAS,mBAAmB,CAAC;AAC3E,QAAM,OAAO,MAAM,SAAS,OAAO;AACnC,aAAW,EAAE,OAAO,WAAW,UAAU,KAAK,SAAS,qBAAqB,CAAC;AAC7E,QAAM,aAAa,MAAM,kBAAkB,SAAS,MAAM;AAC1D,aAAW,EAAE,OAAO,WAAW,UAAU,KAAK,SAAS,kBAAkB,CAAC;AAE1E,QAAM,WAAyB,CAAC;AAChC,MAAI,wBAAwB,IAAI,WAAW,CAAC;AAG5C,QAAM,aAAa,CAAC,MAAc,SAAqB;AACtD,aAAS,KAAK,IAAI;AAClB,UAAM,SAAS,sBAAsB,SAAS,KAAK;AACnD,UAAM,WAAW,IAAI,WAAW,MAAM;AACtC,aAAS,IAAI,uBAAuB,CAAC;AACrC,aAAS,IAAI,MAAM,sBAAsB,MAAM;AAC/C,4BAAwB;AAAA,EACzB;AAGA,QAAM,QAAgC,CAAC;AACvC,aAAW,CAAC,KAAK,GAAG,KAAK,eAAe;AACvC,UAAM,GAAG,IAAI;AAAA,EACd;AACA,aAAW,kBAAkB,kBAAkB,kBAAkB,KAAK,CAAC;AAGvE,QAAM,UAAkC,CAAC;AACzC,aAAW,OAAO,MAAM;AACvB,YAAQ,IAAI,GAAG,IAAI,IAAI;AAAA,EACxB;AACA,aAAW,QAAQ,kBAAkB,QAAQ,OAAO,CAAC;AAGrD,QAAM,UAAU,GAAG,WAAW,IAAI,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA;AACxE,aAAW,cAAc,cAAc,cAAc,IAAI,YAAY,EAAE,OAAO,OAAO,CAAC,CAAC;AAGvF,MAAI,eAAe;AACnB,MAAI,gBAAgB;AACnB,eAAW,OAAO,aAAa;AAC9B,YAAM,UAAU,MAAM,sBAAsB,SAAS,GAAG;AACxD,UAAI,QAAQ,SAAS,GAAG;AACvB,cAAM,QAAQ,GAAG,QAAQ,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA;AACjE,cAAM,cAAc,WAAW,GAAG;AAClC,mBAAW,aAAa,cAAc,aAAa,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC,CAAC;AACnF,wBAAgB,QAAQ;AAAA,MACzB;AAAA,IACD;AAAA,EACD;AACA,aAAW,EAAE,OAAO,WAAW,UAAU,KAAK,SAAS,qBAAqB,CAAC;AAG7E,QAAM,cAAc,MAAM,cAAc,qBAAqB;AAC7D,aAAW,EAAE,OAAO,WAAW,UAAU,GAAG,SAAS,aAAa,CAAC;AAGnE,QAAM,WAA2B;AAAA,IAChC,SAAS;AAAA,IACT,WAAW,KAAK,IAAI;AAAA,IACpB;AAAA,IACA;AAAA,IACA,gBAAgB,WAAW;AAAA,IAC3B;AAAA,IACA,iBAAiB;AAAA,IACjB,UAAU;AAAA,EACX;AAGA,QAAM,SAAS,IAAI,WAAW,CAAC;AAG/B,QAAM,kBAAkB,kBAAkB,YAAY,QAAQ;AAC9D,QAAM,kBAAkB,cAAc,YAAY,IAAI,YAAY,EAAE,OAAO,WAAW,CAAC;AAEvF,QAAM,WAAW,gBAAgB,SAAS,sBAAsB,SAAS,gBAAgB;AACzF,QAAM,SAAS,IAAI,WAAW,QAAQ;AACtC,MAAI,MAAM;AACV,SAAO,IAAI,iBAAiB,GAAG;AAC/B,SAAO,gBAAgB;AACvB,SAAO,IAAI,uBAAuB,GAAG;AACrC,SAAO,sBAAsB;AAC7B,SAAO,IAAI,iBAAiB,GAAG;AAE/B,SAAO;AACR;AASO,SAAS,mBAAmB,MAAkC;AACpE,QAAM,WAAW,cAAc,IAAI;AACnC,QAAM,WAAW,iBAAiC,UAAU,UAAU;AACtE,MAAI,CAAC,UAAU;AACd,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC7D;AACA,SAAO;AACR;AAQA,eAAsB,qBAAqB,MAAoC;AAC9E,QAAM,WAAW,cAAc,IAAI;AACnC,QAAM,WAAW,iBAAiC,UAAU,UAAU;AACtE,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,iBAAiB,SAAS;AAChC,MAAI,CAAC,eAAgB,QAAO;AAG5B,QAAM,kBAAkB,YAAY,UAAU,UAAU;AACxD,MAAI,CAAC,gBAAiB,QAAO;AAG7B,QAAM,kBAAkB,YAAY,UAAU,UAAU;AAGxD,QAAM,gBAAgB,kBACnB,IAAI,YAAY,EAAE,OAAO,SAAS,QAAQ,IAC1C,IAAI,WAAW,CAAC;AAGnB,QAAM,kBAAkB,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,UAAU;AAE7F,MAAI,iBAAiB,IAAI,WAAW,CAAC;AACrC,aAAW,WAAW,iBAAiB;AACtC,UAAM,eAAe,cAAc,QAAQ,MAAM,QAAQ,OAAO;AAChE,UAAM,SAAS,eAAe,SAAS,aAAa;AACpD,UAAM,WAAW,IAAI,WAAW,MAAM;AACtC,aAAS,IAAI,gBAAgB,CAAC;AAC9B,aAAS,IAAI,cAAc,eAAe,MAAM;AAChD,qBAAiB;AAAA,EAClB;AAEA,QAAM,WAAW,MAAM,cAAc,cAAc;AACnD,SAAO,aAAa;AACrB;AAWA,eAAsB,cACrB,SACA,QACA,MACA,SACyB;AACzB,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,aAAa,SAAS,eAAe,MAAM;AAAA,EAAC;AAClD,QAAM,mBAAmB,SAAS;AAElC,aAAW,EAAE,OAAO,aAAa,UAAU,GAAG,SAAS,iBAAiB,CAAC;AAEzE,QAAM,WAAW,cAAc,IAAI;AACnC,QAAM,WAAW,iBAAiC,UAAU,UAAU;AACtE,MAAI,CAAC,UAAU;AACd,WAAO;AAAA,MACN,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,OAAO;AAAA,MACP,UAAU,KAAK,IAAI,IAAI;AAAA,IACxB;AAAA,EACD;AAGA,MAAI,SAAS,YAAY,gBAAgB;AACxC,WAAO;AAAA,MACN,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,OAAO,+BAA+B,SAAS,OAAO;AAAA,MACtD,UAAU,KAAK,IAAI,IAAI;AAAA,IACxB;AAAA,EACD;AAEA,aAAW,EAAE,OAAO,aAAa,UAAU,KAAK,SAAS,qBAAqB,CAAC;AAG/E,QAAM,QAAQ,MAAM,qBAAqB,IAAI;AAC7C,MAAI,CAAC,OAAO;AACX,WAAO;AAAA,MACN,oBAAoB;AAAA,MACpB,iBAAiB;AAAA,MACjB,SAAS;AAAA,MACT,OAAO;AAAA,MACP,UAAU,KAAK,IAAI,IAAI;AAAA,IACxB;AAAA,EACD;AAEA,aAAW,EAAE,OAAO,aAAa,UAAU,KAAK,SAAS,qBAAqB,CAAC;AAG/E,QAAM,aAAa,YAAY,UAAU,YAAY;AACrD,MAAI,aAA0B,CAAC;AAC/B,MAAI,YAAY;AACf,UAAM,OAAO,IAAI,YAAY,EAAE,OAAO,UAAU;AAChD,UAAM,QAAQ,KACZ,KAAK,EACL,MAAM,IAAI,EACV,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC5B,iBAAa,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAAc;AAAA,EAC/D;AAGA,MAAI,kBAAkB;AACrB,iBAAa,WAAW,OAAO,CAAC,OAAO,iBAAiB,SAAS,GAAG,UAAU,CAAC;AAAA,EAChF;AAGA,QAAM,SAAS,iBAAyC,UAAU,gBAAgB;AAGlF,QAAM,WAAW,iBAAyC,UAAU,MAAM;AAE1E,aAAW,EAAE,OAAO,aAAa,UAAU,KAAK,SAAS,sBAAsB,CAAC;AAEhF,MAAI,SAAS,OAAO;AAInB,UAAM,QAAQ,YAAY,OAAO,OAAO;AAEvC,UAAI,QAAQ;AACX,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAChD,gBAAM,GAAG;AAAA,YACR;AAAA,YACA,CAAC,KAAK,GAAG;AAAA,UACV;AAAA,QACD;AAAA,MACD;AAGA,UAAI,UAAU;AACb,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACpD,gBAAM,GAAG,QAAQ,gEAAgE;AAAA,YAChF;AAAA,YACA;AAAA,UACD,CAAC;AAAA,QACF;AAAA,MACD;AAGA,iBAAW,MAAM,YAAY;AAC5B,cAAM,aAAa,GAAG;AACtB,cAAM,QAAQ;AAAA,UACb,IAAI,GAAG;AAAA,UACP,SAAS,GAAG;AAAA,UACZ,MAAM,GAAG;AAAA,UACT,WAAW,GAAG;AAAA,UACd,MAAM,GAAG,SAAS,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI;AAAA,UACnD,eAAe,GAAG,iBAAiB,OAAO,KAAK,UAAU,GAAG,YAAY,IAAI;AAAA,UAC5E,WAAW,KAAK,UAAU,GAAG,SAAS;AAAA,UACtC,iBAAiB,GAAG;AAAA,UACpB,aAAa,KAAK,UAAU,GAAG,UAAU;AAAA,UACzC,gBAAgB,GAAG;AAAA,QACpB;AAEA,cAAM,GAAG;AAAA,UACR,mCAAmC,UAAU;AAAA,UAC7C;AAAA,YACC,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF,OAAO;AAEN,UAAM,QAAQ,YAAY,OAAO,OAAO;AAEvC,iBAAW,cAAc,OAAO,KAAK,OAAO,WAAW,GAAG;AACzD,cAAM,GAAG,QAAQ,yBAAyB,UAAU,EAAE;AACtD,cAAM,GAAG,QAAQ,eAAe,UAAU,EAAE;AAAA,MAC7C;AACA,YAAM,GAAG,QAAQ,kCAAkC;AACnD,YAAM,GAAG,QAAQ,wBAAwB;AAGzC,UAAI,QAAQ;AACX,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAChD,gBAAM,GAAG;AAAA,YACR;AAAA,YACA,CAAC,KAAK,GAAG;AAAA,UACV;AAAA,QACD;AAAA,MACD;AAGA,UAAI,UAAU;AACb,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACpD,gBAAM,GAAG,QAAQ,qDAAqD,CAAC,KAAK,KAAK,CAAC;AAAA,QACnF;AAAA,MACD;AAGA,iBAAW,MAAM,YAAY;AAC5B,cAAM,aAAa,GAAG;AACtB,cAAM,GAAG;AAAA,UACR,yBAAyB,UAAU;AAAA,UACnC;AAAA,YACC,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG,SAAS,OAAO,KAAK,UAAU,GAAG,IAAI,IAAI;AAAA,YAC7C,GAAG,iBAAiB,OAAO,KAAK,UAAU,GAAG,YAAY,IAAI;AAAA,YAC7D,KAAK,UAAU,GAAG,SAAS;AAAA,YAC3B,GAAG;AAAA,YACH,KAAK,UAAU,GAAG,UAAU;AAAA,YAC5B,GAAG;AAAA,UACJ;AAAA,QACD;AAAA,MACD;AAAA,IACD,CAAC;AAAA,EACF;AAEA,aAAW,EAAE,OAAO,aAAa,UAAU,KAAK,SAAS,oBAAoB,CAAC;AAG9E,MAAI,kBAAkB;AACtB,MAAI,SAAS,iBAAiB;AAC7B,eAAW,OAAO,SAAS,aAAa;AACvC,UAAI,oBAAoB,CAAC,iBAAiB,SAAS,GAAG,EAAG;AAEzD,YAAM,iBAAiB,YAAY,UAAU,WAAW,GAAG,EAAE;AAC7D,UAAI,CAAC,eAAgB;AAErB,YAAM,OAAO,IAAI,YAAY,EAAE,OAAO,cAAc;AACpD,YAAM,QAAQ,KACZ,KAAK,EACL,MAAM,IAAI,EACV,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC5B,YAAM,UAAU,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,CAA4B;AAE/E,UAAI,QAAQ,WAAW,EAAG;AAE1B,YAAM,QAAQ,YAAY,OAAO,OAAO;AACvC,mBAAW,UAAU,SAAS;AAC7B,gBAAM,OAAO,OAAO,KAAK,MAAM;AAC/B,gBAAM,eAAe,KAAK,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AAClD,gBAAM,SAAS,KAAK,IAAI,CAAC,MAAM;AAC9B,kBAAM,IAAI,OAAO,CAAC;AAClB,gBAAI,OAAO,MAAM,UAAW,QAAO,IAAI,IAAI;AAC3C,gBAAI,MAAM,QAAQ,CAAC,KAAM,OAAO,MAAM,YAAY,MAAM,KAAO,QAAO,KAAK,UAAU,CAAC;AACtF,mBAAO;AAAA,UACR,CAAC;AAED,gBAAM,GAAG;AAAA,YACR,0BAA0B,GAAG,KAAK,KAAK,KAAK,IAAI,CAAC,aAAa,YAAY;AAAA,YAC1E;AAAA,UACD;AAAA,QACD;AAAA,MACD,CAAC;AAED,yBAAmB,QAAQ;AAAA,IAC5B;AAAA,EACD;AAEA,aAAW,EAAE,OAAO,aAAa,UAAU,GAAG,SAAS,OAAO,CAAC;AAE/D,SAAO;AAAA,IACN,oBAAoB,WAAW;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,IACT,UAAU,KAAK,IAAI,IAAI;AAAA,EACxB;AACD;AAIA,eAAe,kBAAkB,SAAuD;AACvF,QAAM,OAAO,MAAM,QAAQ;AAAA,IAC1B;AAAA,EACD;AACA,QAAM,KAAK,oBAAI,IAAoB;AACnC,aAAW,OAAO,MAAM;AACvB,OAAG,IAAI,IAAI,SAAS,IAAI,eAAe;AAAA,EACxC;AACA,SAAO;AACR;AAOA,eAAe,SAAS,SAA6C;AACpE,SAAO,QAAQ,MAAe,mCAAmC;AAClE;AAEA,eAAe,kBACd,SACA,QACuB;AACvB,QAAM,SAAsB,CAAC;AAE7B,aAAW,kBAAkB,OAAO,KAAK,OAAO,WAAW,GAAG;AAC7D,UAAM,OAAO,MAAM,QAAQ;AAAA,MAC1B,2BAA2B,cAAc;AAAA,IAC1C;AAEA,eAAW,OAAO,MAAM;AACvB,aAAO,KAAK,mCAAmC,KAAK,cAAc,CAAC;AAAA,IACpE;AAAA,EACD;AAEA,SAAO;AACR;AAEA,eAAe,sBACd,SACA,YACqC;AACrC,SAAO,QAAQ,MAA+B,iBAAiB,UAAU,qBAAqB;AAC/F;AAIA,eAAe,cAAc,MAAgD;AAC5E,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AAC7D,QAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,SAAO,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACrE;","names":[]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/audit/audit-trace-store.ts
|
|
2
|
+
import { generateUUIDv7 } from "@korajs/core";
|
|
3
|
+
|
|
4
|
+
// src/audit/audit-json.ts
|
|
5
|
+
var WIRE_BYTES_KEY = "__kora_bytes__";
|
|
6
|
+
function auditJsonReplacer(_key, value) {
|
|
7
|
+
if (value instanceof Uint8Array) {
|
|
8
|
+
let binary = "";
|
|
9
|
+
for (let i = 0; i < value.length; i++) {
|
|
10
|
+
binary += String.fromCharCode(value[i]);
|
|
11
|
+
}
|
|
12
|
+
return { [WIRE_BYTES_KEY]: btoa(binary) };
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function auditJsonReviver(_key, value) {
|
|
17
|
+
if (value !== null && typeof value === "object" && WIRE_BYTES_KEY in value && typeof value[WIRE_BYTES_KEY] === "string") {
|
|
18
|
+
const encoded = value[WIRE_BYTES_KEY];
|
|
19
|
+
if (typeof encoded !== "string") {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
const binary = atob(encoded);
|
|
23
|
+
const bytes = new Uint8Array(binary.length);
|
|
24
|
+
for (let i = 0; i < binary.length; i++) {
|
|
25
|
+
bytes[i] = binary.charCodeAt(i);
|
|
26
|
+
}
|
|
27
|
+
return bytes;
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function serializeAuditJson(value) {
|
|
32
|
+
return JSON.stringify(value, auditJsonReplacer);
|
|
33
|
+
}
|
|
34
|
+
function deserializeAuditJson(json) {
|
|
35
|
+
return JSON.parse(json, auditJsonReviver);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/audit/audit-trace-store.ts
|
|
39
|
+
function persistedAuditTraceFromEvent(event) {
|
|
40
|
+
return {
|
|
41
|
+
id: generateUUIDv7(),
|
|
42
|
+
recordedAt: Date.now(),
|
|
43
|
+
eventType: event.type,
|
|
44
|
+
constraint: event.type === "constraint:violated" ? event.constraint : void 0,
|
|
45
|
+
trace: event.trace
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function appendAuditTrace(adapter, trace) {
|
|
49
|
+
const collection = trace.trace.operationA.collection;
|
|
50
|
+
const recordId = trace.trace.operationA.recordId;
|
|
51
|
+
await adapter.execute(
|
|
52
|
+
`INSERT INTO _kora_audit_traces (
|
|
53
|
+
id, recorded_at, event_type, collection, record_id, field, strategy, tier, constraint_name, trace_json
|
|
54
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
55
|
+
[
|
|
56
|
+
trace.id,
|
|
57
|
+
trace.recordedAt,
|
|
58
|
+
trace.eventType,
|
|
59
|
+
collection,
|
|
60
|
+
recordId,
|
|
61
|
+
trace.trace.field,
|
|
62
|
+
trace.trace.strategy,
|
|
63
|
+
trace.trace.tier,
|
|
64
|
+
trace.constraint ?? null,
|
|
65
|
+
serializeAuditJson(trace)
|
|
66
|
+
]
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
async function readAuditTraces(adapter, query) {
|
|
70
|
+
const conditions = [];
|
|
71
|
+
const params = [];
|
|
72
|
+
if (query?.collections && query.collections.length > 0) {
|
|
73
|
+
const placeholders = query.collections.map(() => "?").join(", ");
|
|
74
|
+
conditions.push(`collection IN (${placeholders})`);
|
|
75
|
+
params.push(...query.collections);
|
|
76
|
+
}
|
|
77
|
+
if (query?.since !== void 0) {
|
|
78
|
+
conditions.push("recorded_at >= ?");
|
|
79
|
+
params.push(query.since);
|
|
80
|
+
}
|
|
81
|
+
if (query?.until !== void 0) {
|
|
82
|
+
conditions.push("recorded_at <= ?");
|
|
83
|
+
params.push(query.until);
|
|
84
|
+
}
|
|
85
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
86
|
+
const rows = await adapter.query(
|
|
87
|
+
`SELECT * FROM _kora_audit_traces ${whereClause} ORDER BY recorded_at ASC, id ASC`,
|
|
88
|
+
params
|
|
89
|
+
);
|
|
90
|
+
return rows.map(rowToPersistedTrace);
|
|
91
|
+
}
|
|
92
|
+
function rowToPersistedTrace(row) {
|
|
93
|
+
return deserializeAuditJson(row.trace_json);
|
|
94
|
+
}
|
|
95
|
+
async function countAuditTraces(adapter) {
|
|
96
|
+
const rows = await adapter.query(
|
|
97
|
+
"SELECT COUNT(*) as count FROM _kora_audit_traces"
|
|
98
|
+
);
|
|
99
|
+
return rows[0]?.count ?? 0;
|
|
100
|
+
}
|
|
101
|
+
function auditTraceIndexFields(trace) {
|
|
102
|
+
return {
|
|
103
|
+
collection: trace.operationA.collection,
|
|
104
|
+
recordId: trace.operationA.recordId
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
serializeAuditJson,
|
|
110
|
+
deserializeAuditJson,
|
|
111
|
+
persistedAuditTraceFromEvent,
|
|
112
|
+
appendAuditTrace,
|
|
113
|
+
readAuditTraces,
|
|
114
|
+
countAuditTraces,
|
|
115
|
+
auditTraceIndexFields
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=chunk-MYSJN3DF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audit/audit-trace-store.ts","../src/audit/audit-json.ts"],"sourcesContent":["import { generateUUIDv7 } from '@korajs/core'\nimport type { KoraEvent, MergeTrace } from '@korajs/core'\nimport type { StorageAdapter } from '../types'\nimport { deserializeAuditJson, serializeAuditJson } from './audit-json'\nimport type { AuditTraceQuery, PersistedAuditTrace } from './types'\n\ninterface AuditTraceRow {\n\tid: string\n\trecorded_at: number\n\tevent_type: string\n\tcollection: string\n\trecord_id: string\n\tfield: string\n\tstrategy: string\n\ttier: number\n\tconstraint_name: string | null\n\ttrace_json: string\n}\n\n/**\n * Build a {@link PersistedAuditTrace} from a merge-related Kora event.\n */\nexport function persistedAuditTraceFromEvent(\n\tevent: Extract<KoraEvent, { type: 'merge:completed' | 'merge:conflict' | 'constraint:violated' }>,\n): PersistedAuditTrace {\n\treturn {\n\t\tid: generateUUIDv7(),\n\t\trecordedAt: Date.now(),\n\t\teventType: event.type,\n\t\tconstraint: event.type === 'constraint:violated' ? event.constraint : undefined,\n\t\ttrace: event.trace,\n\t}\n}\n\n/**\n * Append a merge trace to the durable audit log.\n */\nexport async function appendAuditTrace(\n\tadapter: StorageAdapter,\n\ttrace: PersistedAuditTrace,\n): Promise<void> {\n\tconst collection = trace.trace.operationA.collection\n\tconst recordId = trace.trace.operationA.recordId\n\n\tawait adapter.execute(\n\t\t`INSERT INTO _kora_audit_traces (\n\t\t\tid, recorded_at, event_type, collection, record_id, field, strategy, tier, constraint_name, trace_json\n\t\t) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t\t[\n\t\t\ttrace.id,\n\t\t\ttrace.recordedAt,\n\t\t\ttrace.eventType,\n\t\t\tcollection,\n\t\t\trecordId,\n\t\t\ttrace.trace.field,\n\t\t\ttrace.trace.strategy,\n\t\t\ttrace.trace.tier,\n\t\t\ttrace.constraint ?? null,\n\t\t\tserializeAuditJson(trace),\n\t\t],\n\t)\n}\n\n/**\n * Load persisted audit traces with optional filters.\n */\nexport async function readAuditTraces(\n\tadapter: StorageAdapter,\n\tquery?: AuditTraceQuery,\n): Promise<PersistedAuditTrace[]> {\n\tconst conditions: string[] = []\n\tconst params: unknown[] = []\n\n\tif (query?.collections && query.collections.length > 0) {\n\t\tconst placeholders = query.collections.map(() => '?').join(', ')\n\t\tconditions.push(`collection IN (${placeholders})`)\n\t\tparams.push(...query.collections)\n\t}\n\tif (query?.since !== undefined) {\n\t\tconditions.push('recorded_at >= ?')\n\t\tparams.push(query.since)\n\t}\n\tif (query?.until !== undefined) {\n\t\tconditions.push('recorded_at <= ?')\n\t\tparams.push(query.until)\n\t}\n\n\tconst whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n\tconst rows = await adapter.query<AuditTraceRow>(\n\t\t`SELECT * FROM _kora_audit_traces ${whereClause} ORDER BY recorded_at ASC, id ASC`,\n\t\tparams,\n\t)\n\n\treturn rows.map(rowToPersistedTrace)\n}\n\nfunction rowToPersistedTrace(row: AuditTraceRow): PersistedAuditTrace {\n\treturn deserializeAuditJson<PersistedAuditTrace>(row.trace_json)\n}\n\n/**\n * Count persisted audit traces (for diagnostics).\n */\nexport async function countAuditTraces(adapter: StorageAdapter): Promise<number> {\n\tconst rows = await adapter.query<{ count: number }>(\n\t\t'SELECT COUNT(*) as count FROM _kora_audit_traces',\n\t)\n\treturn rows[0]?.count ?? 0\n}\n\n/**\n * Extract collection and record id from a merge trace for indexing.\n */\nexport function auditTraceIndexFields(trace: MergeTrace): {\n\tcollection: string\n\trecordId: string\n} {\n\treturn {\n\t\tcollection: trace.operationA.collection,\n\t\trecordId: trace.operationA.recordId,\n\t}\n}\n","const WIRE_BYTES_KEY = '__kora_bytes__'\n\n/**\n * JSON.stringify replacer that preserves binary field values (e.g. richtext).\n */\nexport function auditJsonReplacer(_key: string, value: unknown): unknown {\n\tif (value instanceof Uint8Array) {\n\t\tlet binary = ''\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tbinary += String.fromCharCode(value[i] as number)\n\t\t}\n\t\treturn { [WIRE_BYTES_KEY]: btoa(binary) }\n\t}\n\treturn value\n}\n\n/**\n * JSON.parse reviver that restores binary field values.\n */\nexport function auditJsonReviver(_key: string, value: unknown): unknown {\n\tif (\n\t\tvalue !== null &&\n\t\ttypeof value === 'object' &&\n\t\tWIRE_BYTES_KEY in value &&\n\t\ttypeof (value as Record<string, unknown>)[WIRE_BYTES_KEY] === 'string'\n\t) {\n\t\tconst encoded = (value as Record<string, unknown>)[WIRE_BYTES_KEY]\n\t\tif (typeof encoded !== 'string') {\n\t\t\treturn value\n\t\t}\n\t\tconst binary = atob(encoded)\n\t\tconst bytes = new Uint8Array(binary.length)\n\t\tfor (let i = 0; i < binary.length; i++) {\n\t\t\tbytes[i] = binary.charCodeAt(i)\n\t\t}\n\t\treturn bytes\n\t}\n\treturn value\n}\n\n/**\n * Serialize a value for durable audit storage or export.\n */\nexport function serializeAuditJson(value: unknown): string {\n\treturn JSON.stringify(value, auditJsonReplacer)\n}\n\n/**\n * Deserialize audit JSON back to typed values.\n */\nexport function deserializeAuditJson<T>(json: string): T {\n\treturn JSON.parse(json, auditJsonReviver) as T\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/sqlite-wasm-channel.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\nimport { WorkerTimeoutError } from '../errors'\n\n// === Message Protocol ===\n\n/**\n * Request message sent from the main thread to the SQLite WASM worker.\n * Each request carries a unique `id` for response correlation.\n */\nexport type WorkerRequest =\n\t| { id: number; type: 'open'; ddlStatements: string[]; dbName?: string }\n\t| { id: number; type: 'close' }\n\t| { id: number; type: 'execute'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'query'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'begin' }\n\t| { id: number; type: 'commit' }\n\t| { id: number; type: 'rollback' }\n\t| { id: number; type: 'migrate'; from: number; to: number; statements: string[] }\n\t| { id: number; type: 'export' }\n\t| { id: number; type: 'import'; data: Uint8Array }\n\n/**\n * Response message sent from the worker back to the main thread.\n * Matches the request `id` for correlation.\n */\nexport type WorkerResponse =\n\t| { id: number; type: 'success'; data?: unknown }\n\t| { id: number; type: 'error'; message: string; code: string; context?: Record<string, unknown> }\n\n// === WorkerBridge Interface ===\n\n/**\n * Abstraction over the communication channel with the SQLite WASM worker.\n * In browsers, this is backed by a real Web Worker via MessagePort.\n * In Node.js tests, this is backed by better-sqlite3 via MockWorkerBridge.\n */\nexport interface WorkerBridge {\n\t/** Send a request to the worker and wait for a response. */\n\tsend(request: WorkerRequest): Promise<WorkerResponse>\n\n\t/** Terminate the worker. Safe to call multiple times. */\n\tterminate(): void\n}\n\n// === Mutex ===\n\n/**\n * Async mutex for serializing transaction access across the async worker boundary.\n * Only one transaction may be active at a time.\n */\nexport class Mutex {\n\tprivate locked = false\n\tprivate waiters: Array<() => void> = []\n\n\t/**\n\t * Acquire the mutex. Returns a release function.\n\t * If the mutex is already held, the caller waits until it's released.\n\t */\n\tasync acquire(): Promise<() => void> {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true\n\t\t\treturn this.createRelease()\n\t\t}\n\n\t\treturn new Promise<() => void>((resolve) => {\n\t\t\tthis.waiters.push(() => {\n\t\t\t\tresolve(this.createRelease())\n\t\t\t})\n\t\t})\n\t}\n\n\tprivate createRelease(): () => void {\n\t\tlet released = false\n\t\treturn () => {\n\t\t\tif (released) return\n\t\t\treleased = true\n\t\t\tconst next = this.waiters.shift()\n\t\t\tif (next) {\n\t\t\t\tnext()\n\t\t\t} else {\n\t\t\t\tthis.locked = false\n\t\t\t}\n\t\t}\n\t}\n}\n\n// === WebWorkerBridge ===\n\n/**\n * WorkerBridge implementation for browser environments.\n * Communicates with an actual Web Worker running SQLite WASM.\n */\nexport class WebWorkerBridge implements WorkerBridge {\n\tprivate worker: Worker\n\tprivate pending = new Map<\n\t\tnumber,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate nextId = 1\n\tprivate terminated = false\n\tprivate timeoutMs: number\n\n\t/**\n\t * @param workerUrl - URL to the sqlite-wasm-worker script\n\t * @param timeoutMs - Timeout for worker responses in milliseconds (default: 30000)\n\t */\n\tconstructor(workerUrl: string | URL, timeoutMs = 30000) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.worker = new Worker(workerUrl, { type: 'module' })\n\t\tthis.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\tconst response = event.data\n\t\t\tconst entry = this.pending.get(response.id)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(response.id)\n\t\t\t\tentry.resolve(response)\n\t\t\t}\n\t\t}\n\t\tthis.worker.onerror = (event) => {\n\t\t\t// Reject all pending requests on worker error\n\t\t\tconst error = new Error(`Worker error: ${event.message}`)\n\t\t\tfor (const [id, entry] of this.pending) {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\tentry.reject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Worker has been terminated',\n\t\t\t\tcode: 'WORKER_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst id = this.nextId++\n\t\tconst req = { ...request, id }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\treject(new WorkerTimeoutError(req.type, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.worker.postMessage(req)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) return\n\t\tthis.terminated = true\n\t\tthis.worker.terminate()\n\t\t// Reject any pending requests\n\t\tfor (const [id, entry] of this.pending) {\n\t\t\tthis.pending.delete(id)\n\t\t\tentry.reject(new Error('Worker terminated'))\n\t\t}\n\t}\n}\n"],"mappings":";;;;;AAkDO,IAAM,QAAN,MAAY;AAAA,EACV,SAAS;AAAA,EACT,UAA6B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtC,MAAM,UAA+B;AACpC,QAAI,CAAC,KAAK,QAAQ;AACjB,WAAK,SAAS;AACd,aAAO,KAAK,cAAc;AAAA,IAC3B;AAEA,WAAO,IAAI,QAAoB,CAAC,YAAY;AAC3C,WAAK,QAAQ,KAAK,MAAM;AACvB,gBAAQ,KAAK,cAAc,CAAC;AAAA,MAC7B,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAAA,EAEQ,gBAA4B;AACnC,QAAI,WAAW;AACf,WAAO,MAAM;AACZ,UAAI,SAAU;AACd,iBAAW;AACX,YAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,UAAI,MAAM;AACT,aAAK;AAAA,MACN,OAAO;AACN,aAAK,SAAS;AAAA,MACf;AAAA,IACD;AAAA,EACD;AACD;AAQO,IAAM,kBAAN,MAA8C;AAAA,EAC5C;AAAA,EACA,UAAU,oBAAI,IAGpB;AAAA,EACM,SAAS;AAAA,EACT,aAAa;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,WAAyB,YAAY,KAAO;AACvD,SAAK,YAAY;AACjB,SAAK,SAAS,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;AACtD,SAAK,OAAO,YAAY,CAAC,UAAwC;AAChE,YAAM,WAAW,MAAM;AACvB,YAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS,EAAE;AAC1C,UAAI,OAAO;AACV,aAAK,QAAQ,OAAO,SAAS,EAAE;AAC/B,cAAM,QAAQ,QAAQ;AAAA,MACvB;AAAA,IACD;AACA,SAAK,OAAO,UAAU,CAAC,UAAU;AAEhC,YAAM,QAAQ,IAAI,MAAM,iBAAiB,MAAM,OAAO,EAAE;AACxD,iBAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACvC,aAAK,QAAQ,OAAO,EAAE;AACtB,cAAM,OAAO,KAAK;AAAA,MACnB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAM,KAAK,SAAiD;AAC3D,QAAI,KAAK,YAAY;AACpB,aAAO;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,MACP;AAAA,IACD;AAEA,UAAM,KAAK,KAAK;AAChB,UAAM,MAAM,EAAE,GAAG,SAAS,GAAG;AAE7B,WAAO,IAAI,QAAwB,CAAC,SAAS,WAAW;AACvD,YAAM,QAAQ,WAAW,MAAM;AAC9B,aAAK,QAAQ,OAAO,EAAE;AACtB,eAAO,IAAI,mBAAmB,IAAI,MAAM,KAAK,SAAS,CAAC;AAAA,MACxD,GAAG,KAAK,SAAS;AAEjB,WAAK,QAAQ,IAAI,IAAI;AAAA,QACpB,SAAS,CAAC,aAAa;AACtB,uBAAa,KAAK;AAClB,kBAAQ,QAAQ;AAAA,QACjB;AAAA,QACA,QAAQ,CAAC,UAAU;AAClB,uBAAa,KAAK;AAClB,iBAAO,KAAK;AAAA,QACb;AAAA,MACD,CAAC;AAED,WAAK,OAAO,YAAY,GAAG;AAAA,IAC5B,CAAC;AAAA,EACF;AAAA,EAEA,YAAkB;AACjB,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,SAAK,OAAO,UAAU;AAEtB,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,SAAS;AACvC,WAAK,QAAQ,OAAO,EAAE;AACtB,YAAM,OAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,IAC5C;AAAA,EACD;AACD;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeAuditExport,
|
|
3
|
+
exportAudit,
|
|
4
|
+
readAuditExportManifest,
|
|
5
|
+
verifyAuditExportChecksum
|
|
6
|
+
} from "./chunk-AGN3CIPB.js";
|
|
7
|
+
import "./chunk-MYSJN3DF.js";
|
|
8
|
+
import "./chunk-56LL4XP6.js";
|
|
9
|
+
export {
|
|
10
|
+
decodeAuditExport,
|
|
11
|
+
exportAudit,
|
|
12
|
+
readAuditExportManifest,
|
|
13
|
+
verifyAuditExportChecksum
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=export-audit-5GV6VRDZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|