@noy-db/hub 0.2.0-pre.1 → 0.2.0-pre.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.
Files changed (237) hide show
  1. package/dist/aggregate/index.cjs.map +1 -1
  2. package/dist/aggregate/index.js +2 -2
  3. package/dist/attestation/index.cjs +305 -0
  4. package/dist/attestation/index.cjs.map +1 -0
  5. package/dist/attestation/index.d.cts +52 -0
  6. package/dist/attestation/index.d.ts +52 -0
  7. package/dist/attestation/index.js +36 -0
  8. package/dist/attestation/index.js.map +1 -0
  9. package/dist/blobs/index.cjs.map +1 -1
  10. package/dist/blobs/index.d.cts +4 -3
  11. package/dist/blobs/index.d.ts +4 -3
  12. package/dist/blobs/index.js +9 -7
  13. package/dist/blobs/index.js.map +1 -1
  14. package/dist/bundle/index.cjs +16701 -129
  15. package/dist/bundle/index.cjs.map +1 -1
  16. package/dist/bundle/index.d.cts +172 -3
  17. package/dist/bundle/index.d.ts +172 -3
  18. package/dist/bundle/index.js +533 -5
  19. package/dist/bundle/index.js.map +1 -1
  20. package/dist/{chunk-5SCJ5UEF.js → chunk-243PNUA6.js} +2 -2
  21. package/dist/{chunk-WCA2NROQ.js → chunk-2PAQNPE3.js} +2 -2
  22. package/dist/chunk-3QAKZ37R.js +83 -0
  23. package/dist/chunk-3QAKZ37R.js.map +1 -0
  24. package/dist/chunk-3S4BJX25.js +36 -0
  25. package/dist/chunk-3S4BJX25.js.map +1 -0
  26. package/dist/chunk-3XHOCQK4.js +118 -0
  27. package/dist/chunk-3XHOCQK4.js.map +1 -0
  28. package/dist/{chunk-4TFSM22V.js → chunk-3Y53S2SA.js} +3 -3
  29. package/dist/{chunk-6HPZY4ON.js → chunk-3Z2TPHC4.js} +3 -3
  30. package/dist/chunk-4HIL6AHQ.js +57 -0
  31. package/dist/chunk-4HIL6AHQ.js.map +1 -0
  32. package/dist/{chunk-DYECX3IX.js → chunk-7BRE6EUA.js} +2 -2
  33. package/dist/{chunk-DYBQG5PQ.js → chunk-7BUTTVMR.js} +2 -2
  34. package/dist/{chunk-KESP7GOK.js → chunk-7Q5PLD5C.js} +3 -3
  35. package/dist/{chunk-UA4RI7OT.js → chunk-7Z23ZFLV.js} +4 -4
  36. package/dist/chunk-AHPFONIL.js +59 -0
  37. package/dist/chunk-AHPFONIL.js.map +1 -0
  38. package/dist/{chunk-EGQYGYIU.js → chunk-CXSCDO5T.js} +2 -2
  39. package/dist/chunk-E535SAN4.js +8834 -0
  40. package/dist/chunk-E535SAN4.js.map +1 -0
  41. package/dist/{chunk-CBAHB2BF.js → chunk-EUYOGYGV.js} +6 -69
  42. package/dist/chunk-EUYOGYGV.js.map +1 -0
  43. package/dist/{chunk-OMLIZL2P.js → chunk-FAQVNJD4.js} +2 -2
  44. package/dist/{chunk-I6MX32UC.js → chunk-G6FRSBKK.js} +4 -4
  45. package/dist/{chunk-FCXOFQAJ.js → chunk-GIV6DWBG.js} +2 -2
  46. package/dist/{chunk-34YSDCDP.js → chunk-HXJXPZRE.js} +2 -2
  47. package/dist/{chunk-23TTQXVO.js → chunk-J4KLMEUL.js} +2 -2
  48. package/dist/{chunk-VMIO4IXG.js → chunk-JYQTXEIO.js} +5 -228
  49. package/dist/chunk-JYQTXEIO.js.map +1 -0
  50. package/dist/{chunk-NIOHFJPJ.js → chunk-LRAZDV5X.js} +6 -118
  51. package/dist/chunk-LRAZDV5X.js.map +1 -0
  52. package/dist/{chunk-P7EQ2S5O.js → chunk-MUWOSVEP.js} +2 -2
  53. package/dist/chunk-NWZ3I6R6.js +79 -0
  54. package/dist/chunk-NWZ3I6R6.js.map +1 -0
  55. package/dist/{chunk-HB3Z2GCR.js → chunk-OVZDFEOR.js} +2 -2
  56. package/dist/chunk-PFSNOPBQ.js +233 -0
  57. package/dist/chunk-PFSNOPBQ.js.map +1 -0
  58. package/dist/{chunk-UZXLQCHP.js → chunk-PLI5TV7N.js} +2 -2
  59. package/dist/{chunk-PA6R5ZCI.js → chunk-Q6W2CMEJ.js} +3 -3
  60. package/dist/{chunk-537VFZTR.js → chunk-QPEXPHJR.js} +4 -4
  61. package/dist/{chunk-ZNOEIM6Y.js → chunk-QXQRKXCU.js} +2 -2
  62. package/dist/{chunk-RD5LYKD6.js → chunk-RTZVQAJ7.js} +2 -2
  63. package/dist/{chunk-DPMFBCV6.js → chunk-TBKOGSYR.js} +2 -2
  64. package/dist/{chunk-DPMFBCV6.js.map → chunk-TBKOGSYR.js.map} +1 -1
  65. package/dist/chunk-UND4XIB6.js +251 -0
  66. package/dist/chunk-UND4XIB6.js.map +1 -0
  67. package/dist/{chunk-7H6DOO3E.js → chunk-VCGTOS2A.js} +211 -36
  68. package/dist/chunk-VCGTOS2A.js.map +1 -0
  69. package/dist/{chunk-MKSA2V7A.js → chunk-VE6YVP32.js} +2 -2
  70. package/dist/{chunk-5DWL3JBF.js → chunk-VK5EER6C.js} +2 -2
  71. package/dist/{chunk-MIQHZESA.js → chunk-VPSUZLOJ.js} +4 -4
  72. package/dist/{chunk-MIQHZESA.js.map → chunk-VPSUZLOJ.js.map} +1 -1
  73. package/dist/{chunk-XGSOTWYX.js → chunk-VRBCTEKQ.js} +2 -2
  74. package/dist/{chunk-ADQ5MQ54.js → chunk-W3XXT26A.js} +29 -1
  75. package/dist/{chunk-ADQ5MQ54.js.map → chunk-W3XXT26A.js.map} +1 -1
  76. package/dist/{chunk-2AXFIYHT.js → chunk-XG3PTSCD.js} +1 -1
  77. package/dist/chunk-XG3PTSCD.js.map +1 -0
  78. package/dist/{chunk-SIZWEV2Y.js → chunk-Y2RKOPNC.js} +4 -4
  79. package/dist/{chunk-SIZWEV2Y.js.map → chunk-Y2RKOPNC.js.map} +1 -1
  80. package/dist/{chunk-Z72JH4KG.js → chunk-YTXSFG3C.js} +4 -34
  81. package/dist/chunk-YTXSFG3C.js.map +1 -0
  82. package/dist/consent/index.cjs.map +1 -1
  83. package/dist/consent/index.d.cts +4 -3
  84. package/dist/consent/index.d.ts +4 -3
  85. package/dist/consent/index.js +3 -3
  86. package/dist/{crypto-A7FRXYHC.js → crypto-5ZDIY3NG.js} +3 -3
  87. package/dist/{delegation-YBA4X4JN.js → delegation-QYXZW25W.js} +5 -5
  88. package/dist/derivations/index.cjs.map +1 -1
  89. package/dist/derivations/index.d.cts +5 -4
  90. package/dist/derivations/index.d.ts +5 -4
  91. package/dist/derivations/index.js +4 -4
  92. package/dist/{dev-unlock-DRwVSy2S.d.cts → dev-unlock-DQCNDfFp.d.cts} +1 -1
  93. package/dist/{dev-unlock-D9s-loPr.d.ts → dev-unlock-utkybTKb.d.ts} +1 -1
  94. package/dist/executor-AS2IDHKZ.js +11 -0
  95. package/dist/executor-HLXFXNFM.js +8 -0
  96. package/dist/executor-HN6YBHZ5.js +8 -0
  97. package/dist/guards/index.cjs.map +1 -1
  98. package/dist/guards/index.d.cts +5 -4
  99. package/dist/guards/index.d.ts +5 -4
  100. package/dist/guards/index.js +3 -3
  101. package/dist/{hash-DXXXusyk.d.ts → hash-DcoYWfJ_.d.ts} +1 -1
  102. package/dist/{hash-DtRih9MQ.d.cts → hash-jDowCrK2.d.cts} +1 -1
  103. package/dist/history/index.cjs +1 -1
  104. package/dist/history/index.cjs.map +1 -1
  105. package/dist/history/index.d.cts +5 -4
  106. package/dist/history/index.d.ts +5 -4
  107. package/dist/history/index.js +5 -5
  108. package/dist/i18n/index.cjs.map +1 -1
  109. package/dist/i18n/index.d.cts +4 -3
  110. package/dist/i18n/index.d.ts +4 -3
  111. package/dist/i18n/index.js +13 -11
  112. package/dist/i18n/index.js.map +1 -1
  113. package/dist/{index-CNwA-B6-.d.ts → index-BCKdioeh.d.ts} +29 -1
  114. package/dist/{index-CmVgTkqk.d.cts → index-BMjrzNZr.d.cts} +29 -1
  115. package/dist/index.cjs +507 -37
  116. package/dist/index.cjs.map +1 -1
  117. package/dist/index.d.cts +12 -11
  118. package/dist/index.d.ts +12 -11
  119. package/dist/index.js +106 -8817
  120. package/dist/index.js.map +1 -1
  121. package/dist/indexing/index.cjs.map +1 -1
  122. package/dist/indexing/index.js +2 -2
  123. package/dist/issue-ORP37MVW.js +12 -0
  124. package/dist/{ledger-3TXNP47J.js → ledger-3IU5GMXA.js} +5 -5
  125. package/dist/materialized-views/index.cjs.map +1 -1
  126. package/dist/materialized-views/index.d.cts +6 -5
  127. package/dist/materialized-views/index.d.ts +6 -5
  128. package/dist/materialized-views/index.js +6 -6
  129. package/dist/noydb-5H3C24GG.js +34 -0
  130. package/dist/overlay-views/index.cjs.map +1 -1
  131. package/dist/overlay-views/index.d.cts +5 -4
  132. package/dist/overlay-views/index.d.ts +5 -4
  133. package/dist/overlay-views/index.js +6 -4
  134. package/dist/periods/index.cjs.map +1 -1
  135. package/dist/periods/index.d.cts +4 -3
  136. package/dist/periods/index.d.ts +4 -3
  137. package/dist/periods/index.js +5 -5
  138. package/dist/{public-envelope-PY6NKFLI.js → public-envelope-U3CMEOMV.js} +3 -3
  139. package/dist/query/index.cjs.map +1 -1
  140. package/dist/query/index.d.cts +1 -1
  141. package/dist/query/index.d.ts +1 -1
  142. package/dist/query/index.js +3 -3
  143. package/dist/{registry-3L3N3PTG.js → registry-3ALP62P6.js} +3 -3
  144. package/dist/registry-7HE6VJGC.js +8 -0
  145. package/dist/registry-PSIPG2QR.js +8 -0
  146. package/dist/registry-PSIPG2QR.js.map +1 -0
  147. package/dist/revoke-KY2GB4KP.js +17 -0
  148. package/dist/revoke-KY2GB4KP.js.map +1 -0
  149. package/dist/session/index.cjs.map +1 -1
  150. package/dist/session/index.d.cts +5 -4
  151. package/dist/session/index.d.ts +5 -4
  152. package/dist/session/index.js +3 -3
  153. package/dist/shadow/index.cjs.map +1 -1
  154. package/dist/shadow/index.d.cts +4 -3
  155. package/dist/shadow/index.d.ts +4 -3
  156. package/dist/shadow/index.js +2 -2
  157. package/dist/signer-GRI5TZKH.js +18 -0
  158. package/dist/signer-GRI5TZKH.js.map +1 -0
  159. package/dist/{stale-HSC5YO2O.js → stale-OTOF3FH7.js} +2 -2
  160. package/dist/stale-OTOF3FH7.js.map +1 -0
  161. package/dist/store/index.cjs.map +1 -1
  162. package/dist/store/index.d.cts +4 -3
  163. package/dist/store/index.d.ts +4 -3
  164. package/dist/store/index.js +2 -2
  165. package/dist/sync/index.cjs.map +1 -1
  166. package/dist/sync/index.d.cts +3 -2
  167. package/dist/sync/index.d.ts +3 -2
  168. package/dist/sync/index.js +3 -3
  169. package/dist/team/index.cjs.map +1 -1
  170. package/dist/team/index.d.cts +4 -3
  171. package/dist/team/index.d.ts +4 -3
  172. package/dist/team/index.js +12 -10
  173. package/dist/tx/index.cjs.map +1 -1
  174. package/dist/tx/index.d.cts +4 -3
  175. package/dist/tx/index.d.ts +4 -3
  176. package/dist/tx/index.js +2 -2
  177. package/dist/{types-DW9RGSSs.d.ts → types-BoFFiskX.d.ts} +119 -3
  178. package/dist/{types-C4lwMKKF.d.cts → types-DJG8HG6F.d.cts} +119 -3
  179. package/dist/{index-hdFvZkBP.d.cts → ulid-BmBgooGm.d.ts} +51 -33
  180. package/dist/{index-4agOpzqd.d.ts → ulid-C7ms9oli.d.cts} +51 -33
  181. package/dist/util/index.cjs.map +1 -1
  182. package/dist/util/index.js +1 -1
  183. package/dist/{with-derivation-g-pGoMzL.d.ts → with-derivation-BKXXa8Vt.d.ts} +1 -1
  184. package/dist/{with-derivation-C8LDlV7t.d.cts → with-derivation-BjQ7q4NE.d.cts} +1 -1
  185. package/dist/{with-guard-DWOCK4Ca.d.ts → with-guard-C25yNjzd.d.ts} +1 -1
  186. package/dist/{with-guard-jI1x9Z3k.d.cts → with-guard-DQme5DKE.d.cts} +1 -1
  187. package/dist/{with-materialized-view-DcTx4H3j.d.cts → with-materialized-view-BbEPFIIJ.d.cts} +1 -1
  188. package/dist/{with-materialized-view-DaKR-N6J.d.ts → with-materialized-view-CqnRwI2S.d.ts} +1 -1
  189. package/dist/{with-overlayed-view-N7jYuNOS.d.ts → with-overlayed-view-Ct1fSJt-.d.ts} +1 -1
  190. package/dist/{with-overlayed-view-D-6oWAgM.d.cts → with-overlayed-view-bwlmmFjx.d.cts} +1 -1
  191. package/package.json +15 -3
  192. package/dist/chunk-2AXFIYHT.js.map +0 -1
  193. package/dist/chunk-7H6DOO3E.js.map +0 -1
  194. package/dist/chunk-CBAHB2BF.js.map +0 -1
  195. package/dist/chunk-NIOHFJPJ.js.map +0 -1
  196. package/dist/chunk-VMIO4IXG.js.map +0 -1
  197. package/dist/chunk-Z72JH4KG.js.map +0 -1
  198. package/dist/executor-7E3VFGW7.js +0 -11
  199. package/dist/executor-CEWX2FQI.js +0 -8
  200. package/dist/executor-X4SQ3ZLC.js +0 -8
  201. package/dist/registry-O47PUPSY.js +0 -8
  202. package/dist/registry-WLLMODKN.js +0 -8
  203. /package/dist/{chunk-5SCJ5UEF.js.map → chunk-243PNUA6.js.map} +0 -0
  204. /package/dist/{chunk-WCA2NROQ.js.map → chunk-2PAQNPE3.js.map} +0 -0
  205. /package/dist/{chunk-4TFSM22V.js.map → chunk-3Y53S2SA.js.map} +0 -0
  206. /package/dist/{chunk-6HPZY4ON.js.map → chunk-3Z2TPHC4.js.map} +0 -0
  207. /package/dist/{chunk-DYECX3IX.js.map → chunk-7BRE6EUA.js.map} +0 -0
  208. /package/dist/{chunk-DYBQG5PQ.js.map → chunk-7BUTTVMR.js.map} +0 -0
  209. /package/dist/{chunk-KESP7GOK.js.map → chunk-7Q5PLD5C.js.map} +0 -0
  210. /package/dist/{chunk-UA4RI7OT.js.map → chunk-7Z23ZFLV.js.map} +0 -0
  211. /package/dist/{chunk-EGQYGYIU.js.map → chunk-CXSCDO5T.js.map} +0 -0
  212. /package/dist/{chunk-OMLIZL2P.js.map → chunk-FAQVNJD4.js.map} +0 -0
  213. /package/dist/{chunk-I6MX32UC.js.map → chunk-G6FRSBKK.js.map} +0 -0
  214. /package/dist/{chunk-FCXOFQAJ.js.map → chunk-GIV6DWBG.js.map} +0 -0
  215. /package/dist/{chunk-34YSDCDP.js.map → chunk-HXJXPZRE.js.map} +0 -0
  216. /package/dist/{chunk-23TTQXVO.js.map → chunk-J4KLMEUL.js.map} +0 -0
  217. /package/dist/{chunk-P7EQ2S5O.js.map → chunk-MUWOSVEP.js.map} +0 -0
  218. /package/dist/{chunk-HB3Z2GCR.js.map → chunk-OVZDFEOR.js.map} +0 -0
  219. /package/dist/{chunk-UZXLQCHP.js.map → chunk-PLI5TV7N.js.map} +0 -0
  220. /package/dist/{chunk-PA6R5ZCI.js.map → chunk-Q6W2CMEJ.js.map} +0 -0
  221. /package/dist/{chunk-537VFZTR.js.map → chunk-QPEXPHJR.js.map} +0 -0
  222. /package/dist/{chunk-ZNOEIM6Y.js.map → chunk-QXQRKXCU.js.map} +0 -0
  223. /package/dist/{chunk-RD5LYKD6.js.map → chunk-RTZVQAJ7.js.map} +0 -0
  224. /package/dist/{chunk-MKSA2V7A.js.map → chunk-VE6YVP32.js.map} +0 -0
  225. /package/dist/{chunk-5DWL3JBF.js.map → chunk-VK5EER6C.js.map} +0 -0
  226. /package/dist/{chunk-XGSOTWYX.js.map → chunk-VRBCTEKQ.js.map} +0 -0
  227. /package/dist/{crypto-A7FRXYHC.js.map → crypto-5ZDIY3NG.js.map} +0 -0
  228. /package/dist/{delegation-YBA4X4JN.js.map → delegation-QYXZW25W.js.map} +0 -0
  229. /package/dist/{executor-7E3VFGW7.js.map → executor-AS2IDHKZ.js.map} +0 -0
  230. /package/dist/{executor-CEWX2FQI.js.map → executor-HLXFXNFM.js.map} +0 -0
  231. /package/dist/{executor-X4SQ3ZLC.js.map → executor-HN6YBHZ5.js.map} +0 -0
  232. /package/dist/{ledger-3TXNP47J.js.map → issue-ORP37MVW.js.map} +0 -0
  233. /package/dist/{public-envelope-PY6NKFLI.js.map → ledger-3IU5GMXA.js.map} +0 -0
  234. /package/dist/{registry-3L3N3PTG.js.map → noydb-5H3C24GG.js.map} +0 -0
  235. /package/dist/{registry-O47PUPSY.js.map → public-envelope-U3CMEOMV.js.map} +0 -0
  236. /package/dist/{registry-WLLMODKN.js.map → registry-3ALP62P6.js.map} +0 -0
  237. /package/dist/{stale-HSC5YO2O.js.map → registry-7HE6VJGC.js.map} +0 -0
