@korajs/store 1.0.0-beta.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/dist/adapters/better-sqlite3.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/indexeddb.d.cts +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +1 -1
- package/dist/adapters/sqlite-wasm.d.ts +1 -1
- package/dist/{backup-5XZ7THNJ.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-ETDJEH2I.js → chunk-6NKLDBTY.js} +11 -1
- package/dist/{chunk-ETDJEH2I.js.map → chunk-6NKLDBTY.js.map} +1 -1
- package/dist/{chunk-EIVXWF4B.js → chunk-FFYA7J5H.js} +2 -2
- package/dist/chunk-W4MTTNEZ.js +55 -0
- package/dist/chunk-W4MTTNEZ.js.map +1 -0
- package/dist/{chunk-5R4QMY3Z.js → chunk-X57MWW35.js} +2 -2
- package/dist/{chunk-OUQDC5Z6.js → chunk-ZLWAYQEQ.js} +28 -12
- 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-VPGQWNR6.js → export-audit-AJ7RORNK.js} +3 -3
- package/dist/index.cjs +632 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +397 -6
- package/dist/index.d.ts +397 -6
- package/dist/index.js +561 -26
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +25 -5
- 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/{types-CyjXwrjW.d.cts → types-5h4Hqcwc.d.ts} +10 -3
- package/dist/{types-DAxCladl.d.ts → types-DfPiPwKr.d.cts} +10 -3
- package/dist/{types-CJOVKxLw.d.ts → types-k6AZgiH4.d.cts} +6 -1
- package/dist/{types-CJOVKxLw.d.cts → types-k6AZgiH4.d.ts} +6 -1
- package/package.json +13 -2
- package/dist/chunk-OUQDC5Z6.js.map +0 -1
- /package/dist/{backup-5XZ7THNJ.js.map → backup-ZV4Y424B.js.map} +0 -0
- /package/dist/{chunk-EIVXWF4B.js.map → chunk-FFYA7J5H.js.map} +0 -0
- /package/dist/{chunk-5R4QMY3Z.js.map → chunk-X57MWW35.js.map} +0 -0
- /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-AJ7RORNK.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
exportBackup,
|
|
3
|
+
readBackupManifest,
|
|
4
|
+
restoreBackup,
|
|
5
|
+
verifyBackupChecksum
|
|
6
|
+
} from "./chunk-X57MWW35.js";
|
|
1
7
|
import {
|
|
2
8
|
decodeAuditExport,
|
|
3
9
|
readAuditExportManifest,
|
|
4
10
|
verifyAuditExportChecksum
|
|
5
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-FFYA7J5H.js";
|
|
6
12
|
import {
|
|
7
13
|
persistedAuditTraceFromEvent
|
|
8
14
|
} from "./chunk-MYSJN3DF.js";
|
|
15
|
+
import {
|
|
16
|
+
BlobIntegrityError,
|
|
17
|
+
MemoryBlobStore
|
|
18
|
+
} from "./chunk-W4MTTNEZ.js";
|
|
9
19
|
import {
|
|
10
20
|
InvalidStateTransitionError,
|
|
11
21
|
allocateNextSequenceInTransaction,
|
|
@@ -29,23 +39,7 @@ import {
|
|
|
29
39
|
serializeRowVersion,
|
|
30
40
|
validateStateTransition,
|
|
31
41
|
validateUpdateStateMachine
|
|
32
|
-
} from "./chunk-
|
|
33
|
-
import {
|
|
34
|
-
AdapterError,
|
|
35
|
-
OptimisticLockError,
|
|
36
|
-
PersistenceError,
|
|
37
|
-
QueryError,
|
|
38
|
-
RecordNotFoundError,
|
|
39
|
-
StoreNotOpenError,
|
|
40
|
-
WorkerInitError,
|
|
41
|
-
WorkerTimeoutError
|
|
42
|
-
} from "./chunk-QL2XVZRN.js";
|
|
43
|
-
import {
|
|
44
|
-
exportBackup,
|
|
45
|
-
readBackupManifest,
|
|
46
|
-
restoreBackup,
|
|
47
|
-
verifyBackupChecksum
|
|
48
|
-
} from "./chunk-5R4QMY3Z.js";
|
|
42
|
+
} from "./chunk-ZLWAYQEQ.js";
|
|
49
43
|
import {
|
|
50
44
|
decodeRichtext,
|
|
51
45
|
decodeRichtextFieldsFromOpData,
|
|
@@ -57,7 +51,17 @@ import {
|
|
|
57
51
|
richtextToPlainText,
|
|
58
52
|
serializeOperation,
|
|
59
53
|
serializeRecord
|
|
60
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-6NKLDBTY.js";
|
|
55
|
+
import {
|
|
56
|
+
AdapterError,
|
|
57
|
+
OptimisticLockError,
|
|
58
|
+
PersistenceError,
|
|
59
|
+
QueryError,
|
|
60
|
+
RecordNotFoundError,
|
|
61
|
+
StoreNotOpenError,
|
|
62
|
+
WorkerInitError,
|
|
63
|
+
WorkerTimeoutError
|
|
64
|
+
} from "./chunk-QL2XVZRN.js";
|
|
61
65
|
|
|
62
66
|
// src/compaction/types.ts
|
|
63
67
|
var COMPACTION_BASELINE_META_KEY = "compaction_baseline_at";
|
|
@@ -210,7 +214,7 @@ import {
|
|
|
210
214
|
|
|
211
215
|
// src/collection/collection.ts
|
|
212
216
|
var Collection = class {
|
|
213
|
-
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
|
|
217
|
+
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
|
|
214
218
|
this.name = name;
|
|
215
219
|
this.definition = definition;
|
|
216
220
|
this.schema = schema;
|
|
@@ -222,6 +226,7 @@ var Collection = class {
|
|
|
222
226
|
this.relationEnforcer = relationEnforcer;
|
|
223
227
|
this.mutationHandler = mutationHandler;
|
|
224
228
|
this.causalTracker = causalTracker;
|
|
229
|
+
this.secretKeyProvider = secretKeyProvider;
|
|
225
230
|
}
|
|
226
231
|
name;
|
|
227
232
|
definition;
|
|
@@ -234,6 +239,7 @@ var Collection = class {
|
|
|
234
239
|
relationEnforcer;
|
|
235
240
|
mutationHandler;
|
|
236
241
|
causalTracker;
|
|
242
|
+
secretKeyProvider;
|
|
237
243
|
mutationContext() {
|
|
238
244
|
return {
|
|
239
245
|
collection: this.name,
|
|
@@ -246,7 +252,8 @@ var Collection = class {
|
|
|
246
252
|
onMutation: this.onMutation,
|
|
247
253
|
relationEnforcer: this.relationEnforcer,
|
|
248
254
|
causalTracker: this.causalTracker,
|
|
249
|
-
inTransaction: false
|
|
255
|
+
inTransaction: false,
|
|
256
|
+
secretKeyProvider: this.secretKeyProvider
|
|
250
257
|
};
|
|
251
258
|
}
|
|
252
259
|
async insert(data) {
|
|
@@ -2020,6 +2027,7 @@ var Store = class {
|
|
|
2020
2027
|
localMutationHandler;
|
|
2021
2028
|
relationEnforcer = null;
|
|
2022
2029
|
causalTracker = null;
|
|
2030
|
+
secretKeyProvider;
|
|
2023
2031
|
constructor(config) {
|
|
2024
2032
|
this.schema = config.schema;
|
|
2025
2033
|
this.adapter = config.adapter;
|
|
@@ -2028,6 +2036,7 @@ var Store = class {
|
|
|
2028
2036
|
this.isolation = config.isolation ?? "shared";
|
|
2029
2037
|
this.emitter = config.emitter ?? null;
|
|
2030
2038
|
this.localMutationHandler = config.localMutationHandler ?? null;
|
|
2039
|
+
this.secretKeyProvider = config.secretKeyProvider;
|
|
2031
2040
|
this.subscriptionManager = new SubscriptionManager({
|
|
2032
2041
|
onQuerySubscribed: config.onQuerySubscribed
|
|
2033
2042
|
});
|
|
@@ -2070,7 +2079,8 @@ var Store = class {
|
|
|
2070
2079
|
},
|
|
2071
2080
|
this.relationEnforcer,
|
|
2072
2081
|
this.localMutationHandler,
|
|
2073
|
-
this.causalTracker
|
|
2082
|
+
this.causalTracker,
|
|
2083
|
+
this.secretKeyProvider
|
|
2074
2084
|
);
|
|
2075
2085
|
this.collections.set(name, col);
|
|
2076
2086
|
}
|
|
@@ -2486,7 +2496,7 @@ var Store = class {
|
|
|
2486
2496
|
*/
|
|
2487
2497
|
async exportAudit(options) {
|
|
2488
2498
|
this.ensureOpen();
|
|
2489
|
-
const { exportAudit: doExport } = await import("./export-audit-
|
|
2499
|
+
const { exportAudit: doExport } = await import("./export-audit-AJ7RORNK.js");
|
|
2490
2500
|
return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
|
|
2491
2501
|
}
|
|
2492
2502
|
/**
|
|
@@ -2531,7 +2541,8 @@ var Store = class {
|
|
|
2531
2541
|
relationEnforcer: this.relationEnforcer,
|
|
2532
2542
|
causalTracker: this.causalTracker,
|
|
2533
2543
|
inTransaction: options?.inTransaction ?? false,
|
|
2534
|
-
extraCausalDeps: options?.extraCausalDeps
|
|
2544
|
+
extraCausalDeps: options?.extraCausalDeps,
|
|
2545
|
+
secretKeyProvider: this.secretKeyProvider
|
|
2535
2546
|
};
|
|
2536
2547
|
}
|
|
2537
2548
|
/**
|
|
@@ -2780,7 +2791,7 @@ var Store = class {
|
|
|
2780
2791
|
*/
|
|
2781
2792
|
async exportBackup(options) {
|
|
2782
2793
|
this.ensureOpen();
|
|
2783
|
-
const { exportBackup: doExport } = await import("./backup-
|
|
2794
|
+
const { exportBackup: doExport } = await import("./backup-ZV4Y424B.js");
|
|
2784
2795
|
return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
|
|
2785
2796
|
}
|
|
2786
2797
|
/**
|
|
@@ -2792,7 +2803,7 @@ var Store = class {
|
|
|
2792
2803
|
*/
|
|
2793
2804
|
async importBackup(data, options) {
|
|
2794
2805
|
this.ensureOpen();
|
|
2795
|
-
const { restoreBackup: doRestore } = await import("./backup-
|
|
2806
|
+
const { restoreBackup: doRestore } = await import("./backup-ZV4Y424B.js");
|
|
2796
2807
|
return doRestore(this.adapter, this.schema, data, options);
|
|
2797
2808
|
}
|
|
2798
2809
|
/**
|
|
@@ -3422,12 +3433,518 @@ function asRichTextSyncEngine(engine) {
|
|
|
3422
3433
|
}
|
|
3423
3434
|
return engine;
|
|
3424
3435
|
}
|
|
3436
|
+
|
|
3437
|
+
// src/blob/opfs-blob-store.ts
|
|
3438
|
+
import { createBlobRef, hashBlob } from "@korajs/core";
|
|
3439
|
+
var OpfsBlobStore = class {
|
|
3440
|
+
constructor(dir) {
|
|
3441
|
+
this.dir = dir;
|
|
3442
|
+
}
|
|
3443
|
+
dir;
|
|
3444
|
+
shardFor(hash) {
|
|
3445
|
+
return hash.slice(0, 2);
|
|
3446
|
+
}
|
|
3447
|
+
async put(bytes, metadata = {}) {
|
|
3448
|
+
const ref = await createBlobRef(bytes, metadata);
|
|
3449
|
+
const shard = this.shardFor(ref.hash);
|
|
3450
|
+
if (await this.dir.has(shard, ref.hash)) {
|
|
3451
|
+
return ref;
|
|
3452
|
+
}
|
|
3453
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
3454
|
+
out.set(bytes);
|
|
3455
|
+
await this.dir.write(shard, ref.hash, out);
|
|
3456
|
+
return ref;
|
|
3457
|
+
}
|
|
3458
|
+
async get(hash) {
|
|
3459
|
+
const bytes = await this.dir.read(this.shardFor(hash), hash);
|
|
3460
|
+
if (bytes === null) {
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
const actual = await hashBlob(bytes);
|
|
3464
|
+
if (actual !== hash) {
|
|
3465
|
+
throw new BlobIntegrityError(hash, actual);
|
|
3466
|
+
}
|
|
3467
|
+
return bytes;
|
|
3468
|
+
}
|
|
3469
|
+
async has(hash) {
|
|
3470
|
+
return this.dir.has(this.shardFor(hash), hash);
|
|
3471
|
+
}
|
|
3472
|
+
async delete(hash) {
|
|
3473
|
+
return this.dir.remove(this.shardFor(hash), hash);
|
|
3474
|
+
}
|
|
3475
|
+
async size() {
|
|
3476
|
+
return this.dir.count();
|
|
3477
|
+
}
|
|
3478
|
+
async list() {
|
|
3479
|
+
return this.dir.keys();
|
|
3480
|
+
}
|
|
3481
|
+
};
|
|
3482
|
+
function getStorageManager() {
|
|
3483
|
+
const nav = globalThis.navigator;
|
|
3484
|
+
if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
|
|
3485
|
+
throw new Error(
|
|
3486
|
+
"OPFS is unavailable in this environment. The OPFS blob store requires a browser with Origin Private File System support; use MemoryBlobStore or a server-side store elsewhere."
|
|
3487
|
+
);
|
|
3488
|
+
}
|
|
3489
|
+
return nav.storage;
|
|
3490
|
+
}
|
|
3491
|
+
function isNotFoundError(error) {
|
|
3492
|
+
return typeof error === "object" && error !== null && error.name === "NotFoundError";
|
|
3493
|
+
}
|
|
3494
|
+
async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
|
|
3495
|
+
const storage = getStorageManager();
|
|
3496
|
+
if (typeof storage.persist === "function") {
|
|
3497
|
+
try {
|
|
3498
|
+
await storage.persist();
|
|
3499
|
+
} catch {
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
const opfsRoot = await storage.getDirectory();
|
|
3503
|
+
const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
|
|
3504
|
+
async function shardHandle(shard, create) {
|
|
3505
|
+
try {
|
|
3506
|
+
return await root.getDirectoryHandle(shard, { create });
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
if (isNotFoundError(error)) {
|
|
3509
|
+
return null;
|
|
3510
|
+
}
|
|
3511
|
+
throw error;
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
return {
|
|
3515
|
+
async read(shard, name) {
|
|
3516
|
+
const dir = await shardHandle(shard, false);
|
|
3517
|
+
if (!dir) {
|
|
3518
|
+
return null;
|
|
3519
|
+
}
|
|
3520
|
+
let handle;
|
|
3521
|
+
try {
|
|
3522
|
+
handle = await dir.getFileHandle(name, { create: false });
|
|
3523
|
+
} catch (error) {
|
|
3524
|
+
if (isNotFoundError(error)) {
|
|
3525
|
+
return null;
|
|
3526
|
+
}
|
|
3527
|
+
throw error;
|
|
3528
|
+
}
|
|
3529
|
+
const file = await handle.getFile();
|
|
3530
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
3531
|
+
},
|
|
3532
|
+
async write(shard, name, bytes) {
|
|
3533
|
+
const dir = await root.getDirectoryHandle(shard, { create: true });
|
|
3534
|
+
const handle = await dir.getFileHandle(name, { create: true });
|
|
3535
|
+
const writable = await handle.createWritable({ keepExistingData: false });
|
|
3536
|
+
try {
|
|
3537
|
+
await writable.write(bytes);
|
|
3538
|
+
} finally {
|
|
3539
|
+
await writable.close();
|
|
3540
|
+
}
|
|
3541
|
+
},
|
|
3542
|
+
async remove(shard, name) {
|
|
3543
|
+
const dir = await shardHandle(shard, false);
|
|
3544
|
+
if (!dir) {
|
|
3545
|
+
return false;
|
|
3546
|
+
}
|
|
3547
|
+
try {
|
|
3548
|
+
await dir.removeEntry(name);
|
|
3549
|
+
return true;
|
|
3550
|
+
} catch (error) {
|
|
3551
|
+
if (isNotFoundError(error)) {
|
|
3552
|
+
return false;
|
|
3553
|
+
}
|
|
3554
|
+
throw error;
|
|
3555
|
+
}
|
|
3556
|
+
},
|
|
3557
|
+
async has(shard, name) {
|
|
3558
|
+
const dir = await shardHandle(shard, false);
|
|
3559
|
+
if (!dir) {
|
|
3560
|
+
return false;
|
|
3561
|
+
}
|
|
3562
|
+
try {
|
|
3563
|
+
await dir.getFileHandle(name, { create: false });
|
|
3564
|
+
return true;
|
|
3565
|
+
} catch (error) {
|
|
3566
|
+
if (isNotFoundError(error)) {
|
|
3567
|
+
return false;
|
|
3568
|
+
}
|
|
3569
|
+
throw error;
|
|
3570
|
+
}
|
|
3571
|
+
},
|
|
3572
|
+
async count() {
|
|
3573
|
+
let total = 0;
|
|
3574
|
+
for await (const shard of root.keys()) {
|
|
3575
|
+
const dir = await shardHandle(shard, false);
|
|
3576
|
+
if (!dir) {
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
for await (const name of dir.keys()) {
|
|
3580
|
+
if (!name.endsWith(".tmp")) {
|
|
3581
|
+
total++;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
return total;
|
|
3586
|
+
},
|
|
3587
|
+
async keys() {
|
|
3588
|
+
const names = [];
|
|
3589
|
+
for await (const shard of root.keys()) {
|
|
3590
|
+
const dir = await shardHandle(shard, false);
|
|
3591
|
+
if (!dir) {
|
|
3592
|
+
continue;
|
|
3593
|
+
}
|
|
3594
|
+
for await (const name of dir.keys()) {
|
|
3595
|
+
if (!name.endsWith(".tmp")) {
|
|
3596
|
+
names.push(name);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
return names;
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
async function createOpfsBlobStore(rootDirName = "kora-blobs") {
|
|
3605
|
+
return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
// src/blob/blob-chunking.ts
|
|
3609
|
+
import { hashBlob as hashBlob2 } from "@korajs/core";
|
|
3610
|
+
var DEFAULT_CHUNK_SIZE = 256 * 1024;
|
|
3611
|
+
async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
|
|
3612
|
+
if (chunkSize <= 0) {
|
|
3613
|
+
throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
|
|
3614
|
+
}
|
|
3615
|
+
const chunkHashes = [];
|
|
3616
|
+
const chunks = /* @__PURE__ */ new Map();
|
|
3617
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
3618
|
+
const end = Math.min(offset + chunkSize, bytes.byteLength);
|
|
3619
|
+
const chunk = new Uint8Array(end - offset);
|
|
3620
|
+
chunk.set(bytes.subarray(offset, end));
|
|
3621
|
+
const hash = await hashBlob2(chunk);
|
|
3622
|
+
chunkHashes.push(hash);
|
|
3623
|
+
if (!chunks.has(hash)) {
|
|
3624
|
+
chunks.set(hash, chunk);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
const blobHash = await hashBlob2(bytes);
|
|
3628
|
+
const manifest = {
|
|
3629
|
+
blobHash,
|
|
3630
|
+
size: bytes.byteLength,
|
|
3631
|
+
chunkSize,
|
|
3632
|
+
chunkHashes
|
|
3633
|
+
};
|
|
3634
|
+
if (metadata.mimeType !== void 0) {
|
|
3635
|
+
manifest.mimeType = metadata.mimeType;
|
|
3636
|
+
}
|
|
3637
|
+
if (metadata.filename !== void 0) {
|
|
3638
|
+
manifest.filename = metadata.filename;
|
|
3639
|
+
}
|
|
3640
|
+
return { manifest, chunks };
|
|
3641
|
+
}
|
|
3642
|
+
async function reassembleBlob(manifest, chunkStore) {
|
|
3643
|
+
const parts = [];
|
|
3644
|
+
let total = 0;
|
|
3645
|
+
for (const hash of manifest.chunkHashes) {
|
|
3646
|
+
const chunk = await chunkStore.get(hash);
|
|
3647
|
+
if (chunk === null) {
|
|
3648
|
+
throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
|
|
3649
|
+
}
|
|
3650
|
+
parts.push(chunk);
|
|
3651
|
+
total += chunk.byteLength;
|
|
3652
|
+
}
|
|
3653
|
+
const out = new Uint8Array(total);
|
|
3654
|
+
let offset = 0;
|
|
3655
|
+
for (const part of parts) {
|
|
3656
|
+
out.set(part, offset);
|
|
3657
|
+
offset += part.byteLength;
|
|
3658
|
+
}
|
|
3659
|
+
const actual = await hashBlob2(out);
|
|
3660
|
+
if (actual !== manifest.blobHash) {
|
|
3661
|
+
throw new BlobIntegrityError(manifest.blobHash, actual);
|
|
3662
|
+
}
|
|
3663
|
+
return out;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
// src/blob/blob-transfer.ts
|
|
3667
|
+
import { hashBlob as hashBlob3 } from "@korajs/core";
|
|
3668
|
+
async function receiveBlob(manifest, provider, stores) {
|
|
3669
|
+
let chunksFetched = 0;
|
|
3670
|
+
let chunksSkipped = 0;
|
|
3671
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3672
|
+
for (const hash of manifest.chunkHashes) {
|
|
3673
|
+
if (seen.has(hash)) {
|
|
3674
|
+
continue;
|
|
3675
|
+
}
|
|
3676
|
+
seen.add(hash);
|
|
3677
|
+
if (await stores.chunkStore.has(hash)) {
|
|
3678
|
+
chunksSkipped++;
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
const bytes = await provider.getChunk(hash);
|
|
3682
|
+
if (bytes === null) {
|
|
3683
|
+
throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
|
|
3684
|
+
}
|
|
3685
|
+
const actual = await hashBlob3(bytes);
|
|
3686
|
+
if (actual !== hash) {
|
|
3687
|
+
throw new BlobIntegrityError(hash, actual);
|
|
3688
|
+
}
|
|
3689
|
+
await stores.chunkStore.put(bytes);
|
|
3690
|
+
chunksFetched++;
|
|
3691
|
+
}
|
|
3692
|
+
const full = await reassembleBlob(manifest, stores.chunkStore);
|
|
3693
|
+
const ref = await stores.blobStore.put(full, {
|
|
3694
|
+
mimeType: manifest.mimeType,
|
|
3695
|
+
filename: manifest.filename
|
|
3696
|
+
});
|
|
3697
|
+
return { ref, chunksFetched, chunksSkipped };
|
|
3698
|
+
}
|
|
3699
|
+
async function prepareBlobForSend(bytes, chunkStore, options = {}) {
|
|
3700
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
3701
|
+
mimeType: options.mimeType,
|
|
3702
|
+
filename: options.filename
|
|
3703
|
+
});
|
|
3704
|
+
for (const chunk of chunks.values()) {
|
|
3705
|
+
await chunkStore.put(chunk);
|
|
3706
|
+
}
|
|
3707
|
+
const provider = {
|
|
3708
|
+
getChunk: (hash) => chunkStore.get(hash)
|
|
3709
|
+
};
|
|
3710
|
+
return { manifest, provider };
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
// src/blob/blob-manifest-transfer.ts
|
|
3714
|
+
import { hashBlob as hashBlob4 } from "@korajs/core";
|
|
3715
|
+
function serializeBlobManifest(manifest) {
|
|
3716
|
+
const canonical = {
|
|
3717
|
+
blobHash: manifest.blobHash,
|
|
3718
|
+
size: manifest.size,
|
|
3719
|
+
chunkSize: manifest.chunkSize,
|
|
3720
|
+
chunkHashes: manifest.chunkHashes
|
|
3721
|
+
};
|
|
3722
|
+
if (manifest.mimeType !== void 0) {
|
|
3723
|
+
canonical.mimeType = manifest.mimeType;
|
|
3724
|
+
}
|
|
3725
|
+
if (manifest.filename !== void 0) {
|
|
3726
|
+
canonical.filename = manifest.filename;
|
|
3727
|
+
}
|
|
3728
|
+
return new TextEncoder().encode(JSON.stringify(canonical));
|
|
3729
|
+
}
|
|
3730
|
+
function parseBlobManifest(bytes) {
|
|
3731
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
3732
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
3733
|
+
throw new Error("Invalid blob manifest: not an object");
|
|
3734
|
+
}
|
|
3735
|
+
const obj = parsed;
|
|
3736
|
+
if (typeof obj.blobHash !== "string") {
|
|
3737
|
+
throw new Error("Invalid blob manifest: missing blobHash");
|
|
3738
|
+
}
|
|
3739
|
+
if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
|
|
3740
|
+
throw new Error("Invalid blob manifest: missing size or chunkSize");
|
|
3741
|
+
}
|
|
3742
|
+
if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
|
|
3743
|
+
throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
|
|
3744
|
+
}
|
|
3745
|
+
const manifest = {
|
|
3746
|
+
blobHash: obj.blobHash,
|
|
3747
|
+
size: obj.size,
|
|
3748
|
+
chunkSize: obj.chunkSize,
|
|
3749
|
+
chunkHashes: obj.chunkHashes
|
|
3750
|
+
};
|
|
3751
|
+
if (typeof obj.mimeType === "string") {
|
|
3752
|
+
manifest.mimeType = obj.mimeType;
|
|
3753
|
+
}
|
|
3754
|
+
if (typeof obj.filename === "string") {
|
|
3755
|
+
manifest.filename = obj.filename;
|
|
3756
|
+
}
|
|
3757
|
+
return manifest;
|
|
3758
|
+
}
|
|
3759
|
+
async function putBlobForTransfer(store, bytes, options = {}) {
|
|
3760
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
3761
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
3762
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
3763
|
+
});
|
|
3764
|
+
for (const chunk of chunks.values()) {
|
|
3765
|
+
await store.put(chunk);
|
|
3766
|
+
}
|
|
3767
|
+
const baseRef = await store.put(bytes, {
|
|
3768
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
3769
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
3770
|
+
});
|
|
3771
|
+
const manifestRef = await store.put(serializeBlobManifest(manifest));
|
|
3772
|
+
return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
|
|
3773
|
+
}
|
|
3774
|
+
async function fetchBlobManifest(provider, manifestHash) {
|
|
3775
|
+
const bytes = await provider.getChunk(manifestHash);
|
|
3776
|
+
if (bytes === null) {
|
|
3777
|
+
throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
|
|
3778
|
+
}
|
|
3779
|
+
const actual = await hashBlob4(bytes);
|
|
3780
|
+
if (actual !== manifestHash) {
|
|
3781
|
+
throw new BlobIntegrityError(manifestHash, actual);
|
|
3782
|
+
}
|
|
3783
|
+
return parseBlobManifest(bytes);
|
|
3784
|
+
}
|
|
3785
|
+
async function resolveBlobManifest(provider, ref) {
|
|
3786
|
+
if (ref.manifestHash === void 0) {
|
|
3787
|
+
throw new Error(
|
|
3788
|
+
`Blob ${ref.hash} has no manifest to resolve. Store it with putBlobForTransfer (or app.blobs.put) so its reference carries a manifestHash, or pull with an explicit manifest.`
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
return fetchBlobManifest(provider, ref.manifestHash);
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
// src/blob/server-blob-callbacks.ts
|
|
3795
|
+
import { hashBlob as hashBlob5 } from "@korajs/core";
|
|
3796
|
+
function toServerBlobCallbacks(store) {
|
|
3797
|
+
return {
|
|
3798
|
+
resolveBlobChunk: (hash) => store.get(hash),
|
|
3799
|
+
async persistBlobChunk(hash, bytes) {
|
|
3800
|
+
const actual = await hashBlob5(bytes);
|
|
3801
|
+
if (actual !== hash) {
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
await store.put(bytes);
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
function createMemoryServerBlobStore() {
|
|
3809
|
+
const store = new MemoryBlobStore();
|
|
3810
|
+
return { store, callbacks: toServerBlobCallbacks(store) };
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
// src/blob/blob-gc.ts
|
|
3814
|
+
import { isBlobRef } from "@korajs/core";
|
|
3815
|
+
function extractBlobRefs(record) {
|
|
3816
|
+
const refs = [];
|
|
3817
|
+
for (const value of Object.values(record)) {
|
|
3818
|
+
if (isBlobRef(value)) {
|
|
3819
|
+
refs.push(value);
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
return refs;
|
|
3823
|
+
}
|
|
3824
|
+
async function collectBlobGarbage(store, liveRefs, options = {}) {
|
|
3825
|
+
const live = /* @__PURE__ */ new Set();
|
|
3826
|
+
for (const ref of liveRefs) {
|
|
3827
|
+
live.add(ref.hash);
|
|
3828
|
+
if (ref.manifestHash === void 0) {
|
|
3829
|
+
continue;
|
|
3830
|
+
}
|
|
3831
|
+
live.add(ref.manifestHash);
|
|
3832
|
+
try {
|
|
3833
|
+
const manifestBytes = await store.get(ref.manifestHash);
|
|
3834
|
+
if (manifestBytes !== null) {
|
|
3835
|
+
const manifest = parseBlobManifest(manifestBytes);
|
|
3836
|
+
for (const chunkHash of manifest.chunkHashes) {
|
|
3837
|
+
live.add(chunkHash);
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
} catch {
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
const all = await store.list();
|
|
3844
|
+
const collectedHashes = [];
|
|
3845
|
+
for (const hash of all) {
|
|
3846
|
+
if (live.has(hash)) {
|
|
3847
|
+
continue;
|
|
3848
|
+
}
|
|
3849
|
+
collectedHashes.push(hash);
|
|
3850
|
+
if (!options.dryRun) {
|
|
3851
|
+
await store.delete(hash);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
return {
|
|
3855
|
+
scanned: all.length,
|
|
3856
|
+
live: live.size,
|
|
3857
|
+
collected: collectedHashes.length,
|
|
3858
|
+
collectedHashes
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
// src/blob/blob-chunk-transport.ts
|
|
3863
|
+
import { generateUUIDv7 as generateUUIDv74 } from "@korajs/core";
|
|
3864
|
+
function createRemoteChunkProvider(port, options = {}) {
|
|
3865
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
3866
|
+
const pending = /* @__PURE__ */ new Map();
|
|
3867
|
+
port.onMessage((message) => {
|
|
3868
|
+
if (message.type !== "blob-chunk-response") {
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3871
|
+
const entry = pending.get(message.requestId);
|
|
3872
|
+
if (!entry) {
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
pending.delete(message.requestId);
|
|
3876
|
+
clearTimeout(entry.timer);
|
|
3877
|
+
entry.resolve(message.bytes);
|
|
3878
|
+
});
|
|
3879
|
+
return {
|
|
3880
|
+
pendingCount: () => pending.size,
|
|
3881
|
+
getChunk(hash) {
|
|
3882
|
+
const requestId = generateUUIDv74();
|
|
3883
|
+
return new Promise((resolve, reject) => {
|
|
3884
|
+
const timer = setTimeout(() => {
|
|
3885
|
+
pending.delete(requestId);
|
|
3886
|
+
reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
|
|
3887
|
+
}, timeoutMs);
|
|
3888
|
+
pending.set(requestId, { resolve, reject, timer });
|
|
3889
|
+
port.send({ type: "blob-chunk-request", requestId, hash });
|
|
3890
|
+
});
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
function serveBlobChunks(port, blobStore) {
|
|
3895
|
+
port.onMessage((message) => {
|
|
3896
|
+
if (message.type !== "blob-chunk-request") {
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
void blobStore.get(message.hash).then(
|
|
3900
|
+
(bytes) => {
|
|
3901
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
|
|
3902
|
+
},
|
|
3903
|
+
() => {
|
|
3904
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
|
|
3905
|
+
}
|
|
3906
|
+
);
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3909
|
+
function createChunkPortPair() {
|
|
3910
|
+
const handlersA = [];
|
|
3911
|
+
const handlersB = [];
|
|
3912
|
+
const a = {
|
|
3913
|
+
send(message) {
|
|
3914
|
+
queueMicrotask(() => {
|
|
3915
|
+
for (const handler of handlersB) {
|
|
3916
|
+
handler(message);
|
|
3917
|
+
}
|
|
3918
|
+
});
|
|
3919
|
+
},
|
|
3920
|
+
onMessage(handler) {
|
|
3921
|
+
handlersA.push(handler);
|
|
3922
|
+
}
|
|
3923
|
+
};
|
|
3924
|
+
const b = {
|
|
3925
|
+
send(message) {
|
|
3926
|
+
queueMicrotask(() => {
|
|
3927
|
+
for (const handler of handlersA) {
|
|
3928
|
+
handler(message);
|
|
3929
|
+
}
|
|
3930
|
+
});
|
|
3931
|
+
},
|
|
3932
|
+
onMessage(handler) {
|
|
3933
|
+
handlersB.push(handler);
|
|
3934
|
+
}
|
|
3935
|
+
};
|
|
3936
|
+
return { a, b };
|
|
3937
|
+
}
|
|
3425
3938
|
export {
|
|
3426
3939
|
AdapterError,
|
|
3940
|
+
BlobIntegrityError,
|
|
3427
3941
|
COMPACTION_BASELINE_META_KEY,
|
|
3428
3942
|
Collection,
|
|
3943
|
+
DEFAULT_CHUNK_SIZE,
|
|
3429
3944
|
InvalidStateTransitionError,
|
|
3430
3945
|
LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
3946
|
+
MemoryBlobStore,
|
|
3947
|
+
OpfsBlobStore,
|
|
3431
3948
|
OptimisticLockError,
|
|
3432
3949
|
PersistenceError,
|
|
3433
3950
|
QueryBuilder,
|
|
@@ -3445,26 +3962,44 @@ export {
|
|
|
3445
3962
|
WorkerTimeoutError,
|
|
3446
3963
|
asRichTextSyncEngine,
|
|
3447
3964
|
assertQueryReady,
|
|
3965
|
+
chunkBlob,
|
|
3966
|
+
collectBlobGarbage,
|
|
3448
3967
|
collectOperationsAheadOfServer,
|
|
3449
3968
|
compactOperationLog,
|
|
3450
3969
|
computeAckCompactionWatermark,
|
|
3970
|
+
createChunkPortPair,
|
|
3971
|
+
createMemoryServerBlobStore,
|
|
3972
|
+
createOpfsBlobDirectory,
|
|
3973
|
+
createOpfsBlobStore,
|
|
3974
|
+
createRemoteChunkProvider,
|
|
3451
3975
|
createRichTextController,
|
|
3452
3976
|
decodeAuditExport,
|
|
3453
3977
|
decodeRichtext,
|
|
3454
3978
|
deserializeVersionVectorFromMeta,
|
|
3455
3979
|
encodeRichtext,
|
|
3456
3980
|
exportBackup,
|
|
3981
|
+
extractBlobRefs,
|
|
3982
|
+
fetchBlobManifest,
|
|
3457
3983
|
getSharedQueryStoreCache,
|
|
3458
3984
|
mergeVersionVectors,
|
|
3985
|
+
parseBlobManifest,
|
|
3459
3986
|
persistedAuditTraceFromEvent,
|
|
3460
3987
|
pluralize,
|
|
3988
|
+
prepareBlobForSend,
|
|
3989
|
+
putBlobForTransfer,
|
|
3461
3990
|
readAuditExportManifest,
|
|
3462
3991
|
readBackupManifest,
|
|
3992
|
+
reassembleBlob,
|
|
3993
|
+
receiveBlob,
|
|
3994
|
+
resolveBlobManifest,
|
|
3463
3995
|
restoreBackup,
|
|
3464
3996
|
richtextStatesEqual,
|
|
3465
3997
|
richtextToPlainText,
|
|
3998
|
+
serializeBlobManifest,
|
|
3466
3999
|
serializeVersionVectorToMeta,
|
|
4000
|
+
serveBlobChunks,
|
|
3467
4001
|
singularize,
|
|
4002
|
+
toServerBlobCallbacks,
|
|
3468
4003
|
validateStateTransition,
|
|
3469
4004
|
validateUpdateStateMachine,
|
|
3470
4005
|
verifyAuditExportChecksum,
|