@korajs/store 1.0.0-beta.0 → 1.0.0-beta.2
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 +39 -5
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +1 -1
- package/dist/adapters/indexeddb.d.ts +1 -1
- package/dist/adapters/indexeddb.js +13 -6
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs +25 -3
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +25 -3
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +29 -0
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +17 -2
- package/dist/adapters/sqlite-wasm.d.ts +17 -2
- package/dist/adapters/sqlite-wasm.js +1 -1
- package/dist/{backup-5XZ7THNJ.js → backup-IQE4QC7Q.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-OUQDC5Z6.js → chunk-2VRPF25R.js} +58 -38
- package/dist/chunk-2VRPF25R.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-5R4QMY3Z.js → chunk-OCOGQRHW.js} +13 -10
- package/dist/chunk-OCOGQRHW.js.map +1 -0
- package/dist/{chunk-RBPVEAVM.js → chunk-QUJGXSZ6.js} +30 -1
- package/dist/chunk-QUJGXSZ6.js.map +1 -0
- package/dist/{chunk-EIVXWF4B.js → chunk-RCTG3IJG.js} +4 -3
- package/dist/chunk-RCTG3IJG.js.map +1 -0
- package/dist/chunk-W4MTTNEZ.js +55 -0
- package/dist/chunk-W4MTTNEZ.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-MT2LTMIN.js} +3 -3
- package/dist/index.cjs +765 -128
- 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 +603 -60
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +68 -43
- 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.cts → types-k6AZgiH4.d.cts} +6 -1
- package/dist/{types-CJOVKxLw.d.ts → types-k6AZgiH4.d.ts} +6 -1
- package/package.json +13 -2
- package/dist/chunk-5R4QMY3Z.js.map +0 -1
- package/dist/chunk-EIVXWF4B.js.map +0 -1
- package/dist/chunk-OUQDC5Z6.js.map +0 -1
- package/dist/chunk-RBPVEAVM.js.map +0 -1
- /package/dist/{backup-5XZ7THNJ.js.map → backup-IQE4QC7Q.js.map} +0 -0
- /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-MT2LTMIN.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-OCOGQRHW.js";
|
|
1
7
|
import {
|
|
2
8
|
decodeAuditExport,
|
|
3
9
|
readAuditExportManifest,
|
|
4
10
|
verifyAuditExportChecksum
|
|
5
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-RCTG3IJG.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-2VRPF25R.js";
|
|
49
43
|
import {
|
|
50
44
|
decodeRichtext,
|
|
51
45
|
decodeRichtextFieldsFromOpData,
|
|
@@ -57,13 +51,23 @@ 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";
|
|
64
68
|
|
|
65
69
|
// src/compaction/compact-operation-log.ts
|
|
66
|
-
import { HybridLogicalClock, createVersionVector } from "@korajs/core";
|
|
70
|
+
import { HybridLogicalClock, createVersionVector, quoteIdent } from "@korajs/core";
|
|
67
71
|
function computeAckCompactionWatermark(serverVector) {
|
|
68
72
|
const watermark = createVersionVector();
|
|
69
73
|
for (const [nodeId, seq] of serverVector) {
|
|
@@ -91,7 +95,7 @@ async function compactOperationLog(adapter, schema, strategy, serverVector) {
|
|
|
91
95
|
const collectionNames = Object.keys(schema.collections);
|
|
92
96
|
await adapter.transaction(async (tx) => {
|
|
93
97
|
for (const collectionName of collectionNames) {
|
|
94
|
-
const table = `_kora_ops_${collectionName}
|
|
98
|
+
const table = quoteIdent(`_kora_ops_${collectionName}`);
|
|
95
99
|
for (const [nodeId, maxSeq] of watermark) {
|
|
96
100
|
if (maxSeq <= 0) continue;
|
|
97
101
|
const countSql = buildCountSql(table, ageCutoffSerialized !== null);
|
|
@@ -205,12 +209,14 @@ import {
|
|
|
205
209
|
HybridLogicalClock as HybridLogicalClock3,
|
|
206
210
|
createVersionVector as createVersionVector3,
|
|
207
211
|
generateUUIDv7 as generateUUIDv73,
|
|
208
|
-
migrationStepsToSQL
|
|
212
|
+
migrationStepsToSQL,
|
|
213
|
+
quoteIdent as quoteIdent7
|
|
209
214
|
} from "@korajs/core";
|
|
210
215
|
|
|
211
216
|
// src/collection/collection.ts
|
|
217
|
+
import { quoteIdent as quoteIdent2 } from "@korajs/core";
|
|
212
218
|
var Collection = class {
|
|
213
|
-
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
|
|
219
|
+
constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
|
|
214
220
|
this.name = name;
|
|
215
221
|
this.definition = definition;
|
|
216
222
|
this.schema = schema;
|
|
@@ -222,6 +228,7 @@ var Collection = class {
|
|
|
222
228
|
this.relationEnforcer = relationEnforcer;
|
|
223
229
|
this.mutationHandler = mutationHandler;
|
|
224
230
|
this.causalTracker = causalTracker;
|
|
231
|
+
this.secretKeyProvider = secretKeyProvider;
|
|
225
232
|
}
|
|
226
233
|
name;
|
|
227
234
|
definition;
|
|
@@ -234,6 +241,7 @@ var Collection = class {
|
|
|
234
241
|
relationEnforcer;
|
|
235
242
|
mutationHandler;
|
|
236
243
|
causalTracker;
|
|
244
|
+
secretKeyProvider;
|
|
237
245
|
mutationContext() {
|
|
238
246
|
return {
|
|
239
247
|
collection: this.name,
|
|
@@ -246,7 +254,8 @@ var Collection = class {
|
|
|
246
254
|
onMutation: this.onMutation,
|
|
247
255
|
relationEnforcer: this.relationEnforcer,
|
|
248
256
|
causalTracker: this.causalTracker,
|
|
249
|
-
inTransaction: false
|
|
257
|
+
inTransaction: false,
|
|
258
|
+
secretKeyProvider: this.secretKeyProvider
|
|
250
259
|
};
|
|
251
260
|
}
|
|
252
261
|
async insert(data) {
|
|
@@ -257,7 +266,7 @@ var Collection = class {
|
|
|
257
266
|
}
|
|
258
267
|
async findById(id) {
|
|
259
268
|
const rows = await this.adapter.query(
|
|
260
|
-
`SELECT * FROM ${this.name} WHERE id = ? AND _deleted = 0`,
|
|
269
|
+
`SELECT * FROM ${quoteIdent2(this.name)} WHERE id = ? AND _deleted = 0`,
|
|
261
270
|
[id]
|
|
262
271
|
);
|
|
263
272
|
const row = rows[0];
|
|
@@ -289,6 +298,9 @@ var Collection = class {
|
|
|
289
298
|
}
|
|
290
299
|
};
|
|
291
300
|
|
|
301
|
+
// src/query/query-builder.ts
|
|
302
|
+
import { quoteIdent as quoteIdent3 } from "@korajs/core";
|
|
303
|
+
|
|
292
304
|
// src/query/pluralize.ts
|
|
293
305
|
function isVowel(char) {
|
|
294
306
|
if (!char) return false;
|
|
@@ -518,7 +530,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
518
530
|
const relatedDef = this.schema?.collections[relatedCollection];
|
|
519
531
|
if (!relatedDef) return;
|
|
520
532
|
const placeholders = uniqueFks.map(() => "?").join(", ");
|
|
521
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
533
|
+
const sql = `SELECT * FROM ${quoteIdent3(relatedCollection)} WHERE id IN (${placeholders}) AND _deleted = 0`;
|
|
522
534
|
const rows = await this.adapter.query(sql, uniqueFks);
|
|
523
535
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
524
536
|
const lookup = /* @__PURE__ */ new Map();
|
|
@@ -541,7 +553,7 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
541
553
|
if (!relatedDef) return;
|
|
542
554
|
const fkField = relation.field;
|
|
543
555
|
const placeholders = primaryIds.map(() => "?").join(", ");
|
|
544
|
-
const sql = `SELECT * FROM ${relatedCollection} WHERE ${fkField} IN (${placeholders}) AND _deleted = 0`;
|
|
556
|
+
const sql = `SELECT * FROM ${quoteIdent3(relatedCollection)} WHERE ${quoteIdent3(fkField)} IN (${placeholders}) AND _deleted = 0`;
|
|
545
557
|
const rows = await this.adapter.query(sql, primaryIds);
|
|
546
558
|
const relatedRecords = rows.map((row) => deserializeRecord(row, relatedDef.fields));
|
|
547
559
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -583,7 +595,7 @@ var QueryError2 = class extends Error {
|
|
|
583
595
|
};
|
|
584
596
|
|
|
585
597
|
// src/relations/relation-enforcer.ts
|
|
586
|
-
import { KoraError, createOperation } from "@korajs/core";
|
|
598
|
+
import { KoraError, createOperation, quoteIdent as quoteIdent4 } from "@korajs/core";
|
|
587
599
|
|
|
588
600
|
// src/relations/relation-lookup.ts
|
|
589
601
|
function buildRelationLookup(schema) {
|
|
@@ -686,7 +698,7 @@ var RelationEnforcer = class {
|
|
|
686
698
|
async enforceCascade(incoming, deletedRecordId, tx, causalDeps) {
|
|
687
699
|
const { sourceCollection, foreignKeyField } = incoming;
|
|
688
700
|
const referencingRows = await tx.query(
|
|
689
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
701
|
+
`SELECT id FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
|
|
690
702
|
[deletedRecordId]
|
|
691
703
|
);
|
|
692
704
|
if (referencingRows.length === 0) {
|
|
@@ -733,7 +745,7 @@ var RelationEnforcer = class {
|
|
|
733
745
|
return [];
|
|
734
746
|
}
|
|
735
747
|
const referencingRows = await tx.query(
|
|
736
|
-
`SELECT id FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
748
|
+
`SELECT id FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
|
|
737
749
|
[deletedRecordId]
|
|
738
750
|
);
|
|
739
751
|
if (referencingRows.length === 0) {
|
|
@@ -781,7 +793,7 @@ var RelationEnforcer = class {
|
|
|
781
793
|
async enforceRestrict(incoming, deletedRecordId, tx) {
|
|
782
794
|
const { sourceCollection, foreignKeyField, relationName } = incoming;
|
|
783
795
|
const countRows = await tx.query(
|
|
784
|
-
`SELECT COUNT(*) as cnt FROM ${sourceCollection} WHERE ${foreignKeyField} = ? AND _deleted = 0`,
|
|
796
|
+
`SELECT COUNT(*) as cnt FROM ${quoteIdent4(sourceCollection)} WHERE ${quoteIdent4(foreignKeyField)} = ? AND _deleted = 0`,
|
|
785
797
|
[deletedRecordId]
|
|
786
798
|
);
|
|
787
799
|
const count = countRows[0]?.cnt ?? 0;
|
|
@@ -1469,7 +1481,7 @@ var SubscriptionManager = class {
|
|
|
1469
1481
|
};
|
|
1470
1482
|
|
|
1471
1483
|
// src/sync/rebase-unsynced-operations.ts
|
|
1472
|
-
import { HybridLogicalClock as HybridLogicalClock2, MAX_LOGICAL } from "@korajs/core";
|
|
1484
|
+
import { HybridLogicalClock as HybridLogicalClock2, MAX_LOGICAL, quoteIdent as quoteIdent5 } from "@korajs/core";
|
|
1473
1485
|
import { computeOperationId } from "@korajs/core/internal";
|
|
1474
1486
|
var LOGICAL_COUNTER_LIMIT = MAX_LOGICAL + 1;
|
|
1475
1487
|
function emptyResult() {
|
|
@@ -1483,7 +1495,9 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1483
1495
|
const rebased = [];
|
|
1484
1496
|
let maxOtherSerializedTs = null;
|
|
1485
1497
|
for (const collectionName of Object.keys(schema.collections)) {
|
|
1486
|
-
const rows = await adapter.query(
|
|
1498
|
+
const rows = await adapter.query(
|
|
1499
|
+
`SELECT * FROM ${quoteIdent5(`_kora_ops_${collectionName}`)}`
|
|
1500
|
+
);
|
|
1487
1501
|
for (const row of rows) {
|
|
1488
1502
|
if (rebaseIds.has(row.id)) {
|
|
1489
1503
|
rebased.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -1552,7 +1566,7 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1552
1566
|
continue;
|
|
1553
1567
|
}
|
|
1554
1568
|
const opsTable = `_kora_ops_${oldOp.collection}`;
|
|
1555
|
-
await tx.execute(`DELETE FROM ${opsTable} WHERE id = ?`, [oldOp.id]);
|
|
1569
|
+
await tx.execute(`DELETE FROM ${quoteIdent5(opsTable)} WHERE id = ?`, [oldOp.id]);
|
|
1556
1570
|
const opInsert = buildInsertQuery(
|
|
1557
1571
|
opsTable,
|
|
1558
1572
|
serializeOperation(newOp)
|
|
@@ -1561,11 +1575,11 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1561
1575
|
const oldVersion = serializeRowVersion(oldOp.timestamp);
|
|
1562
1576
|
const newVersion = serializeRowVersion(newOp.timestamp);
|
|
1563
1577
|
await tx.execute(
|
|
1564
|
-
`UPDATE ${oldOp.collection} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
1578
|
+
`UPDATE ${quoteIdent5(oldOp.collection)} SET _version = ?, _updated_at = ? WHERE id = ? AND _version = ?`,
|
|
1565
1579
|
[newVersion, newOp.timestamp.wallTime, oldOp.recordId, oldVersion]
|
|
1566
1580
|
);
|
|
1567
1581
|
const rows = await tx.query(
|
|
1568
|
-
`SELECT _field_versions FROM ${oldOp.collection} WHERE id = ?`,
|
|
1582
|
+
`SELECT _field_versions FROM ${quoteIdent5(oldOp.collection)} WHERE id = ?`,
|
|
1569
1583
|
[oldOp.recordId]
|
|
1570
1584
|
);
|
|
1571
1585
|
const fieldVersions = parseFieldVersions(rows[0]?._field_versions);
|
|
@@ -1577,14 +1591,14 @@ async function rebaseUnsyncedOperationsInLog(adapter, schema, unsyncedOpIds, cor
|
|
|
1577
1591
|
}
|
|
1578
1592
|
}
|
|
1579
1593
|
if (changed) {
|
|
1580
|
-
await tx.execute(
|
|
1581
|
-
|
|
1582
|
-
oldOp.recordId
|
|
1583
|
-
|
|
1594
|
+
await tx.execute(
|
|
1595
|
+
`UPDATE ${quoteIdent5(oldOp.collection)} SET _field_versions = ? WHERE id = ?`,
|
|
1596
|
+
[serializeFieldVersions(fieldVersions), oldOp.recordId]
|
|
1597
|
+
);
|
|
1584
1598
|
}
|
|
1585
1599
|
if (oldOp.type === "insert") {
|
|
1586
1600
|
await tx.execute(
|
|
1587
|
-
`UPDATE ${oldOp.collection} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
1601
|
+
`UPDATE ${quoteIdent5(oldOp.collection)} SET _created_at = ? WHERE id = ? AND _created_at = ?`,
|
|
1588
1602
|
[newOp.timestamp.wallTime, oldOp.recordId, oldOp.timestamp.wallTime]
|
|
1589
1603
|
);
|
|
1590
1604
|
}
|
|
@@ -1604,6 +1618,7 @@ import {
|
|
|
1604
1618
|
createOperation as createOperation2,
|
|
1605
1619
|
generateUUIDv7,
|
|
1606
1620
|
isAtomicOp,
|
|
1621
|
+
quoteIdent as quoteIdent6,
|
|
1607
1622
|
resolveAtomicOp,
|
|
1608
1623
|
toAtomicOp,
|
|
1609
1624
|
validateRecord
|
|
@@ -1920,7 +1935,7 @@ var TransactionContext = class {
|
|
|
1920
1935
|
}
|
|
1921
1936
|
}
|
|
1922
1937
|
const rows = await this.config.adapter.query(
|
|
1923
|
-
`SELECT * FROM ${collectionName} WHERE id = ? AND _deleted = 0`,
|
|
1938
|
+
`SELECT * FROM ${quoteIdent6(collectionName)} WHERE id = ? AND _deleted = 0`,
|
|
1924
1939
|
[id]
|
|
1925
1940
|
);
|
|
1926
1941
|
let record = rows[0] ? deserializeRecord(rows[0], definition.fields) : null;
|
|
@@ -2020,6 +2035,7 @@ var Store = class {
|
|
|
2020
2035
|
localMutationHandler;
|
|
2021
2036
|
relationEnforcer = null;
|
|
2022
2037
|
causalTracker = null;
|
|
2038
|
+
secretKeyProvider;
|
|
2023
2039
|
constructor(config) {
|
|
2024
2040
|
this.schema = config.schema;
|
|
2025
2041
|
this.adapter = config.adapter;
|
|
@@ -2028,6 +2044,7 @@ var Store = class {
|
|
|
2028
2044
|
this.isolation = config.isolation ?? "shared";
|
|
2029
2045
|
this.emitter = config.emitter ?? null;
|
|
2030
2046
|
this.localMutationHandler = config.localMutationHandler ?? null;
|
|
2047
|
+
this.secretKeyProvider = config.secretKeyProvider;
|
|
2031
2048
|
this.subscriptionManager = new SubscriptionManager({
|
|
2032
2049
|
onQuerySubscribed: config.onQuerySubscribed
|
|
2033
2050
|
});
|
|
@@ -2070,7 +2087,8 @@ var Store = class {
|
|
|
2070
2087
|
},
|
|
2071
2088
|
this.relationEnforcer,
|
|
2072
2089
|
this.localMutationHandler,
|
|
2073
|
-
this.causalTracker
|
|
2090
|
+
this.causalTracker,
|
|
2091
|
+
this.secretKeyProvider
|
|
2074
2092
|
);
|
|
2075
2093
|
this.collections.set(name, col);
|
|
2076
2094
|
}
|
|
@@ -2152,7 +2170,7 @@ var Store = class {
|
|
|
2152
2170
|
return "skipped";
|
|
2153
2171
|
}
|
|
2154
2172
|
const existing = await this.adapter.query(
|
|
2155
|
-
`SELECT id FROM _kora_ops_${collection} WHERE id = ?`,
|
|
2173
|
+
`SELECT id FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE id = ?`,
|
|
2156
2174
|
[op.id]
|
|
2157
2175
|
);
|
|
2158
2176
|
if (existing.length > 0) {
|
|
@@ -2257,7 +2275,7 @@ var Store = class {
|
|
|
2257
2275
|
const serializedChanges = serializeRecord(materializeSource, definition.fields);
|
|
2258
2276
|
const changedFields = Object.keys(serializedChanges);
|
|
2259
2277
|
const currentRows = await tx.query(
|
|
2260
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
2278
|
+
`SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2261
2279
|
[op.recordId]
|
|
2262
2280
|
);
|
|
2263
2281
|
const currentRow = currentRows[0];
|
|
@@ -2353,7 +2371,7 @@ var Store = class {
|
|
|
2353
2371
|
*/
|
|
2354
2372
|
async foldOrphanedOperations(tx, collection, definition, recordId) {
|
|
2355
2373
|
const orphanRows = await tx.query(
|
|
2356
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
2374
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ? AND type IN ('update', 'delete')`,
|
|
2357
2375
|
[recordId]
|
|
2358
2376
|
);
|
|
2359
2377
|
if (orphanRows.length === 0) {
|
|
@@ -2366,7 +2384,7 @@ var Store = class {
|
|
|
2366
2384
|
if (orphan.type === "update" && orphan.data) {
|
|
2367
2385
|
const orphanChanges = serializeRecord(orphan.data, definition.fields);
|
|
2368
2386
|
const rows = await tx.query(
|
|
2369
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
2387
|
+
`SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2370
2388
|
[recordId]
|
|
2371
2389
|
);
|
|
2372
2390
|
const current = rows[0];
|
|
@@ -2416,7 +2434,7 @@ var Store = class {
|
|
|
2416
2434
|
const allOps = [];
|
|
2417
2435
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
2418
2436
|
const rows = await this.adapter.query(
|
|
2419
|
-
`SELECT * FROM _kora_ops_${collectionName} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
2437
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collectionName}`)} WHERE node_id = ? AND sequence_number >= ? AND sequence_number <= ? ORDER BY sequence_number ASC`,
|
|
2420
2438
|
[nodeId, fromSeq, toSeq]
|
|
2421
2439
|
);
|
|
2422
2440
|
for (const row of rows) {
|
|
@@ -2435,7 +2453,7 @@ var Store = class {
|
|
|
2435
2453
|
const allOps = [];
|
|
2436
2454
|
for (const collectionName of Object.keys(this.schema.collections)) {
|
|
2437
2455
|
const rows = await this.adapter.query(
|
|
2438
|
-
`SELECT * FROM _kora_ops_${collectionName} ORDER BY sequence_number ASC`
|
|
2456
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collectionName}`)} ORDER BY sequence_number ASC`
|
|
2439
2457
|
);
|
|
2440
2458
|
for (const row of rows) {
|
|
2441
2459
|
allOps.push(deserializeOperationWithCollection(row, collectionName));
|
|
@@ -2486,7 +2504,7 @@ var Store = class {
|
|
|
2486
2504
|
*/
|
|
2487
2505
|
async exportAudit(options) {
|
|
2488
2506
|
this.ensureOpen();
|
|
2489
|
-
const { exportAudit: doExport } = await import("./export-audit-
|
|
2507
|
+
const { exportAudit: doExport } = await import("./export-audit-MT2LTMIN.js");
|
|
2490
2508
|
return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
|
|
2491
2509
|
}
|
|
2492
2510
|
/**
|
|
@@ -2531,7 +2549,8 @@ var Store = class {
|
|
|
2531
2549
|
relationEnforcer: this.relationEnforcer,
|
|
2532
2550
|
causalTracker: this.causalTracker,
|
|
2533
2551
|
inTransaction: options?.inTransaction ?? false,
|
|
2534
|
-
extraCausalDeps: options?.extraCausalDeps
|
|
2552
|
+
extraCausalDeps: options?.extraCausalDeps,
|
|
2553
|
+
secretKeyProvider: this.secretKeyProvider
|
|
2535
2554
|
};
|
|
2536
2555
|
}
|
|
2537
2556
|
/**
|
|
@@ -2544,7 +2563,7 @@ var Store = class {
|
|
|
2544
2563
|
return null;
|
|
2545
2564
|
}
|
|
2546
2565
|
const rows = await this.adapter.query(
|
|
2547
|
-
`SELECT * FROM ${collection} WHERE id = ?`,
|
|
2566
|
+
`SELECT * FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2548
2567
|
[recordId]
|
|
2549
2568
|
);
|
|
2550
2569
|
const row = rows[0];
|
|
@@ -2569,7 +2588,7 @@ var Store = class {
|
|
|
2569
2588
|
return null;
|
|
2570
2589
|
}
|
|
2571
2590
|
const rows = await this.adapter.query(
|
|
2572
|
-
`SELECT _version, _field_versions FROM ${collection} WHERE id = ?`,
|
|
2591
|
+
`SELECT _version, _field_versions FROM ${quoteIdent7(collection)} WHERE id = ?`,
|
|
2573
2592
|
[recordId]
|
|
2574
2593
|
);
|
|
2575
2594
|
const row = rows[0];
|
|
@@ -2675,7 +2694,7 @@ var Store = class {
|
|
|
2675
2694
|
async getLatestLocalOperationForRecord(collection, recordId) {
|
|
2676
2695
|
this.ensureOpen();
|
|
2677
2696
|
const rows = await this.adapter.query(
|
|
2678
|
-
`SELECT * FROM _kora_ops_${collection} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
2697
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE node_id = ? AND record_id = ? ORDER BY sequence_number DESC LIMIT 1`,
|
|
2679
2698
|
[this.nodeId, recordId]
|
|
2680
2699
|
);
|
|
2681
2700
|
const row = rows[0];
|
|
@@ -2690,7 +2709,7 @@ var Store = class {
|
|
|
2690
2709
|
async getLatestOperationForRecord(collection, recordId) {
|
|
2691
2710
|
this.ensureOpen();
|
|
2692
2711
|
const rows = await this.adapter.query(
|
|
2693
|
-
`SELECT * FROM _kora_ops_${collection} WHERE record_id = ?`,
|
|
2712
|
+
`SELECT * FROM ${quoteIdent7(`_kora_ops_${collection}`)} WHERE record_id = ?`,
|
|
2694
2713
|
[recordId]
|
|
2695
2714
|
);
|
|
2696
2715
|
let latest = null;
|
|
@@ -2780,7 +2799,7 @@ var Store = class {
|
|
|
2780
2799
|
*/
|
|
2781
2800
|
async exportBackup(options) {
|
|
2782
2801
|
this.ensureOpen();
|
|
2783
|
-
const { exportBackup: doExport } = await import("./backup-
|
|
2802
|
+
const { exportBackup: doExport } = await import("./backup-IQE4QC7Q.js");
|
|
2784
2803
|
return doExport(this.adapter, this.schema, this.nodeId, this.schema.version, options);
|
|
2785
2804
|
}
|
|
2786
2805
|
/**
|
|
@@ -2792,7 +2811,7 @@ var Store = class {
|
|
|
2792
2811
|
*/
|
|
2793
2812
|
async importBackup(data, options) {
|
|
2794
2813
|
this.ensureOpen();
|
|
2795
|
-
const { restoreBackup: doRestore } = await import("./backup-
|
|
2814
|
+
const { restoreBackup: doRestore } = await import("./backup-IQE4QC7Q.js");
|
|
2796
2815
|
return doRestore(this.adapter, this.schema, data, options);
|
|
2797
2816
|
}
|
|
2798
2817
|
/**
|
|
@@ -2877,14 +2896,14 @@ var Store = class {
|
|
|
2877
2896
|
*/
|
|
2878
2897
|
async runBackfill(collection, transform) {
|
|
2879
2898
|
const rows = await this.adapter.query(
|
|
2880
|
-
`SELECT * FROM ${collection} WHERE _deleted = 0`
|
|
2899
|
+
`SELECT * FROM ${quoteIdent7(collection)} WHERE _deleted = 0`
|
|
2881
2900
|
);
|
|
2882
2901
|
await this.adapter.transaction(async (tx) => {
|
|
2883
2902
|
for (const row of rows) {
|
|
2884
2903
|
const updates = transform(row);
|
|
2885
2904
|
const fields = Object.keys(updates);
|
|
2886
2905
|
if (fields.length === 0) continue;
|
|
2887
|
-
const setClauses = fields.map((f) => `${f} = ?`).join(", ");
|
|
2906
|
+
const setClauses = fields.map((f) => `${quoteIdent7(f)} = ?`).join(", ");
|
|
2888
2907
|
const values = fields.map((f) => {
|
|
2889
2908
|
const val = updates[f];
|
|
2890
2909
|
if (typeof val === "boolean") return val ? 1 : 0;
|
|
@@ -2894,7 +2913,7 @@ var Store = class {
|
|
|
2894
2913
|
return val;
|
|
2895
2914
|
});
|
|
2896
2915
|
values.push(row.id);
|
|
2897
|
-
await tx.execute(`UPDATE ${collection} SET ${setClauses} WHERE id = ?`, values);
|
|
2916
|
+
await tx.execute(`UPDATE ${quoteIdent7(collection)} SET ${setClauses} WHERE id = ?`, values);
|
|
2898
2917
|
}
|
|
2899
2918
|
});
|
|
2900
2919
|
}
|
|
@@ -3422,12 +3441,518 @@ function asRichTextSyncEngine(engine) {
|
|
|
3422
3441
|
}
|
|
3423
3442
|
return engine;
|
|
3424
3443
|
}
|
|
3444
|
+
|
|
3445
|
+
// src/blob/opfs-blob-store.ts
|
|
3446
|
+
import { createBlobRef, hashBlob } from "@korajs/core";
|
|
3447
|
+
var OpfsBlobStore = class {
|
|
3448
|
+
constructor(dir) {
|
|
3449
|
+
this.dir = dir;
|
|
3450
|
+
}
|
|
3451
|
+
dir;
|
|
3452
|
+
shardFor(hash) {
|
|
3453
|
+
return hash.slice(0, 2);
|
|
3454
|
+
}
|
|
3455
|
+
async put(bytes, metadata = {}) {
|
|
3456
|
+
const ref = await createBlobRef(bytes, metadata);
|
|
3457
|
+
const shard = this.shardFor(ref.hash);
|
|
3458
|
+
if (await this.dir.has(shard, ref.hash)) {
|
|
3459
|
+
return ref;
|
|
3460
|
+
}
|
|
3461
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
3462
|
+
out.set(bytes);
|
|
3463
|
+
await this.dir.write(shard, ref.hash, out);
|
|
3464
|
+
return ref;
|
|
3465
|
+
}
|
|
3466
|
+
async get(hash) {
|
|
3467
|
+
const bytes = await this.dir.read(this.shardFor(hash), hash);
|
|
3468
|
+
if (bytes === null) {
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
const actual = await hashBlob(bytes);
|
|
3472
|
+
if (actual !== hash) {
|
|
3473
|
+
throw new BlobIntegrityError(hash, actual);
|
|
3474
|
+
}
|
|
3475
|
+
return bytes;
|
|
3476
|
+
}
|
|
3477
|
+
async has(hash) {
|
|
3478
|
+
return this.dir.has(this.shardFor(hash), hash);
|
|
3479
|
+
}
|
|
3480
|
+
async delete(hash) {
|
|
3481
|
+
return this.dir.remove(this.shardFor(hash), hash);
|
|
3482
|
+
}
|
|
3483
|
+
async size() {
|
|
3484
|
+
return this.dir.count();
|
|
3485
|
+
}
|
|
3486
|
+
async list() {
|
|
3487
|
+
return this.dir.keys();
|
|
3488
|
+
}
|
|
3489
|
+
};
|
|
3490
|
+
function getStorageManager() {
|
|
3491
|
+
const nav = globalThis.navigator;
|
|
3492
|
+
if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
|
|
3493
|
+
throw new Error(
|
|
3494
|
+
"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."
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
return nav.storage;
|
|
3498
|
+
}
|
|
3499
|
+
function isNotFoundError(error) {
|
|
3500
|
+
return typeof error === "object" && error !== null && error.name === "NotFoundError";
|
|
3501
|
+
}
|
|
3502
|
+
async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
|
|
3503
|
+
const storage = getStorageManager();
|
|
3504
|
+
if (typeof storage.persist === "function") {
|
|
3505
|
+
try {
|
|
3506
|
+
await storage.persist();
|
|
3507
|
+
} catch {
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
const opfsRoot = await storage.getDirectory();
|
|
3511
|
+
const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
|
|
3512
|
+
async function shardHandle(shard, create) {
|
|
3513
|
+
try {
|
|
3514
|
+
return await root.getDirectoryHandle(shard, { create });
|
|
3515
|
+
} catch (error) {
|
|
3516
|
+
if (isNotFoundError(error)) {
|
|
3517
|
+
return null;
|
|
3518
|
+
}
|
|
3519
|
+
throw error;
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
return {
|
|
3523
|
+
async read(shard, name) {
|
|
3524
|
+
const dir = await shardHandle(shard, false);
|
|
3525
|
+
if (!dir) {
|
|
3526
|
+
return null;
|
|
3527
|
+
}
|
|
3528
|
+
let handle;
|
|
3529
|
+
try {
|
|
3530
|
+
handle = await dir.getFileHandle(name, { create: false });
|
|
3531
|
+
} catch (error) {
|
|
3532
|
+
if (isNotFoundError(error)) {
|
|
3533
|
+
return null;
|
|
3534
|
+
}
|
|
3535
|
+
throw error;
|
|
3536
|
+
}
|
|
3537
|
+
const file = await handle.getFile();
|
|
3538
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
3539
|
+
},
|
|
3540
|
+
async write(shard, name, bytes) {
|
|
3541
|
+
const dir = await root.getDirectoryHandle(shard, { create: true });
|
|
3542
|
+
const handle = await dir.getFileHandle(name, { create: true });
|
|
3543
|
+
const writable = await handle.createWritable({ keepExistingData: false });
|
|
3544
|
+
try {
|
|
3545
|
+
await writable.write(bytes);
|
|
3546
|
+
} finally {
|
|
3547
|
+
await writable.close();
|
|
3548
|
+
}
|
|
3549
|
+
},
|
|
3550
|
+
async remove(shard, name) {
|
|
3551
|
+
const dir = await shardHandle(shard, false);
|
|
3552
|
+
if (!dir) {
|
|
3553
|
+
return false;
|
|
3554
|
+
}
|
|
3555
|
+
try {
|
|
3556
|
+
await dir.removeEntry(name);
|
|
3557
|
+
return true;
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
if (isNotFoundError(error)) {
|
|
3560
|
+
return false;
|
|
3561
|
+
}
|
|
3562
|
+
throw error;
|
|
3563
|
+
}
|
|
3564
|
+
},
|
|
3565
|
+
async has(shard, name) {
|
|
3566
|
+
const dir = await shardHandle(shard, false);
|
|
3567
|
+
if (!dir) {
|
|
3568
|
+
return false;
|
|
3569
|
+
}
|
|
3570
|
+
try {
|
|
3571
|
+
await dir.getFileHandle(name, { create: false });
|
|
3572
|
+
return true;
|
|
3573
|
+
} catch (error) {
|
|
3574
|
+
if (isNotFoundError(error)) {
|
|
3575
|
+
return false;
|
|
3576
|
+
}
|
|
3577
|
+
throw error;
|
|
3578
|
+
}
|
|
3579
|
+
},
|
|
3580
|
+
async count() {
|
|
3581
|
+
let total = 0;
|
|
3582
|
+
for await (const shard of root.keys()) {
|
|
3583
|
+
const dir = await shardHandle(shard, false);
|
|
3584
|
+
if (!dir) {
|
|
3585
|
+
continue;
|
|
3586
|
+
}
|
|
3587
|
+
for await (const name of dir.keys()) {
|
|
3588
|
+
if (!name.endsWith(".tmp")) {
|
|
3589
|
+
total++;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
return total;
|
|
3594
|
+
},
|
|
3595
|
+
async keys() {
|
|
3596
|
+
const names = [];
|
|
3597
|
+
for await (const shard of root.keys()) {
|
|
3598
|
+
const dir = await shardHandle(shard, false);
|
|
3599
|
+
if (!dir) {
|
|
3600
|
+
continue;
|
|
3601
|
+
}
|
|
3602
|
+
for await (const name of dir.keys()) {
|
|
3603
|
+
if (!name.endsWith(".tmp")) {
|
|
3604
|
+
names.push(name);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
return names;
|
|
3609
|
+
}
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
async function createOpfsBlobStore(rootDirName = "kora-blobs") {
|
|
3613
|
+
return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
// src/blob/blob-chunking.ts
|
|
3617
|
+
import { hashBlob as hashBlob2 } from "@korajs/core";
|
|
3618
|
+
var DEFAULT_CHUNK_SIZE = 256 * 1024;
|
|
3619
|
+
async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
|
|
3620
|
+
if (chunkSize <= 0) {
|
|
3621
|
+
throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
|
|
3622
|
+
}
|
|
3623
|
+
const chunkHashes = [];
|
|
3624
|
+
const chunks = /* @__PURE__ */ new Map();
|
|
3625
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
3626
|
+
const end = Math.min(offset + chunkSize, bytes.byteLength);
|
|
3627
|
+
const chunk = new Uint8Array(end - offset);
|
|
3628
|
+
chunk.set(bytes.subarray(offset, end));
|
|
3629
|
+
const hash = await hashBlob2(chunk);
|
|
3630
|
+
chunkHashes.push(hash);
|
|
3631
|
+
if (!chunks.has(hash)) {
|
|
3632
|
+
chunks.set(hash, chunk);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
const blobHash = await hashBlob2(bytes);
|
|
3636
|
+
const manifest = {
|
|
3637
|
+
blobHash,
|
|
3638
|
+
size: bytes.byteLength,
|
|
3639
|
+
chunkSize,
|
|
3640
|
+
chunkHashes
|
|
3641
|
+
};
|
|
3642
|
+
if (metadata.mimeType !== void 0) {
|
|
3643
|
+
manifest.mimeType = metadata.mimeType;
|
|
3644
|
+
}
|
|
3645
|
+
if (metadata.filename !== void 0) {
|
|
3646
|
+
manifest.filename = metadata.filename;
|
|
3647
|
+
}
|
|
3648
|
+
return { manifest, chunks };
|
|
3649
|
+
}
|
|
3650
|
+
async function reassembleBlob(manifest, chunkStore) {
|
|
3651
|
+
const parts = [];
|
|
3652
|
+
let total = 0;
|
|
3653
|
+
for (const hash of manifest.chunkHashes) {
|
|
3654
|
+
const chunk = await chunkStore.get(hash);
|
|
3655
|
+
if (chunk === null) {
|
|
3656
|
+
throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
|
|
3657
|
+
}
|
|
3658
|
+
parts.push(chunk);
|
|
3659
|
+
total += chunk.byteLength;
|
|
3660
|
+
}
|
|
3661
|
+
const out = new Uint8Array(total);
|
|
3662
|
+
let offset = 0;
|
|
3663
|
+
for (const part of parts) {
|
|
3664
|
+
out.set(part, offset);
|
|
3665
|
+
offset += part.byteLength;
|
|
3666
|
+
}
|
|
3667
|
+
const actual = await hashBlob2(out);
|
|
3668
|
+
if (actual !== manifest.blobHash) {
|
|
3669
|
+
throw new BlobIntegrityError(manifest.blobHash, actual);
|
|
3670
|
+
}
|
|
3671
|
+
return out;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
// src/blob/blob-transfer.ts
|
|
3675
|
+
import { hashBlob as hashBlob3 } from "@korajs/core";
|
|
3676
|
+
async function receiveBlob(manifest, provider, stores) {
|
|
3677
|
+
let chunksFetched = 0;
|
|
3678
|
+
let chunksSkipped = 0;
|
|
3679
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3680
|
+
for (const hash of manifest.chunkHashes) {
|
|
3681
|
+
if (seen.has(hash)) {
|
|
3682
|
+
continue;
|
|
3683
|
+
}
|
|
3684
|
+
seen.add(hash);
|
|
3685
|
+
if (await stores.chunkStore.has(hash)) {
|
|
3686
|
+
chunksSkipped++;
|
|
3687
|
+
continue;
|
|
3688
|
+
}
|
|
3689
|
+
const bytes = await provider.getChunk(hash);
|
|
3690
|
+
if (bytes === null) {
|
|
3691
|
+
throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
|
|
3692
|
+
}
|
|
3693
|
+
const actual = await hashBlob3(bytes);
|
|
3694
|
+
if (actual !== hash) {
|
|
3695
|
+
throw new BlobIntegrityError(hash, actual);
|
|
3696
|
+
}
|
|
3697
|
+
await stores.chunkStore.put(bytes);
|
|
3698
|
+
chunksFetched++;
|
|
3699
|
+
}
|
|
3700
|
+
const full = await reassembleBlob(manifest, stores.chunkStore);
|
|
3701
|
+
const ref = await stores.blobStore.put(full, {
|
|
3702
|
+
mimeType: manifest.mimeType,
|
|
3703
|
+
filename: manifest.filename
|
|
3704
|
+
});
|
|
3705
|
+
return { ref, chunksFetched, chunksSkipped };
|
|
3706
|
+
}
|
|
3707
|
+
async function prepareBlobForSend(bytes, chunkStore, options = {}) {
|
|
3708
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
3709
|
+
mimeType: options.mimeType,
|
|
3710
|
+
filename: options.filename
|
|
3711
|
+
});
|
|
3712
|
+
for (const chunk of chunks.values()) {
|
|
3713
|
+
await chunkStore.put(chunk);
|
|
3714
|
+
}
|
|
3715
|
+
const provider = {
|
|
3716
|
+
getChunk: (hash) => chunkStore.get(hash)
|
|
3717
|
+
};
|
|
3718
|
+
return { manifest, provider };
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
// src/blob/blob-manifest-transfer.ts
|
|
3722
|
+
import { hashBlob as hashBlob4 } from "@korajs/core";
|
|
3723
|
+
function serializeBlobManifest(manifest) {
|
|
3724
|
+
const canonical = {
|
|
3725
|
+
blobHash: manifest.blobHash,
|
|
3726
|
+
size: manifest.size,
|
|
3727
|
+
chunkSize: manifest.chunkSize,
|
|
3728
|
+
chunkHashes: manifest.chunkHashes
|
|
3729
|
+
};
|
|
3730
|
+
if (manifest.mimeType !== void 0) {
|
|
3731
|
+
canonical.mimeType = manifest.mimeType;
|
|
3732
|
+
}
|
|
3733
|
+
if (manifest.filename !== void 0) {
|
|
3734
|
+
canonical.filename = manifest.filename;
|
|
3735
|
+
}
|
|
3736
|
+
return new TextEncoder().encode(JSON.stringify(canonical));
|
|
3737
|
+
}
|
|
3738
|
+
function parseBlobManifest(bytes) {
|
|
3739
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
3740
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
3741
|
+
throw new Error("Invalid blob manifest: not an object");
|
|
3742
|
+
}
|
|
3743
|
+
const obj = parsed;
|
|
3744
|
+
if (typeof obj.blobHash !== "string") {
|
|
3745
|
+
throw new Error("Invalid blob manifest: missing blobHash");
|
|
3746
|
+
}
|
|
3747
|
+
if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
|
|
3748
|
+
throw new Error("Invalid blob manifest: missing size or chunkSize");
|
|
3749
|
+
}
|
|
3750
|
+
if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
|
|
3751
|
+
throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
|
|
3752
|
+
}
|
|
3753
|
+
const manifest = {
|
|
3754
|
+
blobHash: obj.blobHash,
|
|
3755
|
+
size: obj.size,
|
|
3756
|
+
chunkSize: obj.chunkSize,
|
|
3757
|
+
chunkHashes: obj.chunkHashes
|
|
3758
|
+
};
|
|
3759
|
+
if (typeof obj.mimeType === "string") {
|
|
3760
|
+
manifest.mimeType = obj.mimeType;
|
|
3761
|
+
}
|
|
3762
|
+
if (typeof obj.filename === "string") {
|
|
3763
|
+
manifest.filename = obj.filename;
|
|
3764
|
+
}
|
|
3765
|
+
return manifest;
|
|
3766
|
+
}
|
|
3767
|
+
async function putBlobForTransfer(store, bytes, options = {}) {
|
|
3768
|
+
const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
|
|
3769
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
3770
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
3771
|
+
});
|
|
3772
|
+
for (const chunk of chunks.values()) {
|
|
3773
|
+
await store.put(chunk);
|
|
3774
|
+
}
|
|
3775
|
+
const baseRef = await store.put(bytes, {
|
|
3776
|
+
...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
|
|
3777
|
+
...options.filename !== void 0 ? { filename: options.filename } : {}
|
|
3778
|
+
});
|
|
3779
|
+
const manifestRef = await store.put(serializeBlobManifest(manifest));
|
|
3780
|
+
return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
|
|
3781
|
+
}
|
|
3782
|
+
async function fetchBlobManifest(provider, manifestHash) {
|
|
3783
|
+
const bytes = await provider.getChunk(manifestHash);
|
|
3784
|
+
if (bytes === null) {
|
|
3785
|
+
throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
|
|
3786
|
+
}
|
|
3787
|
+
const actual = await hashBlob4(bytes);
|
|
3788
|
+
if (actual !== manifestHash) {
|
|
3789
|
+
throw new BlobIntegrityError(manifestHash, actual);
|
|
3790
|
+
}
|
|
3791
|
+
return parseBlobManifest(bytes);
|
|
3792
|
+
}
|
|
3793
|
+
async function resolveBlobManifest(provider, ref) {
|
|
3794
|
+
if (ref.manifestHash === void 0) {
|
|
3795
|
+
throw new Error(
|
|
3796
|
+
`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.`
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
return fetchBlobManifest(provider, ref.manifestHash);
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
// src/blob/server-blob-callbacks.ts
|
|
3803
|
+
import { hashBlob as hashBlob5 } from "@korajs/core";
|
|
3804
|
+
function toServerBlobCallbacks(store) {
|
|
3805
|
+
return {
|
|
3806
|
+
resolveBlobChunk: (hash) => store.get(hash),
|
|
3807
|
+
async persistBlobChunk(hash, bytes) {
|
|
3808
|
+
const actual = await hashBlob5(bytes);
|
|
3809
|
+
if (actual !== hash) {
|
|
3810
|
+
return;
|
|
3811
|
+
}
|
|
3812
|
+
await store.put(bytes);
|
|
3813
|
+
}
|
|
3814
|
+
};
|
|
3815
|
+
}
|
|
3816
|
+
function createMemoryServerBlobStore() {
|
|
3817
|
+
const store = new MemoryBlobStore();
|
|
3818
|
+
return { store, callbacks: toServerBlobCallbacks(store) };
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
// src/blob/blob-gc.ts
|
|
3822
|
+
import { isBlobRef } from "@korajs/core";
|
|
3823
|
+
function extractBlobRefs(record) {
|
|
3824
|
+
const refs = [];
|
|
3825
|
+
for (const value of Object.values(record)) {
|
|
3826
|
+
if (isBlobRef(value)) {
|
|
3827
|
+
refs.push(value);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
return refs;
|
|
3831
|
+
}
|
|
3832
|
+
async function collectBlobGarbage(store, liveRefs, options = {}) {
|
|
3833
|
+
const live = /* @__PURE__ */ new Set();
|
|
3834
|
+
for (const ref of liveRefs) {
|
|
3835
|
+
live.add(ref.hash);
|
|
3836
|
+
if (ref.manifestHash === void 0) {
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
live.add(ref.manifestHash);
|
|
3840
|
+
try {
|
|
3841
|
+
const manifestBytes = await store.get(ref.manifestHash);
|
|
3842
|
+
if (manifestBytes !== null) {
|
|
3843
|
+
const manifest = parseBlobManifest(manifestBytes);
|
|
3844
|
+
for (const chunkHash of manifest.chunkHashes) {
|
|
3845
|
+
live.add(chunkHash);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
} catch {
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
const all = await store.list();
|
|
3852
|
+
const collectedHashes = [];
|
|
3853
|
+
for (const hash of all) {
|
|
3854
|
+
if (live.has(hash)) {
|
|
3855
|
+
continue;
|
|
3856
|
+
}
|
|
3857
|
+
collectedHashes.push(hash);
|
|
3858
|
+
if (!options.dryRun) {
|
|
3859
|
+
await store.delete(hash);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
return {
|
|
3863
|
+
scanned: all.length,
|
|
3864
|
+
live: live.size,
|
|
3865
|
+
collected: collectedHashes.length,
|
|
3866
|
+
collectedHashes
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
// src/blob/blob-chunk-transport.ts
|
|
3871
|
+
import { generateUUIDv7 as generateUUIDv74 } from "@korajs/core";
|
|
3872
|
+
function createRemoteChunkProvider(port, options = {}) {
|
|
3873
|
+
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
3874
|
+
const pending = /* @__PURE__ */ new Map();
|
|
3875
|
+
port.onMessage((message) => {
|
|
3876
|
+
if (message.type !== "blob-chunk-response") {
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
const entry = pending.get(message.requestId);
|
|
3880
|
+
if (!entry) {
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3883
|
+
pending.delete(message.requestId);
|
|
3884
|
+
clearTimeout(entry.timer);
|
|
3885
|
+
entry.resolve(message.bytes);
|
|
3886
|
+
});
|
|
3887
|
+
return {
|
|
3888
|
+
pendingCount: () => pending.size,
|
|
3889
|
+
getChunk(hash) {
|
|
3890
|
+
const requestId = generateUUIDv74();
|
|
3891
|
+
return new Promise((resolve, reject) => {
|
|
3892
|
+
const timer = setTimeout(() => {
|
|
3893
|
+
pending.delete(requestId);
|
|
3894
|
+
reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
|
|
3895
|
+
}, timeoutMs);
|
|
3896
|
+
pending.set(requestId, { resolve, reject, timer });
|
|
3897
|
+
port.send({ type: "blob-chunk-request", requestId, hash });
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
function serveBlobChunks(port, blobStore) {
|
|
3903
|
+
port.onMessage((message) => {
|
|
3904
|
+
if (message.type !== "blob-chunk-request") {
|
|
3905
|
+
return;
|
|
3906
|
+
}
|
|
3907
|
+
void blobStore.get(message.hash).then(
|
|
3908
|
+
(bytes) => {
|
|
3909
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
|
|
3910
|
+
},
|
|
3911
|
+
() => {
|
|
3912
|
+
port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
|
|
3913
|
+
}
|
|
3914
|
+
);
|
|
3915
|
+
});
|
|
3916
|
+
}
|
|
3917
|
+
function createChunkPortPair() {
|
|
3918
|
+
const handlersA = [];
|
|
3919
|
+
const handlersB = [];
|
|
3920
|
+
const a = {
|
|
3921
|
+
send(message) {
|
|
3922
|
+
queueMicrotask(() => {
|
|
3923
|
+
for (const handler of handlersB) {
|
|
3924
|
+
handler(message);
|
|
3925
|
+
}
|
|
3926
|
+
});
|
|
3927
|
+
},
|
|
3928
|
+
onMessage(handler) {
|
|
3929
|
+
handlersA.push(handler);
|
|
3930
|
+
}
|
|
3931
|
+
};
|
|
3932
|
+
const b = {
|
|
3933
|
+
send(message) {
|
|
3934
|
+
queueMicrotask(() => {
|
|
3935
|
+
for (const handler of handlersA) {
|
|
3936
|
+
handler(message);
|
|
3937
|
+
}
|
|
3938
|
+
});
|
|
3939
|
+
},
|
|
3940
|
+
onMessage(handler) {
|
|
3941
|
+
handlersB.push(handler);
|
|
3942
|
+
}
|
|
3943
|
+
};
|
|
3944
|
+
return { a, b };
|
|
3945
|
+
}
|
|
3425
3946
|
export {
|
|
3426
3947
|
AdapterError,
|
|
3948
|
+
BlobIntegrityError,
|
|
3427
3949
|
COMPACTION_BASELINE_META_KEY,
|
|
3428
3950
|
Collection,
|
|
3951
|
+
DEFAULT_CHUNK_SIZE,
|
|
3429
3952
|
InvalidStateTransitionError,
|
|
3430
3953
|
LAST_ACKED_SERVER_VECTOR_META_KEY,
|
|
3954
|
+
MemoryBlobStore,
|
|
3955
|
+
OpfsBlobStore,
|
|
3431
3956
|
OptimisticLockError,
|
|
3432
3957
|
PersistenceError,
|
|
3433
3958
|
QueryBuilder,
|
|
@@ -3445,26 +3970,44 @@ export {
|
|
|
3445
3970
|
WorkerTimeoutError,
|
|
3446
3971
|
asRichTextSyncEngine,
|
|
3447
3972
|
assertQueryReady,
|
|
3973
|
+
chunkBlob,
|
|
3974
|
+
collectBlobGarbage,
|
|
3448
3975
|
collectOperationsAheadOfServer,
|
|
3449
3976
|
compactOperationLog,
|
|
3450
3977
|
computeAckCompactionWatermark,
|
|
3978
|
+
createChunkPortPair,
|
|
3979
|
+
createMemoryServerBlobStore,
|
|
3980
|
+
createOpfsBlobDirectory,
|
|
3981
|
+
createOpfsBlobStore,
|
|
3982
|
+
createRemoteChunkProvider,
|
|
3451
3983
|
createRichTextController,
|
|
3452
3984
|
decodeAuditExport,
|
|
3453
3985
|
decodeRichtext,
|
|
3454
3986
|
deserializeVersionVectorFromMeta,
|
|
3455
3987
|
encodeRichtext,
|
|
3456
3988
|
exportBackup,
|
|
3989
|
+
extractBlobRefs,
|
|
3990
|
+
fetchBlobManifest,
|
|
3457
3991
|
getSharedQueryStoreCache,
|
|
3458
3992
|
mergeVersionVectors,
|
|
3993
|
+
parseBlobManifest,
|
|
3459
3994
|
persistedAuditTraceFromEvent,
|
|
3460
3995
|
pluralize,
|
|
3996
|
+
prepareBlobForSend,
|
|
3997
|
+
putBlobForTransfer,
|
|
3461
3998
|
readAuditExportManifest,
|
|
3462
3999
|
readBackupManifest,
|
|
4000
|
+
reassembleBlob,
|
|
4001
|
+
receiveBlob,
|
|
4002
|
+
resolveBlobManifest,
|
|
3463
4003
|
restoreBackup,
|
|
3464
4004
|
richtextStatesEqual,
|
|
3465
4005
|
richtextToPlainText,
|
|
4006
|
+
serializeBlobManifest,
|
|
3466
4007
|
serializeVersionVectorToMeta,
|
|
4008
|
+
serveBlobChunks,
|
|
3467
4009
|
singularize,
|
|
4010
|
+
toServerBlobCallbacks,
|
|
3468
4011
|
validateStateTransition,
|
|
3469
4012
|
validateUpdateStateMachine,
|
|
3470
4013
|
verifyAuditExportChecksum,
|