@noy-db/hub 0.1.0-pre.9 → 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.
- package/dist/aggregate/index.cjs +91 -36
- package/dist/aggregate/index.cjs.map +1 -1
- package/dist/aggregate/index.d.cts +2 -2
- package/dist/aggregate/index.d.ts +2 -2
- package/dist/aggregate/index.js +16 -9
- package/dist/aggregate/index.js.map +1 -1
- package/dist/attestation/index.cjs +305 -0
- package/dist/attestation/index.cjs.map +1 -0
- package/dist/attestation/index.d.cts +52 -0
- package/dist/attestation/index.d.ts +52 -0
- package/dist/attestation/index.js +36 -0
- package/dist/attestation/index.js.map +1 -0
- package/dist/blobs/index.cjs.map +1 -1
- package/dist/blobs/index.d.cts +7 -6
- package/dist/blobs/index.d.ts +7 -6
- package/dist/blobs/index.js +10 -8
- package/dist/blobs/index.js.map +1 -1
- package/dist/bundle/index.cjs +16923 -60
- package/dist/bundle/index.cjs.map +1 -1
- package/dist/bundle/index.d.cts +175 -6
- package/dist/bundle/index.d.ts +175 -6
- package/dist/bundle/index.js +543 -4
- package/dist/bundle/index.js.map +1 -1
- package/dist/{chunk-PTVMYYON.js → chunk-243PNUA6.js} +3 -3
- package/dist/{chunk-MR4424N3.js → chunk-2PAQNPE3.js} +2 -2
- package/dist/chunk-3QAKZ37R.js +83 -0
- package/dist/chunk-3QAKZ37R.js.map +1 -0
- package/dist/chunk-3S4BJX25.js +36 -0
- package/dist/chunk-3S4BJX25.js.map +1 -0
- package/dist/chunk-3XHOCQK4.js +118 -0
- package/dist/chunk-3XHOCQK4.js.map +1 -0
- package/dist/{chunk-AVVPZ4BC.js → chunk-3Y53S2SA.js} +4 -4
- package/dist/chunk-3Z2TPHC4.js +291 -0
- package/dist/chunk-3Z2TPHC4.js.map +1 -0
- package/dist/chunk-4HIL6AHQ.js +57 -0
- package/dist/chunk-4HIL6AHQ.js.map +1 -0
- package/dist/chunk-5ZGZ6HIZ.js +100 -0
- package/dist/chunk-5ZGZ6HIZ.js.map +1 -0
- package/dist/{chunk-ZFKD4QMV.js → chunk-7BRE6EUA.js} +3 -3
- package/dist/chunk-7BUTTVMR.js +34 -0
- package/dist/chunk-7BUTTVMR.js.map +1 -0
- package/dist/{chunk-VQBTTTUN.js → chunk-7Q5PLD5C.js} +4 -4
- package/dist/{chunk-VQBTTTUN.js.map → chunk-7Q5PLD5C.js.map} +1 -1
- package/dist/{chunk-QAVUREFT.js → chunk-7Z23ZFLV.js} +12 -6
- package/dist/chunk-7Z23ZFLV.js.map +1 -0
- package/dist/chunk-AHPFONIL.js +59 -0
- package/dist/chunk-AHPFONIL.js.map +1 -0
- package/dist/chunk-CXSCDO5T.js +51 -0
- package/dist/chunk-CXSCDO5T.js.map +1 -0
- package/dist/chunk-E535SAN4.js +8834 -0
- package/dist/chunk-E535SAN4.js.map +1 -0
- package/dist/chunk-EUYOGYGV.js +830 -0
- package/dist/chunk-EUYOGYGV.js.map +1 -0
- package/dist/chunk-FAQVNJD4.js +61 -0
- package/dist/chunk-FAQVNJD4.js.map +1 -0
- package/dist/{chunk-SCZXXXU4.js → chunk-G6FRSBKK.js} +7 -32
- package/dist/chunk-G6FRSBKK.js.map +1 -0
- package/dist/chunk-GIV6DWBG.js +79 -0
- package/dist/chunk-GIV6DWBG.js.map +1 -0
- package/dist/chunk-HXJXPZRE.js +73 -0
- package/dist/chunk-HXJXPZRE.js.map +1 -0
- package/dist/{chunk-GOUT6DND.js → chunk-J4KLMEUL.js} +173 -91
- package/dist/chunk-J4KLMEUL.js.map +1 -0
- package/dist/{chunk-2CSJGFCB.js → chunk-JYQTXEIO.js} +6 -229
- package/dist/chunk-JYQTXEIO.js.map +1 -0
- package/dist/{chunk-MDDTIZUO.js → chunk-LRAZDV5X.js} +7 -119
- package/dist/chunk-LRAZDV5X.js.map +1 -0
- package/dist/{chunk-M5INGEFC.js → chunk-MRIBLZL3.js} +3 -1
- package/dist/chunk-MRIBLZL3.js.map +1 -0
- package/dist/{chunk-USKYUS74.js → chunk-MUWOSVEP.js} +2 -2
- package/dist/{chunk-4PWAI7Q4.js → chunk-NWZ3I6R6.js} +5 -5
- package/dist/chunk-OVZDFEOR.js +124 -0
- package/dist/chunk-OVZDFEOR.js.map +1 -0
- package/dist/chunk-PEULZC6M.js +118 -0
- package/dist/chunk-PEULZC6M.js.map +1 -0
- package/dist/chunk-PFSNOPBQ.js +233 -0
- package/dist/chunk-PFSNOPBQ.js.map +1 -0
- package/dist/chunk-PLI5TV7N.js +53 -0
- package/dist/chunk-PLI5TV7N.js.map +1 -0
- package/dist/{chunk-WDM5XGGS.js → chunk-Q6W2CMEJ.js} +181 -11
- package/dist/chunk-Q6W2CMEJ.js.map +1 -0
- package/dist/{chunk-QGZRWRSL.js → chunk-QPEXPHJR.js} +4 -4
- package/dist/{chunk-R36SIKES.js → chunk-QXQRKXCU.js} +2 -2
- package/dist/chunk-RTZVQAJ7.js +82 -0
- package/dist/chunk-RTZVQAJ7.js.map +1 -0
- package/dist/chunk-TBKOGSYR.js +296 -0
- package/dist/chunk-TBKOGSYR.js.map +1 -0
- package/dist/chunk-UMLVJTYV.js +20 -0
- package/dist/chunk-UMLVJTYV.js.map +1 -0
- package/dist/chunk-UND4XIB6.js +251 -0
- package/dist/chunk-UND4XIB6.js.map +1 -0
- package/dist/chunk-VCGTOS2A.js +795 -0
- package/dist/chunk-VCGTOS2A.js.map +1 -0
- package/dist/chunk-VE6YVP32.js +19 -0
- package/dist/chunk-VE6YVP32.js.map +1 -0
- package/dist/{chunk-M62XNWRA.js → chunk-VK5EER6C.js} +2 -2
- package/dist/{chunk-NXFEYLVG.js → chunk-VPSUZLOJ.js} +4 -3
- package/dist/{chunk-NXFEYLVG.js.map → chunk-VPSUZLOJ.js.map} +1 -1
- package/dist/{chunk-TDR6T5CJ.js → chunk-VRBCTEKQ.js} +91 -132
- package/dist/chunk-VRBCTEKQ.js.map +1 -0
- package/dist/{chunk-ACLDOTNQ.js → chunk-W3XXT26A.js} +303 -3
- package/dist/chunk-W3XXT26A.js.map +1 -0
- package/dist/{chunk-CIMZBAZB.js → chunk-XG3PTSCD.js} +1 -1
- package/dist/chunk-XG3PTSCD.js.map +1 -0
- package/dist/chunk-Y2RKOPNC.js +145 -0
- package/dist/chunk-Y2RKOPNC.js.map +1 -0
- package/dist/{chunk-NPC4LFV5.js → chunk-YMYK7US4.js} +2 -2
- package/dist/{chunk-RKJ6OL7K.js → chunk-YS3POABP.js} +1 -1
- package/dist/chunk-YS3POABP.js.map +1 -0
- package/dist/chunk-YTXSFG3C.js +179 -0
- package/dist/chunk-YTXSFG3C.js.map +1 -0
- package/dist/consent/index.cjs.map +1 -1
- package/dist/consent/index.d.cts +7 -6
- package/dist/consent/index.d.ts +7 -6
- package/dist/consent/index.js +3 -3
- package/dist/{crypto-IVKU7YTT.js → crypto-5ZDIY3NG.js} +3 -3
- package/dist/{delegation-2DBS2EOH.js → delegation-QYXZW25W.js} +5 -4
- package/dist/derivations/index.cjs +351 -0
- package/dist/derivations/index.cjs.map +1 -0
- package/dist/derivations/index.d.cts +72 -0
- package/dist/derivations/index.d.ts +72 -0
- package/dist/derivations/index.js +27 -0
- package/dist/{dev-unlock-Da1B0TIK.d.cts → dev-unlock-DQCNDfFp.d.cts} +1 -1
- package/dist/{dev-unlock-BdPp68qn.d.ts → dev-unlock-utkybTKb.d.ts} +1 -1
- package/dist/executor-AS2IDHKZ.js +11 -0
- package/dist/executor-HLXFXNFM.js +8 -0
- package/dist/executor-HLXFXNFM.js.map +1 -0
- package/dist/executor-HN6YBHZ5.js +8 -0
- package/dist/executor-HN6YBHZ5.js.map +1 -0
- package/dist/fanout-sidecar-VJ52RIEY.js +51 -0
- package/dist/fanout-sidecar-VJ52RIEY.js.map +1 -0
- package/dist/guards/index.cjs +315 -0
- package/dist/guards/index.cjs.map +1 -0
- package/dist/guards/index.d.cts +31 -0
- package/dist/guards/index.d.ts +31 -0
- package/dist/guards/index.js +29 -0
- package/dist/guards/index.js.map +1 -0
- package/dist/{hash-lsoL3eEW.d.ts → hash-DcoYWfJ_.d.ts} +1 -1
- package/dist/{hash-BEfzPKwo.d.cts → hash-jDowCrK2.d.cts} +1 -1
- package/dist/history/index.cjs +8 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +8 -7
- package/dist/history/index.d.ts +8 -7
- package/dist/history/index.js +6 -6
- package/dist/i18n/index.cjs +81 -0
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.d.cts +7 -6
- package/dist/i18n/index.d.ts +7 -6
- package/dist/i18n/index.js +27 -12
- package/dist/i18n/index.js.map +1 -1
- package/dist/{index-6xNpPsxR.d.cts → index-BCKdioeh.d.ts} +331 -5
- package/dist/{index-DJTf9yxn.d.ts → index-BMjrzNZr.d.cts} +331 -5
- package/dist/index.cjs +6065 -959
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -16
- package/dist/index.d.ts +208 -16
- package/dist/index.js +242 -7392
- package/dist/index.js.map +1 -1
- package/dist/indexing/index.cjs +2 -0
- package/dist/indexing/index.cjs.map +1 -1
- package/dist/indexing/index.d.cts +3 -3
- package/dist/indexing/index.d.ts +3 -3
- package/dist/indexing/index.js +4 -4
- package/dist/issue-ORP37MVW.js +12 -0
- package/dist/issue-ORP37MVW.js.map +1 -0
- package/dist/{lazy-builder-CZVLKh0Z.d.cts → lazy-builder-C-rPfWG0.d.cts} +1 -1
- package/dist/{lazy-builder-BwEoBQZ9.d.ts → lazy-builder-Rpd-V3jP.d.ts} +1 -1
- package/dist/{ledger-QZTTHQAQ.js → ledger-3IU5GMXA.js} +6 -6
- package/dist/ledger-3IU5GMXA.js.map +1 -0
- package/dist/materialized-views/index.cjs +837 -0
- package/dist/materialized-views/index.cjs.map +1 -0
- package/dist/materialized-views/index.d.cts +184 -0
- package/dist/materialized-views/index.d.ts +184 -0
- package/dist/materialized-views/index.js +45 -0
- package/dist/materialized-views/index.js.map +1 -0
- package/dist/noydb-5H3C24GG.js +34 -0
- package/dist/noydb-5H3C24GG.js.map +1 -0
- package/dist/overlay-views/index.cjs +359 -0
- package/dist/overlay-views/index.cjs.map +1 -0
- package/dist/overlay-views/index.d.cts +82 -0
- package/dist/overlay-views/index.d.ts +82 -0
- package/dist/overlay-views/index.js +25 -0
- package/dist/overlay-views/index.js.map +1 -0
- package/dist/periods/index.cjs +7 -1
- package/dist/periods/index.cjs.map +1 -1
- package/dist/periods/index.d.cts +7 -6
- package/dist/periods/index.d.ts +7 -6
- package/dist/periods/index.js +6 -6
- package/dist/{predicate-SBHmi6D0.d.cts → predicate-Dnu81tsS.d.cts} +25 -1
- package/dist/{predicate-SBHmi6D0.d.ts → predicate-Dnu81tsS.d.ts} +25 -1
- package/dist/{public-envelope-6JTACYJV.js → public-envelope-U3CMEOMV.js} +4 -4
- package/dist/public-envelope-U3CMEOMV.js.map +1 -0
- package/dist/query/index.cjs +302 -124
- package/dist/query/index.cjs.map +1 -1
- package/dist/query/index.d.cts +3 -3
- package/dist/query/index.d.ts +3 -3
- package/dist/query/index.js +26 -11
- package/dist/read-only-facade-ITU6L7BL.js +7 -0
- package/dist/read-only-facade-ITU6L7BL.js.map +1 -0
- package/dist/registry-3ALP62P6.js +10 -0
- package/dist/registry-3ALP62P6.js.map +1 -0
- package/dist/registry-7HE6VJGC.js +8 -0
- package/dist/registry-7HE6VJGC.js.map +1 -0
- package/dist/registry-PSIPG2QR.js +8 -0
- package/dist/registry-PSIPG2QR.js.map +1 -0
- package/dist/registry-RFGGMVNJ.js +7 -0
- package/dist/registry-RFGGMVNJ.js.map +1 -0
- package/dist/revoke-KY2GB4KP.js +17 -0
- package/dist/revoke-KY2GB4KP.js.map +1 -0
- package/dist/session/index.cjs +7 -1
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +8 -7
- package/dist/session/index.d.ts +8 -7
- package/dist/session/index.js +10 -3
- package/dist/session/index.js.map +1 -1
- package/dist/shadow/index.cjs.map +1 -1
- package/dist/shadow/index.d.cts +7 -6
- package/dist/shadow/index.d.ts +7 -6
- package/dist/shadow/index.js +2 -2
- package/dist/signer-GRI5TZKH.js +18 -0
- package/dist/signer-GRI5TZKH.js.map +1 -0
- package/dist/stale-OTOF3FH7.js +13 -0
- package/dist/stale-OTOF3FH7.js.map +1 -0
- package/dist/store/index.cjs +14 -0
- package/dist/store/index.cjs.map +1 -1
- package/dist/store/index.d.cts +7 -6
- package/dist/store/index.d.ts +7 -6
- package/dist/store/index.js +5 -2
- package/dist/{strategy-D-SrOLCl.d.cts → strategy-DSTrsZ8t.d.cts} +72 -19
- package/dist/{strategy-D-SrOLCl.d.ts → strategy-DSTrsZ8t.d.ts} +72 -19
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +6 -5
- package/dist/sync/index.d.ts +6 -5
- package/dist/sync/index.js +4 -4
- package/dist/team/index.cjs +1554 -2
- package/dist/team/index.cjs.map +1 -1
- package/dist/team/index.d.cts +7 -6
- package/dist/team/index.d.ts +7 -6
- package/dist/team/index.js +77 -8
- package/dist/tx/index.cjs +296 -44
- package/dist/tx/index.cjs.map +1 -1
- package/dist/tx/index.d.cts +7 -6
- package/dist/tx/index.d.ts +7 -6
- package/dist/tx/index.js +2 -2
- package/dist/{types-Bo7NSXJr.d.ts → types-BoFFiskX.d.ts} +2714 -321
- package/dist/{types-Bnb82f5R.d.cts → types-DJG8HG6F.d.cts} +2714 -321
- package/dist/{index-CywCC1qZ.d.cts → ulid-BmBgooGm.d.ts} +215 -26
- package/dist/{index-8QDuznDr.d.ts → ulid-C7ms9oli.d.cts} +215 -26
- package/dist/util/index.cjs.map +1 -1
- package/dist/util/index.js +1 -1
- package/dist/with-derivation-BKXXa8Vt.d.ts +13 -0
- package/dist/with-derivation-BjQ7q4NE.d.cts +13 -0
- package/dist/with-guard-C25yNjzd.d.ts +18 -0
- package/dist/with-guard-DQme5DKE.d.cts +18 -0
- package/dist/with-materialized-view-BbEPFIIJ.d.cts +27 -0
- package/dist/with-materialized-view-CqnRwI2S.d.ts +27 -0
- package/dist/with-overlayed-view-Ct1fSJt-.d.ts +13 -0
- package/dist/with-overlayed-view-bwlmmFjx.d.cts +13 -0
- package/package.json +65 -2
- package/dist/chunk-2CSJGFCB.js.map +0 -1
- package/dist/chunk-ACLDOTNQ.js.map +0 -1
- package/dist/chunk-BTDCBVJW.js +0 -160
- package/dist/chunk-BTDCBVJW.js.map +0 -1
- package/dist/chunk-CIMZBAZB.js.map +0 -1
- package/dist/chunk-EXHNQEV4.js +0 -392
- package/dist/chunk-EXHNQEV4.js.map +0 -1
- package/dist/chunk-GOUT6DND.js.map +0 -1
- package/dist/chunk-M5INGEFC.js.map +0 -1
- package/dist/chunk-MDDTIZUO.js.map +0 -1
- package/dist/chunk-QAVUREFT.js.map +0 -1
- package/dist/chunk-RKJ6OL7K.js.map +0 -1
- package/dist/chunk-SCZXXXU4.js.map +0 -1
- package/dist/chunk-TDR6T5CJ.js.map +0 -1
- package/dist/chunk-WDM5XGGS.js.map +0 -1
- /package/dist/{chunk-PTVMYYON.js.map → chunk-243PNUA6.js.map} +0 -0
- /package/dist/{chunk-MR4424N3.js.map → chunk-2PAQNPE3.js.map} +0 -0
- /package/dist/{chunk-AVVPZ4BC.js.map → chunk-3Y53S2SA.js.map} +0 -0
- /package/dist/{chunk-ZFKD4QMV.js.map → chunk-7BRE6EUA.js.map} +0 -0
- /package/dist/{chunk-USKYUS74.js.map → chunk-MUWOSVEP.js.map} +0 -0
- /package/dist/{chunk-4PWAI7Q4.js.map → chunk-NWZ3I6R6.js.map} +0 -0
- /package/dist/{chunk-QGZRWRSL.js.map → chunk-QPEXPHJR.js.map} +0 -0
- /package/dist/{chunk-R36SIKES.js.map → chunk-QXQRKXCU.js.map} +0 -0
- /package/dist/{chunk-M62XNWRA.js.map → chunk-VK5EER6C.js.map} +0 -0
- /package/dist/{chunk-NPC4LFV5.js.map → chunk-YMYK7US4.js.map} +0 -0
- /package/dist/{crypto-IVKU7YTT.js.map → crypto-5ZDIY3NG.js.map} +0 -0
- /package/dist/{delegation-2DBS2EOH.js.map → delegation-QYXZW25W.js.map} +0 -0
- /package/dist/{ledger-QZTTHQAQ.js.map → derivations/index.js.map} +0 -0
- /package/dist/{public-envelope-6JTACYJV.js.map → executor-AS2IDHKZ.js.map} +0 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pickLocale
|
|
3
|
+
} from "./chunk-243PNUA6.js";
|
|
4
|
+
import {
|
|
5
|
+
BundleIntegrityError,
|
|
6
|
+
BundleSealMismatchError,
|
|
7
|
+
ValidationError
|
|
8
|
+
} from "./chunk-W3XXT26A.js";
|
|
9
|
+
|
|
10
|
+
// src/bundle/format.ts
|
|
11
|
+
var NOYDB_BUNDLE_MAGIC = new Uint8Array([78, 68, 66, 49]);
|
|
12
|
+
var NOYDB_BUNDLE_PREFIX_BYTES = 10;
|
|
13
|
+
var NOYDB_BUNDLE_FORMAT_VERSION = 1;
|
|
14
|
+
var FLAG_COMPRESSED = 1;
|
|
15
|
+
var FLAG_HAS_INTEGRITY_HASH = 2;
|
|
16
|
+
var COMPRESSION_NONE = 0;
|
|
17
|
+
var COMPRESSION_GZIP = 1;
|
|
18
|
+
var COMPRESSION_BROTLI = 2;
|
|
19
|
+
var ALLOWED_HEADER_KEYS = /* @__PURE__ */ new Set([
|
|
20
|
+
"formatVersion",
|
|
21
|
+
"handle",
|
|
22
|
+
"bodyBytes",
|
|
23
|
+
"bodySha256",
|
|
24
|
+
"publicEnvelope",
|
|
25
|
+
"autoUnlock",
|
|
26
|
+
"bundleKind",
|
|
27
|
+
"transferSeal"
|
|
28
|
+
]);
|
|
29
|
+
function validateBundleHeader(parsed) {
|
|
30
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`.noydb bundle header must be a JSON object, got ${parsed === null ? "null" : typeof parsed}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
for (const key of Object.keys(parsed)) {
|
|
36
|
+
if (!ALLOWED_HEADER_KEYS.has(key)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`.noydb bundle header contains forbidden key "${key}". Only minimum-disclosure fields are allowed: ${[...ALLOWED_HEADER_KEYS].join(", ")}.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const h = parsed;
|
|
43
|
+
if (typeof h["formatVersion"] !== "number" || h["formatVersion"] !== NOYDB_BUNDLE_FORMAT_VERSION) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`.noydb bundle header.formatVersion must be ${NOYDB_BUNDLE_FORMAT_VERSION}, got ${String(h["formatVersion"])}. The reader does not support forward-compat versions; upgrade the reader to handle newer bundles.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (typeof h["handle"] !== "string" || !/^[0-9A-HJKMNP-TV-Z]{26}$/.test(h["handle"])) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`.noydb bundle header.handle must be a 26-character Crockford base32 ULID, got ${typeof h["handle"] === "string" ? `"${h["handle"]}"` : String(h["handle"])}.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (typeof h["bodyBytes"] !== "number" || !Number.isInteger(h["bodyBytes"]) || h["bodyBytes"] < 0) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`.noydb bundle header.bodyBytes must be a non-negative integer, got ${String(h["bodyBytes"])}.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (typeof h["bodySha256"] !== "string" || !/^[0-9a-f]{64}$/.test(h["bodySha256"])) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`.noydb bundle header.bodySha256 must be a 64-character lowercase hex string, got ${typeof h["bodySha256"] === "string" ? `"${h["bodySha256"]}"` : String(h["bodySha256"])}.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (h["publicEnvelope"] !== void 0) {
|
|
64
|
+
const env = h["publicEnvelope"];
|
|
65
|
+
if (env === null || typeof env !== "object" || Array.isArray(env)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`.noydb bundle header.publicEnvelope must be a JSON object when present, got ${typeof env}.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const e = env;
|
|
71
|
+
if (e["_noydb_public"] !== 1) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`.noydb bundle header.publicEnvelope._noydb_public must be 1, got ${String(e["_noydb_public"])}.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (typeof e["version"] !== "number" || !Number.isInteger(e["version"]) || e["version"] < 1) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`.noydb bundle header.publicEnvelope.version must be a positive integer, got ${String(e["version"])}.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (h["autoUnlock"] !== void 0) {
|
|
83
|
+
if (h["autoUnlock"] !== "unsealed" && h["autoUnlock"] !== "sealed") {
|
|
84
|
+
const got = typeof h["autoUnlock"] === "string" ? `"${h["autoUnlock"]}"` : typeof h["autoUnlock"];
|
|
85
|
+
throw new Error(
|
|
86
|
+
`.noydb bundle header.autoUnlock must be 'unsealed' or 'sealed' when present, got ${got}.`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (h["bundleKind"] !== void 0) {
|
|
91
|
+
if (h["bundleKind"] !== "snapshot" && h["bundleKind"] !== "extracted-partition") {
|
|
92
|
+
const got = typeof h["bundleKind"] === "string" ? `"${h["bundleKind"]}"` : typeof h["bundleKind"];
|
|
93
|
+
throw new Error(
|
|
94
|
+
`.noydb bundle header.bundleKind must be 'snapshot' or 'extracted-partition' when present, got ${got}.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (h["transferSeal"] !== void 0) {
|
|
99
|
+
const ts = h["transferSeal"];
|
|
100
|
+
if (ts === null || typeof ts !== "object" || Array.isArray(ts)) {
|
|
101
|
+
throw new Error(`.noydb bundle header.transferSeal must be a JSON object when present, got ${typeof ts}.`);
|
|
102
|
+
}
|
|
103
|
+
const t = ts;
|
|
104
|
+
if (t["v"] !== 1) {
|
|
105
|
+
throw new Error(`.noydb bundle header.transferSeal.v must be 1, got ${String(t["v"])}.`);
|
|
106
|
+
}
|
|
107
|
+
if (t["alg"] !== "aes-256-gcm-pre-shared") {
|
|
108
|
+
throw new Error(`.noydb bundle header.transferSeal.alg must be 'aes-256-gcm-pre-shared', got ${String(t["alg"])}.`);
|
|
109
|
+
}
|
|
110
|
+
if (typeof t["sealId"] !== "string" || t["sealId"].length === 0) {
|
|
111
|
+
throw new Error(`.noydb bundle header.transferSeal.sealId must be a non-empty string, got ${String(t["sealId"])}.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const isExtracted = h["bundleKind"] === "extracted-partition";
|
|
115
|
+
const hasSeal = h["transferSeal"] !== void 0;
|
|
116
|
+
if (hasSeal && !isExtracted) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`.noydb bundle header.transferSeal requires bundleKind === 'extracted-partition'.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (isExtracted && !hasSeal) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`.noydb bundle header with bundleKind === 'extracted-partition' must carry a transferSeal indicator.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (isExtracted && h["autoUnlock"] !== void 0) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`.noydb bundle header cannot carry both autoUnlock and bundleKind === 'extracted-partition' \u2014 an extracted partition is unlocked via its transfer seal, not an auto-credential.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function encodeBundleHeader(header) {
|
|
133
|
+
validateBundleHeader(header);
|
|
134
|
+
const json = JSON.stringify({
|
|
135
|
+
formatVersion: header.formatVersion,
|
|
136
|
+
handle: header.handle,
|
|
137
|
+
bodyBytes: header.bodyBytes,
|
|
138
|
+
bodySha256: header.bodySha256,
|
|
139
|
+
...header.publicEnvelope !== void 0 ? { publicEnvelope: header.publicEnvelope } : {},
|
|
140
|
+
...header.autoUnlock !== void 0 ? { autoUnlock: header.autoUnlock } : {},
|
|
141
|
+
...header.bundleKind !== void 0 ? { bundleKind: header.bundleKind } : {},
|
|
142
|
+
...header.transferSeal !== void 0 ? { transferSeal: header.transferSeal } : {}
|
|
143
|
+
});
|
|
144
|
+
return new TextEncoder().encode(json);
|
|
145
|
+
}
|
|
146
|
+
function decodeBundleHeader(bytes) {
|
|
147
|
+
const json = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
148
|
+
let parsed;
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(json);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`.noydb bundle header is not valid JSON: ${err.message}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
validateBundleHeader(parsed);
|
|
157
|
+
return parsed;
|
|
158
|
+
}
|
|
159
|
+
function readUint32BE(bytes, offset) {
|
|
160
|
+
return (bytes[offset] << 24 >>> 0) + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3];
|
|
161
|
+
}
|
|
162
|
+
function writeUint32BE(bytes, offset, value) {
|
|
163
|
+
bytes[offset] = value >>> 24 & 255;
|
|
164
|
+
bytes[offset + 1] = value >>> 16 & 255;
|
|
165
|
+
bytes[offset + 2] = value >>> 8 & 255;
|
|
166
|
+
bytes[offset + 3] = value & 255;
|
|
167
|
+
}
|
|
168
|
+
function hasNoydbBundleMagic(bytes) {
|
|
169
|
+
if (bytes.length < NOYDB_BUNDLE_MAGIC.length) return false;
|
|
170
|
+
for (let i = 0; i < NOYDB_BUNDLE_MAGIC.length; i++) {
|
|
171
|
+
if (bytes[i] !== NOYDB_BUNDLE_MAGIC[i]) return false;
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/bundle/bundle.ts
|
|
177
|
+
function toAutoCredentials(m) {
|
|
178
|
+
return Object.fromEntries(
|
|
179
|
+
Object.entries(m).map(([u, value]) => [u, { kind: "passphrase", value }])
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function normalizeAutoUnlock(opts) {
|
|
183
|
+
const set = [
|
|
184
|
+
opts.autoCredentials,
|
|
185
|
+
opts.sealedCredentials,
|
|
186
|
+
opts.autoPassphrases,
|
|
187
|
+
opts.sealedPassphrases
|
|
188
|
+
].filter((v) => v !== void 0).length;
|
|
189
|
+
if (set === 0) return null;
|
|
190
|
+
if (set > 1) {
|
|
191
|
+
throw new ValidationError(
|
|
192
|
+
"writeNoydbBundle: only one of autoCredentials / sealedCredentials / autoPassphrases / sealedPassphrases may be set."
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (opts.autoCredentials !== void 0) {
|
|
196
|
+
return { mode: "unsealed", perUser: opts.autoCredentials.perUser };
|
|
197
|
+
}
|
|
198
|
+
if (opts.autoPassphrases !== void 0) {
|
|
199
|
+
return { mode: "unsealed", perUser: toAutoCredentials(opts.autoPassphrases.perUser) };
|
|
200
|
+
}
|
|
201
|
+
if (opts.sealedCredentials !== void 0) {
|
|
202
|
+
if (opts.sealedCredentials.mode === "recipient-target") {
|
|
203
|
+
const perUser = {};
|
|
204
|
+
const hints = {};
|
|
205
|
+
for (const [userId, entry] of Object.entries(opts.sealedCredentials.perUser)) {
|
|
206
|
+
perUser[userId] = entry.credential;
|
|
207
|
+
hints[userId] = entry.hint;
|
|
208
|
+
}
|
|
209
|
+
return { mode: "sealed-recipient", provider: opts.sealedCredentials.provider, perUser, hints };
|
|
210
|
+
}
|
|
211
|
+
return { mode: "sealed-self", provider: opts.sealedCredentials.provider, perUser: opts.sealedCredentials.perUser };
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
mode: "sealed-self",
|
|
215
|
+
provider: opts.sealedPassphrases.provider,
|
|
216
|
+
perUser: toAutoCredentials(opts.sealedPassphrases.perUser)
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function validateAutoUnlockOptions(opts, normalized) {
|
|
220
|
+
if (normalized === null) return null;
|
|
221
|
+
const VALID_KINDS = /* @__PURE__ */ new Set(["passphrase", "password", "pin"]);
|
|
222
|
+
for (const [userId, cred] of Object.entries(normalized.perUser)) {
|
|
223
|
+
if (!VALID_KINDS.has(cred.kind)) {
|
|
224
|
+
throw new ValidationError(
|
|
225
|
+
`writeNoydbBundle: credential for user '${userId}' has unsupported kind '${cred.kind}'. auto-unlock supports passphrase/password/pin only; WebAuthn is hardware-bound and cannot be bundled.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (normalized.mode === "unsealed") {
|
|
230
|
+
const policy = opts.autoCredentials?.policy ?? opts.autoPassphrases?.policy;
|
|
231
|
+
if (policy !== "public-by-design") {
|
|
232
|
+
throw new ValidationError(
|
|
233
|
+
'writeNoydbBundle: `autoCredentials` (or `autoPassphrases`) requires `policy: "public-by-design"`. This is an explicit opt-in marker \u2014 bundling plaintext credentials is safe only when those credentials are intended to be public (demo data, sample vaults). For production credentials, use `sealedCredentials` instead.'
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const userCount2 = Object.keys(normalized.perUser).length;
|
|
237
|
+
if (userCount2 === 0) {
|
|
238
|
+
throw new ValidationError(
|
|
239
|
+
"writeNoydbBundle: `autoCredentials.perUser` (or `autoPassphrases.perUser`) must have at least one entry."
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return "unsealed";
|
|
243
|
+
}
|
|
244
|
+
if (normalized.mode === "sealed-recipient") {
|
|
245
|
+
const provider = normalized.provider;
|
|
246
|
+
if (provider === void 0 || typeof provider.publishRecipientHint !== "function" || typeof provider.sealForRecipient !== "function") {
|
|
247
|
+
throw new ValidationError(
|
|
248
|
+
"writeNoydbBundle: `sealedCredentials.provider` for mode 'recipient-target' must be a RecipientSealer (publishRecipientHint + sealForRecipient). Self-only providers (MemorySealingKeyProvider, at-macos-keychain, etc.) do not satisfy this contract."
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const hints = normalized.hints;
|
|
252
|
+
if (hints === void 0) {
|
|
253
|
+
throw new Error("unreachable \u2014 sealed-recipient normalization must populate hints");
|
|
254
|
+
}
|
|
255
|
+
for (const userId of Object.keys(normalized.perUser)) {
|
|
256
|
+
const hint = hints[userId];
|
|
257
|
+
if (hint === void 0) {
|
|
258
|
+
throw new ValidationError(
|
|
259
|
+
`writeNoydbBundle: \`sealedCredentials.perUser['${userId}']\` missing required \`hint\` for mode 'recipient-target'.`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (hint.v !== 1) {
|
|
263
|
+
throw new ValidationError(
|
|
264
|
+
`writeNoydbBundle: \`sealedCredentials.perUser['${userId}'].hint.v\` must be 1 (got ${String(hint.v)}).`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (typeof hint.pid !== "string" || hint.pid.length === 0) {
|
|
268
|
+
throw new ValidationError(
|
|
269
|
+
`writeNoydbBundle: \`sealedCredentials.perUser['${userId}'].hint.pid\` must be a non-empty string identifying the recipient.`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (hint.alg !== "rsa-oaep-sha256") {
|
|
273
|
+
throw new ValidationError(
|
|
274
|
+
`writeNoydbBundle: \`sealedCredentials.perUser['${userId}'].hint.alg\` must be 'rsa-oaep-sha256' in slice 1 (got '${String(hint.alg)}').`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const userCount2 = Object.keys(normalized.perUser).length;
|
|
279
|
+
if (userCount2 === 0) {
|
|
280
|
+
throw new ValidationError(
|
|
281
|
+
"writeNoydbBundle: `sealedCredentials.perUser` must have at least one entry."
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return "sealed";
|
|
285
|
+
}
|
|
286
|
+
const selfTargetMode = opts.sealedCredentials?.mode ?? opts.sealedPassphrases?.mode;
|
|
287
|
+
if (selfTargetMode !== "self-target") {
|
|
288
|
+
throw new ValidationError(
|
|
289
|
+
`writeNoydbBundle: \`sealedCredentials.mode\` (or \`sealedPassphrases.mode\`) must be 'self-target' or 'recipient-target' (got '${String(selfTargetMode)}').`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (normalized.provider === void 0) {
|
|
293
|
+
throw new ValidationError(
|
|
294
|
+
"writeNoydbBundle: `sealedCredentials.provider` (or `sealedPassphrases.provider`) is required (a `SealingKeyProvider`)."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
const userCount = Object.keys(normalized.perUser).length;
|
|
298
|
+
if (userCount === 0) {
|
|
299
|
+
throw new ValidationError(
|
|
300
|
+
"writeNoydbBundle: `sealedCredentials.perUser` (or `sealedPassphrases.perUser`) must have at least one entry."
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return "sealed";
|
|
304
|
+
}
|
|
305
|
+
async function buildAutoUnlockWrapper(dumpJson, normalized) {
|
|
306
|
+
if (normalized.mode === "unsealed") {
|
|
307
|
+
return {
|
|
308
|
+
_noydb_bundle_body: 1,
|
|
309
|
+
dump: dumpJson,
|
|
310
|
+
_autoUnlock: {
|
|
311
|
+
kind: "unsealed",
|
|
312
|
+
perUser: { ...normalized.perUser }
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const provider = normalized.provider;
|
|
317
|
+
if (provider === void 0) {
|
|
318
|
+
throw new Error("unreachable \u2014 validation should have caught this");
|
|
319
|
+
}
|
|
320
|
+
const sealedPerUser = {};
|
|
321
|
+
const encoder = new TextEncoder();
|
|
322
|
+
if (normalized.mode === "sealed-recipient") {
|
|
323
|
+
const recipientSealer = provider;
|
|
324
|
+
const hints = normalized.hints;
|
|
325
|
+
if (hints === void 0) {
|
|
326
|
+
throw new Error("unreachable \u2014 sealed-recipient normalization must populate hints");
|
|
327
|
+
}
|
|
328
|
+
for (const [userId, cred] of Object.entries(normalized.perUser)) {
|
|
329
|
+
const hint = hints[userId];
|
|
330
|
+
const sealed = await recipientSealer.sealForRecipient(encoder.encode(cred.value), hint);
|
|
331
|
+
sealedPerUser[userId] = {
|
|
332
|
+
pid: hint.pid,
|
|
333
|
+
// use the recipient's pid, not the sender's
|
|
334
|
+
sealed: bytesToBase64(sealed),
|
|
335
|
+
alg: "aes-256-gcm",
|
|
336
|
+
kind: cred.kind,
|
|
337
|
+
hint
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
const selfSealer = provider;
|
|
342
|
+
for (const [userId, cred] of Object.entries(normalized.perUser)) {
|
|
343
|
+
const sealed = await selfSealer.seal(encoder.encode(cred.value));
|
|
344
|
+
sealedPerUser[userId] = {
|
|
345
|
+
pid: selfSealer.id,
|
|
346
|
+
sealed: bytesToBase64(sealed),
|
|
347
|
+
alg: "aes-256-gcm",
|
|
348
|
+
kind: cred.kind
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
_noydb_bundle_body: 1,
|
|
354
|
+
dump: dumpJson,
|
|
355
|
+
_autoUnlock: { kind: "sealed", perUser: sealedPerUser }
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function parseAutoUnlockBody(bodyString) {
|
|
359
|
+
let parsed;
|
|
360
|
+
try {
|
|
361
|
+
parsed = JSON.parse(bodyString);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
throw new BundleIntegrityError(
|
|
364
|
+
"header declared autoUnlock but body could not be parsed as JSON wrapper: " + (err instanceof Error ? err.message : String(err))
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
368
|
+
throw new BundleIntegrityError("autoUnlock body is not a JSON object");
|
|
369
|
+
}
|
|
370
|
+
const obj = parsed;
|
|
371
|
+
if (obj["_noydb_bundle_body"] !== 1) {
|
|
372
|
+
throw new BundleIntegrityError(
|
|
373
|
+
"autoUnlock body missing `_noydb_bundle_body: 1` discriminator"
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
if (typeof obj["dump"] !== "string") {
|
|
377
|
+
throw new BundleIntegrityError("autoUnlock body must carry a string `dump` field");
|
|
378
|
+
}
|
|
379
|
+
const blob = obj["_autoUnlock"];
|
|
380
|
+
if (typeof blob !== "object" || blob === null) {
|
|
381
|
+
throw new BundleIntegrityError("autoUnlock body missing `_autoUnlock` blob");
|
|
382
|
+
}
|
|
383
|
+
const blobObj = blob;
|
|
384
|
+
const kind = blobObj["kind"];
|
|
385
|
+
if (kind !== "unsealed" && kind !== "sealed") {
|
|
386
|
+
throw new BundleIntegrityError(
|
|
387
|
+
`autoUnlock blob has invalid kind ${String(kind)}; expected 'unsealed' or 'sealed'`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
dump: obj["dump"],
|
|
392
|
+
blob
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function buildExtractedPartitionWrapper(dumpJson, seal) {
|
|
396
|
+
return { _noydb_bundle_body: 1, dump: dumpJson, _transferSeal: seal };
|
|
397
|
+
}
|
|
398
|
+
function parseExtractedPartitionBody(bodyString) {
|
|
399
|
+
let parsed;
|
|
400
|
+
try {
|
|
401
|
+
parsed = JSON.parse(bodyString);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
throw new BundleIntegrityError(
|
|
404
|
+
"header declared extracted-partition but body could not be parsed as JSON wrapper: " + (err instanceof Error ? err.message : String(err))
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
408
|
+
throw new BundleIntegrityError("extracted-partition body is not a JSON object");
|
|
409
|
+
}
|
|
410
|
+
const obj = parsed;
|
|
411
|
+
if (obj["_noydb_bundle_body"] !== 1) {
|
|
412
|
+
throw new BundleIntegrityError(
|
|
413
|
+
"extracted-partition body missing `_noydb_bundle_body: 1` discriminator"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (typeof obj["dump"] !== "string") {
|
|
417
|
+
throw new BundleIntegrityError("extracted-partition body must carry a string `dump` field");
|
|
418
|
+
}
|
|
419
|
+
const seal = obj["_transferSeal"];
|
|
420
|
+
if (typeof seal !== "object" || seal === null) {
|
|
421
|
+
throw new BundleIntegrityError("extracted-partition body missing `_transferSeal` blob");
|
|
422
|
+
}
|
|
423
|
+
const s = seal;
|
|
424
|
+
if (s["v"] !== 1 || s["alg"] !== "aes-256-gcm-pre-shared" || typeof s["sealId"] !== "string" || typeof s["payload"] !== "string") {
|
|
425
|
+
throw new BundleIntegrityError("extracted-partition `_transferSeal` blob is malformed");
|
|
426
|
+
}
|
|
427
|
+
return { dump: obj["dump"], seal };
|
|
428
|
+
}
|
|
429
|
+
function coerceUnsealed(entry) {
|
|
430
|
+
if (typeof entry === "string") return { kind: "passphrase", value: entry };
|
|
431
|
+
return entry;
|
|
432
|
+
}
|
|
433
|
+
async function resolveAutoUnlock(blob, opts) {
|
|
434
|
+
if (blob.kind === "unsealed") {
|
|
435
|
+
const resolved = {};
|
|
436
|
+
for (const [userId, entry] of Object.entries(blob.perUser)) {
|
|
437
|
+
resolved[userId] = coerceUnsealed(entry);
|
|
438
|
+
}
|
|
439
|
+
return { kind: "unsealed", perUser: resolved };
|
|
440
|
+
}
|
|
441
|
+
if (opts.sealingProviders === void 0 || opts.sealingProviders.length === 0) {
|
|
442
|
+
const passthrough = {};
|
|
443
|
+
for (const [userId, entry] of Object.entries(blob.perUser)) {
|
|
444
|
+
passthrough[userId] = { kind: entry.kind ?? "passphrase", value: entry.sealed };
|
|
445
|
+
}
|
|
446
|
+
return { kind: "sealed", perUser: passthrough };
|
|
447
|
+
}
|
|
448
|
+
const providersByPid = /* @__PURE__ */ new Map();
|
|
449
|
+
for (const p of opts.sealingProviders) providersByPid.set(p.id, p);
|
|
450
|
+
const decoder = new TextDecoder();
|
|
451
|
+
const unsealedMap = {};
|
|
452
|
+
for (const [userId, entry] of Object.entries(blob.perUser)) {
|
|
453
|
+
const credKind = entry.kind ?? "passphrase";
|
|
454
|
+
const provider = providersByPid.get(entry.pid);
|
|
455
|
+
if (provider === void 0) {
|
|
456
|
+
if (opts.attemptUnsealAcrossProviders === true) {
|
|
457
|
+
let opened = null;
|
|
458
|
+
for (const candidate of opts.sealingProviders) {
|
|
459
|
+
try {
|
|
460
|
+
const plaintextBytes2 = await candidate.unseal(base64ToBytes(entry.sealed));
|
|
461
|
+
opened = decoder.decode(plaintextBytes2);
|
|
462
|
+
break;
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (opened === null) {
|
|
467
|
+
if (entry.hint !== void 0) {
|
|
468
|
+
unsealedMap[userId] = { kind: credKind, value: entry.sealed };
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
throw new BundleSealMismatchError(userId, entry.pid);
|
|
472
|
+
}
|
|
473
|
+
unsealedMap[userId] = { kind: credKind, value: opened };
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (entry.hint !== void 0) {
|
|
477
|
+
unsealedMap[userId] = { kind: credKind, value: entry.sealed };
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
throw new BundleSealMismatchError(userId, entry.pid);
|
|
481
|
+
}
|
|
482
|
+
const plaintextBytes = await provider.unseal(base64ToBytes(entry.sealed));
|
|
483
|
+
unsealedMap[userId] = { kind: credKind, value: decoder.decode(plaintextBytes) };
|
|
484
|
+
}
|
|
485
|
+
return { kind: "sealed", perUser: unsealedMap };
|
|
486
|
+
}
|
|
487
|
+
function bytesToBase64(bytes) {
|
|
488
|
+
let binary = "";
|
|
489
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
490
|
+
return btoa(binary);
|
|
491
|
+
}
|
|
492
|
+
function base64ToBytes(b64) {
|
|
493
|
+
const binary = atob(b64);
|
|
494
|
+
const out = new Uint8Array(binary.length);
|
|
495
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
496
|
+
return out;
|
|
497
|
+
}
|
|
498
|
+
var cachedBrotliSupport = null;
|
|
499
|
+
function supportsBrotliCompression() {
|
|
500
|
+
if (cachedBrotliSupport !== null) return cachedBrotliSupport;
|
|
501
|
+
try {
|
|
502
|
+
new CompressionStream("br");
|
|
503
|
+
cachedBrotliSupport = true;
|
|
504
|
+
} catch {
|
|
505
|
+
cachedBrotliSupport = false;
|
|
506
|
+
}
|
|
507
|
+
return cachedBrotliSupport;
|
|
508
|
+
}
|
|
509
|
+
function resetBrotliSupportCache() {
|
|
510
|
+
cachedBrotliSupport = null;
|
|
511
|
+
}
|
|
512
|
+
function selectCompression(option) {
|
|
513
|
+
const choice = option ?? "auto";
|
|
514
|
+
if (choice === "none") return { format: COMPRESSION_NONE, streamFormat: null };
|
|
515
|
+
if (choice === "gzip") return { format: COMPRESSION_GZIP, streamFormat: "gzip" };
|
|
516
|
+
if (choice === "brotli") {
|
|
517
|
+
if (!supportsBrotliCompression()) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`writeNoydbBundle({ compression: 'brotli' }) is not supported on this runtime. Brotli requires Node 22+, Chrome 124+, or Firefox 122+. Use { compression: 'auto' } to fall back to gzip silently, or { compression: 'gzip' } to be explicit.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return { format: COMPRESSION_BROTLI, streamFormat: "br" };
|
|
523
|
+
}
|
|
524
|
+
if (supportsBrotliCompression()) {
|
|
525
|
+
return { format: COMPRESSION_BROTLI, streamFormat: "br" };
|
|
526
|
+
}
|
|
527
|
+
return { format: COMPRESSION_GZIP, streamFormat: "gzip" };
|
|
528
|
+
}
|
|
529
|
+
async function pumpThroughStream(input, stream) {
|
|
530
|
+
const readable = new Blob([input]).stream().pipeThrough(stream);
|
|
531
|
+
const reader = readable.getReader();
|
|
532
|
+
const chunks = [];
|
|
533
|
+
let total = 0;
|
|
534
|
+
for (; ; ) {
|
|
535
|
+
const { value, done } = await reader.read();
|
|
536
|
+
if (done) break;
|
|
537
|
+
if (value) {
|
|
538
|
+
chunks.push(value);
|
|
539
|
+
total += value.length;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const out = new Uint8Array(total);
|
|
543
|
+
let offset = 0;
|
|
544
|
+
for (const chunk of chunks) {
|
|
545
|
+
out.set(chunk, offset);
|
|
546
|
+
offset += chunk.length;
|
|
547
|
+
}
|
|
548
|
+
return out;
|
|
549
|
+
}
|
|
550
|
+
async function sha256Hex(bytes) {
|
|
551
|
+
const copy = new Uint8Array(bytes.length);
|
|
552
|
+
copy.set(bytes);
|
|
553
|
+
const digest = await crypto.subtle.digest("SHA-256", copy);
|
|
554
|
+
const view = new Uint8Array(digest);
|
|
555
|
+
let hex = "";
|
|
556
|
+
for (let i = 0; i < view.length; i++) {
|
|
557
|
+
hex += view[i].toString(16).padStart(2, "0");
|
|
558
|
+
}
|
|
559
|
+
return hex;
|
|
560
|
+
}
|
|
561
|
+
function concatBytes(parts) {
|
|
562
|
+
let total = 0;
|
|
563
|
+
for (const p of parts) total += p.length;
|
|
564
|
+
const out = new Uint8Array(total);
|
|
565
|
+
let offset = 0;
|
|
566
|
+
for (const p of parts) {
|
|
567
|
+
out.set(p, offset);
|
|
568
|
+
offset += p.length;
|
|
569
|
+
}
|
|
570
|
+
return out;
|
|
571
|
+
}
|
|
572
|
+
async function applyRecipientRewrap(vault, dumpJson, opts) {
|
|
573
|
+
if (opts.exportPassphrase === void 0 && opts.recipients === void 0) {
|
|
574
|
+
return dumpJson;
|
|
575
|
+
}
|
|
576
|
+
const recipients = opts.recipients ?? [
|
|
577
|
+
{
|
|
578
|
+
id: vault.userId,
|
|
579
|
+
passphrase: opts.exportPassphrase,
|
|
580
|
+
role: vault.role
|
|
581
|
+
}
|
|
582
|
+
];
|
|
583
|
+
const recipientKeyrings = await vault.buildBundleRecipientKeyrings(recipients);
|
|
584
|
+
const backup = JSON.parse(dumpJson);
|
|
585
|
+
backup.keyrings = recipientKeyrings;
|
|
586
|
+
return JSON.stringify(backup);
|
|
587
|
+
}
|
|
588
|
+
function applySliceFilters(dumpJson, opts) {
|
|
589
|
+
const collectionsFilter = opts.collections ? new Set(opts.collections) : null;
|
|
590
|
+
const sinceMs = opts.since !== void 0 ? new Date(opts.since).getTime() : null;
|
|
591
|
+
if (collectionsFilter === null && sinceMs === null) return dumpJson;
|
|
592
|
+
const backup = JSON.parse(dumpJson);
|
|
593
|
+
if (backup.collections && typeof backup.collections === "object") {
|
|
594
|
+
const next = {};
|
|
595
|
+
for (const [name, records] of Object.entries(backup.collections)) {
|
|
596
|
+
if (collectionsFilter && !collectionsFilter.has(name)) continue;
|
|
597
|
+
if (sinceMs === null) {
|
|
598
|
+
next[name] = records;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const kept = {};
|
|
602
|
+
for (const [id, env] of Object.entries(records)) {
|
|
603
|
+
const envTs = env._ts ? new Date(env._ts).getTime() : NaN;
|
|
604
|
+
if (Number.isFinite(envTs) && envTs >= sinceMs) {
|
|
605
|
+
kept[id] = env;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
next[name] = kept;
|
|
609
|
+
}
|
|
610
|
+
backup.collections = next;
|
|
611
|
+
}
|
|
612
|
+
return JSON.stringify(backup);
|
|
613
|
+
}
|
|
614
|
+
async function applyPlaintextFilters(vault, dumpJson, opts) {
|
|
615
|
+
if (opts.where === void 0 && opts.tierAtMost === void 0) {
|
|
616
|
+
return dumpJson;
|
|
617
|
+
}
|
|
618
|
+
const backup = JSON.parse(dumpJson);
|
|
619
|
+
if (!backup.collections || typeof backup.collections !== "object") {
|
|
620
|
+
return dumpJson;
|
|
621
|
+
}
|
|
622
|
+
const tierCeiling = opts.tierAtMost;
|
|
623
|
+
const where = opts.where;
|
|
624
|
+
const next = {};
|
|
625
|
+
for (const [collName, records] of Object.entries(backup.collections)) {
|
|
626
|
+
const kept = {};
|
|
627
|
+
for (const [id, env] of Object.entries(records)) {
|
|
628
|
+
if (tierCeiling !== void 0) {
|
|
629
|
+
const tier = env._tier ?? 0;
|
|
630
|
+
if (tier > tierCeiling) continue;
|
|
631
|
+
}
|
|
632
|
+
if (where !== void 0) {
|
|
633
|
+
const record = await vault._decryptEnvelopeForBundleFilter(
|
|
634
|
+
env,
|
|
635
|
+
collName
|
|
636
|
+
);
|
|
637
|
+
const ok = await where(record, { collection: collName, id });
|
|
638
|
+
if (!ok) continue;
|
|
639
|
+
}
|
|
640
|
+
kept[id] = env;
|
|
641
|
+
}
|
|
642
|
+
next[collName] = kept;
|
|
643
|
+
}
|
|
644
|
+
backup.collections = next;
|
|
645
|
+
return JSON.stringify(backup);
|
|
646
|
+
}
|
|
647
|
+
async function assembleBundleContainer(opts) {
|
|
648
|
+
const dumpBytes = new TextEncoder().encode(opts.bodyJsonStr);
|
|
649
|
+
const { format, streamFormat } = selectCompression(opts.compression);
|
|
650
|
+
const body = streamFormat === null ? dumpBytes : await pumpThroughStream(dumpBytes, new CompressionStream(streamFormat));
|
|
651
|
+
const bodySha256 = await sha256Hex(body);
|
|
652
|
+
const header = {
|
|
653
|
+
formatVersion: NOYDB_BUNDLE_FORMAT_VERSION,
|
|
654
|
+
handle: opts.handle,
|
|
655
|
+
bodyBytes: body.length,
|
|
656
|
+
bodySha256,
|
|
657
|
+
...opts.headerExtras?.publicEnvelope !== void 0 ? { publicEnvelope: opts.headerExtras.publicEnvelope } : {},
|
|
658
|
+
...opts.headerExtras?.autoUnlock !== void 0 ? { autoUnlock: opts.headerExtras.autoUnlock } : {},
|
|
659
|
+
...opts.headerExtras?.bundleKind !== void 0 ? { bundleKind: opts.headerExtras.bundleKind } : {},
|
|
660
|
+
...opts.headerExtras?.transferSeal !== void 0 ? { transferSeal: opts.headerExtras.transferSeal } : {}
|
|
661
|
+
};
|
|
662
|
+
const headerBytes = encodeBundleHeader(header);
|
|
663
|
+
const prefix = new Uint8Array(NOYDB_BUNDLE_PREFIX_BYTES);
|
|
664
|
+
prefix.set(NOYDB_BUNDLE_MAGIC, 0);
|
|
665
|
+
prefix[4] = (streamFormat === null ? 0 : FLAG_COMPRESSED) | FLAG_HAS_INTEGRITY_HASH;
|
|
666
|
+
prefix[5] = format;
|
|
667
|
+
writeUint32BE(prefix, 6, headerBytes.length);
|
|
668
|
+
return concatBytes([prefix, headerBytes, body]);
|
|
669
|
+
}
|
|
670
|
+
async function writeNoydbBundle(vault, opts = {}) {
|
|
671
|
+
if (opts.exportPassphrase !== void 0 && opts.recipients !== void 0) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
"writeNoydbBundle: pass either exportPassphrase or recipients, not both"
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
const normalizedAutoUnlock = normalizeAutoUnlock(opts);
|
|
677
|
+
const autoUnlockMode = validateAutoUnlockOptions(opts, normalizedAutoUnlock);
|
|
678
|
+
const handle = await vault.getBundleHandle();
|
|
679
|
+
const dumpJson = await vault.dump();
|
|
680
|
+
const rekeyed = await applyRecipientRewrap(vault, dumpJson, opts);
|
|
681
|
+
const plainFiltered = await applyPlaintextFilters(vault, rekeyed, opts);
|
|
682
|
+
const filtered = applySliceFilters(plainFiltered, opts);
|
|
683
|
+
const bodyJsonStr = normalizedAutoUnlock === null ? filtered : JSON.stringify(await buildAutoUnlockWrapper(filtered, normalizedAutoUnlock));
|
|
684
|
+
const publicEnvelope = await vault.getPublicEnvelope();
|
|
685
|
+
return assembleBundleContainer({
|
|
686
|
+
handle,
|
|
687
|
+
bodyJsonStr,
|
|
688
|
+
compression: opts.compression,
|
|
689
|
+
headerExtras: {
|
|
690
|
+
...publicEnvelope !== void 0 ? { publicEnvelope } : {},
|
|
691
|
+
...autoUnlockMode !== null ? { autoUnlock: autoUnlockMode } : {}
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
function parsePrefixAndHeader(bytes) {
|
|
696
|
+
if (!hasNoydbBundleMagic(bytes)) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Not a .noydb bundle: missing 'NDB1' magic prefix. The first 4 bytes are ${[...bytes.slice(0, 4)].map((b) => b.toString(16).padStart(2, "0")).join(" ")}.`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
if (bytes.length < NOYDB_BUNDLE_PREFIX_BYTES) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
`Truncated .noydb bundle: file is only ${bytes.length} bytes, which is less than the ${NOYDB_BUNDLE_PREFIX_BYTES}-byte fixed prefix.`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const flags = bytes[4];
|
|
707
|
+
const algo = bytes[5];
|
|
708
|
+
if (algo !== COMPRESSION_NONE && algo !== COMPRESSION_GZIP && algo !== COMPRESSION_BROTLI) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
`.noydb bundle declares unknown compression algorithm ${algo}. Known values: 0 (none), 1 (gzip), 2 (brotli).`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const headerLength = readUint32BE(bytes, 6);
|
|
714
|
+
const bodyOffset = NOYDB_BUNDLE_PREFIX_BYTES + headerLength;
|
|
715
|
+
if (bodyOffset > bytes.length) {
|
|
716
|
+
throw new Error(
|
|
717
|
+
`Truncated .noydb bundle: declared header length ${headerLength} would extend past end of file (${bytes.length} bytes).`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
const headerBytes = bytes.slice(NOYDB_BUNDLE_PREFIX_BYTES, bodyOffset);
|
|
721
|
+
const header = decodeBundleHeader(headerBytes);
|
|
722
|
+
return { header, bodyOffset, algo, flags };
|
|
723
|
+
}
|
|
724
|
+
function readNoydbBundleHeader(bytes) {
|
|
725
|
+
return parsePrefixAndHeader(bytes).header;
|
|
726
|
+
}
|
|
727
|
+
function readNoydbBundlePublicEnvelope(bytes, opts = {}) {
|
|
728
|
+
const header = parsePrefixAndHeader(bytes).header;
|
|
729
|
+
const env = header.publicEnvelope;
|
|
730
|
+
if (!env) return void 0;
|
|
731
|
+
if (opts.locale === void 0) return env;
|
|
732
|
+
return {
|
|
733
|
+
...env,
|
|
734
|
+
...env.name !== void 0 ? { name: pickLocale(env.name, opts.locale, env.defaultLocale) } : {},
|
|
735
|
+
...env.description !== void 0 ? { description: pickLocale(env.description, opts.locale, env.defaultLocale) } : {}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
async function readNoydbBundle(bytes, opts = {}) {
|
|
739
|
+
const { header, bodyOffset, algo } = parsePrefixAndHeader(bytes);
|
|
740
|
+
const body = bytes.slice(bodyOffset);
|
|
741
|
+
if (body.length !== header.bodyBytes) {
|
|
742
|
+
throw new BundleIntegrityError(
|
|
743
|
+
`body length ${body.length} does not match header.bodyBytes ${header.bodyBytes}. The bundle was truncated or padded between write and read.`
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
const actualSha = await sha256Hex(body);
|
|
747
|
+
if (actualSha !== header.bodySha256) {
|
|
748
|
+
throw new BundleIntegrityError(
|
|
749
|
+
`body sha256 ${actualSha} does not match header.bodySha256 ${header.bodySha256}. The bundle bytes were modified between write and read \u2014 refuse to decompress.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
let dumpBytes;
|
|
753
|
+
if (algo === COMPRESSION_NONE) {
|
|
754
|
+
dumpBytes = body;
|
|
755
|
+
} else {
|
|
756
|
+
const streamFormat = algo === COMPRESSION_BROTLI ? "br" : "gzip";
|
|
757
|
+
try {
|
|
758
|
+
dumpBytes = await pumpThroughStream(body, new DecompressionStream(streamFormat));
|
|
759
|
+
} catch (err) {
|
|
760
|
+
throw new BundleIntegrityError(
|
|
761
|
+
`decompression failed: ${err.message}. The bundle passed the integrity hash but the body is not valid ${streamFormat} data \u2014 likely a producer bug.`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const bodyString = new TextDecoder("utf-8", { fatal: true }).decode(dumpBytes);
|
|
766
|
+
if (header.autoUnlock === void 0) {
|
|
767
|
+
return { header, dumpJson: bodyString };
|
|
768
|
+
}
|
|
769
|
+
const { dump, blob } = parseAutoUnlockBody(bodyString);
|
|
770
|
+
const autoUnlock = await resolveAutoUnlock(blob, opts);
|
|
771
|
+
return { header, dumpJson: dump, autoUnlock };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export {
|
|
775
|
+
NOYDB_BUNDLE_MAGIC,
|
|
776
|
+
NOYDB_BUNDLE_PREFIX_BYTES,
|
|
777
|
+
NOYDB_BUNDLE_FORMAT_VERSION,
|
|
778
|
+
FLAG_COMPRESSED,
|
|
779
|
+
FLAG_HAS_INTEGRITY_HASH,
|
|
780
|
+
COMPRESSION_NONE,
|
|
781
|
+
COMPRESSION_GZIP,
|
|
782
|
+
COMPRESSION_BROTLI,
|
|
783
|
+
validateBundleHeader,
|
|
784
|
+
encodeBundleHeader,
|
|
785
|
+
hasNoydbBundleMagic,
|
|
786
|
+
buildExtractedPartitionWrapper,
|
|
787
|
+
parseExtractedPartitionBody,
|
|
788
|
+
resetBrotliSupportCache,
|
|
789
|
+
assembleBundleContainer,
|
|
790
|
+
writeNoydbBundle,
|
|
791
|
+
readNoydbBundleHeader,
|
|
792
|
+
readNoydbBundlePublicEnvelope,
|
|
793
|
+
readNoydbBundle
|
|
794
|
+
};
|
|
795
|
+
//# sourceMappingURL=chunk-VCGTOS2A.js.map
|