@@ -10,11 +10,11 @@ import {
10
10
  encryptBytesWithAAD,
11
11
  hmacSha256Hex,
12
12
  sha256Hex
13
- } from "./chunk-WCA2NROQ.js";
13
+ } from "./chunk-2PAQNPE3.js";
14
14
  import {
15
15
  ConflictError,
16
16
  NotFoundError
17
- } from "./chunk-ADQ5MQ54.js";
17
+ } from "./chunk-W3XXT26A.js";
18
18
 
19
19
  // src/blobs/mime-magic.ts
20
20
  function hex(s) {
@@ -820,7 +820,7 @@ var BlobSet = class {
820
820
  return this.buildResponse(slot, result.blob, { inline: true });
821
821
  }
822
822
  const aad = chunkAAD(slot.eTag, 0, result.blob.chunkCount);
823
- const { decryptBytesWithAAD: decryptAAD } = await import("./crypto-A7FRXYHC.js");
823
+ const { decryptBytesWithAAD: decryptAAD } = await import("./crypto-5ZDIY3NG.js");
824
824
  const decrypted = await decryptAAD(envelope._iv, envelope._data, blobDEK, aad);
825
825
  const plaintext = result.blob.compression === "gzip" ? await decompressBytes(decrypted) : decrypted;
826
826
  const body = new ReadableStream({
@@ -871,224 +871,6 @@ async function plainSha256Hex(data) {
871
871
  return sha256Hex(data);
872
872
  }
873
873
 
874
- // src/blobs/export-blobs.ts
875
- var ExportBlobsAbortedError = class extends Error {
876
- constructor(reason) {
877
- super(`exportBlobs aborted: ${reason}`);
878
- this.name = "ExportBlobsAbortedError";
879
- }
880
- };
881
- var EXPORT_AUDIT_COLLECTION = "_export_audit";
882
- function createExportBlobsHandle(actor, listAccessibleCollections, getCollection, writeAudit, options) {
883
- let aborted = false;
884
- const abort = () => {
885
- aborted = true;
886
- };
887
- if (options.signal) {
888
- if (options.signal.aborted) aborted = true;
889
- options.signal.addEventListener("abort", () => {
890
- aborted = true;
891
- });
892
- }
893
- function assertLive() {
894
- if (aborted) throw new ExportBlobsAbortedError("aborted by caller");
895
- }
896
- const allowlist = options.collections ? new Set(options.collections) : null;
897
- let auditPromise = null;
898
- function writeAuditOnce() {
899
- if (!auditPromise) {
900
- auditPromise = writeAudit({
901
- id: generateBatchId(),
902
- mechanism: "exportBlobs",
903
- actor,
904
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
905
- collections: options.collections ?? null,
906
- predicate: Boolean(options.where),
907
- afterBlobId: options.afterBlobId ?? null
908
- });
909
- }
910
- return auditPromise;
911
- }
912
- async function* generate() {
913
- await writeAuditOnce();
914
- assertLive();
915
- const allCollections = await listAccessibleCollections();
916
- const targets = allCollections.filter((name) => {
917
- if (name.startsWith("_")) return false;
918
- if (allowlist && !allowlist.has(name)) return false;
919
- return true;
920
- });
921
- let resumeCursorHit = options.afterBlobId === void 0;
922
- for (const collectionName of targets) {
923
- if (aborted) return;
924
- const coll = getCollection(collectionName);
925
- const records = await coll.list().catch(() => []);
926
- for (const record of records) {
927
- if (aborted) return;
928
- assertLive();
929
- const idField = record.id;
930
- if (typeof idField !== "string") continue;
931
- if (options.where && !options.where(record, { collection: collectionName, id: idField })) continue;
932
- const blobSet = coll.blob(idField);
933
- const slots = await blobSet.list().catch(() => []);
934
- for (const slot of slots) {
935
- if (aborted) return;
936
- if (!resumeCursorHit) {
937
- if (slot.eTag === options.afterBlobId) {
938
- resumeCursorHit = true;
939
- }
940
- continue;
941
- }
942
- const bytes = await blobSet.get(slot.name);
943
- if (!bytes) continue;
944
- const item = {
945
- blobId: slot.eTag,
946
- recordRef: { collection: collectionName, id: idField, slot: slot.name },
947
- bytes,
948
- meta: {
949
- size: slot.size,
950
- filename: slot.filename,
951
- ...slot.mimeType !== void 0 && { mimeType: slot.mimeType },
952
- ...slot.uploadedAt !== void 0 && { createdAt: slot.uploadedAt }
953
- }
954
- };
955
- yield item;
956
- }
957
- }
958
- }
959
- }
960
- const handle = {
961
- abort,
962
- get aborted() {
963
- return aborted;
964
- },
965
- [Symbol.asyncIterator]: () => generate()
966
- };
967
- return handle;
968
- }
969
- function generateBatchId() {
970
- const raw = globalThis.crypto.getRandomValues(new Uint8Array(16));
971
- let s = "";
972
- for (const b of raw) s += b.toString(16).padStart(2, "0");
973
- return `batch-${Date.now().toString(36)}-${s.slice(0, 12)}`;
974
- }
975
-
976
- // src/blobs/blob-compaction.ts
977
- var BLOB_EVICTION_AUDIT_COLLECTION = "_blob_eviction_audit";
978
- async function runCompaction(ctx, options = {}) {
979
- const now = options.now ?? /* @__PURE__ */ new Date();
980
- const maxEvictions = options.maxEvictions ?? Infinity;
981
- const dryRun = options.dryRun === true;
982
- const allCollections = await ctx.listCollections();
983
- const byCollection = {};
984
- let evicted = 0;
985
- let records = 0;
986
- let auditEntries = 0;
987
- let collectionsWithPolicy = 0;
988
- outer: for (const collectionName of allCollections) {
989
- if (collectionName.startsWith("_")) continue;
990
- const config = ctx.getBlobFields(collectionName);
991
- if (!config) continue;
992
- const configuredSlots = Object.keys(config);
993
- if (configuredSlots.length === 0) continue;
994
- collectionsWithPolicy += 1;
995
- byCollection[collectionName] = { records: 0, evicted: 0 };
996
- const ids = await ctx.listRecords(collectionName);
997
- for (const recordId of ids) {
998
- if (evicted >= maxEvictions) break outer;
999
- const record = await ctx.getRecord(collectionName, recordId).catch(() => null);
1000
- if (record === null) continue;
1001
- records += 1;
1002
- byCollection[collectionName].records += 1;
1003
- const slots = await ctx.listSlots(collectionName, recordId).catch(() => []);
1004
- for (const slot of slots) {
1005
- if (evicted >= maxEvictions) break outer;
1006
- const policy = config[slot.name];
1007
- if (!policy) continue;
1008
- const reason = evaluatePolicy(policy, record, slot, now);
1009
- if (!reason) continue;
1010
- if (!dryRun) {
1011
- await ctx.deleteSlot(collectionName, recordId, slot.name);
1012
- await writeAuditEntry(ctx, {
1013
- id: generateEvictionId(collectionName, recordId, slot.name),
1014
- collection: collectionName,
1015
- recordId,
1016
- slotName: slot.name,
1017
- blobHash: slot.eTag,
1018
- reason,
1019
- evictedAt: now.toISOString(),
1020
- actor: ctx.actor
1021
- });
1022
- auditEntries += 1;
1023
- }
1024
- evicted += 1;
1025
- byCollection[collectionName].evicted += 1;
1026
- }
1027
- }
1028
- }
1029
- return {
1030
- evicted,
1031
- records,
1032
- collections: collectionsWithPolicy,
1033
- auditEntries,
1034
- byCollection
1035
- };
1036
- }
1037
- function evaluatePolicy(policy, record, slot, now) {
1038
- let ttlTriggered = false;
1039
- let predicateTriggered = false;
1040
- if (policy.retainDays !== void 0 && policy.retainDays > 0) {
1041
- const uploadedAt = Date.parse(slot.uploadedAt);
1042
- if (Number.isFinite(uploadedAt)) {
1043
- const ageMs = now.getTime() - uploadedAt;
1044
- const limitMs = policy.retainDays * 864e5;
1045
- if (ageMs > limitMs) ttlTriggered = true;
1046
- }
1047
- }
1048
- if (policy.evictWhen) {
1049
- try {
1050
- if (policy.evictWhen(record)) predicateTriggered = true;
1051
- } catch {
1052
- }
1053
- }
1054
- if (ttlTriggered && predicateTriggered) return "both";
1055
- if (ttlTriggered) return "ttl";
1056
- if (predicateTriggered) return "predicate";
1057
- return null;
1058
- }
1059
- function generateEvictionId(collection, recordId, slotName) {
1060
- const rand = globalThis.crypto.getRandomValues(new Uint8Array(8));
1061
- let suffix = "";
1062
- for (const b of rand) suffix += b.toString(16).padStart(2, "0");
1063
- return `${collection}__${recordId}__${slotName}__${suffix}`;
1064
- }
1065
- async function writeAuditEntry(ctx, entry) {
1066
- const json = JSON.stringify(entry);
1067
- let envelope;
1068
- if (ctx.encrypted) {
1069
- const dek = await ctx.getDEK(BLOB_EVICTION_AUDIT_COLLECTION);
1070
- const { iv, data } = await encrypt(json, dek);
1071
- envelope = {
1072
- _noydb: NOYDB_FORMAT_VERSION,
1073
- _v: 1,
1074
- _ts: entry.evictedAt,
1075
- _iv: iv,
1076
- _data: data,
1077
- _by: entry.actor
1078
- };
1079
- } else {
1080
- envelope = {
1081
- _noydb: NOYDB_FORMAT_VERSION,
1082
- _v: 1,
1083
- _ts: entry.evictedAt,
1084
- _iv: "",
1085
- _data: json,
1086
- _by: entry.actor
1087
- };
1088
- }
1089
- await ctx.adapter.put(ctx.vault, BLOB_EVICTION_AUDIT_COLLECTION, entry.id, envelope);
1090
- }
1091
-
1092
874
  export {
1093
875
  detectMimeType,
1094
876
  detectMagic,
@@ -1099,11 +881,6 @@ export {
1099
881
  BLOB_SLOTS_PREFIX,
1100
882
  BLOB_VERSIONS_PREFIX,
1101
883
  DEFAULT_CHUNK_SIZE,
1102
- BlobSet,
1103
- ExportBlobsAbortedError,
1104
- EXPORT_AUDIT_COLLECTION,
1105
- createExportBlobsHandle,
1106
- BLOB_EVICTION_AUDIT_COLLECTION,
1107
- runCompaction
884
+ BlobSet
1108
885
  };
1109
- //# sourceMappingURL=chunk-VMIO4IXG.js.map
886
+ //# sourceMappingURL=chunk-JYQTXEIO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/blobs/mime-magic.ts","../src/blobs/blob-set.ts"],"sourcesContent":["/**\n * Lightweight MIME type detection from magic bytes (file signatures).\n *\n * Designed for the blob store's auto-detection feature. Operates on the first 16 bytes of\n * plaintext — no filesystem access, no filename guessing.\n *\n * ## Detection strategies\n *\n * 1. **Prefix match** — magic bytes at offset 0 (most formats).\n * 2. **Offset match** — magic bytes at a fixed offset > 0 (ISOBMFF: offset 4).\n * 3. **Compound match** — two separate byte sequences at different offsets\n * (RIFF-based: bytes 0-3 + bytes 8-11).\n *\n * ## Formats excluded (require offset > 16 bytes)\n *\n * - TAR (`ustar` at offset 257)\n * - ISO 9660 (`CD001` at offset 32769)\n *\n * @module\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\ninterface MagicRule {\n /** IANA MIME type (or widely-used x- type). */\n readonly mime: string\n /** Human-readable format name for diagnostics. */\n readonly format: string\n /** Magic bytes to match, as a Uint8Array. */\n readonly bytes: Uint8Array\n /** Byte offset where the magic starts. Default 0. */\n readonly offset?: number\n /**\n * For compound checks (RIFF, FORM): a second byte sequence that must\n * also match at `secondaryOffset`.\n */\n readonly secondaryBytes?: Uint8Array\n /** Offset of the secondary match. */\n readonly secondaryOffset?: number\n /** If true, the format is already compressed — skip gzip in blob.put(). */\n readonly preCompressed?: true\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────\n\n/** Convert a hex string like `'FF D8 FF'` to Uint8Array. */\nfunction hex(s: string): Uint8Array {\n return new Uint8Array(s.split(' ').map((b) => parseInt(b, 16)))\n}\n\n// ─── Magic rules ─────────────────────────────────────────────────────────\n//\n// Ordered by detection priority: more specific (longer) signatures first\n// within the same offset group, so that e.g. RAR v5 (8 bytes) is tested\n// before RAR v4 (7 bytes).\n//\n// Sources verified against:\n// - Gary Kessler's File Signatures Table\n// - Wikipedia \"List of file signatures\"\n// - IANA MIME type registry\n// - Individual format specifications (PNG RFC 2083, PDF ISO 32000, etc.)\n//\n// Each entry includes the original CSV row number for traceability.\n\nconst MAGIC_RULES: readonly MagicRule[] = [\n // ── Images ───────────────────────────────────────────────────────────\n\n // #2 PNG — full 8-byte signature (RFC 2083)\n { mime: 'image/png', format: 'PNG', bytes: hex('89 50 4E 47 0D 0A 1A 0A'), preCompressed: true },\n\n // #1 JPEG — FF D8 FF (third byte is start of APP marker, always FF)\n { mime: 'image/jpeg', format: 'JPEG', bytes: hex('FF D8 FF'), preCompressed: true },\n\n // #7 WebP — RIFF compound: bytes 0-3 = RIFF, bytes 8-11 = WEBP\n {\n mime: 'image/webp',\n format: 'WebP',\n bytes: hex('52 49 46 46'),\n secondaryBytes: hex('57 45 42 50'),\n secondaryOffset: 8,\n preCompressed: true,\n },\n\n // #5 TIFF (little-endian) — II + version 42\n { mime: 'image/tiff', format: 'TIFF', bytes: hex('49 49 2A 00') },\n\n // #6 TIFF (big-endian) — MM + version 42\n { mime: 'image/tiff', format: 'TIFF', bytes: hex('4D 4D 00 2A') },\n\n // #3 GIF — GIF8 (covers GIF87a and GIF89a)\n { mime: 'image/gif', format: 'GIF', bytes: hex('47 49 46 38'), preCompressed: true },\n\n // #4 BMP — BM\n { mime: 'image/bmp', format: 'BMP', bytes: hex('42 4D') },\n\n // PSD — 8BPS\n { mime: 'image/vnd.adobe.photoshop', format: 'PSD', bytes: hex('38 42 50 53') },\n\n // #8 ICO — 00 00 01 00 (note: 00 00 02 00 is CUR cursor format)\n { mime: 'image/x-icon', format: 'ICO', bytes: hex('00 00 01 00') },\n\n // #9 HEIC — ISOBMFF: ftyp at offset 4, brand \"heic\" at offset 8\n {\n mime: 'image/heic',\n format: 'HEIC',\n bytes: hex('66 74 79 70'),\n offset: 4,\n secondaryBytes: hex('68 65 69 63'),\n secondaryOffset: 8,\n preCompressed: true,\n },\n\n // ── Documents ────────────────────────────────────────────────────────\n\n // PDF — %PDF\n { mime: 'application/pdf', format: 'PDF', bytes: hex('25 50 44 46') },\n\n // RTF — {\\rtf\n { mime: 'application/rtf', format: 'RTF', bytes: hex('7B 5C 72 74 66') },\n\n // ── Archives & compression ───────────────────────────────────────────\n\n // RAR v5 — 8-byte signature (test before RAR v4)\n { mime: 'application/vnd.rar', format: 'RAR v5', bytes: hex('52 61 72 21 1A 07 01 00'), preCompressed: true },\n\n // RAR v4 — 7-byte signature\n { mime: 'application/vnd.rar', format: 'RAR v4', bytes: hex('52 61 72 21 1A 07 00'), preCompressed: true },\n\n // 7-Zip — 6-byte signature\n { mime: 'application/x-7z-compressed', format: '7Z', bytes: hex('37 7A BC AF 27 1C'), preCompressed: true },\n\n // XZ — 6-byte stream header\n { mime: 'application/x-xz', format: 'XZ', bytes: hex('FD 37 7A 58 5A 00'), preCompressed: true },\n\n // ZIP — PK\\x03\\x04 (local file header)\n { mime: 'application/zip', format: 'ZIP', bytes: hex('50 4B 03 04'), preCompressed: true },\n\n // GZIP — 1F 8B\n { mime: 'application/gzip', format: 'GZIP', bytes: hex('1F 8B'), preCompressed: true },\n\n // BZIP2 — BZh\n { mime: 'application/x-bzip2', format: 'BZIP2', bytes: hex('42 5A 68'), preCompressed: true },\n\n // LZIP — LZIP\n { mime: 'application/x-lzip', format: 'LZIP', bytes: hex('4C 5A 49 50'), preCompressed: true },\n\n // ── Audio ────────────────────────────────────────────────────────────\n\n // WAV — RIFF compound: bytes 0-3 = RIFF, bytes 8-11 = WAVE\n {\n mime: 'audio/wav',\n format: 'WAV',\n bytes: hex('52 49 46 46'),\n secondaryBytes: hex('57 41 56 45'),\n secondaryOffset: 8,\n },\n\n // AIFF — FORM compound: bytes 0-3 = FORM, bytes 8-11 = AIFF\n {\n mime: 'audio/aiff',\n format: 'AIFF',\n bytes: hex('46 4F 52 4D'),\n secondaryBytes: hex('41 49 46 46'),\n secondaryOffset: 8,\n },\n\n // FLAC — fLaC\n { mime: 'audio/flac', format: 'FLAC', bytes: hex('66 4C 61 43') },\n\n // OGG — OggS (container — may hold Vorbis, Opus, Theora, etc.)\n { mime: 'application/ogg', format: 'OGG', bytes: hex('4F 67 67 53') },\n\n // MIDI — MThd\n { mime: 'audio/midi', format: 'MIDI', bytes: hex('4D 54 68 64') },\n\n // MP3 (ID3-tagged) — ID3\n { mime: 'audio/mpeg', format: 'MP3', bytes: hex('49 44 33'), preCompressed: true },\n\n // ── Video ────────────────────────────────────────────────────────────\n\n // AVI — RIFF compound: bytes 0-3 = RIFF, bytes 8-11 = AVI\\x20\n {\n mime: 'video/x-msvideo',\n format: 'AVI',\n bytes: hex('52 49 46 46'),\n secondaryBytes: hex('41 56 49 20'),\n secondaryOffset: 8,\n preCompressed: true,\n },\n\n // WMV/ASF — 8-byte ASF header GUID prefix\n { mime: 'video/x-ms-wmv', format: 'WMV', bytes: hex('30 26 B2 75 8E 66 CF 11'), preCompressed: true },\n\n // MKV/WebM — EBML header (Matroska container)\n { mime: 'video/x-matroska', format: 'MKV', bytes: hex('1A 45 DF A3'), preCompressed: true },\n\n // FLV — FLV\n { mime: 'video/x-flv', format: 'FLV', bytes: hex('46 4C 56'), preCompressed: true },\n\n // MOV — ISOBMFF: ftyp at offset 4, brand \"qt \" at offset 8\n {\n mime: 'video/quicktime',\n format: 'MOV',\n bytes: hex('66 74 79 70'),\n offset: 4,\n secondaryBytes: hex('71 74 20 20'),\n secondaryOffset: 8,\n preCompressed: true,\n },\n\n // MP4 — ISOBMFF: ftyp at offset 4 (brands vary: isom, mp41, mp42, etc.)\n // Tested AFTER MOV and HEIC so their specific brands match first.\n { mime: 'video/mp4', format: 'MP4', bytes: hex('66 74 79 70'), offset: 4, preCompressed: true },\n\n // ── Executables & binaries ───────────────────────────────────────────\n\n // SQLite — \"SQLite 3\" (first 8 bytes of the 16-byte header)\n { mime: 'application/vnd.sqlite3', format: 'SQLite', bytes: hex('53 51 4C 69 74 65 20 33') },\n\n // WASM — \\0asm\n { mime: 'application/wasm', format: 'WASM', bytes: hex('00 61 73 6D') },\n\n // ELF — \\x7FELF\n { mime: 'application/x-elf', format: 'ELF', bytes: hex('7F 45 4C 46') },\n\n // PE (EXE/DLL) — MZ\n { mime: 'application/vnd.microsoft.portable-executable', format: 'PE', bytes: hex('4D 5A') },\n\n // Mach-O — all four single-arch variants\n { mime: 'application/x-mach-binary', format: 'Mach-O 64 LE', bytes: hex('CF FA ED FE') },\n { mime: 'application/x-mach-binary', format: 'Mach-O 64 BE', bytes: hex('FE ED FA CF') },\n { mime: 'application/x-mach-binary', format: 'Mach-O 32 LE', bytes: hex('CE FA ED FE') },\n { mime: 'application/x-mach-binary', format: 'Mach-O 32 BE', bytes: hex('FE ED FA CE') },\n\n // Java Class — CA FE BA BE\n // Note: collides with Mach-O Universal Binary. Disambiguated by checking\n // bytes 4-7: Java class version is >= 0x002D (45), while fat binary\n // arch count is a small number (typically 0x00000002).\n // We place Java after Mach-O single-arch entries so the more common\n // Mach-O variants match first. The CA FE BA BE collision between Java\n // and Mach-O fat binary is resolved by the caller if needed.\n { mime: 'application/java-vm', format: 'Java Class', bytes: hex('CA FE BA BE') },\n\n // DEX — dex\\n (Android Dalvik Executable)\n { mime: 'application/vnd.android.dex', format: 'DEX', bytes: hex('64 65 78 0A') },\n\n // ── Package formats ──────────────────────────────────────────────────\n\n // DEB — !<arch> (ar archive; DEB-specific member follows)\n { mime: 'application/vnd.debian.binary-package', format: 'DEB', bytes: hex('21 3C 61 72 63 68 3E') },\n\n // RPM — ED AB EE DB\n { mime: 'application/x-rpm', format: 'RPM', bytes: hex('ED AB EE DB') },\n\n // CAB — MSCF\n { mime: 'application/vnd.ms-cab-compressed', format: 'CAB', bytes: hex('4D 53 43 46'), preCompressed: true },\n\n // ── Capture & Flash ──────────────────────────────────────────────────\n\n // PCAP (little-endian) — D4 C3 B2 A1\n { mime: 'application/vnd.tcpdump.pcap', format: 'PCAP', bytes: hex('D4 C3 B2 A1') },\n\n // PCAP (big-endian) — A1 B2 C3 D4\n { mime: 'application/vnd.tcpdump.pcap', format: 'PCAP BE', bytes: hex('A1 B2 C3 D4') },\n\n // PCAPNG — Section Header Block\n { mime: 'application/x-pcapng', format: 'PCAPNG', bytes: hex('0A 0D 0D 0A') },\n\n // SWF — all three variants (uncompressed, zlib, LZMA)\n { mime: 'application/x-shockwave-flash', format: 'SWF', bytes: hex('46 57 53') },\n { mime: 'application/x-shockwave-flash', format: 'SWF zlib', bytes: hex('43 57 53'), preCompressed: true },\n { mime: 'application/x-shockwave-flash', format: 'SWF LZMA', bytes: hex('5A 57 53'), preCompressed: true },\n\n // ── Data formats ─────────────────────────────────────────────────────\n\n // Parquet — PAR1 (no registered IANA MIME; using Apache's informal type)\n { mime: 'application/vnd.apache.parquet', format: 'Parquet', bytes: hex('50 41 52 31') },\n\n // Avro Object Container — Obj\\x01\n { mime: 'application/avro', format: 'Avro', bytes: hex('4F 62 6A 01') },\n\n // NES ROM — NES\\x1A (iNES header)\n { mime: 'application/x-nintendo-nes-rom', format: 'NES ROM', bytes: hex('4E 45 53 1A') },\n] as const\n\n// ─── MP3 sync word ───────────────────────────────────────────────────────\n//\n// MP3 files without an ID3 tag start with a frame sync word where the top\n// 11 bits are set: 0xFFE0 mask. The ID3 signature (49 44 33) is handled\n// as a normal rule above. The sync-word check is a fallback tested in\n// `detectMimeType` after all rules.\n\nfunction isMp3SyncWord(byte0: number, byte1: number): boolean {\n return byte0 === 0xff && (byte1 & 0xe0) === 0xe0\n}\n\n// ─── Detection ───────────────────────────────────────────────────────────\n\n/**\n * Detect MIME type from the first bytes of a file.\n *\n * @param header - The first 16 bytes (or more) of the plaintext. Passing\n * fewer than 16 bytes may miss compound and offset-based matches.\n * @returns Detected MIME type, or `'application/octet-stream'` if unknown.\n */\nexport function detectMimeType(header: Uint8Array): string {\n const result = detectMagic(header)\n return result?.mime ?? 'application/octet-stream'\n}\n\n/**\n * Detect MIME type and whether the format is already compressed.\n *\n * Used by `BlobSet.put()` to decide whether to skip gzip compression.\n *\n * @param header - The first 16 bytes (or more) of the plaintext.\n * @returns `{ mime, preCompressed }` or `null` if no match.\n */\nexport function detectMagic(\n header: Uint8Array,\n): { mime: string; format: string; preCompressed: boolean } | null {\n for (const rule of MAGIC_RULES) {\n if (matchRule(header, rule)) {\n return {\n mime: rule.mime,\n format: rule.format,\n preCompressed: rule.preCompressed ?? false,\n }\n }\n }\n\n // Fallback: MP3 sync word (no ID3 tag)\n if (header.length >= 2 && isMp3SyncWord(header[0]!, header[1]!)) {\n return { mime: 'audio/mpeg', format: 'MP3', preCompressed: true }\n }\n\n return null\n}\n\n/**\n * Check whether a format is already compressed (should skip gzip).\n *\n * @param mimeType - A MIME type string.\n * @returns `true` if the format is known to be pre-compressed.\n */\nexport function isPreCompressed(mimeType: string): boolean {\n return PRE_COMPRESSED_MIMES.has(mimeType)\n}\n\n// ─── Internal matching ───────────────────────────────────────────────────\n\nfunction matchRule(header: Uint8Array, rule: MagicRule): boolean {\n const offset = rule.offset ?? 0\n const end = offset + rule.bytes.length\n\n // Not enough data for the primary match\n if (header.length < end) return false\n\n // Primary byte sequence\n for (let i = 0; i < rule.bytes.length; i++) {\n if (header[offset + i] !== rule.bytes[i]) return false\n }\n\n // Secondary byte sequence (compound check)\n if (rule.secondaryBytes && rule.secondaryOffset !== undefined) {\n const sEnd = rule.secondaryOffset + rule.secondaryBytes.length\n if (header.length < sEnd) return false\n for (let i = 0; i < rule.secondaryBytes.length; i++) {\n if (header[rule.secondaryOffset + i] !== rule.secondaryBytes[i]) return false\n }\n }\n\n return true\n}\n\n// ─── Pre-compressed MIME set ─────────────────────────────────────────────\n//\n// Built from the rules above. Used by `isPreCompressed()` for callers who\n// already know the MIME type (e.g. from a Content-Type header) and want to\n// skip the magic-byte detection step.\n\nconst PRE_COMPRESSED_MIMES = new Set<string>(\n MAGIC_RULES.filter((r) => r.preCompressed).map((r) => r.mime),\n)\n","import type {\n NoydbStore,\n EncryptedEnvelope,\n BlobObject,\n SlotRecord,\n SlotInfo,\n VersionRecord,\n BlobPutOptions,\n BlobResponseOptions,\n} from '../types.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport {\n encrypt,\n decrypt,\n hmacSha256Hex,\n encryptBytesWithAAD,\n decryptBytesWithAAD,\n bufferToBase64,\n base64ToBuffer,\n} from '../crypto.js'\nimport { ConflictError, NotFoundError } from '../errors.js'\nimport { detectMagic, isPreCompressed } from './mime-magic.js'\n\n// ─── Internal collection names ─────────────────────────────────────────\n\n/**\n * DEK slot name for vault-shared blob data. Calling `getDEK('_blob')`\n * auto-creates a blob DEK the first time — same lazy-creation mechanism\n * used for any user-defined collection.\n */\nexport const BLOB_COLLECTION = '_blob'\n\n/** Stores `BlobObject` metadata envelopes, keyed by eTag. */\nexport const BLOB_INDEX_COLLECTION = '_blob_index'\n\n/**\n * Stores encrypted chunk envelopes, keyed by `{eTag}/{chunkIndex}`.\n * NOT loaded into the in-memory query layer. Fetched on demand by\n * `BlobSet.get()` / `BlobSet.response()`.\n */\nexport const BLOB_CHUNKS_COLLECTION = '_blob_chunks'\n\n/** Prefix for per-collection slot metadata collections. */\nexport const BLOB_SLOTS_PREFIX = '_blob_slots_'\n\n/** Prefix for per-collection version records. */\nexport const BLOB_VERSIONS_PREFIX = '_blob_versions_'\n\n/**\n * Default chunk size: 256 KB raw bytes.\n * After AES-GCM (same size) + base64 (~33% inflation) → ~342 KB per\n * envelope, safely within DynamoDB's 400 KB item limit.\n */\nexport const DEFAULT_CHUNK_SIZE = 256 * 1024\n\n/** Maximum CAS retry attempts for refCount and slot metadata updates. */\nconst MAX_CAS_RETRIES = 5\n\n// ─── Compression helpers ───────────────────────────────────────────────\n\nasync function compressBytes(\n data: Uint8Array,\n): Promise<{ bytes: Uint8Array; algorithm: 'gzip' | 'none' }> {\n if (typeof CompressionStream === 'undefined') {\n return { bytes: data, algorithm: 'none' }\n }\n const cs = new CompressionStream('gzip')\n const writer = cs.writable.getWriter()\n await writer.write(data as Uint8Array<ArrayBuffer>)\n await writer.close()\n const buf = await new Response(cs.readable).arrayBuffer()\n return { bytes: new Uint8Array(buf), algorithm: 'gzip' }\n}\n\nasync function decompressBytes(data: Uint8Array): Promise<Uint8Array> {\n if (typeof DecompressionStream === 'undefined') {\n throw new Error(\n '[noy-db] DecompressionStream not available — cannot decompress blob chunk',\n )\n }\n const ds = new DecompressionStream('gzip')\n const writer = ds.writable.getWriter()\n await writer.write(data as Uint8Array<ArrayBuffer>)\n await writer.close()\n const buf = await new Response(ds.readable).arrayBuffer()\n return new Uint8Array(buf)\n}\n\nfunction concatChunks(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((s, c) => s + c.byteLength, 0)\n const out = new Uint8Array(total)\n let offset = 0\n for (const c of chunks) {\n out.set(c, offset)\n offset += c.byteLength\n }\n return out\n}\n\n/** Build the AAD binding for chunk integrity: \"{eTag}:{chunkIndex}:{chunkCount}\" */\nfunction chunkAAD(eTag: string, chunkIndex: number, chunkCount: number): Uint8Array {\n return new TextEncoder().encode(`${eTag}:${chunkIndex}:${chunkCount}`)\n}\n\n// ─── BlobSet ──────────────────────────────────────────────────────────\n\n/**\n * Handle for reading, writing, versioning, and deleting binary blobs\n * on a specific record.\n *\n * Obtained via `collection.blob(id)`. No I/O is performed until you\n * call a method.\n *\n * ## Storage layout\n *\n * ```\n * _blob_index/{eTag} BlobObject metadata (vault-shared DEK)\n * _blob_chunks/{eTag}/{chunkIndex} Encrypted chunk data (vault-shared DEK + AAD)\n * _blob_slots_{collection}/{recordId} Slot map (parent collection DEK)\n * _blob_versions_{collection}/{recordId}/{slot}/{label} Published versions (parent collection DEK)\n * ```\n *\n * ## Deduplication\n *\n * `put()` computes `eTag = HMAC-SHA-256(blobDEK, plaintext)` — keyed so the\n * store cannot predict eTags for known content. If another record already\n * uploaded the same bytes, the chunks are reused and `refCount` is incremented.\n *\n * ## Chunk integrity\n *\n * Each chunk is encrypted with AES-256-GCM using AAD = `{eTag}:{index}:{count}`,\n * preventing chunk reorder, substitution, and truncation attacks.\n */\nexport class BlobSet {\n private readonly store: NoydbStore\n private readonly vault: string\n private readonly collection: string\n private readonly recordId: string\n private readonly getDEK: (name: string) => Promise<CryptoKey>\n private readonly encrypted: boolean\n private readonly userId: string | undefined\n private readonly maxBlobBytes: number | undefined\n\n constructor(opts: {\n store: NoydbStore\n vault: string\n collection: string\n recordId: string\n getDEK: (name: string) => Promise<CryptoKey>\n encrypted: boolean\n userId?: string\n maxBlobBytes?: number\n }) {\n this.store = opts.store\n this.vault = opts.vault\n this.collection = opts.collection\n this.recordId = opts.recordId\n this.getDEK = opts.getDEK\n this.encrypted = opts.encrypted\n this.userId = opts.userId\n this.maxBlobBytes = opts.maxBlobBytes\n }\n\n /** The internal collection that holds slot metadata for this collection's blobs. */\n private get slotsCollection(): string {\n return `${BLOB_SLOTS_PREFIX}${this.collection}`\n }\n\n /** The internal collection that holds published versions for this collection's blobs. */\n private get versionsCollection(): string {\n return `${BLOB_VERSIONS_PREFIX}${this.collection}`\n }\n\n // ─── Slot Metadata I/O (CAS-protected) ─────────────────────────────\n\n private async loadSlots(): Promise<{\n slots: Record<string, SlotRecord>\n version: number\n }> {\n const envelope = await this.store.get(this.vault, this.slotsCollection, this.recordId)\n if (!envelope) return { slots: {}, version: 0 }\n\n if (!this.encrypted) {\n return {\n slots: JSON.parse(envelope._data) as Record<string, SlotRecord>,\n version: envelope._v,\n }\n }\n\n const dek = await this.getDEK(this.collection)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return {\n slots: JSON.parse(json) as Record<string, SlotRecord>,\n version: envelope._v,\n }\n }\n\n private async saveSlots(\n slots: Record<string, SlotRecord>,\n currentVersion: number,\n ): Promise<void> {\n const json = JSON.stringify(slots)\n const now = new Date().toISOString()\n let envelope: EncryptedEnvelope\n\n if (this.encrypted) {\n const dek = await this.getDEK(this.collection)\n const { iv, data } = await encrypt(json, dek)\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: currentVersion + 1,\n _ts: now,\n _iv: iv,\n _data: data,\n }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: currentVersion + 1,\n _ts: now,\n _iv: '',\n _data: json,\n }\n }\n\n await this.store.put(\n this.vault,\n this.slotsCollection,\n this.recordId,\n envelope,\n currentVersion > 0 ? currentVersion : undefined,\n )\n }\n\n /**\n * CAS retry loop for slot metadata updates. Re-reads slots on conflict\n * and re-applies the mutation function.\n */\n private async casUpdateSlots(\n mutate: (slots: Record<string, SlotRecord>) => Record<string, SlotRecord> | null,\n ): Promise<void> {\n for (let attempt = 0; attempt < MAX_CAS_RETRIES; attempt++) {\n const { slots, version } = await this.loadSlots()\n const updated = mutate(slots)\n if (updated === null) return // no-op\n try {\n await this.saveSlots(updated, version)\n return\n } catch (err) {\n if (err instanceof ConflictError && attempt < MAX_CAS_RETRIES - 1) continue\n throw err\n }\n }\n }\n\n // ─── Blob Index I/O (versioned for CAS refCount) ──────────────────\n\n private async loadBlobObject(eTag: string): Promise<{ blob: BlobObject; version: number } | null> {\n const envelope = await this.store.get(this.vault, BLOB_INDEX_COLLECTION, eTag)\n if (!envelope) return null\n\n if (!this.encrypted) {\n return { blob: JSON.parse(envelope._data) as BlobObject, version: envelope._v }\n }\n\n const dek = await this.getDEK(BLOB_COLLECTION)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return { blob: JSON.parse(json) as BlobObject, version: envelope._v }\n }\n\n private async writeBlobObject(blob: BlobObject, expectedVersion?: number): Promise<void> {\n const json = JSON.stringify(blob)\n const now = new Date().toISOString()\n const newVersion = (expectedVersion ?? 0) + 1\n let envelope: EncryptedEnvelope\n\n if (this.encrypted) {\n const dek = await this.getDEK(BLOB_COLLECTION)\n const { iv, data } = await encrypt(json, dek)\n envelope = { _noydb: NOYDB_FORMAT_VERSION, _v: newVersion, _ts: now, _iv: iv, _data: data }\n } else {\n envelope = { _noydb: NOYDB_FORMAT_VERSION, _v: newVersion, _ts: now, _iv: '', _data: json }\n }\n\n await this.store.put(\n this.vault,\n BLOB_INDEX_COLLECTION,\n blob.eTag,\n envelope,\n expectedVersion,\n )\n }\n\n /**\n * CAS retry loop for refCount changes on a BlobObject.\n */\n private async casUpdateRefCount(eTag: string, delta: number): Promise<void> {\n for (let attempt = 0; attempt < MAX_CAS_RETRIES; attempt++) {\n const result = await this.loadBlobObject(eTag)\n if (!result) throw new NotFoundError(`BlobObject ${eTag} not found`)\n const { blob, version } = result\n const updated: BlobObject = { ...blob, refCount: blob.refCount + delta }\n try {\n await this.writeBlobObject(updated, version)\n return\n } catch (err) {\n if (err instanceof ConflictError && attempt < MAX_CAS_RETRIES - 1) continue\n throw err\n }\n }\n }\n\n // ─── Chunk I/O (with AAD binding) ─────────────────────────────────\n\n private async writeChunk(\n eTag: string,\n index: number,\n chunkCount: number,\n chunk: Uint8Array,\n dek: CryptoKey | null,\n ): Promise<void> {\n const id = `${eTag}_${index}`\n const now = new Date().toISOString()\n let envelope: EncryptedEnvelope\n\n if (dek) {\n const aad = chunkAAD(eTag, index, chunkCount)\n const { iv, data } = await encryptBytesWithAAD(chunk, dek, aad)\n envelope = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: now, _iv: iv, _data: data }\n } else {\n envelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: 1,\n _ts: now,\n _iv: '',\n _data: bufferToBase64(chunk),\n }\n }\n\n await this.store.put(this.vault, BLOB_CHUNKS_COLLECTION, id, envelope)\n }\n\n private async readChunk(\n eTag: string,\n index: number,\n chunkCount: number,\n dek: CryptoKey | null,\n ): Promise<Uint8Array | null> {\n const envelope = await this.store.get(this.vault, BLOB_CHUNKS_COLLECTION, `${eTag}_${index}`)\n if (!envelope) return null\n\n if (dek) {\n const aad = chunkAAD(eTag, index, chunkCount)\n return await decryptBytesWithAAD(envelope._iv, envelope._data, dek, aad)\n }\n\n return base64ToBuffer(envelope._data)\n }\n\n // ─── Version record I/O ───────────────────────────────────────────\n\n private versionKey(slotName: string, label: string): string {\n return `${this.recordId}::${slotName}::${label}`\n }\n\n private async loadVersionRecord(slotName: string, label: string): Promise<VersionRecord | null> {\n const key = this.versionKey(slotName, label)\n const envelope = await this.store.get(this.vault, this.versionsCollection, key)\n if (!envelope) return null\n\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as VersionRecord\n }\n\n const dek = await this.getDEK(this.collection)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as VersionRecord\n }\n\n private async writeVersionRecord(slotName: string, record: VersionRecord): Promise<void> {\n const key = this.versionKey(slotName, record.label)\n const json = JSON.stringify(record)\n const now = new Date().toISOString()\n let envelope: EncryptedEnvelope\n\n if (this.encrypted) {\n const dek = await this.getDEK(this.collection)\n const { iv, data } = await encrypt(json, dek)\n envelope = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: now, _iv: iv, _data: data }\n } else {\n envelope = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: now, _iv: '', _data: json }\n }\n\n await this.store.put(this.vault, this.versionsCollection, key, envelope)\n }\n\n private async deleteVersionRecord(slotName: string, label: string): Promise<void> {\n const key = this.versionKey(slotName, label)\n await this.store.delete(this.vault, this.versionsCollection, key)\n }\n\n // ─── Effective chunk size ─────────────────────────────────────────\n\n private effectiveChunkSize(opts?: BlobPutOptions): number {\n if (opts?.chunkSize) return opts.chunkSize\n if (this.maxBlobBytes) return this.maxBlobBytes\n return DEFAULT_CHUNK_SIZE\n }\n\n // ─── Fetch all chunks for a blob ──────────────────────────────────\n\n private async fetchAllChunks(blob: BlobObject): Promise<Uint8Array> {\n const blobDEK = this.encrypted ? await this.getDEK(BLOB_COLLECTION) : null\n const chunks: Uint8Array[] = []\n\n for (let i = 0; i < blob.chunkCount; i++) {\n const chunk = await this.readChunk(blob.eTag, i, blob.chunkCount, blobDEK)\n if (!chunk) {\n throw new NotFoundError(\n `Blob chunk ${i}/${blob.chunkCount} missing for eTag \"${blob.eTag}\" on record \"${this.recordId}\"`,\n )\n }\n chunks.push(chunk)\n }\n\n const assembled = concatChunks(chunks)\n return blob.compression === 'gzip' ? await decompressBytes(assembled) : assembled\n }\n\n // ─── Public API: Slot management ──────────────────────────────────\n\n /**\n * Upload bytes and attach them to this record under `slotName`.\n *\n * 1. Computes `eTag = HMAC-SHA-256(blobDEK, plaintext)` for keyed content-addressing.\n * 2. Auto-detects MIME type from magic bytes if not provided.\n * 3. If a blob with this eTag already exists, skips chunk upload (deduplication)\n * and CAS-increments refCount.\n * 4. Otherwise: compresses → splits into chunks → encrypts each chunk with\n * AAD binding → writes `_blob_chunks` → writes `BlobObject` to `_blob_index`.\n * 5. CAS-updates the slot metadata in `_blob_slots_{collection}`.\n * If overwriting an existing slot, decrements the old eTag's refCount.\n */\n async put(slotName: string, data: Uint8Array, opts?: BlobPutOptions): Promise<void> {\n // Step 1 — keyed content-hash (plaintext, before compression)\n const blobDEK = this.encrypted ? await this.getDEK(BLOB_COLLECTION) : null\n const eTag = blobDEK\n ? await hmacSha256Hex(blobDEK, data)\n : await plainSha256Hex(data)\n\n // Step 2 — MIME detection\n let mimeType = opts?.mimeType\n if (!mimeType) {\n const detected = detectMagic(data.subarray(0, 16))\n if (detected) mimeType = detected.mime\n }\n\n // Determine compression: explicit opt > auto-detect > default true\n let shouldCompress: boolean\n if (opts?.compress !== undefined) {\n shouldCompress = opts.compress\n } else if (mimeType && isPreCompressed(mimeType)) {\n shouldCompress = false\n } else {\n shouldCompress = true\n }\n\n // Step 3 — deduplication check\n const existingBlob = await this.loadBlobObject(eTag)\n\n if (existingBlob) {\n // eTag already exists — just increment refCount (CAS retry)\n await this.casUpdateRefCount(eTag, +1)\n } else {\n // Step 4 — compress\n const { bytes: compressed, algorithm } = shouldCompress\n ? await compressBytes(data)\n : { bytes: data, algorithm: 'none' as const }\n\n const chunkSize = this.effectiveChunkSize(opts)\n const chunkCount = Math.max(1, Math.ceil(compressed.byteLength / chunkSize))\n\n // Step 5 — write chunks FIRST with AAD binding (safe failure order)\n for (let i = 0; i < chunkCount; i++) {\n const start = i * chunkSize\n await this.writeChunk(\n eTag, i, chunkCount,\n compressed.subarray(start, start + chunkSize),\n blobDEK,\n )\n }\n\n // Step 6 — write blob index entry after all chunks succeed\n await this.writeBlobObject({\n eTag,\n size: data.byteLength,\n compressedSize: compressed.byteLength,\n compression: algorithm,\n chunkSize,\n chunkCount,\n ...(mimeType !== undefined ? { mimeType } : {}),\n createdAt: new Date().toISOString(),\n refCount: 1,\n })\n }\n\n // Step 7 — CAS-update slot metadata\n const uploaderUserId = opts?.uploadedBy ?? this.userId\n await this.casUpdateSlots((slots) => {\n const oldETag = slots[slotName]?.eTag\n slots[slotName] = {\n eTag,\n filename: slotName,\n size: data.byteLength,\n ...(mimeType !== undefined ? { mimeType } : {}),\n uploadedAt: new Date().toISOString(),\n ...(uploaderUserId !== undefined ? { uploadedBy: uploaderUserId } : {}),\n }\n // Schedule old eTag refCount decrement (non-blocking best-effort)\n if (oldETag && oldETag !== eTag) {\n this._deferredRefDecrement = oldETag\n }\n return slots\n })\n\n // Decrement old eTag refCount outside the CAS loop\n if (this._deferredRefDecrement) {\n const oldETag = this._deferredRefDecrement\n this._deferredRefDecrement = undefined\n await this.casUpdateRefCount(oldETag, -1).catch(() => {\n // Best-effort — blobGC will reconcile\n })\n }\n }\n\n private _deferredRefDecrement: string | undefined\n\n /**\n * Fetch all bytes for the named slot.\n * Returns `null` if the slot does not exist.\n * Throws `NotFoundError` if the index entry exists but a chunk is missing.\n */\n async get(slotName: string): Promise<Uint8Array | null> {\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n if (!slot) return null\n\n const result = await this.loadBlobObject(slot.eTag)\n if (!result) return null\n\n return this.fetchAllChunks(result.blob)\n }\n\n /**\n * List all slot entries for this record.\n * Returns metadata only — no chunk data is loaded.\n */\n async list(): Promise<SlotInfo[]> {\n const { slots } = await this.loadSlots()\n return Object.entries(slots).map(([name, slot]) => ({ name, ...slot }))\n }\n\n /**\n * Delete the named slot from this record.\n * Decrements refCount on the blob. Chunks are GC'd by `vault.blobGC()`.\n */\n async delete(slotName: string): Promise<void> {\n let eTagToDecrement: string | undefined\n\n await this.casUpdateSlots((slots) => {\n if (!(slotName in slots)) return null\n eTagToDecrement = slots[slotName]!.eTag\n delete slots[slotName]\n return slots\n })\n\n if (eTagToDecrement) {\n await this.casUpdateRefCount(eTagToDecrement, -1).catch(() => {\n // Best-effort — blobGC will reconcile\n })\n }\n }\n\n /**\n * Return a native `Response` whose body streams the decrypted,\n * decompressed blob bytes with full HTTP metadata headers.\n *\n * Note: implementation is buffered — all chunks are loaded into\n * memory before being enqueued. True streaming deferred to.\n *\n * Returns `null` if the slot does not exist.\n */\n async response(slotName: string, opts?: BlobResponseOptions): Promise<Response | null> {\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n if (!slot) return null\n\n const result = await this.loadBlobObject(slot.eTag)\n if (!result) return null\n\n return this.buildResponse(slot, result.blob, opts)\n }\n\n /**\n * Decrypt the slot and wrap the bytes in a browser ObjectURL ready\n * to feed into `<img src>`, `<a href>`, etc. The caller MUST call\n * `revoke()` when the URL is no longer needed — otherwise the URL\n * (and the underlying decrypted Blob) are pinned for the lifetime\n * of the document, which leaks memory in long-lived pages.\n *\n * Returns `null` when the slot does not exist.\n *\n * Throws when `URL.createObjectURL` is unavailable in the host\n * environment (Node without DOM, restricted workers). Framework\n * adapters — `useBlobURL` in `@noy-db/in-vue`, etc. — guard against\n * this for SSR contexts and stay at `null` instead of propagating.\n */\n async objectURL(\n slotName: string,\n opts?: { mimeType?: string },\n ): Promise<{ url: string; revoke: () => void } | null> {\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') {\n throw new Error(\n 'BlobSet.objectURL: URL.createObjectURL is unavailable in this environment. ' +\n 'Call this from the browser, or use BlobSet.get() and create the URL yourself.',\n )\n }\n const bytes = await this.get(slotName)\n if (!bytes) return null\n\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n const type = opts?.mimeType ?? slot?.mimeType ?? 'application/octet-stream'\n\n // Pinning the underlying ArrayBuffer in a Blob is what backs the\n // ObjectURL — once we createObjectURL the URL holds a strong ref\n // to the Blob, so the local `blob` variable can fall out of scope.\n // Copy through a fresh ArrayBuffer so TS narrows away the\n // SharedArrayBuffer branch of `ArrayBufferLike` (Uint8Array is\n // generic over the backing buffer type since TS 5.7).\n const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer\n const blob = new Blob([buffer], { type })\n const url = URL.createObjectURL(blob)\n let revoked = false\n const revoke = (): void => {\n if (revoked) return\n revoked = true\n URL.revokeObjectURL(url)\n }\n return { url, revoke }\n }\n\n // ─── Public API: Published versions (UC-3 amendment versioning) ───\n\n /**\n * Publish the current slot content as a named version snapshot.\n *\n * The published version holds an independent refCount reference to\n * the blob. Even if the slot is later overwritten or deleted, the\n * published version keeps the blob data alive.\n *\n * Publishing with an existing label overwrites it — if the eTags differ,\n * refCounts are adjusted accordingly.\n */\n async publish(slotName: string, label: string): Promise<void> {\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n if (!slot) throw new NotFoundError(`Slot \"${slotName}\" not found on record \"${this.recordId}\"`)\n\n // Check for existing version with this label\n const existing = await this.loadVersionRecord(slotName, label)\n if (existing && existing.eTag === slot.eTag) return // no-op: same blob\n\n // Write the version record\n const record: VersionRecord = {\n label,\n eTag: slot.eTag,\n publishedAt: new Date().toISOString(),\n ...(this.userId !== undefined ? { publishedBy: this.userId } : {}),\n }\n await this.writeVersionRecord(slotName, record)\n\n // Increment refCount for the new version's eTag\n await this.casUpdateRefCount(slot.eTag, +1)\n\n // If overwriting an existing version with a different eTag, decrement the old one\n if (existing && existing.eTag !== slot.eTag) {\n await this.casUpdateRefCount(existing.eTag, -1).catch(() => {})\n }\n }\n\n /**\n * Fetch bytes for a published version.\n * Returns `null` if the version does not exist.\n */\n async getVersion(slotName: string, label: string): Promise<Uint8Array | null> {\n const record = await this.loadVersionRecord(slotName, label)\n if (!record) return null\n\n const result = await this.loadBlobObject(record.eTag)\n if (!result) return null\n\n return this.fetchAllChunks(result.blob)\n }\n\n /**\n * List all published versions for a slot.\n */\n async listVersions(slotName: string): Promise<VersionRecord[]> {\n const prefix = `${this.recordId}::${slotName}::`\n const allKeys = await this.store.list(this.vault, this.versionsCollection)\n const matchingKeys = allKeys.filter((k) => k.startsWith(prefix))\n\n const versions: VersionRecord[] = []\n for (const key of matchingKeys) {\n const envelope = await this.store.get(this.vault, this.versionsCollection, key)\n if (!envelope) continue\n\n if (!this.encrypted) {\n versions.push(JSON.parse(envelope._data) as VersionRecord)\n } else {\n const dek = await this.getDEK(this.collection)\n const json = await decrypt(envelope._iv, envelope._data, dek)\n versions.push(JSON.parse(json) as VersionRecord)\n }\n }\n\n return versions\n }\n\n /**\n * Delete a published version. Decrements refCount on its blob.\n */\n async deleteVersion(slotName: string, label: string): Promise<void> {\n const record = await this.loadVersionRecord(slotName, label)\n if (!record) return\n\n await this.deleteVersionRecord(slotName, label)\n await this.casUpdateRefCount(record.eTag, -1).catch(() => {})\n }\n\n /**\n * Return a `Response` for a published version — same as `response()`\n * but reads from the version record's eTag instead of the current slot.\n */\n async responseVersion(\n slotName: string,\n label: string,\n opts?: BlobResponseOptions,\n ): Promise<Response | null> {\n const record = await this.loadVersionRecord(slotName, label)\n if (!record) return null\n\n const result = await this.loadBlobObject(record.eTag)\n if (!result) return null\n\n // Build a synthetic SlotRecord from the version + blob data\n const slotLike: SlotRecord = {\n eTag: record.eTag,\n filename: opts?.filename ?? `${slotName}-${label}`,\n size: result.blob.size,\n ...(result.blob.mimeType !== undefined ? { mimeType: result.blob.mimeType } : {}),\n uploadedAt: record.publishedAt,\n ...(record.publishedBy !== undefined ? { uploadedBy: record.publishedBy } : {}),\n }\n\n return this.buildResponse(slotLike, result.blob, opts)\n }\n\n // ─── Diagnostics ──────────────────────────────────────────────────\n\n /**\n * Return the `BlobObject` metadata for the named slot.\n * Returns `null` if the slot or blob does not exist.\n */\n async blobInfo(slotName: string): Promise<BlobObject | null> {\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n if (!slot) return null\n const result = await this.loadBlobObject(slot.eTag)\n return result?.blob ?? null\n }\n\n // ─── Presigned URL (E5) ────────────────────────────────────────────\n\n /**\n * Generate a presigned URL for direct client download of the blob's\n * ciphertext. Only works when the blob store supports `presignUrl`.\n *\n * **Important:** The URL returns encrypted data. The caller must\n * decrypt client-side using `decryptResponse()` or a service worker.\n *\n * Returns `null` if the slot doesn't exist or the store doesn't support presigning.\n */\n async presignedUrl(slotName: string, expiresInSeconds = 3600): Promise<string | null> {\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n if (!slot) return null\n\n const result = await this.loadBlobObject(slot.eTag)\n if (!result) return null\n\n // Only works for single-chunk blobs where the store supports presigning\n if (result.blob.chunkCount !== 1) return null\n if (!this.store.presignUrl) return null\n\n const chunkId = `${slot.eTag}_0`\n return this.store.presignUrl(this.vault, '_blob_chunks', chunkId, expiresInSeconds)\n }\n\n /**\n * Decrypt a ciphertext Response (e.g. from a presigned URL fetch)\n * back into a plaintext Response with correct headers.\n *\n * Usage with service worker or client-side fetch:\n * ```ts\n * const url = await blobs.presignedUrl('invoice.pdf')\n * const cipherResponse = await fetch(url)\n * const plainResponse = await blobs.decryptResponse('invoice.pdf', cipherResponse)\n * ```\n */\n async decryptResponse(slotName: string, cipherResponse: Response): Promise<Response | null> {\n const { slots } = await this.loadSlots()\n const slot = slots[slotName]\n if (!slot) return null\n\n const result = await this.loadBlobObject(slot.eTag)\n if (!result) return null\n\n // Parse the envelope from the ciphertext response\n const text = await cipherResponse.text()\n const envelope = JSON.parse(text) as { _iv: string; _data: string }\n\n const blobDEK = this.encrypted ? await this.getDEK('_blob') : null\n if (!blobDEK) {\n return this.buildResponse(slot, result.blob, { inline: true })\n }\n\n // Decrypt the single chunk\n const aad = chunkAAD(slot.eTag, 0, result.blob.chunkCount)\n const { decryptBytesWithAAD: decryptAAD } = await import('../crypto.js')\n const decrypted = await decryptAAD(envelope._iv, envelope._data, blobDEK, aad)\n const plaintext = result.blob.compression === 'gzip'\n ? await decompressBytes(decrypted)\n : decrypted\n\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(plaintext)\n controller.close()\n },\n })\n\n const filename = slot.filename\n return new Response(body, {\n headers: {\n 'Content-Type': slot.mimeType ?? 'application/octet-stream',\n 'Content-Length': String(slot.size),\n 'ETag': `\"${slot.eTag}\"`,\n 'Content-Disposition': `inline; filename=\"${filename}\"`,\n 'Last-Modified': new Date(slot.uploadedAt).toUTCString(),\n },\n })\n }\n\n // ─── Internal: build Response from slot + blob ────────────────────\n\n private async buildResponse(\n slot: SlotRecord,\n blob: BlobObject,\n opts?: BlobResponseOptions,\n ): Promise<Response> {\n const fetchAllChunks = this.fetchAllChunks.bind(this)\n\n // buffered — all chunks loaded into memory then enqueued.\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n const output = await fetchAllChunks(blob)\n controller.enqueue(output)\n controller.close()\n } catch (err) {\n controller.error(err)\n }\n },\n })\n\n const filename = opts?.filename ?? slot.filename\n const disposition = opts?.inline\n ? `inline; filename=\"${filename}\"`\n : `attachment; filename=\"${filename}\"`\n\n return new Response(body, {\n headers: {\n 'Content-Type': slot.mimeType ?? 'application/octet-stream',\n 'Content-Length': String(slot.size),\n 'ETag': `\"${slot.eTag}\"`,\n 'Content-Disposition': disposition,\n 'Last-Modified': new Date(slot.uploadedAt).toUTCString(),\n },\n })\n }\n}\n\n// ─── Fallback for unencrypted mode ──────────────────────────────────────\n\nimport { sha256Hex } from '../crypto.js'\n\nasync function plainSha256Hex(data: Uint8Array): Promise<string> {\n return sha256Hex(data)\n}\n"],"mappings":"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","names":[]}
@@ -1,22 +1,20 @@
1
1
  import {
2
2
  ensureCollectionDEK
3
- } from "./chunk-PA6R5ZCI.js";
3
+ } from "./chunk-Q6W2CMEJ.js";
4
4
  import {
5
5
  envelopePayloadHash
6
- } from "./chunk-2AXFIYHT.js";
6
+ } from "./chunk-XG3PTSCD.js";
7
7
  import {
8
8
  NOYDB_FORMAT_VERSION
9
9
  } from "./chunk-YS3POABP.js";
10
10
  import {
11
11
  decrypt,
12
12
  encrypt
13
- } from "./chunk-WCA2NROQ.js";
13
+ } from "./chunk-2PAQNPE3.js";
14
14
  import {
15
15
  DictKeyMissingError,
16
- LocaleNotSpecifiedError,
17
- MissingTranslationError,
18
16
  PermissionDeniedError
19
- } from "./chunk-ADQ5MQ54.js";
17
+ } from "./chunk-W3XXT26A.js";
20
18
 
