@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.
Files changed (45) hide show
  1. package/dist/adapters/better-sqlite3.d.cts +1 -1
  2. package/dist/adapters/better-sqlite3.d.ts +1 -1
  3. package/dist/adapters/indexeddb.d.cts +1 -1
  4. package/dist/adapters/indexeddb.d.ts +1 -1
  5. package/dist/adapters/sqlite-wasm.d.cts +1 -1
  6. package/dist/adapters/sqlite-wasm.d.ts +1 -1
  7. package/dist/{backup-5XZ7THNJ.js → backup-ZV4Y424B.js} +3 -3
  8. package/dist/blob-fs.cjs +166 -0
  9. package/dist/blob-fs.cjs.map +1 -0
  10. package/dist/blob-fs.d.cts +29 -0
  11. package/dist/blob-fs.d.ts +29 -0
  12. package/dist/blob-fs.js +128 -0
  13. package/dist/blob-fs.js.map +1 -0
  14. package/dist/{chunk-ETDJEH2I.js → chunk-6NKLDBTY.js} +11 -1
  15. package/dist/{chunk-ETDJEH2I.js.map → chunk-6NKLDBTY.js.map} +1 -1
  16. package/dist/{chunk-EIVXWF4B.js → chunk-FFYA7J5H.js} +2 -2
  17. package/dist/chunk-W4MTTNEZ.js +55 -0
  18. package/dist/chunk-W4MTTNEZ.js.map +1 -0
  19. package/dist/{chunk-5R4QMY3Z.js → chunk-X57MWW35.js} +2 -2
  20. package/dist/{chunk-OUQDC5Z6.js → chunk-ZLWAYQEQ.js} +28 -12
  21. package/dist/chunk-ZLWAYQEQ.js.map +1 -0
  22. package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
  23. package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
  24. package/dist/{export-audit-VPGQWNR6.js → export-audit-AJ7RORNK.js} +3 -3
  25. package/dist/index.cjs +632 -9
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +397 -6
  28. package/dist/index.d.ts +397 -6
  29. package/dist/index.js +561 -26
  30. package/dist/index.js.map +1 -1
  31. package/dist/internal.cjs +25 -5
  32. package/dist/internal.cjs.map +1 -1
  33. package/dist/internal.d.cts +3 -3
  34. package/dist/internal.d.ts +3 -3
  35. package/dist/internal.js +3 -3
  36. package/dist/{types-CyjXwrjW.d.cts → types-5h4Hqcwc.d.ts} +10 -3
  37. package/dist/{types-DAxCladl.d.ts → types-DfPiPwKr.d.cts} +10 -3
  38. package/dist/{types-CJOVKxLw.d.ts → types-k6AZgiH4.d.cts} +6 -1
  39. package/dist/{types-CJOVKxLw.d.cts → types-k6AZgiH4.d.ts} +6 -1
  40. package/package.json +13 -2
  41. package/dist/chunk-OUQDC5Z6.js.map +0 -1
  42. /package/dist/{backup-5XZ7THNJ.js.map → backup-ZV4Y424B.js.map} +0 -0
  43. /package/dist/{chunk-EIVXWF4B.js.map → chunk-FFYA7J5H.js.map} +0 -0
  44. /package/dist/{chunk-5R4QMY3Z.js.map → chunk-X57MWW35.js.map} +0 -0
  45. /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-AJ7RORNK.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -239,6 +239,9 @@ function serializeValue(value, descriptor) {
239
239
  case "boolean":
240
240
  return value ? 1 : 0;
241
241
  case "array":
242
+ case "object":
243
+ case "json":
244
+ case "blob":
242
245
  return JSON.stringify(value);
243
246
  case "richtext":
244
247
  return encodeRichtext(value);
@@ -255,6 +258,13 @@ function deserializeValue(value, descriptor) {
255
258
  return JSON.parse(value);
256
259
  }
257
260
  return value;
261
+ case "object":
262
+ case "json":
263
+ case "blob":
264
+ if (typeof value === "string") {
265
+ return JSON.parse(value);
266
+ }
267
+ return value;
258
268
  case "richtext":
259
269
  return decodeRichtext(value);
260
270
  default:
@@ -973,10 +983,14 @@ var init_export_audit = __esm({
973
983
  var index_exports = {};
974
984
  __export(index_exports, {
975
985
  AdapterError: () => AdapterError,
986
+ BlobIntegrityError: () => BlobIntegrityError,
976
987
  COMPACTION_BASELINE_META_KEY: () => COMPACTION_BASELINE_META_KEY,
977
988
  Collection: () => Collection,
989
+ DEFAULT_CHUNK_SIZE: () => DEFAULT_CHUNK_SIZE,
978
990
  InvalidStateTransitionError: () => InvalidStateTransitionError,
979
991
  LAST_ACKED_SERVER_VECTOR_META_KEY: () => LAST_ACKED_SERVER_VECTOR_META_KEY,
992
+ MemoryBlobStore: () => MemoryBlobStore,
993
+ OpfsBlobStore: () => OpfsBlobStore,
980
994
  OptimisticLockError: () => OptimisticLockError,
981
995
  PersistenceError: () => PersistenceError,
982
996
  QueryBuilder: () => QueryBuilder,
@@ -994,26 +1008,44 @@ __export(index_exports, {
994
1008
  WorkerTimeoutError: () => WorkerTimeoutError,
995
1009
  asRichTextSyncEngine: () => asRichTextSyncEngine,
996
1010
  assertQueryReady: () => assertQueryReady,
1011
+ chunkBlob: () => chunkBlob,
1012
+ collectBlobGarbage: () => collectBlobGarbage,
997
1013
  collectOperationsAheadOfServer: () => collectOperationsAheadOfServer,
998
1014
  compactOperationLog: () => compactOperationLog,
999
1015
  computeAckCompactionWatermark: () => computeAckCompactionWatermark,
1016
+ createChunkPortPair: () => createChunkPortPair,
1017
+ createMemoryServerBlobStore: () => createMemoryServerBlobStore,
1018
+ createOpfsBlobDirectory: () => createOpfsBlobDirectory,
1019
+ createOpfsBlobStore: () => createOpfsBlobStore,
1020
+ createRemoteChunkProvider: () => createRemoteChunkProvider,
1000
1021
  createRichTextController: () => createRichTextController,
1001
1022
  decodeAuditExport: () => decodeAuditExport,
1002
1023
  decodeRichtext: () => decodeRichtext,
1003
1024
  deserializeVersionVectorFromMeta: () => deserializeVersionVectorFromMeta,
1004
1025
  encodeRichtext: () => encodeRichtext,
1005
1026
  exportBackup: () => exportBackup,
1027
+ extractBlobRefs: () => extractBlobRefs,
1028
+ fetchBlobManifest: () => fetchBlobManifest,
1006
1029
  getSharedQueryStoreCache: () => getSharedQueryStoreCache,
1007
1030
  mergeVersionVectors: () => mergeVersionVectors,
1031
+ parseBlobManifest: () => parseBlobManifest,
1008
1032
  persistedAuditTraceFromEvent: () => persistedAuditTraceFromEvent,
1009
1033
  pluralize: () => pluralize,
1034
+ prepareBlobForSend: () => prepareBlobForSend,
1035
+ putBlobForTransfer: () => putBlobForTransfer,
1010
1036
  readAuditExportManifest: () => readAuditExportManifest,
1011
1037
  readBackupManifest: () => readBackupManifest,
1038
+ reassembleBlob: () => reassembleBlob,
1039
+ receiveBlob: () => receiveBlob,
1040
+ resolveBlobManifest: () => resolveBlobManifest,
1012
1041
  restoreBackup: () => restoreBackup,
1013
1042
  richtextStatesEqual: () => richtextStatesEqual,
1014
1043
  richtextToPlainText: () => richtextToPlainText,
1044
+ serializeBlobManifest: () => serializeBlobManifest,
1015
1045
  serializeVersionVectorToMeta: () => serializeVersionVectorToMeta,
1046
+ serveBlobChunks: () => serveBlobChunks,
1016
1047
  singularize: () => singularize,
1048
+ toServerBlobCallbacks: () => toServerBlobCallbacks,
1017
1049
  validateStateTransition: () => validateStateTransition,
1018
1050
  validateUpdateStateMachine: () => validateUpdateStateMachine,
1019
1051
  verifyAuditExportChecksum: () => verifyAuditExportChecksum,
@@ -1613,6 +1645,11 @@ async function executeInsert(ctx, data) {
1613
1645
  validated[fieldName] = Date.now();
1614
1646
  }
1615
1647
  }
1648
+ const writeData = await (0, import_core9.transformSecretFieldsForWrite)(
1649
+ validated,
1650
+ ctx.definition,
1651
+ ctx.secretKeyProvider
1652
+ );
1616
1653
  const causalDeps = resolveCausalDeps(ctx);
1617
1654
  let operation;
1618
1655
  let record;
@@ -1627,7 +1664,7 @@ async function executeInsert(ctx, data) {
1627
1664
  // Binary richtext values are tagged as canonical JSON BEFORE the
1628
1665
  // operation is content-hashed, so the hash input, persisted JSON,
1629
1666
  // and wire payload are the identical value.
1630
- data: encodeRichtextFieldsForOpData(validated, ctx.definition.fields),
1667
+ data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
1631
1668
  previousData: null,
1632
1669
  sequenceNumber,
1633
1670
  causalDeps,
@@ -1636,7 +1673,7 @@ async function executeInsert(ctx, data) {
1636
1673
  ctx.clock
1637
1674
  );
1638
1675
  ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1639
- const serializedData = serializeRecord(validated, ctx.definition.fields);
1676
+ const serializedData = serializeRecord(writeData, ctx.definition.fields);
1640
1677
  const version = serializeRowVersion(operation.timestamp);
1641
1678
  record = {
1642
1679
  id: recordId,
@@ -1661,7 +1698,7 @@ async function executeInsert(ctx, data) {
1661
1698
  ctx.onMutation(ctx.collection, operation);
1662
1699
  return {
1663
1700
  id: recordId,
1664
- ...validated,
1701
+ ...writeData,
1665
1702
  createdAt: operation.timestamp.wallTime,
1666
1703
  updatedAt: operation.timestamp.wallTime
1667
1704
  };
@@ -1789,6 +1826,11 @@ async function executeUpdate(ctx, id, data) {
1789
1826
  resolvedData[key] = value;
1790
1827
  }
1791
1828
  }
1829
+ const writeData = await (0, import_core11.transformSecretFieldsForWrite)(
1830
+ resolvedData,
1831
+ ctx.definition,
1832
+ ctx.secretKeyProvider
1833
+ );
1792
1834
  const hasAtomicOps = Object.keys(atomicOps).length > 0;
1793
1835
  const causalDeps = resolveCausalDeps(ctx);
1794
1836
  let operation;
@@ -1803,7 +1845,7 @@ async function executeUpdate(ctx, id, data) {
1803
1845
  // Binary richtext values (new and previous) are tagged as canonical
1804
1846
  // JSON BEFORE the operation is content-hashed, so the hash input,
1805
1847
  // persisted JSON, and wire payload are the identical value.
1806
- data: encodeRichtextFieldsForOpData(resolvedData, ctx.definition.fields),
1848
+ data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
1807
1849
  previousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),
1808
1850
  sequenceNumber,
1809
1851
  causalDeps,
@@ -1813,7 +1855,7 @@ async function executeUpdate(ctx, id, data) {
1813
1855
  ctx.clock
1814
1856
  );
1815
1857
  ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
1816
- const serializedChanges = serializeRecord(resolvedData, ctx.definition.fields);
1858
+ const serializedChanges = serializeRecord(writeData, ctx.definition.fields);
1817
1859
  const version = serializeRowVersion(operation.timestamp);
1818
1860
  const mergedFieldVersions = parseFieldVersions(currentRow._field_versions);
1819
1861
  for (const changedField of Object.keys(serializedChanges)) {
@@ -1847,7 +1889,7 @@ async function executeUpdate(ctx, id, data) {
1847
1889
  // src/collection/collection.ts
1848
1890
  init_serializer();
1849
1891
  var Collection = class {
1850
- constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker) {
1892
+ constructor(name, definition, schema, adapter, clock, nodeId, allocateSequenceNumber, onMutation, relationEnforcer, mutationHandler, causalTracker, secretKeyProvider) {
1851
1893
  this.name = name;
1852
1894
  this.definition = definition;
1853
1895
  this.schema = schema;
@@ -1859,6 +1901,7 @@ var Collection = class {
1859
1901
  this.relationEnforcer = relationEnforcer;
1860
1902
  this.mutationHandler = mutationHandler;
1861
1903
  this.causalTracker = causalTracker;
1904
+ this.secretKeyProvider = secretKeyProvider;
1862
1905
  }
1863
1906
  name;
1864
1907
  definition;
@@ -1871,6 +1914,7 @@ var Collection = class {
1871
1914
  relationEnforcer;
1872
1915
  mutationHandler;
1873
1916
  causalTracker;
1917
+ secretKeyProvider;
1874
1918
  mutationContext() {
1875
1919
  return {
1876
1920
  collection: this.name,
@@ -1883,7 +1927,8 @@ var Collection = class {
1883
1927
  onMutation: this.onMutation,
1884
1928
  relationEnforcer: this.relationEnforcer,
1885
1929
  causalTracker: this.causalTracker,
1886
- inTransaction: false
1930
+ inTransaction: false,
1931
+ secretKeyProvider: this.secretKeyProvider
1887
1932
  };
1888
1933
  }
1889
1934
  async insert(data) {
@@ -3661,6 +3706,7 @@ var Store = class {
3661
3706
  localMutationHandler;
3662
3707
  relationEnforcer = null;
3663
3708
  causalTracker = null;
3709
+ secretKeyProvider;
3664
3710
  constructor(config) {
3665
3711
  this.schema = config.schema;
3666
3712
  this.adapter = config.adapter;
@@ -3669,6 +3715,7 @@ var Store = class {
3669
3715
  this.isolation = config.isolation ?? "shared";
3670
3716
  this.emitter = config.emitter ?? null;
3671
3717
  this.localMutationHandler = config.localMutationHandler ?? null;
3718
+ this.secretKeyProvider = config.secretKeyProvider;
3672
3719
  this.subscriptionManager = new SubscriptionManager({
3673
3720
  onQuerySubscribed: config.onQuerySubscribed
3674
3721
  });
@@ -3711,7 +3758,8 @@ var Store = class {
3711
3758
  },
3712
3759
  this.relationEnforcer,
3713
3760
  this.localMutationHandler,
3714
- this.causalTracker
3761
+ this.causalTracker,
3762
+ this.secretKeyProvider
3715
3763
  );
3716
3764
  this.collections.set(name, col);
3717
3765
  }
@@ -4172,7 +4220,8 @@ var Store = class {
4172
4220
  relationEnforcer: this.relationEnforcer,
4173
4221
  causalTracker: this.causalTracker,
4174
4222
  inTransaction: options?.inTransaction ?? false,
4175
- extraCausalDeps: options?.extraCausalDeps
4223
+ extraCausalDeps: options?.extraCausalDeps,
4224
+ secretKeyProvider: this.secretKeyProvider
4176
4225
  };
4177
4226
  }
4178
4227
  /**
@@ -5075,13 +5124,569 @@ init_backup();
5075
5124
  init_audit_trace_store();
5076
5125
  init_audit_json();
5077
5126
  init_export_audit();
5127
+
5128
+ // src/blob/content-addressed-blob-store.ts
5129
+ var import_core21 = require("@korajs/core");
5130
+ var BlobIntegrityError = class extends Error {
5131
+ constructor(expectedHash, actualHash) {
5132
+ super(`Blob integrity check failed: content under hash ${expectedHash} hashes to ${actualHash}`);
5133
+ this.expectedHash = expectedHash;
5134
+ this.actualHash = actualHash;
5135
+ this.name = "BlobIntegrityError";
5136
+ }
5137
+ expectedHash;
5138
+ actualHash;
5139
+ };
5140
+ var MemoryBlobStore = class {
5141
+ blobs = /* @__PURE__ */ new Map();
5142
+ async put(bytes, metadata = {}) {
5143
+ const ref = await (0, import_core21.createBlobRef)(bytes, metadata);
5144
+ if (!this.blobs.has(ref.hash)) {
5145
+ const stored = new Uint8Array(bytes.byteLength);
5146
+ stored.set(bytes);
5147
+ this.blobs.set(ref.hash, stored);
5148
+ }
5149
+ return ref;
5150
+ }
5151
+ async get(hash) {
5152
+ const stored = this.blobs.get(hash);
5153
+ if (stored === void 0) {
5154
+ return null;
5155
+ }
5156
+ const actualHash = await (0, import_core21.hashBlob)(stored);
5157
+ if (actualHash !== hash) {
5158
+ throw new BlobIntegrityError(hash, actualHash);
5159
+ }
5160
+ const out = new Uint8Array(stored.byteLength);
5161
+ out.set(stored);
5162
+ return out;
5163
+ }
5164
+ async has(hash) {
5165
+ return this.blobs.has(hash);
5166
+ }
5167
+ async delete(hash) {
5168
+ return this.blobs.delete(hash);
5169
+ }
5170
+ async size() {
5171
+ return this.blobs.size;
5172
+ }
5173
+ async list() {
5174
+ return [...this.blobs.keys()];
5175
+ }
5176
+ };
5177
+
5178
+ // src/blob/opfs-blob-store.ts
5179
+ var import_core22 = require("@korajs/core");
5180
+ var OpfsBlobStore = class {
5181
+ constructor(dir) {
5182
+ this.dir = dir;
5183
+ }
5184
+ dir;
5185
+ shardFor(hash) {
5186
+ return hash.slice(0, 2);
5187
+ }
5188
+ async put(bytes, metadata = {}) {
5189
+ const ref = await (0, import_core22.createBlobRef)(bytes, metadata);
5190
+ const shard = this.shardFor(ref.hash);
5191
+ if (await this.dir.has(shard, ref.hash)) {
5192
+ return ref;
5193
+ }
5194
+ const out = new Uint8Array(bytes.byteLength);
5195
+ out.set(bytes);
5196
+ await this.dir.write(shard, ref.hash, out);
5197
+ return ref;
5198
+ }
5199
+ async get(hash) {
5200
+ const bytes = await this.dir.read(this.shardFor(hash), hash);
5201
+ if (bytes === null) {
5202
+ return null;
5203
+ }
5204
+ const actual = await (0, import_core22.hashBlob)(bytes);
5205
+ if (actual !== hash) {
5206
+ throw new BlobIntegrityError(hash, actual);
5207
+ }
5208
+ return bytes;
5209
+ }
5210
+ async has(hash) {
5211
+ return this.dir.has(this.shardFor(hash), hash);
5212
+ }
5213
+ async delete(hash) {
5214
+ return this.dir.remove(this.shardFor(hash), hash);
5215
+ }
5216
+ async size() {
5217
+ return this.dir.count();
5218
+ }
5219
+ async list() {
5220
+ return this.dir.keys();
5221
+ }
5222
+ };
5223
+ function getStorageManager() {
5224
+ const nav = globalThis.navigator;
5225
+ if (!nav?.storage || typeof nav.storage.getDirectory !== "function") {
5226
+ throw new Error(
5227
+ "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."
5228
+ );
5229
+ }
5230
+ return nav.storage;
5231
+ }
5232
+ function isNotFoundError(error) {
5233
+ return typeof error === "object" && error !== null && error.name === "NotFoundError";
5234
+ }
5235
+ async function createOpfsBlobDirectory(rootDirName = "kora-blobs") {
5236
+ const storage = getStorageManager();
5237
+ if (typeof storage.persist === "function") {
5238
+ try {
5239
+ await storage.persist();
5240
+ } catch {
5241
+ }
5242
+ }
5243
+ const opfsRoot = await storage.getDirectory();
5244
+ const root = await opfsRoot.getDirectoryHandle(rootDirName, { create: true });
5245
+ async function shardHandle(shard, create) {
5246
+ try {
5247
+ return await root.getDirectoryHandle(shard, { create });
5248
+ } catch (error) {
5249
+ if (isNotFoundError(error)) {
5250
+ return null;
5251
+ }
5252
+ throw error;
5253
+ }
5254
+ }
5255
+ return {
5256
+ async read(shard, name) {
5257
+ const dir = await shardHandle(shard, false);
5258
+ if (!dir) {
5259
+ return null;
5260
+ }
5261
+ let handle;
5262
+ try {
5263
+ handle = await dir.getFileHandle(name, { create: false });
5264
+ } catch (error) {
5265
+ if (isNotFoundError(error)) {
5266
+ return null;
5267
+ }
5268
+ throw error;
5269
+ }
5270
+ const file = await handle.getFile();
5271
+ return new Uint8Array(await file.arrayBuffer());
5272
+ },
5273
+ async write(shard, name, bytes) {
5274
+ const dir = await root.getDirectoryHandle(shard, { create: true });
5275
+ const handle = await dir.getFileHandle(name, { create: true });
5276
+ const writable = await handle.createWritable({ keepExistingData: false });
5277
+ try {
5278
+ await writable.write(bytes);
5279
+ } finally {
5280
+ await writable.close();
5281
+ }
5282
+ },
5283
+ async remove(shard, name) {
5284
+ const dir = await shardHandle(shard, false);
5285
+ if (!dir) {
5286
+ return false;
5287
+ }
5288
+ try {
5289
+ await dir.removeEntry(name);
5290
+ return true;
5291
+ } catch (error) {
5292
+ if (isNotFoundError(error)) {
5293
+ return false;
5294
+ }
5295
+ throw error;
5296
+ }
5297
+ },
5298
+ async has(shard, name) {
5299
+ const dir = await shardHandle(shard, false);
5300
+ if (!dir) {
5301
+ return false;
5302
+ }
5303
+ try {
5304
+ await dir.getFileHandle(name, { create: false });
5305
+ return true;
5306
+ } catch (error) {
5307
+ if (isNotFoundError(error)) {
5308
+ return false;
5309
+ }
5310
+ throw error;
5311
+ }
5312
+ },
5313
+ async count() {
5314
+ let total = 0;
5315
+ for await (const shard of root.keys()) {
5316
+ const dir = await shardHandle(shard, false);
5317
+ if (!dir) {
5318
+ continue;
5319
+ }
5320
+ for await (const name of dir.keys()) {
5321
+ if (!name.endsWith(".tmp")) {
5322
+ total++;
5323
+ }
5324
+ }
5325
+ }
5326
+ return total;
5327
+ },
5328
+ async keys() {
5329
+ const names = [];
5330
+ for await (const shard of root.keys()) {
5331
+ const dir = await shardHandle(shard, false);
5332
+ if (!dir) {
5333
+ continue;
5334
+ }
5335
+ for await (const name of dir.keys()) {
5336
+ if (!name.endsWith(".tmp")) {
5337
+ names.push(name);
5338
+ }
5339
+ }
5340
+ }
5341
+ return names;
5342
+ }
5343
+ };
5344
+ }
5345
+ async function createOpfsBlobStore(rootDirName = "kora-blobs") {
5346
+ return new OpfsBlobStore(await createOpfsBlobDirectory(rootDirName));
5347
+ }
5348
+
5349
+ // src/blob/blob-chunking.ts
5350
+ var import_core23 = require("@korajs/core");
5351
+ var DEFAULT_CHUNK_SIZE = 256 * 1024;
5352
+ async function chunkBlob(bytes, chunkSize = DEFAULT_CHUNK_SIZE, metadata = {}) {
5353
+ if (chunkSize <= 0) {
5354
+ throw new Error(`chunkBlob requires a positive chunkSize, got ${chunkSize}`);
5355
+ }
5356
+ const chunkHashes = [];
5357
+ const chunks = /* @__PURE__ */ new Map();
5358
+ for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
5359
+ const end = Math.min(offset + chunkSize, bytes.byteLength);
5360
+ const chunk = new Uint8Array(end - offset);
5361
+ chunk.set(bytes.subarray(offset, end));
5362
+ const hash = await (0, import_core23.hashBlob)(chunk);
5363
+ chunkHashes.push(hash);
5364
+ if (!chunks.has(hash)) {
5365
+ chunks.set(hash, chunk);
5366
+ }
5367
+ }
5368
+ const blobHash = await (0, import_core23.hashBlob)(bytes);
5369
+ const manifest = {
5370
+ blobHash,
5371
+ size: bytes.byteLength,
5372
+ chunkSize,
5373
+ chunkHashes
5374
+ };
5375
+ if (metadata.mimeType !== void 0) {
5376
+ manifest.mimeType = metadata.mimeType;
5377
+ }
5378
+ if (metadata.filename !== void 0) {
5379
+ manifest.filename = metadata.filename;
5380
+ }
5381
+ return { manifest, chunks };
5382
+ }
5383
+ async function reassembleBlob(manifest, chunkStore) {
5384
+ const parts = [];
5385
+ let total = 0;
5386
+ for (const hash of manifest.chunkHashes) {
5387
+ const chunk = await chunkStore.get(hash);
5388
+ if (chunk === null) {
5389
+ throw new Error(`Cannot reassemble blob ${manifest.blobHash}: missing chunk ${hash}`);
5390
+ }
5391
+ parts.push(chunk);
5392
+ total += chunk.byteLength;
5393
+ }
5394
+ const out = new Uint8Array(total);
5395
+ let offset = 0;
5396
+ for (const part of parts) {
5397
+ out.set(part, offset);
5398
+ offset += part.byteLength;
5399
+ }
5400
+ const actual = await (0, import_core23.hashBlob)(out);
5401
+ if (actual !== manifest.blobHash) {
5402
+ throw new BlobIntegrityError(manifest.blobHash, actual);
5403
+ }
5404
+ return out;
5405
+ }
5406
+
5407
+ // src/blob/blob-transfer.ts
5408
+ var import_core24 = require("@korajs/core");
5409
+ async function receiveBlob(manifest, provider, stores) {
5410
+ let chunksFetched = 0;
5411
+ let chunksSkipped = 0;
5412
+ const seen = /* @__PURE__ */ new Set();
5413
+ for (const hash of manifest.chunkHashes) {
5414
+ if (seen.has(hash)) {
5415
+ continue;
5416
+ }
5417
+ seen.add(hash);
5418
+ if (await stores.chunkStore.has(hash)) {
5419
+ chunksSkipped++;
5420
+ continue;
5421
+ }
5422
+ const bytes = await provider.getChunk(hash);
5423
+ if (bytes === null) {
5424
+ throw new Error(`Blob transfer failed: provider could not supply chunk ${hash}`);
5425
+ }
5426
+ const actual = await (0, import_core24.hashBlob)(bytes);
5427
+ if (actual !== hash) {
5428
+ throw new BlobIntegrityError(hash, actual);
5429
+ }
5430
+ await stores.chunkStore.put(bytes);
5431
+ chunksFetched++;
5432
+ }
5433
+ const full = await reassembleBlob(manifest, stores.chunkStore);
5434
+ const ref = await stores.blobStore.put(full, {
5435
+ mimeType: manifest.mimeType,
5436
+ filename: manifest.filename
5437
+ });
5438
+ return { ref, chunksFetched, chunksSkipped };
5439
+ }
5440
+ async function prepareBlobForSend(bytes, chunkStore, options = {}) {
5441
+ const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
5442
+ mimeType: options.mimeType,
5443
+ filename: options.filename
5444
+ });
5445
+ for (const chunk of chunks.values()) {
5446
+ await chunkStore.put(chunk);
5447
+ }
5448
+ const provider = {
5449
+ getChunk: (hash) => chunkStore.get(hash)
5450
+ };
5451
+ return { manifest, provider };
5452
+ }
5453
+
5454
+ // src/blob/blob-manifest-transfer.ts
5455
+ var import_core25 = require("@korajs/core");
5456
+ function serializeBlobManifest(manifest) {
5457
+ const canonical = {
5458
+ blobHash: manifest.blobHash,
5459
+ size: manifest.size,
5460
+ chunkSize: manifest.chunkSize,
5461
+ chunkHashes: manifest.chunkHashes
5462
+ };
5463
+ if (manifest.mimeType !== void 0) {
5464
+ canonical.mimeType = manifest.mimeType;
5465
+ }
5466
+ if (manifest.filename !== void 0) {
5467
+ canonical.filename = manifest.filename;
5468
+ }
5469
+ return new TextEncoder().encode(JSON.stringify(canonical));
5470
+ }
5471
+ function parseBlobManifest(bytes) {
5472
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
5473
+ if (typeof parsed !== "object" || parsed === null) {
5474
+ throw new Error("Invalid blob manifest: not an object");
5475
+ }
5476
+ const obj = parsed;
5477
+ if (typeof obj.blobHash !== "string") {
5478
+ throw new Error("Invalid blob manifest: missing blobHash");
5479
+ }
5480
+ if (typeof obj.size !== "number" || typeof obj.chunkSize !== "number") {
5481
+ throw new Error("Invalid blob manifest: missing size or chunkSize");
5482
+ }
5483
+ if (!Array.isArray(obj.chunkHashes) || obj.chunkHashes.some((h) => typeof h !== "string")) {
5484
+ throw new Error("Invalid blob manifest: chunkHashes must be an array of strings");
5485
+ }
5486
+ const manifest = {
5487
+ blobHash: obj.blobHash,
5488
+ size: obj.size,
5489
+ chunkSize: obj.chunkSize,
5490
+ chunkHashes: obj.chunkHashes
5491
+ };
5492
+ if (typeof obj.mimeType === "string") {
5493
+ manifest.mimeType = obj.mimeType;
5494
+ }
5495
+ if (typeof obj.filename === "string") {
5496
+ manifest.filename = obj.filename;
5497
+ }
5498
+ return manifest;
5499
+ }
5500
+ async function putBlobForTransfer(store, bytes, options = {}) {
5501
+ const { manifest, chunks } = await chunkBlob(bytes, options.chunkSize, {
5502
+ ...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
5503
+ ...options.filename !== void 0 ? { filename: options.filename } : {}
5504
+ });
5505
+ for (const chunk of chunks.values()) {
5506
+ await store.put(chunk);
5507
+ }
5508
+ const baseRef = await store.put(bytes, {
5509
+ ...options.mimeType !== void 0 ? { mimeType: options.mimeType } : {},
5510
+ ...options.filename !== void 0 ? { filename: options.filename } : {}
5511
+ });
5512
+ const manifestRef = await store.put(serializeBlobManifest(manifest));
5513
+ return { ref: { ...baseRef, manifestHash: manifestRef.hash }, manifest };
5514
+ }
5515
+ async function fetchBlobManifest(provider, manifestHash) {
5516
+ const bytes = await provider.getChunk(manifestHash);
5517
+ if (bytes === null) {
5518
+ throw new Error(`Blob manifest ${manifestHash} could not be fetched from the provider`);
5519
+ }
5520
+ const actual = await (0, import_core25.hashBlob)(bytes);
5521
+ if (actual !== manifestHash) {
5522
+ throw new BlobIntegrityError(manifestHash, actual);
5523
+ }
5524
+ return parseBlobManifest(bytes);
5525
+ }
5526
+ async function resolveBlobManifest(provider, ref) {
5527
+ if (ref.manifestHash === void 0) {
5528
+ throw new Error(
5529
+ `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.`
5530
+ );
5531
+ }
5532
+ return fetchBlobManifest(provider, ref.manifestHash);
5533
+ }
5534
+
5535
+ // src/blob/server-blob-callbacks.ts
5536
+ var import_core26 = require("@korajs/core");
5537
+ function toServerBlobCallbacks(store) {
5538
+ return {
5539
+ resolveBlobChunk: (hash) => store.get(hash),
5540
+ async persistBlobChunk(hash, bytes) {
5541
+ const actual = await (0, import_core26.hashBlob)(bytes);
5542
+ if (actual !== hash) {
5543
+ return;
5544
+ }
5545
+ await store.put(bytes);
5546
+ }
5547
+ };
5548
+ }
5549
+ function createMemoryServerBlobStore() {
5550
+ const store = new MemoryBlobStore();
5551
+ return { store, callbacks: toServerBlobCallbacks(store) };
5552
+ }
5553
+
5554
+ // src/blob/blob-gc.ts
5555
+ var import_core27 = require("@korajs/core");
5556
+ function extractBlobRefs(record) {
5557
+ const refs = [];
5558
+ for (const value of Object.values(record)) {
5559
+ if ((0, import_core27.isBlobRef)(value)) {
5560
+ refs.push(value);
5561
+ }
5562
+ }
5563
+ return refs;
5564
+ }
5565
+ async function collectBlobGarbage(store, liveRefs, options = {}) {
5566
+ const live = /* @__PURE__ */ new Set();
5567
+ for (const ref of liveRefs) {
5568
+ live.add(ref.hash);
5569
+ if (ref.manifestHash === void 0) {
5570
+ continue;
5571
+ }
5572
+ live.add(ref.manifestHash);
5573
+ try {
5574
+ const manifestBytes = await store.get(ref.manifestHash);
5575
+ if (manifestBytes !== null) {
5576
+ const manifest = parseBlobManifest(manifestBytes);
5577
+ for (const chunkHash of manifest.chunkHashes) {
5578
+ live.add(chunkHash);
5579
+ }
5580
+ }
5581
+ } catch {
5582
+ }
5583
+ }
5584
+ const all = await store.list();
5585
+ const collectedHashes = [];
5586
+ for (const hash of all) {
5587
+ if (live.has(hash)) {
5588
+ continue;
5589
+ }
5590
+ collectedHashes.push(hash);
5591
+ if (!options.dryRun) {
5592
+ await store.delete(hash);
5593
+ }
5594
+ }
5595
+ return {
5596
+ scanned: all.length,
5597
+ live: live.size,
5598
+ collected: collectedHashes.length,
5599
+ collectedHashes
5600
+ };
5601
+ }
5602
+
5603
+ // src/blob/blob-chunk-transport.ts
5604
+ var import_core28 = require("@korajs/core");
5605
+ function createRemoteChunkProvider(port, options = {}) {
5606
+ const timeoutMs = options.timeoutMs ?? 3e4;
5607
+ const pending = /* @__PURE__ */ new Map();
5608
+ port.onMessage((message) => {
5609
+ if (message.type !== "blob-chunk-response") {
5610
+ return;
5611
+ }
5612
+ const entry = pending.get(message.requestId);
5613
+ if (!entry) {
5614
+ return;
5615
+ }
5616
+ pending.delete(message.requestId);
5617
+ clearTimeout(entry.timer);
5618
+ entry.resolve(message.bytes);
5619
+ });
5620
+ return {
5621
+ pendingCount: () => pending.size,
5622
+ getChunk(hash) {
5623
+ const requestId = (0, import_core28.generateUUIDv7)();
5624
+ return new Promise((resolve, reject) => {
5625
+ const timer = setTimeout(() => {
5626
+ pending.delete(requestId);
5627
+ reject(new Error(`Blob chunk request for ${hash} timed out after ${timeoutMs}ms`));
5628
+ }, timeoutMs);
5629
+ pending.set(requestId, { resolve, reject, timer });
5630
+ port.send({ type: "blob-chunk-request", requestId, hash });
5631
+ });
5632
+ }
5633
+ };
5634
+ }
5635
+ function serveBlobChunks(port, blobStore) {
5636
+ port.onMessage((message) => {
5637
+ if (message.type !== "blob-chunk-request") {
5638
+ return;
5639
+ }
5640
+ void blobStore.get(message.hash).then(
5641
+ (bytes) => {
5642
+ port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes });
5643
+ },
5644
+ () => {
5645
+ port.send({ type: "blob-chunk-response", requestId: message.requestId, bytes: null });
5646
+ }
5647
+ );
5648
+ });
5649
+ }
5650
+ function createChunkPortPair() {
5651
+ const handlersA = [];
5652
+ const handlersB = [];
5653
+ const a = {
5654
+ send(message) {
5655
+ queueMicrotask(() => {
5656
+ for (const handler of handlersB) {
5657
+ handler(message);
5658
+ }
5659
+ });
5660
+ },
5661
+ onMessage(handler) {
5662
+ handlersA.push(handler);
5663
+ }
5664
+ };
5665
+ const b = {
5666
+ send(message) {
5667
+ queueMicrotask(() => {
5668
+ for (const handler of handlersA) {
5669
+ handler(message);
5670
+ }
5671
+ });
5672
+ },
5673
+ onMessage(handler) {
5674
+ handlersB.push(handler);
5675
+ }
5676
+ };
5677
+ return { a, b };
5678
+ }
5078
5679
  // Annotate the CommonJS export names for ESM import in node:
5079
5680
  0 && (module.exports = {
5080
5681
  AdapterError,
5682
+ BlobIntegrityError,
5081
5683
  COMPACTION_BASELINE_META_KEY,
5082
5684
  Collection,
5685
+ DEFAULT_CHUNK_SIZE,
5083
5686
  InvalidStateTransitionError,
5084
5687
  LAST_ACKED_SERVER_VECTOR_META_KEY,
5688
+ MemoryBlobStore,
5689
+ OpfsBlobStore,
5085
5690
  OptimisticLockError,
5086
5691
  PersistenceError,
5087
5692
  QueryBuilder,
@@ -5099,26 +5704,44 @@ init_export_audit();
5099
5704
  WorkerTimeoutError,
5100
5705
  asRichTextSyncEngine,
5101
5706
  assertQueryReady,
5707
+ chunkBlob,
5708
+ collectBlobGarbage,
5102
5709
  collectOperationsAheadOfServer,
5103
5710
  compactOperationLog,
5104
5711
  computeAckCompactionWatermark,
5712
+ createChunkPortPair,
5713
+ createMemoryServerBlobStore,
5714
+ createOpfsBlobDirectory,
5715
+ createOpfsBlobStore,
5716
+ createRemoteChunkProvider,
5105
5717
  createRichTextController,
5106
5718
  decodeAuditExport,
5107
5719
  decodeRichtext,
5108
5720
  deserializeVersionVectorFromMeta,
5109
5721
  encodeRichtext,
5110
5722
  exportBackup,
5723
+ extractBlobRefs,
5724
+ fetchBlobManifest,
5111
5725
  getSharedQueryStoreCache,
5112
5726
  mergeVersionVectors,
5727
+ parseBlobManifest,
5113
5728
  persistedAuditTraceFromEvent,
5114
5729
  pluralize,
5730
+ prepareBlobForSend,
5731
+ putBlobForTransfer,
5115
5732
  readAuditExportManifest,
5116
5733
  readBackupManifest,
5734
+ reassembleBlob,
5735
+ receiveBlob,
5736
+ resolveBlobManifest,
5117
5737
  restoreBackup,
5118
5738
  richtextStatesEqual,
5119
5739
  richtextToPlainText,
5740
+ serializeBlobManifest,
5120
5741
  serializeVersionVectorToMeta,
5742
+ serveBlobChunks,
5121
5743
  singularize,
5744
+ toServerBlobCallbacks,
5122
5745
  validateStateTransition,
5123
5746
  validateUpdateStateMachine,
5124
5747
  verifyAuditExportChecksum,