@noy-db/hub 0.4.0-pre.10 → 0.4.0-pre.12

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.
@@ -6,7 +6,7 @@ import {
6
6
  diffVault,
7
7
  unsealDeks,
8
8
  withCargo
9
- } from "../chunk-YZJSJOUI.js";
9
+ } from "../chunk-DSJG26FE.js";
10
10
  import {
11
11
  NO_CARGO,
12
12
  isQuorum,
@@ -26,12 +26,12 @@ import "../chunk-FQZ6XTQ7.js";
26
26
  import "../chunk-I5GEJX3R.js";
27
27
  import "../chunk-X45JYIAY.js";
28
28
  import "../chunk-DMH3VZU5.js";
29
- import {
30
- fuseRetrieval
31
- } from "../chunk-AURFOK3D.js";
32
29
  import {
33
30
  CustodyApi
34
31
  } from "../chunk-YWWQM7KP.js";
32
+ import {
33
+ fuseRetrieval
34
+ } from "../chunk-AURFOK3D.js";
35
35
  import "../chunk-GWWOGNBK.js";
36
36
  import {
37
37
  groupAndReduce,
@@ -12,20 +12,31 @@ function wrapPodStore(bundle, options) {
12
12
  const versions = /* @__PURE__ */ new Map();
13
13
  const loaded = /* @__PURE__ */ new Set();
14
14
  let batchDepth = 0;
15
+ const loading = /* @__PURE__ */ new Map();
15
16
  async function load(vault) {
16
17
  if (loaded.has(vault)) return snapshots.get(vault);
17
- const result = await bundle.readBundle(vault);
18
- if (result) {
19
- const text = new TextDecoder().decode(result.bytes);
20
- const format = JSON.parse(text);
21
- snapshots.set(vault, format.data);
22
- versions.set(vault, result.version);
23
- } else {
24
- snapshots.set(vault, {});
25
- versions.set(vault, null);
18
+ const inFlight = loading.get(vault);
19
+ if (inFlight) return inFlight;
20
+ const pending = (async () => {
21
+ const result = await bundle.readBundle(vault);
22
+ if (result) {
23
+ const text = new TextDecoder().decode(result.bytes);
24
+ const format = JSON.parse(text);
25
+ snapshots.set(vault, format.data);
26
+ versions.set(vault, result.version);
27
+ } else {
28
+ snapshots.set(vault, {});
29
+ versions.set(vault, null);
30
+ }
31
+ loaded.add(vault);
32
+ return snapshots.get(vault);
33
+ })();
34
+ loading.set(vault, pending);
35
+ try {
36
+ return await pending;
37
+ } finally {
38
+ loading.delete(vault);
26
39
  }
27
- loaded.add(vault);
28
- return snapshots.get(vault);
29
40
  }
30
41
  async function flush(vault) {
31
42
  const snapshot = snapshots.get(vault) ?? {};
@@ -59,15 +70,22 @@ function wrapPodStore(bundle, options) {
59
70
  }
60
71
  }
61
72
  }
73
+ const flushChain = /* @__PURE__ */ new Map();
74
+ function serialFlush(vault) {
75
+ const prev = flushChain.get(vault) ?? Promise.resolve();
76
+ const next = prev.then(() => flush(vault), () => flush(vault));
77
+ flushChain.set(vault, next);
78
+ return next;
79
+ }
62
80
  async function maybeFlush(vault) {
63
81
  if (autoFlush && batchDepth === 0) {
64
- await flush(vault);
82
+ await serialFlush(vault);
65
83
  }
66
84
  }
67
85
  const store = {
68
86
  name: bundle.name ?? "bundle",
69
87
  async flush(vaultId) {
70
- await flush(vaultId);
88
+ await serialFlush(vaultId);
71
89
  },
72
90
  async batch(vaultId, fn) {
73
91
  await load(vaultId);
@@ -77,7 +95,7 @@ function wrapPodStore(bundle, options) {
77
95
  } finally {
78
96
  batchDepth--;
79
97
  }
80
- await flush(vaultId);
98
+ await serialFlush(vaultId);
81
99
  },
82
100
  async get(vault, collection, id) {
83
101
  const snap = await load(vault);
@@ -111,12 +129,18 @@ function wrapPodStore(bundle, options) {
111
129
  return Object.keys(snap[collection] ?? {});
112
130
  },
113
131
  async loadAll(vault) {
114
- return await load(vault);
132
+ const snap = await load(vault);
133
+ const out = {};
134
+ for (const [collection, records] of Object.entries(snap)) {
135
+ if (collection.startsWith("_")) continue;
136
+ out[collection] = { ...records };
137
+ }
138
+ return out;
115
139
  },
116
140
  async saveAll(vault, data) {
117
141
  snapshots.set(vault, data);
118
142
  loaded.add(vault);
119
- await flush(vault);
143
+ await serialFlush(vault);
120
144
  }
121
145
  };
122
146
  return store;
@@ -152,4 +176,4 @@ export {
152
176
  wrapBundleStore,
153
177
  createBundleStore
154
178
  };
155
- //# sourceMappingURL=chunk-Y6B6HVKC.js.map
179
+ //# sourceMappingURL=chunk-2J6UEN2V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/with-pod/pod-store.ts"],"sourcesContent":["import type { NoydbStore, NoydbPodStore, VaultSnapshot, EncryptedEnvelope } from '../kernel/types.js'\nimport { ConflictError, PodVersionConflictError } from '../kernel/errors.js'\n\n// ─── Bundle format ─────────────────────────────────────────────────────\n\nconst BUNDLE_STORE_VERSION = 1 as const\n\n/**\n * Wire format written by `wrapPodStore`. A JSON-serialised object that\n * contains the entire `VaultSnapshot` (all encrypted envelopes) plus a small\n * header for integrity checking. The envelopes inside are already AES-GCM\n * encrypted by core — the bundle bytes themselves are not additionally\n * encrypted, but they are safe to store on untrusted blob hosts because\n * every record inside is already ciphertext.\n *\n * @internal\n */\ninterface BundleStoreData {\n readonly _noydb_bundle_store: typeof BUNDLE_STORE_VERSION\n readonly vault: string\n readonly ts: string\n readonly data: VaultSnapshot\n}\n\n// ─── Options ───────────────────────────────────────────────────────────\n\nexport interface WrapPodStoreOptions {\n /**\n * When `true` (default), every `put()` and `delete()` flushes the full\n * vault snapshot to the bundle backend. Set to `false` for bulk operations\n * and call `store.flush(vaultId)` manually.\n */\n autoFlush?: boolean\n}\n\n// ─── Extended NoydbStore with flush/batch ───────────────────────────────\n\nexport interface WrappedPodNoydbStore extends NoydbStore {\n /** Manually flush the in-memory snapshot to the bundle backend. */\n flush(vaultId: string): Promise<void>\n /**\n * Run a batch of mutations without flushing until the callback completes.\n * A single flush is performed at the end.\n */\n batch(vaultId: string, fn: () => Promise<void>): Promise<void>\n}\n\n// ─── wrapPodStore ───────────────────────────────────────────────────\n\nconst MAX_CONFLICT_RETRIES = 3\n\n/**\n * Convert a `NoydbPodStore` (blob-oriented read/write with OCC) into the\n * standard six-method `NoydbStore` interface expected by `createNoydb({ store })`.\n *\n * Bundle stores operate on the entire vault as a single serialised unit —\n * ideal for backends like Google Drive, WebDAV, or iCloud Drive that work\n * best with whole-file I/O rather than per-record KV operations.\n *\n * ## Optimistic concurrency\n *\n * The wrapper tracks the `version` token from the last `readBundle` and\n * passes it as `expectedVersion` on every flush. On\n * `PodVersionConflictError`, it re-reads, merges the remote snapshot\n * (last-write-wins per record key), and retries (max 3 attempts).\n *\n * ## Flush modes\n *\n * By default, flushes on every mutation (O(vault size) per write). Options:\n * - `autoFlush: false` + explicit `store.flush(vaultId)` calls\n * - `store.batch(vaultId, async () => { ... })` — defers flush until end\n * - Pair with `syncPolicy: { push: { mode: 'debounce' } }` from \n */\nexport function wrapPodStore(\n bundle: NoydbPodStore,\n options?: WrapPodStoreOptions,\n): WrappedPodNoydbStore {\n const autoFlush = options?.autoFlush !== false\n\n // Per-vault state\n const snapshots = new Map<string, VaultSnapshot>()\n const versions = new Map<string, string | null>()\n const loaded = new Set<string>()\n\n // Batch mode: when > 0, suppress auto-flush\n let batchDepth = 0\n\n // #908 — in-flight loads, keyed by vault. Without this, every concurrent\n // caller issued its own `readBundle` and then REPLACED `snapshots` with a\n // freshly parsed object — orphaning the mutations earlier callers had\n // already made to the object they were handed. 100 racing puts kept 1.\n const loading = new Map<string, Promise<VaultSnapshot>>()\n\n async function load(vault: string): Promise<VaultSnapshot> {\n if (loaded.has(vault)) return snapshots.get(vault)!\n\n const inFlight = loading.get(vault)\n if (inFlight) return inFlight\n\n const pending = (async () => {\n const result = await bundle.readBundle(vault)\n if (result) {\n const text = new TextDecoder().decode(result.bytes)\n const format = JSON.parse(text) as BundleStoreData\n snapshots.set(vault, format.data)\n versions.set(vault, result.version)\n } else {\n snapshots.set(vault, {})\n versions.set(vault, null)\n }\n\n loaded.add(vault)\n return snapshots.get(vault)!\n })()\n\n loading.set(vault, pending)\n try {\n return await pending\n } finally {\n // Clear on failure too, so a transient read error does not poison\n // every later load of this vault with a rejected promise.\n loading.delete(vault)\n }\n }\n\n async function flush(vault: string): Promise<void> {\n const snapshot = snapshots.get(vault) ?? {}\n const format: BundleStoreData = {\n _noydb_bundle_store: BUNDLE_STORE_VERSION,\n vault,\n ts: new Date().toISOString(),\n data: snapshot,\n }\n const bytes = new TextEncoder().encode(JSON.stringify(format))\n const expectedVersion = versions.get(vault) ?? null\n\n for (let attempt = 0; attempt < MAX_CONFLICT_RETRIES; attempt++) {\n try {\n const { version: newVersion } = await bundle.writeBundle(vault, bytes, expectedVersion)\n versions.set(vault, newVersion)\n return\n } catch (err) {\n if (err instanceof PodVersionConflictError && attempt < MAX_CONFLICT_RETRIES - 1) {\n // Pull remote, merge (last-write-wins by record key), retry\n const remote = await bundle.readBundle(vault)\n if (remote) {\n const remoteText = new TextDecoder().decode(remote.bytes)\n const remoteFormat = JSON.parse(remoteText) as BundleStoreData\n const localSnap = snapshots.get(vault) ?? {}\n const mergedSnap = mergeSnapshots(remoteFormat.data, localSnap)\n snapshots.set(vault, mergedSnap)\n versions.set(vault, remote.version)\n }\n // Re-encode with merged data for the retry\n continue\n }\n throw err\n }\n }\n }\n\n // #908 — one flush at a time per vault. `flush` reads and writes the vault's\n // version token, so concurrent flushes race: each sends the same\n // `expectedVersion`, the losers take the conflict/merge path, and with enough\n // of them the retry budget is exhausted and the error surfaces to a caller\n // whose write was perfectly valid. Serialising makes every flush see the\n // token its predecessor produced.\n const flushChain = new Map<string, Promise<void>>()\n\n function serialFlush(vault: string): Promise<void> {\n const prev = flushChain.get(vault) ?? Promise.resolve()\n // Chain past a rejection as well — one failed flush must not wedge every\n // subsequent write to this vault.\n const next = prev.then(() => flush(vault), () => flush(vault))\n flushChain.set(vault, next)\n return next\n }\n\n async function maybeFlush(vault: string): Promise<void> {\n if (autoFlush && batchDepth === 0) {\n await serialFlush(vault)\n }\n }\n\n const store: WrappedPodNoydbStore = {\n name: bundle.name ?? 'bundle',\n\n async flush(vaultId: string): Promise<void> {\n await serialFlush(vaultId)\n },\n\n async batch(vaultId: string, fn: () => Promise<void>): Promise<void> {\n await load(vaultId) // ensure loaded before batch\n batchDepth++\n try {\n await fn()\n } finally {\n batchDepth--\n }\n await serialFlush(vaultId)\n },\n\n async get(vault: string, collection: string, id: string): Promise<EncryptedEnvelope | null> {\n const snap = await load(vault)\n return snap[collection]?.[id] ?? null\n },\n\n async put(\n vault: string,\n collection: string,\n id: string,\n envelope: EncryptedEnvelope,\n expectedVersion?: number,\n ): Promise<void> {\n const snap = await load(vault)\n\n if (expectedVersion !== undefined) {\n const current = snap[collection]?.[id]\n const currentVersion = current?._v ?? 0\n if (currentVersion !== expectedVersion) {\n throw new ConflictError(\n currentVersion,\n `Expected version ${expectedVersion} but found ${currentVersion} on ${collection}/${id}`,\n )\n }\n }\n\n snap[collection] ??= {}\n snap[collection][id] = envelope\n await maybeFlush(vault)\n },\n\n async delete(vault: string, collection: string, id: string): Promise<void> {\n const snap = await load(vault)\n if (snap[collection]) {\n delete snap[collection][id]\n await maybeFlush(vault)\n }\n },\n\n async list(vault: string, collection: string): Promise<string[]> {\n const snap = await load(vault)\n return Object.keys(snap[collection] ?? {})\n },\n\n async loadAll(vault: string): Promise<VaultSnapshot> {\n const snap = await load(vault)\n // #908 — two contract fixes in one place:\n // 1. internal collections (`_keyring`, `_sync`) are the vault's own\n // bookkeeping and must not appear in a snapshot — `@noy-db/to-file`\n // sets the reference behaviour with this same `_`-prefix rule;\n // 2. copy rather than hand out the live cache, which let a caller\n // mutating the result silently rewrite the wrapper's own state.\n // `get()`/`list()` still serve internal collections; this is about what\n // a *snapshot* claims, not about hiding data.\n const out: VaultSnapshot = {}\n for (const [collection, records] of Object.entries(snap)) {\n if (collection.startsWith('_')) continue\n out[collection] = { ...records }\n }\n return out\n },\n\n async saveAll(vault: string, data: VaultSnapshot): Promise<void> {\n snapshots.set(vault, data)\n loaded.add(vault)\n await serialFlush(vault)\n },\n }\n\n return store\n}\n\n// ─── Snapshot merge (last-write-wins per record) ────────────────────────\n\nfunction mergeSnapshots(remote: VaultSnapshot, local: VaultSnapshot): VaultSnapshot {\n const merged: VaultSnapshot = {}\n\n // Start with all remote collections\n for (const [coll, records] of Object.entries(remote)) {\n merged[coll] = { ...records }\n }\n\n // Overlay local collections — LWW by _ts per record\n for (const [coll, records] of Object.entries(local)) {\n if (!merged[coll]) {\n merged[coll] = { ...records }\n continue\n }\n for (const [id, envelope] of Object.entries(records)) {\n const existing = merged[coll][id]\n if (!existing || envelope._ts >= existing._ts) {\n merged[coll][id] = envelope\n }\n }\n }\n\n return merged\n}\n\n// ─── Factory helper ─────────────────────────────────────────────────────\n\n/**\n * Type-safe factory helper for `NoydbPodStore` implementations,\n * analogous to `createStore` for KV stores.\n */\nexport function createPodStore<TOptions>(\n factory: (options: TOptions) => NoydbPodStore,\n): (options: TOptions) => NoydbPodStore {\n return factory\n}\n\n// ─── Deprecated aliases (pre-rename compatibility) ──────────────────────\n\n/** @deprecated Use wrapPodStore. */\nexport const wrapBundleStore = wrapPodStore\n/** @deprecated Use createPodStore. */\nexport const createBundleStore = createPodStore\n/** @deprecated Use WrappedPodNoydbStore. */\nexport type WrappedBundleNoydbStore = WrappedPodNoydbStore\n/** @deprecated Use WrapPodStoreOptions. */\nexport type WrapBundleStoreOptions = WrapPodStoreOptions\n"],"mappings":"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","names":[]}
@@ -399,7 +399,7 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
399
399
  }
400
400
  }
401
401
  if (isManaged(opts)) {
402
- const { createNoydb } = await import("./noydb-FZZIMLCI.js");
402
+ const { createNoydb } = await import("./noydb-LPUMSDBR.js");
403
403
  const db = await createNoydb({
404
404
  store,
405
405
  user: userId,
@@ -455,4 +455,4 @@ export {
455
455
  withCargo,
456
456
  describeExtraction
457
457
  };
458
- //# sourceMappingURL=chunk-YZJSJOUI.js.map
458
+ //# sourceMappingURL=chunk-DSJG26FE.js.map
@@ -1,3 +1,7 @@
1
+ import {
2
+ hashFields,
3
+ validateSatelliteDeclaration
4
+ } from "./chunk-SHEEBRZ4.js";
1
5
  import {
2
6
  resolveStaleOnRead
3
7
  } from "./chunk-NJ4DYYO5.js";
@@ -8,9 +12,9 @@ import {
8
12
  linkRowKey
9
13
  } from "./chunk-4JQK3L4V.js";
10
14
  import {
11
- hashFields,
12
- validateSatelliteDeclaration
13
- } from "./chunk-SHEEBRZ4.js";
15
+ isBaseLive,
16
+ liveBaseIdSet
17
+ } from "./chunk-GYGLAVCX.js";
14
18
  import {
15
19
  loadFence
16
20
  } from "./chunk-LNZKOUJR.js";
@@ -24,9 +28,10 @@ import {
24
28
  SchemaUpdateGate
25
29
  } from "./chunk-6LJY7X5A.js";
26
30
  import {
27
- isBaseLive,
28
- liveBaseIdSet
29
- } from "./chunk-GYGLAVCX.js";
31
+ NO_CLASSIFIED,
32
+ guardClassifiedCompat,
33
+ resolveClassifiedFields
34
+ } from "./chunk-EE7UHLKH.js";
30
35
  import {
31
36
  NO_TIERS,
32
37
  classifySealedShred,
@@ -50,6 +55,10 @@ import {
50
55
  derivePersistedSchema,
51
56
  isZod4Schema
52
57
  } from "./chunk-6ZNSB3H6.js";
58
+ import {
59
+ CustodyApi,
60
+ NO_CUSTODY
61
+ } from "./chunk-YWWQM7KP.js";
53
62
  import {
54
63
  DEFAULT_POSTURE,
55
64
  ViaGraph
@@ -89,10 +98,8 @@ import {
89
98
  NO_ATTESTATION
90
99
  } from "./chunk-5GQEU4DN.js";
91
100
  import {
92
- NO_CLASSIFIED,
93
- guardClassifiedCompat,
94
- resolveClassifiedFields
95
- } from "./chunk-EE7UHLKH.js";
101
+ NO_CONSENT
102
+ } from "./chunk-PEOZ34EM.js";
96
103
  import {
97
104
  NO_PERIODS
98
105
  } from "./chunk-UDHBXYGG.js";
@@ -128,10 +135,6 @@ import {
128
135
  compileSequenceFormat,
129
136
  resolveSequenceKey
130
137
  } from "./chunk-L2DGWBCT.js";
131
- import {
132
- CustodyApi,
133
- NO_CUSTODY
134
- } from "./chunk-YWWQM7KP.js";
135
138
  import {
136
139
  Query,
137
140
  ScanBuilder
@@ -165,9 +168,6 @@ import {
165
168
  import {
166
169
  NO_CRDT
167
170
  } from "./chunk-PVEWA2DU.js";
168
- import {
169
- NO_CONSENT
170
- } from "./chunk-PEOZ34EM.js";
171
171
  import {
172
172
  dictCollectionName
173
173
  } from "./chunk-FRMPM7WG.js";
@@ -13923,4 +13923,4 @@ export {
13923
13923
  Noydb,
13924
13924
  createNoydb
13925
13925
  };
13926
- //# sourceMappingURL=chunk-NFIBXF4V.js.map
13926
+ //# sourceMappingURL=chunk-Q54PXSQL.js.map
@@ -0,0 +1,29 @@
1
+ import {
2
+ DebugPlaintextError,
3
+ DebugReservedFieldError
4
+ } from "../chunk-QZWTRENR.js";
5
+ import "../chunk-PZ5AY32C.js";
6
+
7
+ // src/kernel/debug.ts
8
+ function readPlaintextRecord(envelope) {
9
+ if (envelope._iv !== "") {
10
+ throw new Error(
11
+ "readPlaintextRecord: envelope is encrypted (non-empty _iv) \u2014 decrypt via the vault, not this helper"
12
+ );
13
+ }
14
+ if (envelope._debug !== void 0) {
15
+ const record = {};
16
+ for (const [key, value] of Object.entries(envelope)) {
17
+ if (!key.startsWith("_")) record[key] = value;
18
+ }
19
+ return record;
20
+ }
21
+ if (!envelope._data) return null;
22
+ return JSON.parse(envelope._data);
23
+ }
24
+ export {
25
+ DebugPlaintextError,
26
+ DebugReservedFieldError,
27
+ readPlaintextRecord
28
+ };
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/kernel/debug.ts"],"sourcesContent":["/**\n * Helpers for reading records out of a *plaintext* store (`encrypt: false`)\n * with native tooling — the programmatic core of a `noydb cat`-style unwrap.\n * See the plaintext/debug-store-mode design.\n */\nimport type { EncryptedEnvelope } from './types.js'\n\n/**\n * The option's two failure modes, re-exported here so a consumer can `catch`\n * them by identity (#914). Both are thrown from the kernel — `createNoydb`\n * raises `DebugPlaintextError` when `debugPlaintext` is combined with\n * encryption, and the record codec raises `DebugReservedFieldError` for a\n * field colliding with the `_`-prefixed metadata. This entry is the only one\n * that publishes them.\n */\nexport { DebugPlaintextError, DebugReservedFieldError } from './errors.js'\n\n/** Re-exported so `readPlaintextRecord`'s parameter is nameable from this entry. */\nexport type { EncryptedEnvelope } from './types.js'\n\n/**\n * Extract the record from a plaintext stored envelope, handling both layouts:\n *\n * - **classic plaintext** (`encrypt: false`): the record is JSON in `_data`.\n * - **debugPlaintext**: the record's fields are inlined beside the\n * `_`-prefixed metadata (marked by `_debug`).\n *\n * Returns `null` for an empty/absent body. Throws if handed an **encrypted**\n * envelope (non-empty `_iv`) — there is no key here; decrypt through the vault\n * instead. Intended for record envelopes, not blob chunks.\n *\n * @example\n * ```ts\n * // node script over a to-file store, no vault needed:\n * const env = JSON.parse(readFileSync('data/acme/invoices/inv-1.json', 'utf8'))\n * console.log(readPlaintextRecord(env)) // → { id: 'inv-1', total: '120.00', … }\n * ```\n */\nexport function readPlaintextRecord<T = Record<string, unknown>>(\n envelope: EncryptedEnvelope,\n): T | null {\n if (envelope._iv !== '') {\n throw new Error(\n 'readPlaintextRecord: envelope is encrypted (non-empty _iv) — decrypt via the vault, not this helper',\n )\n }\n if (envelope._debug !== undefined) {\n const record: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(envelope)) {\n if (!key.startsWith('_')) record[key] = value\n }\n return record as T\n }\n if (!envelope._data) return null\n return JSON.parse(envelope._data) as T\n}\n"],"mappings":";;;;;;;AAsCO,SAAS,oBACd,UACU;AACV,MAAI,SAAS,QAAQ,IAAI;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAS,WAAW,QAAW;AACjC,UAAM,SAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,UAAI,CAAC,IAAI,WAAW,GAAG,EAAG,QAAO,GAAG,IAAI;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AACA,MAAI,CAAC,SAAS,MAAO,QAAO;AAC5B,SAAO,KAAK,MAAM,SAAS,KAAK;AAClC;","names":[]}
package/dist/index.js CHANGED
@@ -16,7 +16,8 @@ import {
16
16
  refArray,
17
17
  via,
18
18
  withArchive
19
- } from "./chunk-NFIBXF4V.js";
19
+ } from "./chunk-Q54PXSQL.js";
20
+ import "./chunk-SHEEBRZ4.js";
20
21
  import "./chunk-NJ4DYYO5.js";
21
22
  import {
22
23
  persistSchemaIfNeeded
@@ -28,11 +29,11 @@ import {
28
29
  LinkIntegrityError,
29
30
  isLinkCollectionName
30
31
  } from "./chunk-4JQK3L4V.js";
31
- import "./chunk-SHEEBRZ4.js";
32
32
  import {
33
33
  ComputedFieldError,
34
34
  evalComputedFields
35
35
  } from "./chunk-5DRROK5W.js";
36
+ import "./chunk-GYGLAVCX.js";
36
37
  import {
37
38
  WithdrawalRequestError,
38
39
  approveWithdrawal,
@@ -42,6 +43,19 @@ import {
42
43
  } from "./chunk-HMU5P6RL.js";
43
44
  import "./chunk-LNZKOUJR.js";
44
45
  import "./chunk-7YVBQJ7K.js";
46
+ import {
47
+ MoneyCurrencyError,
48
+ MoneyPrecisionError,
49
+ MoneyUnsupportedError,
50
+ allocate,
51
+ asMoney,
52
+ isMoneyDescriptor,
53
+ isMoneyString,
54
+ money,
55
+ moneyNumber,
56
+ mulRate,
57
+ scaleForCurrency
58
+ } from "./chunk-SGDPRCC4.js";
45
59
  import {
46
60
  COVER_FIELDS,
47
61
  DEFAULT_COVER_SCHEMA,
@@ -67,14 +81,24 @@ import "./chunk-GJW4HX2O.js";
67
81
  import {
68
82
  UserApi
69
83
  } from "./chunk-DABACXEF.js";
70
- import "./chunk-GYGLAVCX.js";
84
+ import {
85
+ classified,
86
+ luhnCheck
87
+ } from "./chunk-GU3IP74F.js";
88
+ import {
89
+ ClassifiedNeverStoredError,
90
+ ClassifiedValidationError,
91
+ isClassifiedFieldSpec,
92
+ isClassifiedGroup,
93
+ resolveClassifiedFields
94
+ } from "./chunk-EE7UHLKH.js";
71
95
  import "./chunk-Y2NJBNX3.js";
72
96
  import "./chunk-BLBAQRBM.js";
73
97
  import {
74
98
  decryptExtractedPartition,
75
99
  diffVault,
76
100
  withCargo
77
- } from "./chunk-YZJSJOUI.js";
101
+ } from "./chunk-DSJG26FE.js";
78
102
  import {
79
103
  NO_CARGO
80
104
  } from "./chunk-TG634KCO.js";
@@ -123,18 +147,10 @@ import {
123
147
  savePersistedSchema
124
148
  } from "./chunk-DMH3VZU5.js";
125
149
  import {
126
- MoneyCurrencyError,
127
- MoneyPrecisionError,
128
- MoneyUnsupportedError,
129
- allocate,
130
- asMoney,
131
- isMoneyDescriptor,
132
- isMoneyString,
133
- money,
134
- moneyNumber,
135
- mulRate,
136
- scaleForCurrency
137
- } from "./chunk-SGDPRCC4.js";
150
+ CustodyApi,
151
+ NO_CUSTODY,
152
+ withCustody
153
+ } from "./chunk-YWWQM7KP.js";
138
154
  import {
139
155
  withDerivation,
140
156
  withRollup
@@ -145,12 +161,12 @@ import "./chunk-MHMLXJ3I.js";
145
161
  import {
146
162
  withMaterializedView
147
163
  } from "./chunk-MXUT7FTE.js";
164
+ import "./chunk-IYZISPGD.js";
148
165
  import "./chunk-N4BBNYIB.js";
149
166
  import {
150
167
  buildLookupSnapshot
151
168
  } from "./chunk-IPUG56GJ.js";
152
169
  import "./chunk-3HUYRN24.js";
153
- import "./chunk-IYZISPGD.js";
154
170
  import {
155
171
  withOverlayedView
156
172
  } from "./chunk-MSMJJB4J.js";
@@ -172,16 +188,9 @@ import {
172
188
  } from "./chunk-PY4KJPYU.js";
173
189
  import "./chunk-5GQEU4DN.js";
174
190
  import {
175
- classified,
176
- luhnCheck
177
- } from "./chunk-GU3IP74F.js";
178
- import {
179
- ClassifiedNeverStoredError,
180
- ClassifiedValidationError,
181
- isClassifiedFieldSpec,
182
- isClassifiedGroup,
183
- resolveClassifiedFields
184
- } from "./chunk-EE7UHLKH.js";
191
+ CONSENT_AUDIT_COLLECTION
192
+ } from "./chunk-EKRSCRNM.js";
193
+ import "./chunk-PEOZ34EM.js";
185
194
  import "./chunk-QDIH2744.js";
186
195
  import "./chunk-UDHBXYGG.js";
187
196
  import "./chunk-GHVIKOHT.js";
@@ -224,11 +233,6 @@ import {
224
233
  resolveSequenceKey,
225
234
  withSequence
226
235
  } from "./chunk-L2DGWBCT.js";
227
- import {
228
- CustodyApi,
229
- NO_CUSTODY,
230
- withCustody
231
- } from "./chunk-YWWQM7KP.js";
232
236
  import {
233
237
  DEFAULT_CROSS_JOIN_MAX_ROWS,
234
238
  Query,
@@ -293,7 +297,7 @@ import {
293
297
  createPodStore,
294
298
  wrapBundleStore,
295
299
  wrapPodStore
296
- } from "./chunk-Y6B6HVKC.js";
300
+ } from "./chunk-2J6UEN2V.js";
297
301
  import {
298
302
  NOYDB_BUNDLE_FORMAT_VERSION,
299
303
  NOYDB_BUNDLE_MAGIC,
@@ -315,10 +319,6 @@ import {
315
319
  saveCover,
316
320
  validateCoverInput
317
321
  } from "./chunk-TUXQSFHZ.js";
318
- import {
319
- CONSENT_AUDIT_COLLECTION
320
- } from "./chunk-EKRSCRNM.js";
321
- import "./chunk-PEOZ34EM.js";
322
322
  import "./chunk-X3F5YJG2.js";
323
323
  import {
324
324
  dictKey,
@@ -4,6 +4,17 @@
4
4
  * See the plaintext/debug-store-mode design.
5
5
  */
6
6
  import type { EncryptedEnvelope } from './types.js';
7
+ /**
8
+ * The option's two failure modes, re-exported here so a consumer can `catch`
9
+ * them by identity (#914). Both are thrown from the kernel — `createNoydb`
10
+ * raises `DebugPlaintextError` when `debugPlaintext` is combined with
11
+ * encryption, and the record codec raises `DebugReservedFieldError` for a
12
+ * field colliding with the `_`-prefixed metadata. This entry is the only one
13
+ * that publishes them.
14
+ */
15
+ export { DebugPlaintextError, DebugReservedFieldError } from './errors.js';
16
+ /** Re-exported so `readPlaintextRecord`'s parameter is nameable from this entry. */
17
+ export type { EncryptedEnvelope } from './types.js';
7
18
  /**
8
19
  * Extract the record from a plaintext stored envelope, handling both layouts:
9
20
  *
@@ -1,6 +1,12 @@
1
1
  import {
2
2
  withMaterializedView
3
3
  } from "../chunk-MXUT7FTE.js";
4
+ import {
5
+ clearMVStale,
6
+ isMVStale,
7
+ markMVStale,
8
+ resolveStaleMVOnRead
9
+ } from "../chunk-IYZISPGD.js";
4
10
  import {
5
11
  MaterializedViewExecutor
6
12
  } from "../chunk-N4BBNYIB.js";
@@ -12,12 +18,6 @@ import {
12
18
  computeQueryHash,
13
19
  summarizeQueryPlan
14
20
  } from "../chunk-3HUYRN24.js";
15
- import {
16
- clearMVStale,
17
- isMVStale,
18
- markMVStale,
19
- resolveStaleMVOnRead
20
- } from "../chunk-IYZISPGD.js";
21
21
  import "../chunk-5D2ALSM5.js";
22
22
  import "../chunk-V6UQZA2H.js";
23
23
  import "../chunk-KRCBZYTZ.js";
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  Noydb,
3
3
  createNoydb
4
- } from "./chunk-NFIBXF4V.js";
4
+ } from "./chunk-Q54PXSQL.js";
5
+ import "./chunk-SHEEBRZ4.js";
5
6
  import "./chunk-NJ4DYYO5.js";
6
7
  import "./chunk-4JQK3L4V.js";
7
- import "./chunk-SHEEBRZ4.js";
8
+ import "./chunk-GYGLAVCX.js";
8
9
  import "./chunk-LNZKOUJR.js";
9
10
  import "./chunk-7YVBQJ7K.js";
10
11
  import "./chunk-6LJY7X5A.js";
11
- import "./chunk-GYGLAVCX.js";
12
+ import "./chunk-EE7UHLKH.js";
12
13
  import "./chunk-Y2NJBNX3.js";
13
14
  import "./chunk-BLBAQRBM.js";
14
15
  import "./chunk-TG634KCO.js";
@@ -16,12 +17,13 @@ import "./chunk-FQZ6XTQ7.js";
16
17
  import "./chunk-VYYM3MEN.js";
17
18
  import "./chunk-6ZNSB3H6.js";
18
19
  import "./chunk-Y2NBBT6K.js";
20
+ import "./chunk-YWWQM7KP.js";
19
21
  import "./chunk-4ZKNC6A6.js";
20
22
  import "./chunk-IPUG56GJ.js";
21
23
  import "./chunk-ZTLOXFPB.js";
22
24
  import "./chunk-55LKVALY.js";
23
25
  import "./chunk-5GQEU4DN.js";
24
- import "./chunk-EE7UHLKH.js";
26
+ import "./chunk-PEOZ34EM.js";
25
27
  import "./chunk-UDHBXYGG.js";
26
28
  import "./chunk-GHVIKOHT.js";
27
29
  import "./chunk-NM5UYF6Q.js";
@@ -32,7 +34,6 @@ import "./chunk-5D2ALSM5.js";
32
34
  import "./chunk-MCHBCNCE.js";
33
35
  import "./chunk-AURFOK3D.js";
34
36
  import "./chunk-L2DGWBCT.js";
35
- import "./chunk-YWWQM7KP.js";
36
37
  import "./chunk-WBKZUO5V.js";
37
38
  import "./chunk-V6UQZA2H.js";
38
39
  import "./chunk-CXQW2NWO.js";
@@ -44,7 +45,6 @@ import "./chunk-YMUFFJCO.js";
44
45
  import "./chunk-CADUYBQD.js";
45
46
  import "./chunk-IGJCYUT5.js";
46
47
  import "./chunk-PVEWA2DU.js";
47
- import "./chunk-PEOZ34EM.js";
48
48
  import "./chunk-W3G5REIU.js";
49
49
  import "./chunk-FRMPM7WG.js";
50
50
  import "./chunk-UADPR6F4.js";
@@ -78,4 +78,4 @@ export {
78
78
  Noydb,
79
79
  createNoydb
80
80
  };
81
- //# sourceMappingURL=noydb-FZZIMLCI.js.map
81
+ //# sourceMappingURL=noydb-LPUMSDBR.js.map
package/dist/pod/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createPodStore,
4
4
  wrapBundleStore,
5
5
  wrapPodStore
6
- } from "../chunk-Y6B6HVKC.js";
6
+ } from "../chunk-2J6UEN2V.js";
7
7
  import {
8
8
  COMPRESSION_BROTLI,
9
9
  COMPRESSION_GZIP,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noy-db/hub",
3
- "version": "0.4.0-pre.10",
3
+ "version": "0.4.0-pre.12",
4
4
  "description": "Zero-knowledge, offline-first, encrypted document store — core library with AES-256-GCM, PBKDF2, multi-user keyring, and sync engine",
5
5
  "license": "MIT",
6
6
  "author": "vLannaAi <vicio@lanna.ai>",
@@ -52,6 +52,10 @@
52
52
  "types": "./dist/kernel/query/index.d.ts",
53
53
  "default": "./dist/query/index.js"
54
54
  },
55
+ "./debug": {
56
+ "types": "./dist/kernel/debug.d.ts",
57
+ "default": "./dist/debug/index.js"
58
+ },
55
59
  "./blobs": {
56
60
  "types": "./dist/via/blob/index.d.ts",
57
61
  "default": "./dist/blobs/index.js"
@@ -204,14 +208,14 @@
204
208
  "node": ">=22.0.0"
205
209
  },
206
210
  "dependencies": {
207
- "@noy-db/attestation": "0.4.0-pre.10"
211
+ "@noy-db/attestation": "0.4.0-pre.12"
208
212
  },
209
213
  "devDependencies": {
210
214
  "@types/node": "^22.0.0",
211
215
  "esbuild": "^0.25.0",
212
216
  "zod": "^4.0.0",
213
217
  "zod-to-json-schema": "^3.25.2",
214
- "@noy-db/on-shamir": "0.4.0-pre.10"
218
+ "@noy-db/on-shamir": "0.4.0-pre.12"
215
219
  },
216
220
  "peerDependencies": {
217
221
  "zod-to-json-schema": "^3.25.0"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-pod/pod-store.ts"],"sourcesContent":["import type { NoydbStore, NoydbPodStore, VaultSnapshot, EncryptedEnvelope } from '../kernel/types.js'\nimport { ConflictError, PodVersionConflictError } from '../kernel/errors.js'\n\n// ─── Bundle format ─────────────────────────────────────────────────────\n\nconst BUNDLE_STORE_VERSION = 1 as const\n\n/**\n * Wire format written by `wrapPodStore`. A JSON-serialised object that\n * contains the entire `VaultSnapshot` (all encrypted envelopes) plus a small\n * header for integrity checking. The envelopes inside are already AES-GCM\n * encrypted by core — the bundle bytes themselves are not additionally\n * encrypted, but they are safe to store on untrusted blob hosts because\n * every record inside is already ciphertext.\n *\n * @internal\n */\ninterface BundleStoreData {\n readonly _noydb_bundle_store: typeof BUNDLE_STORE_VERSION\n readonly vault: string\n readonly ts: string\n readonly data: VaultSnapshot\n}\n\n// ─── Options ───────────────────────────────────────────────────────────\n\nexport interface WrapPodStoreOptions {\n /**\n * When `true` (default), every `put()` and `delete()` flushes the full\n * vault snapshot to the bundle backend. Set to `false` for bulk operations\n * and call `store.flush(vaultId)` manually.\n */\n autoFlush?: boolean\n}\n\n// ─── Extended NoydbStore with flush/batch ───────────────────────────────\n\nexport interface WrappedPodNoydbStore extends NoydbStore {\n /** Manually flush the in-memory snapshot to the bundle backend. */\n flush(vaultId: string): Promise<void>\n /**\n * Run a batch of mutations without flushing until the callback completes.\n * A single flush is performed at the end.\n */\n batch(vaultId: string, fn: () => Promise<void>): Promise<void>\n}\n\n// ─── wrapPodStore ───────────────────────────────────────────────────\n\nconst MAX_CONFLICT_RETRIES = 3\n\n/**\n * Convert a `NoydbPodStore` (blob-oriented read/write with OCC) into the\n * standard six-method `NoydbStore` interface expected by `createNoydb({ store })`.\n *\n * Bundle stores operate on the entire vault as a single serialised unit —\n * ideal for backends like Google Drive, WebDAV, or iCloud Drive that work\n * best with whole-file I/O rather than per-record KV operations.\n *\n * ## Optimistic concurrency\n *\n * The wrapper tracks the `version` token from the last `readBundle` and\n * passes it as `expectedVersion` on every flush. On\n * `PodVersionConflictError`, it re-reads, merges the remote snapshot\n * (last-write-wins per record key), and retries (max 3 attempts).\n *\n * ## Flush modes\n *\n * By default, flushes on every mutation (O(vault size) per write). Options:\n * - `autoFlush: false` + explicit `store.flush(vaultId)` calls\n * - `store.batch(vaultId, async () => { ... })` — defers flush until end\n * - Pair with `syncPolicy: { push: { mode: 'debounce' } }` from \n */\nexport function wrapPodStore(\n bundle: NoydbPodStore,\n options?: WrapPodStoreOptions,\n): WrappedPodNoydbStore {\n const autoFlush = options?.autoFlush !== false\n\n // Per-vault state\n const snapshots = new Map<string, VaultSnapshot>()\n const versions = new Map<string, string | null>()\n const loaded = new Set<string>()\n\n // Batch mode: when > 0, suppress auto-flush\n let batchDepth = 0\n\n async function load(vault: string): Promise<VaultSnapshot> {\n if (loaded.has(vault)) return snapshots.get(vault)!\n\n const result = await bundle.readBundle(vault)\n if (result) {\n const text = new TextDecoder().decode(result.bytes)\n const format = JSON.parse(text) as BundleStoreData\n snapshots.set(vault, format.data)\n versions.set(vault, result.version)\n } else {\n snapshots.set(vault, {})\n versions.set(vault, null)\n }\n\n loaded.add(vault)\n return snapshots.get(vault)!\n }\n\n async function flush(vault: string): Promise<void> {\n const snapshot = snapshots.get(vault) ?? {}\n const format: BundleStoreData = {\n _noydb_bundle_store: BUNDLE_STORE_VERSION,\n vault,\n ts: new Date().toISOString(),\n data: snapshot,\n }\n const bytes = new TextEncoder().encode(JSON.stringify(format))\n const expectedVersion = versions.get(vault) ?? null\n\n for (let attempt = 0; attempt < MAX_CONFLICT_RETRIES; attempt++) {\n try {\n const { version: newVersion } = await bundle.writeBundle(vault, bytes, expectedVersion)\n versions.set(vault, newVersion)\n return\n } catch (err) {\n if (err instanceof PodVersionConflictError && attempt < MAX_CONFLICT_RETRIES - 1) {\n // Pull remote, merge (last-write-wins by record key), retry\n const remote = await bundle.readBundle(vault)\n if (remote) {\n const remoteText = new TextDecoder().decode(remote.bytes)\n const remoteFormat = JSON.parse(remoteText) as BundleStoreData\n const localSnap = snapshots.get(vault) ?? {}\n const mergedSnap = mergeSnapshots(remoteFormat.data, localSnap)\n snapshots.set(vault, mergedSnap)\n versions.set(vault, remote.version)\n }\n // Re-encode with merged data for the retry\n continue\n }\n throw err\n }\n }\n }\n\n async function maybeFlush(vault: string): Promise<void> {\n if (autoFlush && batchDepth === 0) {\n await flush(vault)\n }\n }\n\n const store: WrappedPodNoydbStore = {\n name: bundle.name ?? 'bundle',\n\n async flush(vaultId: string): Promise<void> {\n await flush(vaultId)\n },\n\n async batch(vaultId: string, fn: () => Promise<void>): Promise<void> {\n await load(vaultId) // ensure loaded before batch\n batchDepth++\n try {\n await fn()\n } finally {\n batchDepth--\n }\n await flush(vaultId)\n },\n\n async get(vault: string, collection: string, id: string): Promise<EncryptedEnvelope | null> {\n const snap = await load(vault)\n return snap[collection]?.[id] ?? null\n },\n\n async put(\n vault: string,\n collection: string,\n id: string,\n envelope: EncryptedEnvelope,\n expectedVersion?: number,\n ): Promise<void> {\n const snap = await load(vault)\n\n if (expectedVersion !== undefined) {\n const current = snap[collection]?.[id]\n const currentVersion = current?._v ?? 0\n if (currentVersion !== expectedVersion) {\n throw new ConflictError(\n currentVersion,\n `Expected version ${expectedVersion} but found ${currentVersion} on ${collection}/${id}`,\n )\n }\n }\n\n snap[collection] ??= {}\n snap[collection][id] = envelope\n await maybeFlush(vault)\n },\n\n async delete(vault: string, collection: string, id: string): Promise<void> {\n const snap = await load(vault)\n if (snap[collection]) {\n delete snap[collection][id]\n await maybeFlush(vault)\n }\n },\n\n async list(vault: string, collection: string): Promise<string[]> {\n const snap = await load(vault)\n return Object.keys(snap[collection] ?? {})\n },\n\n async loadAll(vault: string): Promise<VaultSnapshot> {\n return await load(vault)\n },\n\n async saveAll(vault: string, data: VaultSnapshot): Promise<void> {\n snapshots.set(vault, data)\n loaded.add(vault)\n await flush(vault)\n },\n }\n\n return store\n}\n\n// ─── Snapshot merge (last-write-wins per record) ────────────────────────\n\nfunction mergeSnapshots(remote: VaultSnapshot, local: VaultSnapshot): VaultSnapshot {\n const merged: VaultSnapshot = {}\n\n // Start with all remote collections\n for (const [coll, records] of Object.entries(remote)) {\n merged[coll] = { ...records }\n }\n\n // Overlay local collections — LWW by _ts per record\n for (const [coll, records] of Object.entries(local)) {\n if (!merged[coll]) {\n merged[coll] = { ...records }\n continue\n }\n for (const [id, envelope] of Object.entries(records)) {\n const existing = merged[coll][id]\n if (!existing || envelope._ts >= existing._ts) {\n merged[coll][id] = envelope\n }\n }\n }\n\n return merged\n}\n\n// ─── Factory helper ─────────────────────────────────────────────────────\n\n/**\n * Type-safe factory helper for `NoydbPodStore` implementations,\n * analogous to `createStore` for KV stores.\n */\nexport function createPodStore<TOptions>(\n factory: (options: TOptions) => NoydbPodStore,\n): (options: TOptions) => NoydbPodStore {\n return factory\n}\n\n// ─── Deprecated aliases (pre-rename compatibility) ──────────────────────\n\n/** @deprecated Use wrapPodStore. */\nexport const wrapBundleStore = wrapPodStore\n/** @deprecated Use createPodStore. */\nexport const createBundleStore = createPodStore\n/** @deprecated Use WrappedPodNoydbStore. */\nexport type WrappedBundleNoydbStore = WrappedPodNoydbStore\n/** @deprecated Use WrapPodStoreOptions. */\nexport type WrapBundleStoreOptions = WrapPodStoreOptions\n"],"mappings":"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","names":[]}