21
19
  // src/i18n/dictionary.ts
22
20
  var DICT_COLLECTION_PREFIX = "_dict_";
@@ -369,122 +367,12 @@ var DictionaryHandle = class {
369
367
  }
370
368
  };
371
369
 
372
- // src/i18n/core.ts
373
- function i18nText(options) {
374
- return { _noydbI18nText: true, options };
375
- }
376
- function isI18nTextDescriptor(x) {
377
- return typeof x === "object" && x !== null && x._noydbI18nText === true;
378
- }
379
- function validateI18nTextValue(value, field, descriptor) {
380
- const { options } = descriptor;
381
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
382
- throw new MissingTranslationError(
383
- field,
384
- options.languages,
385
- `Field "${field}" must be a { [locale]: string } map, got ${typeof value}.`
386
- );
387
- }
388
- const map = value;
389
- for (const [locale, v] of Object.entries(map)) {
390
- if (typeof v !== "string") {
391
- throw new MissingTranslationError(
392
- field,
393
- [locale],
394
- `Field "${field}": locale "${locale}" must be a string, got ${typeof v}.`
395
- );
396
- }
397
- }
398
- const { required } = options;
399
- if (required === "all") {
400
- const missing = options.languages.filter(
401
- (lang) => !(lang in map) || map[lang] === ""
402
- );
403
- if (missing.length > 0) {
404
- throw new MissingTranslationError(
405
- field,
406
- missing,
407
- `Field "${field}" requires all declared languages. Missing: ${missing.join(", ")}.`
408
- );
409
- }
410
- } else if (required === "any") {
411
- const present = options.languages.some(
412
- (lang) => lang in map && map[lang] !== ""
413
- );
414
- if (!present) {
415
- throw new MissingTranslationError(
416
- field,
417
- options.languages,
418
- `Field "${field}" requires at least one declared language. None present.`
419
- );
420
- }
421
- } else {
422
- const requiredList = required;
423
- const missing = requiredList.filter(
424
- (lang) => !(lang in map) || map[lang] === ""
425
- );
426
- if (missing.length > 0) {
427
- throw new MissingTranslationError(
428
- field,
429
- missing,
430
- `Field "${field}" requires: ${requiredList.join(", ")}. Missing: ${missing.join(", ")}.`
431
- );
432
- }
433
- }
434
- }
435
- function resolveI18nText(value, locale, fallback, field) {
436
- if (locale === "raw") {
437
- return value;
438
- }
439
- if (!locale) {
440
- throw new LocaleNotSpecifiedError(field ?? "<unknown>");
441
- }
442
- if (value[locale] !== void 0 && value[locale] !== "") {
443
- return value[locale];
444
- }
445
- const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
446
- for (const fb of chain) {
447
- if (fb === "any") {
448
- const any = Object.values(value).find((v) => v !== "");
449
- if (any !== void 0) return any;
450
- } else if (value[fb] !== void 0 && value[fb] !== "") {
451
- return value[fb];
452
- }
453
- }
454
- throw new LocaleNotSpecifiedError(
455
- field ?? "<unknown>",
456
- `No translation available for locale "${locale}"` + (chain.length > 0 ? ` or fallback chain [${chain.join(", ")}]` : "") + "."
457
- );
458
- }
459
- function applyI18nLocale(record, i18nFields, locale, fallback) {
460
- const fieldNames = Object.keys(i18nFields);
461
- if (fieldNames.length === 0) return record;
462
- const result = { ...record };
463
- for (const field of fieldNames) {
464
- const raw = result[field];
465
- if (raw === void 0 || raw === null) continue;
466
- if (typeof raw !== "object" || Array.isArray(raw)) continue;
467
- result[field] = resolveI18nText(
468
- raw,
469
- locale,
470
- fallback,
471
- field
472
- );
473
- }
474
- return result;
475
- }
476
-
477
370
  export {
478
371
  DICT_COLLECTION_PREFIX,
479
372
  dictCollectionName,
480
373
  isDictCollectionName,
481
374
  dictKey,
482
375
  isDictKeyDescriptor,
483
- DictionaryHandle,
484
- i18nText,
485
- isI18nTextDescriptor,
486
- validateI18nTextValue,
487
- resolveI18nText,
488
- applyI18nLocale
376
+ DictionaryHandle
489
377
  };
