@korajs/store 0.6.0 → 1.0.0-beta.1
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/LICENSE +21 -0
- package/dist/adapters/better-sqlite3.cjs +94 -40
- package/dist/adapters/better-sqlite3.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.d.cts +9 -1
- package/dist/adapters/better-sqlite3.d.ts +9 -1
- package/dist/adapters/better-sqlite3.js +62 -41
- package/dist/adapters/better-sqlite3.js.map +1 -1
- package/dist/adapters/indexeddb.cjs +24 -13
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +2 -2
- package/dist/adapters/indexeddb.d.ts +2 -2
- package/dist/adapters/indexeddb.js +4 -3
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +24 -13
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +27 -4
- package/dist/adapters/sqlite-wasm.d.ts +27 -4
- package/dist/adapters/sqlite-wasm.js +6 -4
- package/dist/{backup-JN3LB4YT.js → backup-ZV4Y424B.js} +3 -3
- package/dist/blob-fs.cjs +166 -0
- package/dist/blob-fs.cjs.map +1 -0
- package/dist/blob-fs.d.cts +29 -0
- package/dist/blob-fs.d.ts +29 -0
- package/dist/blob-fs.js +128 -0
- package/dist/blob-fs.js.map +1 -0
- package/dist/{chunk-56LL4XP6.js → chunk-6NKLDBTY.js} +52 -1
- package/dist/chunk-6NKLDBTY.js.map +1 -0
- package/dist/{chunk-AGN3CIPB.js → chunk-FFYA7J5H.js} +2 -2
- package/dist/{chunk-XO4LMPAH.js → chunk-IOEFNHHP.js} +2 -35
- package/dist/chunk-IOEFNHHP.js.map +1 -0
- package/dist/{chunk-LAWV6CFH.js → chunk-QL2XVZRN.js} +12 -1
- package/dist/chunk-QL2XVZRN.js.map +1 -0
- package/dist/{chunk-6AHYGQQT.js → chunk-RBPVEAVM.js} +4 -4
- package/dist/chunk-TVJEC5S2.js +40 -0
- package/dist/chunk-TVJEC5S2.js.map +1 -0
- package/dist/chunk-W4MTTNEZ.js +55 -0
- package/dist/chunk-W4MTTNEZ.js.map +1 -0
- package/dist/{chunk-EZSTD2XC.js → chunk-X57MWW35.js} +2 -2
- package/dist/{chunk-7BPETY5Y.js → chunk-ZLWAYQEQ.js} +128 -15
- package/dist/chunk-ZLWAYQEQ.js.map +1 -0
- package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
- package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
- package/dist/{export-audit-5GV6VRDZ.js → export-audit-AJ7RORNK.js} +3 -3
- package/dist/index.cjs +1245 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +468 -7
- package/dist/index.d.ts +468 -7
- package/dist/index.js +975 -61
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +123 -33
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +3 -3
- package/dist/{sqlite-wasm-channel-I9y_jObg.d.cts → sqlite-wasm-channel-DUDPclTE.d.cts} +2 -15
- package/dist/{sqlite-wasm-channel-I9y_jObg.d.ts → sqlite-wasm-channel-DUDPclTE.d.ts} +2 -15
- package/dist/sqlite-wasm-channel-H43KEZLM.js +12 -0
- package/dist/{types-CTr00g_d.d.cts → types-5h4Hqcwc.d.ts} +10 -3
- package/dist/{types-BMyHmwvn.d.ts → types-DfPiPwKr.d.cts} +10 -3
- package/dist/{types-DepnRDk_.d.cts → types-k6AZgiH4.d.cts} +48 -2
- package/dist/{types-DepnRDk_.d.ts → types-k6AZgiH4.d.ts} +48 -2
- package/package.json +15 -2
- package/dist/chunk-56LL4XP6.js.map +0 -1
- package/dist/chunk-7BPETY5Y.js.map +0 -1
- package/dist/chunk-LAWV6CFH.js.map +0 -1
- package/dist/chunk-XO4LMPAH.js.map +0 -1
- package/dist/sqlite-wasm-channel-HDDW6UBH.js +0 -10
- /package/dist/{backup-JN3LB4YT.js.map → backup-ZV4Y424B.js.map} +0 -0
- /package/dist/{chunk-AGN3CIPB.js.map → chunk-FFYA7J5H.js.map} +0 -0
- /package/dist/{chunk-6AHYGQQT.js.map → chunk-RBPVEAVM.js.map} +0 -0
- /package/dist/{chunk-EZSTD2XC.js.map → chunk-X57MWW35.js.map} +0 -0
- /package/dist/{export-audit-5GV6VRDZ.js.map → export-audit-AJ7RORNK.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-HDDW6UBH.js.map → sqlite-wasm-channel-H43KEZLM.js.map} +0 -0
package/dist/blob-fs.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlobIntegrityError
|
|
3
|
+
} from "./chunk-W4MTTNEZ.js";
|
|
4
|
+
|
|
5
|
+
// src/blob/filesystem-blob-store.ts
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { createBlobRef, hashBlob } from "@korajs/core";
|
|
10
|
+
var FilesystemBlobStore = class {
|
|
11
|
+
constructor(dir) {
|
|
12
|
+
this.dir = dir;
|
|
13
|
+
}
|
|
14
|
+
dir;
|
|
15
|
+
pathFor(hash) {
|
|
16
|
+
return join(this.dir, hash.slice(0, 2), hash);
|
|
17
|
+
}
|
|
18
|
+
async put(bytes, metadata = {}) {
|
|
19
|
+
const ref = await createBlobRef(bytes, metadata);
|
|
20
|
+
const target = this.pathFor(ref.hash);
|
|
21
|
+
if (await this.exists(target)) {
|
|
22
|
+
return ref;
|
|
23
|
+
}
|
|
24
|
+
await mkdir(dirname(target), { recursive: true });
|
|
25
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
26
|
+
out.set(bytes);
|
|
27
|
+
const tmp = `${target}.${randomUUID()}.tmp`;
|
|
28
|
+
await writeFile(tmp, out);
|
|
29
|
+
await rename(tmp, target);
|
|
30
|
+
return ref;
|
|
31
|
+
}
|
|
32
|
+
async get(hash) {
|
|
33
|
+
let contents;
|
|
34
|
+
try {
|
|
35
|
+
contents = await readFile(this.pathFor(hash));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (isNotFound(error)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
const bytes = new Uint8Array(contents);
|
|
43
|
+
const actual = await hashBlob(bytes);
|
|
44
|
+
if (actual !== hash) {
|
|
45
|
+
throw new BlobIntegrityError(hash, actual);
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
async has(hash) {
|
|
50
|
+
return this.exists(this.pathFor(hash));
|
|
51
|
+
}
|
|
52
|
+
async delete(hash) {
|
|
53
|
+
const target = this.pathFor(hash);
|
|
54
|
+
if (!await this.exists(target)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
await rm(target);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
async size() {
|
|
61
|
+
let count = 0;
|
|
62
|
+
let shards;
|
|
63
|
+
try {
|
|
64
|
+
shards = await readdir(this.dir);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (isNotFound(error)) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
for (const shard of shards) {
|
|
72
|
+
try {
|
|
73
|
+
const entries = await readdir(join(this.dir, shard));
|
|
74
|
+
count += entries.filter((name) => !name.endsWith(".tmp")).length;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (!isNotFound(error)) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return count;
|
|
82
|
+
}
|
|
83
|
+
async list() {
|
|
84
|
+
let shards;
|
|
85
|
+
try {
|
|
86
|
+
shards = await readdir(this.dir);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (isNotFound(error)) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const hashes = [];
|
|
94
|
+
for (const shard of shards) {
|
|
95
|
+
try {
|
|
96
|
+
const entries = await readdir(join(this.dir, shard));
|
|
97
|
+
for (const name of entries) {
|
|
98
|
+
if (!name.endsWith(".tmp")) {
|
|
99
|
+
hashes.push(name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (!isNotFound(error)) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return hashes;
|
|
109
|
+
}
|
|
110
|
+
async exists(path) {
|
|
111
|
+
try {
|
|
112
|
+
await stat(path);
|
|
113
|
+
return true;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (isNotFound(error)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function isNotFound(error) {
|
|
123
|
+
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
FilesystemBlobStore
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=blob-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blob/filesystem-blob-store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { type BlobRef, type BlobRefMetadata, createBlobRef, hashBlob } from '@korajs/core'\nimport { BlobIntegrityError, type ContentAddressedBlobStore } from './content-addressed-blob-store'\n\n/**\n * A persistent {@link ContentAddressedBlobStore} backed by the filesystem.\n *\n * Node-only (exported from `@korajs/store/blob-fs` so it never enters a browser\n * bundle). Blobs are stored at `<dir>/<hash[0:2]>/<hash>`, sharded by hash prefix\n * so a single directory never holds millions of entries. Writes are atomic (write\n * to a temp file, then rename) so a crash mid-write cannot leave a half-written\n * blob under a valid hash. Reads verify the bytes hash to the requested key.\n *\n * This is the same content-addressed contract as {@link MemoryBlobStore}, so it\n * drops into the blob transfer path (chunk staging, blob destination) unchanged.\n */\nexport class FilesystemBlobStore implements ContentAddressedBlobStore {\n\tconstructor(private readonly dir: string) {}\n\n\tprivate pathFor(hash: string): string {\n\t\treturn join(this.dir, hash.slice(0, 2), hash)\n\t}\n\n\tasync put(bytes: Uint8Array, metadata: BlobRefMetadata = {}): Promise<BlobRef> {\n\t\tconst ref = await createBlobRef(bytes, metadata)\n\t\tconst target = this.pathFor(ref.hash)\n\n\t\t// Dedup: identical content already stored is a no-op.\n\t\tif (await this.exists(target)) {\n\t\t\treturn ref\n\t\t}\n\n\t\tawait mkdir(dirname(target), { recursive: true })\n\t\t// Copy into an exactly-sized buffer so a subarray view writes its own bytes.\n\t\tconst out = new Uint8Array(bytes.byteLength)\n\t\tout.set(bytes)\n\t\t// Atomic write: temp file then rename, so a crash never leaves a partial\n\t\t// blob under a hash that readers would then trust.\n\t\tconst tmp = `${target}.${randomUUID()}.tmp`\n\t\tawait writeFile(tmp, out)\n\t\tawait rename(tmp, target)\n\t\treturn ref\n\t}\n\n\tasync get(hash: string): Promise<Uint8Array | null> {\n\t\tlet contents: Buffer\n\t\ttry {\n\t\t\tcontents = await readFile(this.pathFor(hash))\n\t\t} catch (error) {\n\t\t\tif (isNotFound(error)) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t\tconst bytes = new Uint8Array(contents)\n\t\tconst actual = await hashBlob(bytes)\n\t\tif (actual !== hash) {\n\t\t\tthrow new BlobIntegrityError(hash, actual)\n\t\t}\n\t\treturn bytes\n\t}\n\n\tasync has(hash: string): Promise<boolean> {\n\t\treturn this.exists(this.pathFor(hash))\n\t}\n\n\tasync delete(hash: string): Promise<boolean> {\n\t\tconst target = this.pathFor(hash)\n\t\tif (!(await this.exists(target))) {\n\t\t\treturn false\n\t\t}\n\t\tawait rm(target)\n\t\treturn true\n\t}\n\n\tasync size(): Promise<number> {\n\t\tlet count = 0\n\t\tlet shards: string[]\n\t\ttry {\n\t\t\tshards = await readdir(this.dir)\n\t\t} catch (error) {\n\t\t\tif (isNotFound(error)) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t\tfor (const shard of shards) {\n\t\t\ttry {\n\t\t\t\tconst entries = await readdir(join(this.dir, shard))\n\t\t\t\tcount += entries.filter((name) => !name.endsWith('.tmp')).length\n\t\t\t} catch (error) {\n\t\t\t\tif (!isNotFound(error)) {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn count\n\t}\n\n\tasync list(): Promise<string[]> {\n\t\tlet shards: string[]\n\t\ttry {\n\t\t\tshards = await readdir(this.dir)\n\t\t} catch (error) {\n\t\t\tif (isNotFound(error)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t\tconst hashes: string[] = []\n\t\tfor (const shard of shards) {\n\t\t\ttry {\n\t\t\t\tconst entries = await readdir(join(this.dir, shard))\n\t\t\t\tfor (const name of entries) {\n\t\t\t\t\tif (!name.endsWith('.tmp')) {\n\t\t\t\t\t\thashes.push(name)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!isNotFound(error)) {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn hashes\n\t}\n\n\tprivate async exists(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait stat(path)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tif (isNotFound(error)) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n}\n\nfunction isNotFound(error: unknown): boolean {\n\treturn (\n\t\ttypeof error === 'object' && error !== null && (error as { code?: string }).code === 'ENOENT'\n\t)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
// src/serialization/richtext-serializer.ts
|
|
2
2
|
import * as Y from "yjs";
|
|
3
|
+
|
|
4
|
+
// src/serialization/op-data-encoding.ts
|
|
5
|
+
import {
|
|
6
|
+
base64ToBytes,
|
|
7
|
+
bytesToBase64,
|
|
8
|
+
decodeBytesFromOpData,
|
|
9
|
+
encodeBytesForOpData,
|
|
10
|
+
isKoraBytesValue,
|
|
11
|
+
isLegacyNumericByteObject
|
|
12
|
+
} from "@korajs/core";
|
|
13
|
+
var decodeRichtextOpDataValue = decodeBytesFromOpData;
|
|
14
|
+
function encodeRichtextFieldsForOpData(data, fields) {
|
|
15
|
+
const result = { ...data };
|
|
16
|
+
for (const [key, value] of Object.entries(data)) {
|
|
17
|
+
if (fields[key]?.kind !== "richtext") {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === "string" || value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
21
|
+
result[key] = encodeBytesForOpData(value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function decodeRichtextFieldsFromOpData(data, fields) {
|
|
27
|
+
const result = { ...data };
|
|
28
|
+
for (const [key, value] of Object.entries(data)) {
|
|
29
|
+
if (fields[key]?.kind !== "richtext" || value === null || value === void 0) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
result[key] = decodeBytesFromOpData(value);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/serialization/richtext-serializer.ts
|
|
3
38
|
var TEXT_KEY = "content";
|
|
4
39
|
function encodeRichtext(value) {
|
|
5
40
|
if (value === null || value === void 0) {
|
|
@@ -16,6 +51,10 @@ function encodeRichtext(value) {
|
|
|
16
51
|
if (value instanceof ArrayBuffer) {
|
|
17
52
|
return new Uint8Array(value);
|
|
18
53
|
}
|
|
54
|
+
if (isKoraBytesValue(value) || isLegacyNumericByteObject(value)) {
|
|
55
|
+
const decoded = decodeRichtextOpDataValue(value);
|
|
56
|
+
return decoded instanceof Uint8Array ? decoded : null;
|
|
57
|
+
}
|
|
19
58
|
throw new Error("Richtext value must be a string, Uint8Array, ArrayBuffer, null, or undefined.");
|
|
20
59
|
}
|
|
21
60
|
function decodeRichtext(value) {
|
|
@@ -168,6 +207,9 @@ function serializeValue(value, descriptor) {
|
|
|
168
207
|
case "boolean":
|
|
169
208
|
return value ? 1 : 0;
|
|
170
209
|
case "array":
|
|
210
|
+
case "object":
|
|
211
|
+
case "json":
|
|
212
|
+
case "blob":
|
|
171
213
|
return JSON.stringify(value);
|
|
172
214
|
case "richtext":
|
|
173
215
|
return encodeRichtext(value);
|
|
@@ -184,6 +226,13 @@ function deserializeValue(value, descriptor) {
|
|
|
184
226
|
return JSON.parse(value);
|
|
185
227
|
}
|
|
186
228
|
return value;
|
|
229
|
+
case "object":
|
|
230
|
+
case "json":
|
|
231
|
+
case "blob":
|
|
232
|
+
if (typeof value === "string") {
|
|
233
|
+
return JSON.parse(value);
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
187
236
|
case "richtext":
|
|
188
237
|
return decodeRichtext(value);
|
|
189
238
|
default:
|
|
@@ -192,6 +241,8 @@ function deserializeValue(value, descriptor) {
|
|
|
192
241
|
}
|
|
193
242
|
|
|
194
243
|
export {
|
|
244
|
+
encodeRichtextFieldsForOpData,
|
|
245
|
+
decodeRichtextFieldsFromOpData,
|
|
195
246
|
encodeRichtext,
|
|
196
247
|
decodeRichtext,
|
|
197
248
|
richtextToPlainText,
|
|
@@ -202,4 +253,4 @@ export {
|
|
|
202
253
|
deserializeOperation,
|
|
203
254
|
deserializeOperationWithCollection
|
|
204
255
|
};
|
|
205
|
-
//# sourceMappingURL=chunk-
|
|
256
|
+
//# sourceMappingURL=chunk-6NKLDBTY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialization/richtext-serializer.ts","../src/serialization/op-data-encoding.ts","../src/serialization/serializer.ts"],"sourcesContent":["import * as Y from 'yjs'\nimport {\n\ttype KoraBytesValue,\n\tdecodeRichtextOpDataValue,\n\tisKoraBytesValue,\n\tisLegacyNumericByteObject,\n} from './op-data-encoding'\n\nconst TEXT_KEY = 'content'\n\nexport type RichtextInput = string | Uint8Array | ArrayBuffer | null | undefined\n\n/**\n * Encodes richtext values into Yjs document updates.\n *\n * Also accepts the tagged `{ $koraBytes }` form that binary richtext values\n * take inside `op.data` (and, for old dev databases, the pre-fix numeric-key\n * object shape), so every consumer that writes op-data values to columns\n * inherits the decoding. Plain strings keep their exact historical behavior:\n * encoded as a fresh Yjs document containing the string.\n */\nexport function encodeRichtext(value: RichtextInput | KoraBytesValue): Uint8Array | null {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tif (typeof value === 'string') {\n\t\tconst doc = new Y.Doc()\n\t\tdoc.getText(TEXT_KEY).insert(0, value)\n\t\treturn Y.encodeStateAsUpdate(doc)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tif (isKoraBytesValue(value) || isLegacyNumericByteObject(value)) {\n\t\tconst decoded = decodeRichtextOpDataValue(value)\n\t\t// Both accepted shapes decode to bytes; strings were handled above.\n\t\treturn decoded instanceof Uint8Array ? decoded : null\n\t}\n\n\tthrow new Error('Richtext value must be a string, Uint8Array, ArrayBuffer, null, or undefined.')\n}\n\n/**\n * Decodes driver-provided richtext values into Uint8Array.\n */\nexport function decodeRichtext(value: unknown): Uint8Array | null {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tif (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tthrow new Error(\n\t\t'Richtext storage value must be Uint8Array, ArrayBuffer, Buffer, null, or undefined.',\n\t)\n}\n\n/**\n * Reads plain text from a richtext Yjs state update.\n */\nexport function richtextToPlainText(value: RichtextInput): string {\n\tconst encoded = encodeRichtext(value)\n\tif (!encoded) return ''\n\n\tconst doc = new Y.Doc()\n\tY.applyUpdate(doc, encoded)\n\treturn doc.getText(TEXT_KEY).toString()\n}\n\n/**\n * Compares two richtext values by decoded plain text.\n * Yjs update bytes are not stable across separate encodes of the same string.\n */\nexport function richtextStatesEqual(a: unknown, b: unknown): boolean {\n\ttry {\n\t\treturn richtextToPlainText(a as RichtextInput) === richtextToPlainText(b as RichtextInput)\n\t} catch {\n\t\treturn false\n\t}\n}\n","import {\n\ttype KoraBytesValue,\n\tbase64ToBytes,\n\tbytesToBase64,\n\tdecodeBytesFromOpData,\n\tencodeBytesForOpData,\n\tisKoraBytesValue,\n\tisLegacyNumericByteObject,\n} from '@korajs/core'\nimport type { FieldDescriptor } from '@korajs/core'\n\n/**\n * The canonical binary op-data encoding lives in `@korajs/core` because\n * `op.data` is a core concept shared by store (creation, persistence) and merge\n * (CRDT merge). These re-exports keep existing store-internal import paths\n * working while the single source of truth is the core module.\n */\nexport type { KoraBytesValue }\nexport { base64ToBytes, bytesToBase64, isKoraBytesValue, isLegacyNumericByteObject }\n\n/** @deprecated Use {@link encodeRichtextForOpData}; kept for existing imports. */\nexport const encodeRichtextForOpData = encodeBytesForOpData\n/** @deprecated Use {@link decodeRichtextOpDataValue}; kept for existing imports. */\nexport const decodeRichtextOpDataValue = decodeBytesFromOpData\n\n/**\n * Apply {@link encodeBytesForOpData} to every richtext-typed field in an\n * operation's data payload. Called at operation creation, BEFORE the payload\n * is content-hashed, so the hash input, persisted JSON, wire payload, and the\n * value the merge engine sees are the identical canonical value. Non-richtext\n * fields are untouched.\n */\nexport function encodeRichtextFieldsForOpData(\n\tdata: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = { ...data }\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (fields[key]?.kind !== 'richtext') {\n\t\t\tcontinue\n\t\t}\n\t\tif (typeof value === 'string' || value instanceof Uint8Array || value instanceof ArrayBuffer) {\n\t\t\tresult[key] = encodeBytesForOpData(value)\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Reverse of {@link encodeRichtextFieldsForOpData}: decode tagged (or legacy\n * numeric-key) richtext values in an op-data payload back to record-shaped\n * values (string or Uint8Array). Null/undefined and plain strings pass\n * through, preserving pre-fix behavior exactly.\n */\nexport function decodeRichtextFieldsFromOpData(\n\tdata: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = { ...data }\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (fields[key]?.kind !== 'richtext' || value === null || value === undefined) {\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] = decodeBytesFromOpData(value)\n\t}\n\treturn result\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { CollectionDefinition, FieldDescriptor, Operation } from '@korajs/core'\nimport type { CollectionRecord, OperationRow, RawCollectionRow } from '../types'\nimport { decodeRichtext, encodeRichtext } from './richtext-serializer'\n\n/**\n * Serialize a JS record to SQL-compatible values for INSERT/UPDATE.\n * Transforms: boolean → 0/1, array → JSON string, richtext → Yjs binary update.\n *\n * @param data - The record data with JS-native types\n * @param fields - The field descriptors from the schema\n * @returns An object with SQL-compatible values\n */\nexport function serializeRecord(\n\tdata: Record<string, unknown>,\n\tfields: Record<string, FieldDescriptor>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {}\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tconst descriptor = fields[key]\n\t\tif (!descriptor) {\n\t\t\tresult[key] = value\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] = serializeValue(value, descriptor)\n\t}\n\treturn result\n}\n\n/**\n * Deserialize a SQL row to JS-native types for the application layer.\n * Transforms: 0/1 → boolean, JSON string → array, strips _deleted, maps _created_at/_updated_at.\n *\n * @param row - The raw SQL row\n * @param fields - The field descriptors from the schema\n * @returns A CollectionRecord with JS-native types\n */\nexport function deserializeRecord(\n\trow: RawCollectionRow,\n\tfields: Record<string, FieldDescriptor>,\n): CollectionRecord {\n\tconst result: CollectionRecord = {\n\t\tid: row.id,\n\t\tcreatedAt: row._created_at,\n\t\tupdatedAt: row._updated_at,\n\t}\n\n\tfor (const [key, descriptor] of Object.entries(fields)) {\n\t\tconst rawValue = row[key]\n\t\tif (rawValue === undefined || rawValue === null) {\n\t\t\tresult[key] = rawValue ?? null\n\t\t\tcontinue\n\t\t}\n\t\tresult[key] = deserializeValue(rawValue, descriptor)\n\t}\n\n\treturn result\n}\n\n/**\n * Internal key used to embed atomicOps metadata in the data JSON column.\n * This avoids adding a new column to the ops table (no migration needed).\n */\nconst ATOMIC_OPS_KEY = '__kora_atomic_ops__'\n\n/**\n * Internal key used to embed transactionId in the data JSON column.\n */\nconst TX_ID_KEY = '__kora_tx_id__'\n\n/**\n * Internal key used to embed mutationName in the data JSON column.\n */\nconst MUTATION_NAME_KEY = '__kora_mutation__'\n\n/**\n * Serialize an Operation to a row for the operations log table.\n *\n * @param op - The operation to serialize\n * @returns An OperationRow suitable for SQL INSERT\n */\nexport function serializeOperation(op: Operation): OperationRow {\n\tconst hasMetadata = op.transactionId !== undefined || op.mutationName !== undefined\n\tlet dataPayload: Record<string, unknown> | null = null\n\tif (op.data) {\n\t\t// Embed metadata in the data JSON when present\n\t\tdataPayload = { ...op.data }\n\t\tif (op.atomicOps !== undefined && Object.keys(op.atomicOps).length > 0) {\n\t\t\tdataPayload[ATOMIC_OPS_KEY] = op.atomicOps\n\t\t}\n\t\tif (op.transactionId !== undefined) {\n\t\t\tdataPayload[TX_ID_KEY] = op.transactionId\n\t\t}\n\t\tif (op.mutationName !== undefined) {\n\t\t\tdataPayload[MUTATION_NAME_KEY] = op.mutationName\n\t\t}\n\t} else if (hasMetadata) {\n\t\t// For delete operations (data is null), we still need to store metadata\n\t\tdataPayload = {}\n\t\tif (op.transactionId !== undefined) {\n\t\t\tdataPayload[TX_ID_KEY] = op.transactionId\n\t\t}\n\t\tif (op.mutationName !== undefined) {\n\t\t\tdataPayload[MUTATION_NAME_KEY] = op.mutationName\n\t\t}\n\t}\n\n\treturn {\n\t\tid: op.id,\n\t\tnode_id: op.nodeId,\n\t\ttype: op.type,\n\t\trecord_id: op.recordId,\n\t\tdata: dataPayload ? JSON.stringify(dataPayload) : null,\n\t\tprevious_data: op.previousData ? JSON.stringify(op.previousData) : null,\n\t\ttimestamp: HybridLogicalClock.serialize(op.timestamp),\n\t\tsequence_number: op.sequenceNumber,\n\t\tcausal_deps: JSON.stringify(op.causalDeps),\n\t\tschema_version: op.schemaVersion,\n\t}\n}\n\n/**\n * Deserialize a row from the operations log table back to an Operation.\n *\n * @param row - The raw operation row from SQL\n * @returns The deserialized Operation object\n */\nexport function deserializeOperation(row: OperationRow): Operation {\n\tlet data: Record<string, unknown> | null = null\n\tlet atomicOps: Record<string, unknown> | undefined\n\tlet transactionId: string | undefined\n\tlet mutationName: string | undefined\n\n\tif (row.data) {\n\t\tconst parsed = JSON.parse(row.data) as Record<string, unknown>\n\t\t// Extract embedded metadata keys\n\t\tif (ATOMIC_OPS_KEY in parsed) {\n\t\t\tatomicOps = parsed[ATOMIC_OPS_KEY] as Record<string, unknown>\n\t\t}\n\t\tif (TX_ID_KEY in parsed) {\n\t\t\ttransactionId = parsed[TX_ID_KEY] as string\n\t\t}\n\t\tif (MUTATION_NAME_KEY in parsed) {\n\t\t\tmutationName = parsed[MUTATION_NAME_KEY] as string\n\t\t}\n\t\t// Remove metadata keys from data\n\t\tconst { [ATOMIC_OPS_KEY]: _a, [TX_ID_KEY]: _t, [MUTATION_NAME_KEY]: _m, ...rest } = parsed\n\t\tdata = Object.keys(rest).length > 0 ? rest : null\n\t}\n\n\treturn {\n\t\tid: row.id,\n\t\tnodeId: row.node_id,\n\t\ttype: row.type as Operation['type'],\n\t\tcollection: '', // Collection name is derived from the table name by the caller\n\t\trecordId: row.record_id,\n\t\tdata,\n\t\tpreviousData: row.previous_data\n\t\t\t? (JSON.parse(row.previous_data) as Record<string, unknown>)\n\t\t\t: null,\n\t\ttimestamp: HybridLogicalClock.deserialize(row.timestamp),\n\t\tsequenceNumber: row.sequence_number,\n\t\tcausalDeps: JSON.parse(row.causal_deps) as string[],\n\t\tschemaVersion: row.schema_version,\n\t\t...(atomicOps !== undefined ? { atomicOps: atomicOps as Operation['atomicOps'] } : {}),\n\t\t...(transactionId !== undefined ? { transactionId } : {}),\n\t\t...(mutationName !== undefined ? { mutationName } : {}),\n\t}\n}\n\n/**\n * Deserialize an operation row with collection name already known.\n */\nexport function deserializeOperationWithCollection(\n\trow: OperationRow,\n\tcollection: string,\n): Operation {\n\tconst op = deserializeOperation(row)\n\treturn { ...op, collection }\n}\n\nfunction serializeValue(value: unknown, descriptor: FieldDescriptor): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn null\n\t}\n\n\tswitch (descriptor.kind) {\n\t\tcase 'boolean':\n\t\t\treturn value ? 1 : 0\n\t\tcase 'array':\n\t\tcase 'object':\n\t\tcase 'json':\n\t\tcase 'blob':\n\t\t\treturn JSON.stringify(value)\n\t\tcase 'richtext':\n\t\t\t// May be a record-shaped value (string/bytes) or an op-data value\n\t\t\t// (tagged { $koraBytes }) — encodeRichtext accepts every form.\n\t\t\treturn encodeRichtext(value as Parameters<typeof encodeRichtext>[0])\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n\nfunction deserializeValue(value: unknown, descriptor: FieldDescriptor): unknown {\n\tswitch (descriptor.kind) {\n\t\tcase 'boolean':\n\t\t\treturn value === 1 || value === true\n\t\tcase 'array':\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn JSON.parse(value) as unknown[]\n\t\t\t}\n\t\t\treturn value\n\t\tcase 'object':\n\t\tcase 'json':\n\t\tcase 'blob':\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn JSON.parse(value) as unknown\n\t\t\t}\n\t\t\treturn value\n\t\tcase 'richtext':\n\t\t\treturn decodeRichtext(value)\n\t\tdefault:\n\t\t\treturn value\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-MYSJN3DF.js";
|
|
6
6
|
import {
|
|
7
7
|
deserializeOperationWithCollection
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6NKLDBTY.js";
|
|
9
9
|
|
|
10
10
|
// src/audit/export-audit.ts
|
|
11
11
|
function encodeSection(name, content) {
|
|
@@ -185,4 +185,4 @@ export {
|
|
|
185
185
|
readAuditExportManifest,
|
|
186
186
|
verifyAuditExportChecksum
|
|
187
187
|
};
|
|
188
|
-
//# sourceMappingURL=chunk-
|
|
188
|
+
//# sourceMappingURL=chunk-FFYA7J5H.js.map
|
|
@@ -1,40 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WorkerTimeoutError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QL2XVZRN.js";
|
|
4
4
|
|
|
5
5
|
// src/adapters/sqlite-wasm-channel.ts
|
|
6
|
-
var Mutex = class {
|
|
7
|
-
locked = false;
|
|
8
|
-
waiters = [];
|
|
9
|
-
/**
|
|
10
|
-
* Acquire the mutex. Returns a release function.
|
|
11
|
-
* If the mutex is already held, the caller waits until it's released.
|
|
12
|
-
*/
|
|
13
|
-
async acquire() {
|
|
14
|
-
if (!this.locked) {
|
|
15
|
-
this.locked = true;
|
|
16
|
-
return this.createRelease();
|
|
17
|
-
}
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
this.waiters.push(() => {
|
|
20
|
-
resolve(this.createRelease());
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
createRelease() {
|
|
25
|
-
let released = false;
|
|
26
|
-
return () => {
|
|
27
|
-
if (released) return;
|
|
28
|
-
released = true;
|
|
29
|
-
const next = this.waiters.shift();
|
|
30
|
-
if (next) {
|
|
31
|
-
next();
|
|
32
|
-
} else {
|
|
33
|
-
this.locked = false;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
6
|
var WebWorkerBridge = class {
|
|
39
7
|
worker;
|
|
40
8
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -105,7 +73,6 @@ var WebWorkerBridge = class {
|
|
|
105
73
|
};
|
|
106
74
|
|
|
107
75
|
export {
|
|
108
|
-
Mutex,
|
|
109
76
|
WebWorkerBridge
|
|
110
77
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
78
|
+
//# sourceMappingURL=chunk-IOEFNHHP.js.map
|
|
@@ -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// The Mutex now lives in its own module so non-browser adapters (better-sqlite3)\n// can serialize transactions without importing browser-worker code.\nexport { Mutex } from './mutex'\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":";;;;;AAwDO,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":[]}
|
|
@@ -15,6 +15,16 @@ var RecordNotFoundError = class extends KoraError {
|
|
|
15
15
|
this.name = "RecordNotFoundError";
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
+
var OptimisticLockError = class extends KoraError {
|
|
19
|
+
constructor(collection, recordId) {
|
|
20
|
+
super(
|
|
21
|
+
`Row version changed while merging record "${recordId}" in "${collection}"; retry with fresh state`,
|
|
22
|
+
"OPTIMISTIC_LOCK",
|
|
23
|
+
{ collection, recordId }
|
|
24
|
+
);
|
|
25
|
+
this.name = "OptimisticLockError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
18
28
|
var AdapterError = class extends KoraError {
|
|
19
29
|
constructor(message, context) {
|
|
20
30
|
super(message, "ADAPTER_ERROR", context);
|
|
@@ -53,10 +63,11 @@ var PersistenceError = class extends KoraError {
|
|
|
53
63
|
export {
|
|
54
64
|
QueryError,
|
|
55
65
|
RecordNotFoundError,
|
|
66
|
+
OptimisticLockError,
|
|
56
67
|
AdapterError,
|
|
57
68
|
StoreNotOpenError,
|
|
58
69
|
WorkerInitError,
|
|
59
70
|
WorkerTimeoutError,
|
|
60
71
|
PersistenceError
|
|
61
72
|
};
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
73
|
+
//# sourceMappingURL=chunk-QL2XVZRN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\n\n/**\n * Thrown when a query is invalid (bad field names, invalid operators, etc.).\n */\nexport class QueryError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'QUERY_ERROR', context)\n\t\tthis.name = 'QueryError'\n\t}\n}\n\n/**\n * Thrown when a record is not found by ID (findById, update, delete on missing record).\n */\nexport class RecordNotFoundError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(`Record \"${recordId}\" not found in collection \"${collection}\"`, 'RECORD_NOT_FOUND', {\n\t\t\tcollection,\n\t\t\trecordId,\n\t\t})\n\t\tthis.name = 'RecordNotFoundError'\n\t}\n}\n\n/**\n * Thrown inside `applyRemoteOperation` when the row's version state changed\n * between the caller's snapshot read and the guarded write (a concurrent local\n * mutation landed in the window). The transaction is rolled back with nothing\n * written; the caller re-reads fresh state, recomputes its merge, and retries.\n * This is the optimistic-concurrency guard that keeps merge-engine results\n * (richtext, add-wins arrays, constraint resolutions) from clobbering newer\n * local edits they never saw.\n */\nexport class OptimisticLockError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(\n\t\t\t`Row version changed while merging record \"${recordId}\" in \"${collection}\"; retry with fresh state`,\n\t\t\t'OPTIMISTIC_LOCK',\n\t\t\t{ collection, recordId },\n\t\t)\n\t\tthis.name = 'OptimisticLockError'\n\t}\n}\n\n/**\n * Thrown when a storage adapter operation fails.\n */\nexport class AdapterError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ADAPTER_ERROR', context)\n\t\tthis.name = 'AdapterError'\n\t}\n}\n\n/**\n * Thrown when an operation is attempted on a store that has not been opened.\n */\nexport class StoreNotOpenError extends KoraError {\n\tconstructor() {\n\t\tsuper('Store is not open. Call store.open() before performing operations.', 'STORE_NOT_OPEN')\n\t\tthis.name = 'StoreNotOpenError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker fails to initialize (WASM load failure, OPFS unavailable, etc.).\n */\nexport class WorkerInitError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Worker initialization failed: ${message}`, 'WORKER_INIT_ERROR', context)\n\t\tthis.name = 'WorkerInitError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker does not respond within the configured timeout.\n */\nexport class WorkerTimeoutError extends KoraError {\n\tconstructor(operation: string, timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Worker did not respond within ${timeoutMs}ms for operation \"${operation}\"`,\n\t\t\t'WORKER_TIMEOUT',\n\t\t\t{ operation, timeoutMs },\n\t\t)\n\t\tthis.name = 'WorkerTimeoutError'\n\t}\n}\n\n/**\n * Thrown when IndexedDB persistence operations fail (serialize/deserialize).\n */\nexport class PersistenceError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Persistence error: ${message}`, 'PERSISTENCE_ERROR', context)\n\t\tthis.name = 'PersistenceError'\n\t}\n}\n"],"mappings":";AAAA,SAAS,iBAAiB;AAKnB,IAAM,aAAN,cAAyB,UAAU;AAAA,EACzC,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,eAAe,OAAO;AACrC,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,sBAAN,cAAkC,UAAU;AAAA,EAClD,YAAY,YAAoB,UAAkB;AACjD,UAAM,WAAW,QAAQ,8BAA8B,UAAU,KAAK,oBAAoB;AAAA,MACzF;AAAA,MACA;AAAA,IACD,CAAC;AACD,SAAK,OAAO;AAAA,EACb;AACD;AAWO,IAAM,sBAAN,cAAkC,UAAU;AAAA,EAClD,YAAY,YAAoB,UAAkB;AACjD;AAAA,MACC,6CAA6C,QAAQ,SAAS,UAAU;AAAA,MACxE;AAAA,MACA,EAAE,YAAY,SAAS;AAAA,IACxB;AACA,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC3C,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,iBAAiB,OAAO;AACvC,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,oBAAN,cAAgC,UAAU;AAAA,EAChD,cAAc;AACb,UAAM,sEAAsE,gBAAgB;AAC5F,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,kBAAN,cAA8B,UAAU;AAAA,EAC9C,YAAY,SAAiB,SAAmC;AAC/D,UAAM,iCAAiC,OAAO,IAAI,qBAAqB,OAAO;AAC9E,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,qBAAN,cAAiC,UAAU;AAAA,EACjD,YAAY,WAAmB,WAAmB;AACjD;AAAA,MACC,iCAAiC,SAAS,qBAAqB,SAAS;AAAA,MACxE;AAAA,MACA,EAAE,WAAW,UAAU;AAAA,IACxB;AACA,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC/C,YAAY,SAAiB,SAAmC;AAC/D,UAAM,sBAAsB,OAAO,IAAI,qBAAqB,OAAO;AACnE,SAAK,OAAO;AAAA,EACb;AACD;","names":[]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Mutex
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TVJEC5S2.js";
|
|
4
4
|
import {
|
|
5
5
|
AdapterError,
|
|
6
6
|
StoreNotOpenError,
|
|
7
7
|
WorkerTimeoutError
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QL2XVZRN.js";
|
|
9
9
|
|
|
10
10
|
// src/adapters/sqlite-wasm-adapter.ts
|
|
11
11
|
import { generateFullDDL } from "@korajs/core";
|
|
@@ -251,7 +251,7 @@ var SqliteWasmAdapter = class {
|
|
|
251
251
|
this.bridge = new SharedWorkerClientBridge(this.sharedWorker, this.dbName, workerUrlString);
|
|
252
252
|
} else if (this.workerUrl) {
|
|
253
253
|
this.tabSession = await acquireTabStorageSession(this.dbName);
|
|
254
|
-
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-
|
|
254
|
+
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-H43KEZLM.js");
|
|
255
255
|
if (this.tabSession.role === "leader") {
|
|
256
256
|
const workerBridge = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs);
|
|
257
257
|
this.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge);
|
|
@@ -422,4 +422,4 @@ var SqliteWasmAdapter = class {
|
|
|
422
422
|
export {
|
|
423
423
|
SqliteWasmAdapter
|
|
424
424
|
};
|
|
425
|
-
//# sourceMappingURL=chunk-
|
|
425
|
+
//# sourceMappingURL=chunk-RBPVEAVM.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/adapters/mutex.ts
|
|
2
|
+
var Mutex = class {
|
|
3
|
+
locked = false;
|
|
4
|
+
waiters = [];
|
|
5
|
+
/**
|
|
6
|
+
* Acquire the mutex. Resolves with a release function once the lock is held.
|
|
7
|
+
* If the mutex is already held, the caller waits (FIFO) until it is released.
|
|
8
|
+
*/
|
|
9
|
+
async acquire() {
|
|
10
|
+
if (!this.locked) {
|
|
11
|
+
this.locked = true;
|
|
12
|
+
return this.createRelease();
|
|
13
|
+
}
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
this.waiters.push(() => {
|
|
16
|
+
resolve(this.createRelease());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
createRelease() {
|
|
21
|
+
let released = false;
|
|
22
|
+
return () => {
|
|
23
|
+
if (released) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
released = true;
|
|
27
|
+
const next = this.waiters.shift();
|
|
28
|
+
if (next) {
|
|
29
|
+
next();
|
|
30
|
+
} else {
|
|
31
|
+
this.locked = false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
Mutex
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=chunk-TVJEC5S2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/mutex.ts"],"sourcesContent":["/**\n * Async mutex for serializing access to a resource across `await` boundaries.\n *\n * This exists because an adapter's `transaction()` is async (its callback may\n * await), but the underlying database transaction (BEGIN…COMMIT) is not\n * reentrant: starting a second transaction while one is open is an error. When\n * a client applies a relayed remote operation while a local write is in flight,\n * the two `transaction()` calls interleave at their `await` points, and without\n * serialization the second `BEGIN` collides with the first, silently dropping\n * the operation. Serializing every transaction through this mutex guarantees at\n * most one is active at a time.\n */\nexport class Mutex {\n\tprivate locked = false\n\tprivate readonly waiters: Array<() => void> = []\n\n\t/**\n\t * Acquire the mutex. Resolves with a release function once the lock is held.\n\t * If the mutex is already held, the caller waits (FIFO) until it is 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) {\n\t\t\t\treturn\n\t\t\t}\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"],"mappings":";AAYO,IAAM,QAAN,MAAY;AAAA,EACV,SAAS;AAAA,EACA,UAA6B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/C,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,UAAU;AACb;AAAA,MACD;AACA,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;","names":[]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/blob/content-addressed-blob-store.ts
|
|
2
|
+
import { createBlobRef, hashBlob } from "@korajs/core";
|
|
3
|
+
var BlobIntegrityError = class extends Error {
|
|
4
|
+
constructor(expectedHash, actualHash) {
|
|
5
|
+
super(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`);
|
|
6
|
+
this.expectedHash = expectedHash;
|
|
7
|
+
this.actualHash = actualHash;
|
|
8
|
+
this.name = "BlobIntegrityError";
|
|
9
|
+
}
|
|
10
|
+
expectedHash;
|
|
11
|
+
actualHash;
|
|
12
|
+
};
|
|
13
|
+
var MemoryBlobStore = class {
|
|
14
|
+
blobs = /* @__PURE__ */ new Map();
|
|
15
|
+
async put(bytes, metadata = {}) {
|
|
16
|
+
const ref = await createBlobRef(bytes, metadata);
|
|
17
|
+
if (!this.blobs.has(ref.hash)) {
|
|
18
|
+
const stored = new Uint8Array(bytes.byteLength);
|
|
19
|
+
stored.set(bytes);
|
|
20
|
+
this.blobs.set(ref.hash, stored);
|
|
21
|
+
}
|
|
22
|
+
return ref;
|
|
23
|
+
}
|
|
24
|
+
async get(hash) {
|
|
25
|
+
const stored = this.blobs.get(hash);
|
|
26
|
+
if (stored === void 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const actualHash = await hashBlob(stored);
|
|
30
|
+
if (actualHash !== hash) {
|
|
31
|
+
throw new BlobIntegrityError(hash, actualHash);
|
|
32
|
+
}
|
|
33
|
+
const out = new Uint8Array(stored.byteLength);
|
|
34
|
+
out.set(stored);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
async has(hash) {
|
|
38
|
+
return this.blobs.has(hash);
|
|
39
|
+
}
|
|
40
|
+
async delete(hash) {
|
|
41
|
+
return this.blobs.delete(hash);
|
|
42
|
+
}
|
|
43
|
+
async size() {
|
|
44
|
+
return this.blobs.size;
|
|
45
|
+
}
|
|
46
|
+
async list() {
|
|
47
|
+
return [...this.blobs.keys()];
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
BlobIntegrityError,
|
|
53
|
+
MemoryBlobStore
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=chunk-W4MTTNEZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/blob/content-addressed-blob-store.ts"],"sourcesContent":["import { type BlobRef, type BlobRefMetadata, createBlobRef, hashBlob } from '@korajs/core'\n\n/**\n * Thrown when a blob read fails its integrity check: the bytes stored under a\n * hash do not actually hash to that value (corruption or tampering).\n */\nexport class BlobIntegrityError extends Error {\n\tconstructor(\n\t\treadonly expectedHash: string,\n\t\treadonly actualHash: string,\n\t) {\n\t\tsuper(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`)\n\t\tthis.name = 'BlobIntegrityError'\n\t}\n}\n\n/**\n * A content-addressed store for the bytes behind `blob` fields.\n *\n * Blobs are keyed by the SHA-256 hash of their content, so identical content is\n * stored exactly once (deduplication) and a peer never needs to transfer bytes\n * it already holds. Reads are integrity-checked: the returned bytes are verified\n * to hash to the requested key.\n */\nexport interface ContentAddressedBlobStore {\n\t/**\n\t * Store bytes and return their content-addressed reference. Storing content\n\t * that already exists is a no-op that returns the same reference (dedup).\n\t */\n\tput(bytes: Uint8Array, metadata?: BlobRefMetadata): Promise<BlobRef>\n\t/**\n\t * Retrieve the bytes for a hash, or null if absent. The returned bytes are\n\t * verified to hash to the requested value; a mismatch throws\n\t * {@link BlobIntegrityError} rather than returning corrupt data.\n\t */\n\tget(hash: string): Promise<Uint8Array | null>\n\t/** Whether the store holds content for the given hash. */\n\thas(hash: string): Promise<boolean>\n\t/** Remove the content for a hash. Returns whether anything was removed. */\n\tdelete(hash: string): Promise<boolean>\n\t/** Number of distinct blobs held. */\n\tsize(): Promise<number>\n\t/**\n\t * List the hashes of every blob currently held. Used by garbage collection to\n\t * find content no live reference points to. Order is unspecified.\n\t */\n\tlist(): Promise<string[]>\n}\n\n/**\n * In-memory {@link ContentAddressedBlobStore}. The reference backend used for\n * tests and for environments without a persistent blob backend yet. Persistent\n * backends (OPFS client-side, filesystem/S3 server-side) implement the same\n * interface.\n */\nexport class MemoryBlobStore implements ContentAddressedBlobStore {\n\tprivate readonly blobs = new Map<string, Uint8Array>()\n\n\tasync put(bytes: Uint8Array, metadata: BlobRefMetadata = {}): Promise<BlobRef> {\n\t\tconst ref = await createBlobRef(bytes, metadata)\n\t\tif (!this.blobs.has(ref.hash)) {\n\t\t\t// Copy so later mutation of the caller's buffer cannot corrupt the store.\n\t\t\tconst stored = new Uint8Array(bytes.byteLength)\n\t\t\tstored.set(bytes)\n\t\t\tthis.blobs.set(ref.hash, stored)\n\t\t}\n\t\treturn ref\n\t}\n\n\tasync get(hash: string): Promise<Uint8Array | null> {\n\t\tconst stored = this.blobs.get(hash)\n\t\tif (stored === undefined) {\n\t\t\treturn null\n\t\t}\n\t\tconst actualHash = await hashBlob(stored)\n\t\tif (actualHash !== hash) {\n\t\t\tthrow new BlobIntegrityError(hash, actualHash)\n\t\t}\n\t\t// Return a copy so callers cannot mutate the stored bytes.\n\t\tconst out = new Uint8Array(stored.byteLength)\n\t\tout.set(stored)\n\t\treturn out\n\t}\n\n\tasync has(hash: string): Promise<boolean> {\n\t\treturn this.blobs.has(hash)\n\t}\n\n\tasync delete(hash: string): Promise<boolean> {\n\t\treturn this.blobs.delete(hash)\n\t}\n\n\tasync size(): Promise<number> {\n\t\treturn this.blobs.size\n\t}\n\n\tasync list(): Promise<string[]> {\n\t\treturn [...this.blobs.keys()]\n\t}\n}\n"],"mappings":";AAAA,SAA6C,eAAe,gBAAgB;AAMrE,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC7C,YACU,cACA,YACR;AACD,UAAM,mDAAmD,YAAY,cAAc,UAAU,EAAE;AAHtF;AACA;AAGT,SAAK,OAAO;AAAA,EACb;AAAA,EALU;AAAA,EACA;AAKX;AAyCO,IAAM,kBAAN,MAA2D;AAAA,EAChD,QAAQ,oBAAI,IAAwB;AAAA,EAErD,MAAM,IAAI,OAAmB,WAA4B,CAAC,GAAqB;AAC9E,UAAM,MAAM,MAAM,cAAc,OAAO,QAAQ;AAC/C,QAAI,CAAC,KAAK,MAAM,IAAI,IAAI,IAAI,GAAG;AAE9B,YAAM,SAAS,IAAI,WAAW,MAAM,UAAU;AAC9C,aAAO,IAAI,KAAK;AAChB,WAAK,MAAM,IAAI,IAAI,MAAM,MAAM;AAAA,IAChC;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,IAAI,MAA0C;AACnD,UAAM,SAAS,KAAK,MAAM,IAAI,IAAI;AAClC,QAAI,WAAW,QAAW;AACzB,aAAO;AAAA,IACR;AACA,UAAM,aAAa,MAAM,SAAS,MAAM;AACxC,QAAI,eAAe,MAAM;AACxB,YAAM,IAAI,mBAAmB,MAAM,UAAU;AAAA,IAC9C;AAEA,UAAM,MAAM,IAAI,WAAW,OAAO,UAAU;AAC5C,QAAI,IAAI,MAAM;AACd,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,IAAI,MAAgC;AACzC,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAM,OAAO,MAAgC;AAC5C,WAAO,KAAK,MAAM,OAAO,IAAI;AAAA,EAC9B;AAAA,EAEA,MAAM,OAAwB;AAC7B,WAAO,KAAK,MAAM;AAAA,EACnB;AAAA,EAEA,MAAM,OAA0B;AAC/B,WAAO,CAAC,GAAG,KAAK,MAAM,KAAK,CAAC;AAAA,EAC7B;AACD;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deserializeOperationWithCollection
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6NKLDBTY.js";
|
|
4
4
|
|
|
5
5
|
// src/backup/backup.ts
|
|
6
6
|
import "@korajs/core";
|
|
@@ -377,4 +377,4 @@ export {
|
|
|
377
377
|
verifyBackupChecksum,
|
|
378
378
|
restoreBackup
|
|
379
379
|
};
|
|
380
|
-
//# sourceMappingURL=chunk-
|
|
380
|
+
//# sourceMappingURL=chunk-X57MWW35.js.map
|