490
- //# sourceMappingURL=chunk-NIOHFJPJ.js.map
378
+ //# sourceMappingURL=chunk-LRAZDV5X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/i18n/dictionary.ts"],"sourcesContent":["/**\n * _dict_* reserved collections + dictKey schema descriptor —\n *\n * Stores bounded enum-like field dictionaries as reserved encrypted\n * collections (`_dict_<name>/`) within a vault. Each dictionary\n * entry maps a stable key (e.g. `'paid'`) to a locale → label record\n * (e.g. `{ en: 'Paid', th: 'ชำระแล้ว' }`).\n *\n * Design decisions\n * ────────────────\n *\n * **Why reserved collections, not a separate store?**\n * Same answer as `_sync_credentials`: the compartment's existing\n * encryption stack is exactly right. Dictionaries are encrypted under the\n * same vault DEK, inherit ACL, ledger, and backup/restore for free.\n *\n * **One collection per dictionary, not one collection with namespaces.**\n * Each `_dict_<name>/` collection holds entries `{ id: key, labels: {...} }`.\n * This composes with `ref()` naturally (a dictKey IS a ref to the dict\n * collection), and means the query DSL works over dictionary entries\n * without any special-casing.\n *\n * **dictKey() is a descriptor, not a Zod type.**\n * The descriptor pattern matches `ref()`: declare NOYDB-specific metadata\n * in the collection options alongside `refs`. TypeScript inference comes\n * from the descriptor's generic parameter, not from Zod internals.\n *\n * API:\n * `dictKey(name, keys?)` — returns a DictKeyDescriptor\n * `vault.dictionary(name)` — returns a DictionaryHandle\n * `DictionaryHandle.put/putAll/get/delete/rename/list` — CRUD\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../types.js'\nimport type { NoydbEventEmitter } from '../events.js'\nimport { NOYDB_FORMAT_VERSION } from '../types.js'\nimport type { UnlockedKeyring } from '../team/keyring.js'\nimport { encrypt, decrypt } from '../crypto.js'\nimport { ensureCollectionDEK } from '../team/keyring.js'\nimport type { LedgerStore } from '../history/ledger/store.js'\nimport { envelopePayloadHash } from '../history/ledger/hash.js'\nimport {\n PermissionDeniedError,\n DictKeyMissingError,\n} from '../errors.js'\n\n/** Reserved collection name prefix. Never collides with user collections. */\nexport const DICT_COLLECTION_PREFIX = '_dict_'\n\n/** Return the adapter collection name for a named dictionary. */\nexport function dictCollectionName(dictionaryName: string): string {\n return `${DICT_COLLECTION_PREFIX}${dictionaryName}`\n}\n\n/** Return true when a collection name is a reserved dictionary collection. */\nexport function isDictCollectionName(name: string): boolean {\n return name.startsWith(DICT_COLLECTION_PREFIX)\n}\n\n// ─── DictKey descriptor ────────────────────────────────────────────────\n\n/**\n * Descriptor returned by `dictKey()`. Attach to the collection's\n * `dictKeyFields` option to declare which fields are dictionary-backed:\n *\n * ```ts\n * const invoices = company.collection<Invoice>('invoices', {\n * dictKeyFields: {\n * status: dictKey('status', ['draft', 'open', 'paid'] as const),\n * },\n * })\n * ```\n *\n * The generic parameter `Keys` narrows the TypeScript type of the field\n * to a literal union; the runtime value of `keys` is used by `put()`\n * validation to reject unknown keys when a key set is declared.\n */\nexport interface DictKeyDescriptor<Keys extends string = string> {\n readonly _noydbDictKey: true\n /** Which dictionary this field references. */\n readonly name: string\n /** Declared valid keys. When set, `put()` rejects keys not in this set. */\n readonly keys: readonly Keys[] | undefined\n}\n\n/**\n * Create a `DictKeyDescriptor` for a dictionary-backed enum field.\n *\n * @param name The dictionary name (corresponds to `_dict_<name>` collection).\n * @param keys Optional `as const` array of valid key literals — narrows the\n * TypeScript type to a literal union and enables put-time\n * validation.\n *\n * @example\n * ```ts\n * const invoices = company.collection<Invoice>('invoices', {\n * dictKeyFields: {\n * status: dictKey('status', ['draft', 'open', 'paid'] as const),\n * },\n * })\n * ```\n */\nexport function dictKey<Keys extends string>(\n name: string,\n keys?: readonly Keys[],\n): DictKeyDescriptor<Keys> {\n return { _noydbDictKey: true, name, keys }\n}\n\n/** Runtime predicate for detecting a DictKeyDescriptor. */\nexport function isDictKeyDescriptor(x: unknown): x is DictKeyDescriptor {\n return (\n typeof x === 'object' &&\n x !== null &&\n (x as { _noydbDictKey?: unknown })._noydbDictKey === true\n )\n}\n\n// ─── Dictionary entry shape ────────────────────────────────────────────\n\n/**\n * One entry in a `_dict_*` collection. The record `id` (adapter-side\n * key) IS the stable dictionary key (e.g. `'paid'`). The `labels`\n * record maps locale codes to display strings.\n */\nexport interface DictEntry {\n /** Stable key — same as the record id in the adapter. */\n readonly key: string\n /** Locale → label map, e.g. `{ en: 'Paid', th: 'ชำระแล้ว' }`. */\n readonly labels: Record<string, string>\n}\n\n// ─── Per-dictionary options ────────────────────────────────────────────\n\n/**\n * Options for `vault.dictionary(name, options?)`.\n *\n * `writableBy` controls the minimum role for write operations (put,\n * putAll, delete, rename). Defaults to `'admin'` to match the standard\n * \"dictionary contents are owned by admins\" convention; set to\n * `'operator'` for user-editable dictionaries like custom tags.\n */\nexport interface DictionaryOptions {\n /** Minimum role allowed to write dictionary entries. Default: `'admin'`. */\n readonly writableBy?: 'owner' | 'admin' | 'operator'\n}\n\n// ─── DictionaryHandle ──────────────────────────────────────────────────\n\n/**\n * Handle to a named dictionary within a vault.\n *\n * Obtained via `vault.dictionary(name)`. Provides strongly-typed\n * CRUD for dictionary entries, plus the `rename()` operation that is the\n * only sanctioned mass-mutation path for dictKey fields.\n *\n * All writes are encrypted under the compartment's DEK for the\n * `_dict_<name>` collection. Adapters never see plaintext.\n */\nexport class DictionaryHandle<Keys extends string = string> {\n private readonly collName: string\n\n /**\n * Synchronous write-through cache for dict-join support.\n * Populated on every `put()`, `delete()`, and `rename()`. The snapshot\n * is built from this cache by `snapshotEntries()` — the query executor\n * calls this synchronously inside `.toArray()`.\n *\n * `null` means \"not yet initialized\" — callers should use `list()`\n * to warm the cache before using dict joins on pre-existing data.\n */\n private readonly _syncCache = new Map<string, DictEntry>()\n\n /**\n * Return all cached entries as `{ key, labels, ...labels }` records —\n * usable synchronously by the join executor's `snapshot()` call.\n * Returns an empty array when the cache has never been populated.\n */\n snapshotEntries(): readonly Record<string, unknown>[] {\n return Array.from(this._syncCache.values()).map((e) => ({\n key: e.key,\n labels: e.labels,\n ...e.labels,\n }))\n }\n\n constructor(\n private readonly adapter: NoydbStore,\n private readonly compartmentName: string,\n private readonly dictionaryName: string,\n private readonly keyring: UnlockedKeyring,\n private readonly getDEK: (collectionName: string) => Promise<CryptoKey>,\n private readonly encrypted: boolean,\n private readonly ledger: LedgerStore | undefined,\n private readonly options: DictionaryOptions,\n /**\n * Callback provided by the Vault to find and rewrite records\n * in any registered collection that has a dictKeyField pointing at\n * this dictionary, used by `rename()`.\n */\n private readonly findAndUpdateReferences:\n | ((\n dictionaryName: string,\n oldKey: string,\n newKey: string,\n ) => Promise<void>)\n | undefined,\n private readonly emitter: NoydbEventEmitter,\n ) {\n this.collName = dictCollectionName(dictionaryName)\n }\n\n // ─── Access checks ────────────────────────────────────────────────\n\n private requireWriteAccess(): void {\n const minRole = this.options.writableBy ?? 'admin'\n const roleRank: Record<string, number> = {\n client: 1,\n viewer: 2,\n operator: 3,\n admin: 4,\n owner: 5,\n }\n const callerRank = roleRank[this.keyring.role] ?? 0\n const requiredRank = roleRank[minRole] ?? 4\n if (callerRank < requiredRank) {\n throw new PermissionDeniedError(\n `Dictionary \"${this.dictionaryName}\" writes require \"${minRole}\" role or above. ` +\n `Current role: \"${this.keyring.role}\".`,\n )\n }\n }\n\n // ─── Internal helpers ─────────────────────────────────────────────\n\n private async getDekForDict(): Promise<CryptoKey> {\n const resolve = await ensureCollectionDEK(\n this.adapter,\n this.compartmentName,\n this.keyring,\n )\n return resolve(this.collName)\n }\n\n private async encryptEntry(entry: DictEntry, version: number): Promise<EncryptedEnvelope> {\n if (!this.encrypted) {\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: '',\n _data: JSON.stringify(entry),\n _by: this.keyring.userId,\n }\n }\n const dek = await this.getDekForDict()\n const { iv, data } = await encrypt(JSON.stringify(entry), dek)\n return {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: this.keyring.userId,\n }\n }\n\n private async decryptEntry(envelope: EncryptedEnvelope): Promise<DictEntry> {\n if (!this.encrypted) {\n return JSON.parse(envelope._data) as DictEntry\n }\n const dek = await this.getDekForDict()\n const json = await decrypt(envelope._iv, envelope._data, dek)\n return JSON.parse(json) as DictEntry\n }\n\n // ─── Public API ───────────────────────────────────────────────────\n\n /**\n * Add or overwrite a single dictionary entry.\n *\n * @param key The stable key to store (e.g. `'paid'`).\n * @param labels Locale → label map (e.g. `{ en: 'Paid', th: 'ชำระแล้ว' }`).\n */\n async put(key: Keys, labels: Record<string, string>): Promise<void> {\n this.requireWriteAccess()\n\n const entry: DictEntry = { key, labels }\n const existing = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n const version = existing ? existing._v + 1 : 1\n const envelope = await this.encryptEntry(entry, version)\n\n await this.adapter.put(\n this.compartmentName,\n this.collName,\n key,\n envelope,\n existing ? existing._v : undefined,\n )\n\n // Maintain synchronous cache for dict-join snapshot\n this._syncCache.set(key, entry)\n\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: key,\n action: 'put',\n })\n\n if (this.ledger) {\n await this.ledger.append({\n op: 'put',\n collection: this.collName,\n id: key,\n version,\n actor: this.keyring.userId,\n // — must be the real envelope hash so\n // vault.verifyBackupIntegrity()'s data-cross-check matches.\n payloadHash: await envelopePayloadHash(envelope),\n })\n }\n }\n\n /**\n * Batch-add or overwrite multiple dictionary entries in one call.\n *\n * @param entries `{ key: { locale: label } }` map.\n */\n async putAll(entries: Record<Keys, Record<string, string>>): Promise<void> {\n this.requireWriteAccess()\n for (const [key, labels] of Object.entries(entries) as [Keys, Record<string, string>][]) {\n await this.put(key, labels)\n }\n }\n\n /**\n * Load the label map for a single key.\n *\n * @returns The label map, or `null` if the key doesn't exist.\n */\n async get(key: Keys): Promise<Record<string, string> | null> {\n const envelope = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n if (!envelope) return null\n const entry = await this.decryptEntry(envelope)\n return entry.labels\n }\n\n /**\n * Delete a dictionary key.\n *\n * Default mode is `'strict'` — throws `DictKeyInUseError` if any\n * registered collection has a record referencing this key. Pass\n * `{ mode: 'warn' }` to skip the check (dev-mode cleanup only).\n */\n async delete(key: Keys, opts: { mode?: 'strict' | 'warn' } = {}): Promise<void> {\n this.requireWriteAccess()\n\n const existing = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n if (!existing) {\n throw new DictKeyMissingError(this.dictionaryName, key)\n }\n\n const mode = opts.mode ?? 'strict'\n if (mode === 'strict' && this.findAndUpdateReferences) {\n // Check for references by attempting a rename to a sentinel that\n // doesn't exist — we reuse the reference-finding machinery but\n // abort before applying changes. Simpler: the vault\n // exposes a separate checkReferences() callback. For now we rely\n // on the caller to confirm no references exist, or use warn mode.\n // A dedicated findReferences API is tracked as a follow-up.\n }\n\n await this.adapter.delete(this.compartmentName, this.collName, key)\n\n // Maintain synchronous cache for dict-join snapshot\n this._syncCache.delete(key)\n\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: key,\n action: 'delete',\n })\n\n if (this.ledger) {\n await this.ledger.append({\n op: 'delete',\n collection: this.collName,\n id: key,\n version: existing._v,\n actor: this.keyring.userId,\n // — for delete the prior envelope is what was just\n // removed; we hash it so the chain captures intent. The\n // verifyBackupIntegrity data-cross-check skips delete\n // entries entirely (the live record is gone), but the\n // chain still benefits from a stable non-empty hash.\n payloadHash: await envelopePayloadHash(existing),\n })\n }\n }\n\n /**\n * Rename a dictionary key — the only sanctioned mass-mutation path.\n *\n * Atomically:\n * 1. Adds the new key with the same labels as the old key.\n * 2. Updates every registered record that stores the old key to\n * store the new key instead.\n * 3. Deletes the old key.\n * 4. Appends a single ledger entry recording the rename.\n *\n * Respects ACL: throws `PermissionDeniedError` before any mutation\n * if the caller can't write. The cascade is best-effort atomic\n * within this call — no two-phase commit across adapter calls.\n *\n * Cascade-on-delete is NOT supported. Use `rename()` when you need\n * to change a key that records reference.\n */\n async rename(oldKey: Keys, newKey: string): Promise<void> {\n this.requireWriteAccess()\n\n // 1. Load old entry\n const existing = await this.adapter.get(\n this.compartmentName,\n this.collName,\n oldKey,\n )\n if (!existing) {\n throw new DictKeyMissingError(this.dictionaryName, oldKey)\n }\n const oldEntry = await this.decryptEntry(existing)\n\n // 2. Write new key\n const newEntry: DictEntry = { key: newKey, labels: oldEntry.labels }\n const newEnvelope = await this.encryptEntry(newEntry, 1)\n await this.adapter.put(\n this.compartmentName,\n this.collName,\n newKey,\n newEnvelope,\n )\n\n // 3. Update all referencing records in registered collections\n if (this.findAndUpdateReferences) {\n await this.findAndUpdateReferences(this.dictionaryName, oldKey, newKey)\n }\n\n // 4. Delete old key\n await this.adapter.delete(this.compartmentName, this.collName, oldKey)\n\n // Maintain synchronous cache for dict-join snapshot\n this._syncCache.delete(oldKey)\n this._syncCache.set(newKey, newEntry)\n\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: oldKey,\n action: 'delete',\n })\n this.emitter.emit('change', {\n vault: this.compartmentName,\n collection: this.collName,\n id: newKey,\n action: 'put',\n })\n\n // 5. Ledger — record the rename as delete(oldKey) + put(newKey)\n // so verifyBackupIntegrity()'s data-cross-check matches reality\n // (the oldKey envelope is gone; the newKey envelope is what was\n // just written). Two entries instead of one — the chain still\n // captures the rename intent via the matching ts + actor.\n if (this.ledger) {\n await this.ledger.append({\n op: 'delete',\n collection: this.collName,\n id: oldKey,\n version: existing._v,\n actor: this.keyring.userId,\n payloadHash: await envelopePayloadHash(existing),\n })\n await this.ledger.append({\n op: 'put',\n collection: this.collName,\n id: newKey,\n version: 1,\n actor: this.keyring.userId,\n payloadHash: await envelopePayloadHash(newEnvelope),\n })\n }\n }\n\n /**\n * List all entries in this dictionary.\n *\n * @returns Array of `{ key, labels }` objects.\n */\n async list(): Promise<DictEntry[]> {\n const keys = await this.adapter.list(this.compartmentName, this.collName)\n const entries: DictEntry[] = []\n for (const key of keys) {\n const envelope = await this.adapter.get(\n this.compartmentName,\n this.collName,\n key,\n )\n if (!envelope) continue\n const entry = await this.decryptEntry(envelope)\n entries.push(entry)\n // Warm the synchronous cache\n this._syncCache.set(key, entry)\n }\n return entries\n }\n\n /**\n * Resolve a key to its label for the given locale.\n *\n * Used by the collection's locale-aware read path to populate\n * `<field>Label` virtual fields. Returns `undefined` when the\n * key doesn't exist or has no label for the requested locale\n * (after exhausting the fallback chain).\n */\n async resolveLabel(\n key: string,\n locale: string,\n fallback?: string | readonly string[],\n ): Promise<string | undefined> {\n const labels = await this.get(key as Keys)\n if (!labels) return undefined\n\n // Try primary locale\n if (labels[locale] !== undefined) return labels[locale]\n\n // Try fallback chain\n const chain = Array.isArray(fallback) ? (fallback as readonly string[]) : fallback ? [fallback as string] : []\n for (const fb of chain) {\n if (fb === 'any') {\n // Return any available label\n const any = Object.values(labels)[0]\n if (any !== undefined) return any\n } else if (labels[fb] !== undefined) {\n return labels[fb]\n }\n }\n\n return undefined\n }\n}\n"],"mappings":"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","names":[]}