@noy-db/hub 0.4.0-pre.7 → 0.4.0-pre.9

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/with-commit/numbering/descriptor.ts","../src/via/computed/descriptor.ts","../src/via/lookup/binding.ts","../src/via/lookup/descriptor.ts"],"sourcesContent":["/**\n * @category capability\n * Deferred-numbering config descriptor. See\n * docs/superpowers/specs/2026-06-08-sealed-numbering-and-store-clock-design.md.\n */\n\n/** A registered deferred-numbering series. */\nexport interface DeferredNumberingConfig {\n /** Series name — the key passed to `vault.sequence(series)`. */\n readonly series: string\n /** Collection holding the records to number. */\n readonly collection: string\n /** Field on each record where the assigned serial is written. */\n readonly field: string\n /**\n * Minimum wall-clock age (ms) before an entry is eligible at a pass, in\n * addition to the interval commit-wait. Default 0 — the store-clock\n * interval (`storeLatest ≤ now.earliest`) is the correctness mechanism.\n */\n readonly settleWindowMs: number\n}\n\n/**\n * Options for {@link withDeferredNumbering} (#844b — was an inline literal, so\n * unnameable). Same shape as {@link DeferredNumberingConfig} except\n * `settleWindowMs` is optional; the factory defaults it to 0.\n */\nexport interface WithDeferredNumberingOptions {\n /** Series name — the key passed to `vault.sequence(series)`. */\n readonly series: string\n /** Collection holding the records to number. */\n readonly collection: string\n /** Field on each record where the assigned serial is written. */\n readonly field: string\n /** See {@link DeferredNumberingConfig.settleWindowMs}. Default 0. */\n readonly settleWindowMs?: number\n}\n\n/** Declare a deferred-numbering series. Pass the result in `createNoydb({ numbering: [...] })`. */\nexport function withDeferredNumbering(opts: WithDeferredNumberingOptions): DeferredNumberingConfig {\n return {\n series: opts.series,\n collection: opts.collection,\n field: opts.field,\n settleWindowMs: opts.settleWindowMs ?? 0,\n }\n}\n","/**\n * computed() — the declaration factory for a field whose value is DERIVED\n * from other fields on the same record (#638 Task 7, spec §6). Composes\n * with `via()` (the locked grammar since phase A: `via(computed(fn, { deps,\n * mode }), money('EUR'))`), grouped by `_viaBrand` like every other via\n * feature (`kernel/via/compose.ts#mergeViaFields`).\n *\n * `mode` picks where the function runs:\n * - `'materialized'` (default) — TODAY's stage-5 write-time eager compute\n * (`with-formula/computed/index.ts#evalComputedFields`), stored like any\n * other field. Byte-for-byte the existing `computed: { field: fn }` sugar.\n * - `'virtual'` — rides the `present` read phase (the money-`Formatted`/\n * i18n-`Label` precedent, seam map Part 4): computed fresh on every\n * read, NEVER stored, `queryable: 'none'`, excluded from export unless\n * its declared `deps` permit (identical taint rule to materialized —\n * see `via/computed/binding.ts`).\n *\n * `deps` names the OTHER fields `fn` reads — feeds `ViaGraph` (Task 1/2) so\n * a source's taint (e.g. a classified field) propagates to this derived\n * field. A depsless entry is fine UNLESS the collection also declares\n * classified fields (`kernel/collection-config.ts#resolveComputedEdges`\n * refuses it — closes the #636 opaque-function leak).\n */\nimport type { ViaDescriptor } from '../../kernel/via/index.js'\n\nexport interface ComputedDescriptor extends ViaDescriptor {\n readonly _viaBrand: 'computed'\n readonly fn: (record: Record<string, unknown>) => unknown\n readonly deps?: readonly string[]\n readonly mode: 'materialized' | 'virtual'\n}\n\nexport function computed(\n fn: (record: Record<string, unknown>) => unknown,\n opts?: { readonly deps?: readonly string[]; readonly mode?: 'materialized' | 'virtual' },\n): ComputedDescriptor {\n return {\n _viaBrand: 'computed',\n fn,\n ...(opts?.deps !== undefined ? { deps: opts.deps } : {}),\n mode: opts?.mode ?? 'materialized',\n }\n}\n\nexport function isComputedDescriptor(x: unknown): x is ComputedDescriptor {\n return typeof x === 'object' && x !== null && (x as { _viaBrand?: unknown })._viaBrand === 'computed'\n}\n","/**\n * The `'lookup'` `ViaBinding` — wires the lookup engine (present-time label\n * dressing across all three backing tiers) into the kernel's generic Via\n * port. Mirrors `via/i18n/binding.ts`'s #553 static-link pattern; the\n * present-time label-dressing algorithm below is adapted from\n * `via-i18n/binding.ts:253-337` (the same wildcard/array/scalar handling,\n * the same `onMissing`/`substitute` policy engine), generalized to branch on\n * `backing` instead of a static-vs-dynamic descriptor-shape check. For the\n * `'static'` and `'reserved'` tiers this delegates to `cfg.lookupLabelResolver`\n * — the SAME vault-built closure the i18n binding's `dictLabelResolver` uses\n * (static table first, else the `vault.dictionary()` handle) — so a native\n * `dict()`/`lookup(static)` field resolves through the identical label data\n * as its `dictKey()`/`staticDict()` alias (the byte-equivalence lock).\n *\n * `lookup()`/`enumOf()`/`dict()` each call {@link linkLookupVia} first — the\n * same #553 pattern `money()`/`dictKey()` use.\n *\n * `buildClause` (label-predicate queries) is still undeclared — out of scope\n * for #650. `compareForOrder` (#650 Task 6, spec §5; matrix tier added Task\n * 7) resolves a `sortBy`-declared field's ordering via `cfg.snapshotFor`'s\n * sync snapshot; the hook signature is UNCHANGED (`via/index.ts:128-129` — no\n * locale param), so it closes over each descriptor's own `displayLocale`\n * (the same locale-less-hinge default `runLookupPresent`'s\n * `hasStaticDisplay` branch already uses). `resolveOrderLabel` (#650 Task\n * 7) is the PER-CALL-locale sibling `orderBy(..., {by:'label'})` needs —\n * see its own doc comment below. `describeFragment` (#650 Task 7) is the\n * first-ever consumed `ViaBinding.describeFragment` implementation — see\n * `with-shape/introspection/describe.ts`'s `buildDescription`.\n */\nimport type { ViaBinding, ViaReadCtx } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { LookupDescriptor, LookupBacking, Vocabulary, OnDelete } from './descriptor.js'\nimport { resolvePolicy, type Layer } from '../i18n/policy.js'\nimport { LocaleNotSpecifiedError, UnknownLookupKeyError, ValidationError } from '../../kernel/errors.js'\nimport { getAtPath, setAtPathInPlace } from '../../kernel/paths.js'\nimport type { MaterializedBacking } from './registry.js'\nimport { buildLookupSnapshot } from './snapshot.js'\n\n/**\n * Config a collection's lookup declarations resolve to — the binding's\n * construction input. `lookupLabelResolver`/`getLookupBacking`/`membership`/\n * `snapshotFor` are vault-built closures (never a `Collection` handle,\n * keyring, or DEK/CEK — the zero-knowledge boundary).\n */\nexport interface LookupViaConfig {\n readonly lookupFields: Record<string, LookupDescriptor>\n readonly lookupLabelResolver?: (dimension: string, key: string, locale: string, fallback?: unknown) => Promise<string | undefined>\n /**\n * The matrix (collection) tier's present-time backing-row source, keyed by the full\n * descriptor (not a bare dimension name) so the closure can resolve by `descriptor.key`,\n * not the backing row's PUT-id (#651 Task 3 — the same descriptor-gaining dispatch Task 7\n * already applied to `snapshotFor`).\n */\n readonly getLookupBacking?: (descriptor: LookupDescriptor) => (key: string) => Promise<Record<string, unknown> | undefined>\n /** Closed-vocabulary write-time membership test (#650 Task 3) — `(field, key) => known?`. */\n readonly membership?: (field: string, key: string) => boolean | Promise<boolean>\n /** Sync per-descriptor altKey index — `ingest`'s normalization source (#650 Task 3). */\n readonly getAltIndex?: (desc: LookupDescriptor) => MaterializedBacking | undefined\n /**\n * Sync materialized `key -> row` rows for a lookup descriptor (#650 Task\n * 6, spec §5; matrix-tier routing added #650 Task 7) — `compareForOrder`\n * and `resolveOrderLabel` below, and `snapshot.ts`'s `presentForJoin`\n * builder, all read this same vault-built closure. Reserved AND\n * collection (matrix) tier route here (the vault wires\n * `dimension -> LookupHandle.snapshotEntries()` / `dimension ->\n * collection.querySourceForJoin().snapshot()` respectively, keyed by\n * `descriptor.key` for the matrix case — see `registry.ts`'s\n * `buildLookupSnapshotRows`); static tier is resolved locally from\n * `descriptor.table` without calling this.\n */\n readonly snapshotFor?: (descriptor: LookupDescriptor) => ReadonlyMap<string, Record<string, unknown>> | undefined\n readonly collectionName: string\n}\n\n/** Enum tier (`backing:'static'`, no `table`) has no label source at all — never dressed. */\nfunction hasLabelSource(desc: LookupDescriptor): boolean {\n return !(desc.backing === 'static' && desc.table === undefined)\n}\n\n/**\n * Resolve one key's label. `'static'`/`'reserved'` both go through\n * `cfg.lookupLabelResolver` (mirrors `dictLabelResolver`'s own static-table-\n * first-else-reserved-handle branching — the SAME closure is reused, see\n * `kernel/vault.ts`). `'collection'` (matrix tier) reads the declared\n * `present.label` off the backing row via `cfg.getLookupBacking`; when\n * `present.by` is set, that field's value is a `{ locale -> label }` map\n * indexed by the effective locale.\n */\nasync function fetchLookupLabel(\n desc: LookupDescriptor,\n key: string,\n effLocale: string,\n fieldFallback: string | readonly string[] | undefined,\n cfg: LookupViaConfig,\n): Promise<string | undefined> {\n if (desc.backing === 'collection') {\n const getRow = cfg.getLookupBacking?.(desc)\n const row = getRow ? await getRow(key) : undefined\n const labelField = desc.present?.label\n if (!row || labelField === undefined) return undefined\n const raw = row[labelField]\n if (desc.present?.by !== undefined) {\n return raw && typeof raw === 'object' ? (raw as Record<string, unknown>)[effLocale] as string | undefined : undefined\n }\n return typeof raw === 'string' ? raw : undefined\n }\n return cfg.lookupLabelResolver?.(desc.dimension, key, effLocale, fieldFallback)\n}\n\n/** `present` — resolve `<field>Label` for every declared lookup field. Adapted from `via-i18n/binding.ts:253-337`. */\nasync function runLookupPresent(\n record: Record<string, unknown>,\n ctx: ViaReadCtx,\n cfg: LookupViaConfig,\n): Promise<Record<string, unknown>> {\n const fields = Object.entries(cfg.lookupFields).filter(([, d]) => hasLabelSource(d))\n if (fields.length === 0) return record\n\n const locale = typeof ctx.locale === 'string' ? ctx.locale : undefined\n const fallback = ctx.fallback as string | readonly string[] | undefined\n const layer = ctx.layer as Layer\n\n // `{ locale: 'raw' }` wants the untouched record — mirrors\n // `via-i18n/binding.ts`'s `locale !== 'raw'` dict-label gate exactly (no\n // synthetic `<field>Label` derivative on a raw read).\n if (locale === 'raw') return record\n\n // Static-display hybrid hinge: a `backing:'static'` field with a declared\n // `displayLocale` resolves its `<field>Label` even under a locale-less\n // read (mirrors staticDict's `hasStaticDisplay` gate).\n const hasStaticDisplay = fields.some(([, d]) => d.backing === 'static' && d.displayLocale !== undefined)\n if (!locale && !hasStaticDisplay) return record\n\n let result = record\n const withLabels = { ...result }\n\n for (const [field, desc] of fields) {\n const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : 'null'\n const fieldFallback = policy === 'substitute' ? (fallback ?? desc.substitute) : fallback\n const effLocale = locale ?? (desc.backing === 'static' ? desc.displayLocale : undefined)\n\n const resolveKey = async (key: string): Promise<string | null> => {\n if (!effLocale) {\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(field, `lookup \"${field}\": no locale active to resolve key \"${key}\".`)\n }\n return null\n }\n const label = await fetchLookupLabel(desc, key, effLocale, fieldFallback, cfg)\n if (label === undefined) {\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(field, `lookup \"${field}\": no label for key \"${key}\" in locale \"${effLocale}\".`)\n }\n return null\n }\n return label\n }\n\n if (field.includes('[].')) {\n const parts = field.split('[].')\n const arrayKey = parts[0]!\n const leaf = parts[1]\n if (!leaf || leaf.includes('.')) continue\n const arr = (withLabels as Record<string, unknown>)[arrayKey]\n if (!Array.isArray(arr)) continue\n const labelKey = `${leaf}Label`\n ;(withLabels as Record<string, unknown>)[arrayKey] = await Promise.all(\n arr.map(async (el) => {\n if (!el || typeof el !== 'object' || Array.isArray(el)) return el\n const k = (el as Record<string, unknown>)[leaf]\n if (typeof k !== 'string') return el\n return { ...(el as Record<string, unknown>), [labelKey]: await resolveKey(k) }\n }),\n )\n continue\n }\n\n const val = result[field]\n if (Array.isArray(val)) {\n withLabels[`${field}Label`] = await Promise.all(\n val.map(async (k) => ({ key: k, label: typeof k === 'string' ? await resolveKey(k) : null })),\n )\n } else if (typeof val === 'string') {\n const label = await resolveKey(val)\n if (label !== null) withLabels[`${field}Label`] = label\n }\n }\n\n result = withLabels\n return result\n}\n\n/**\n * One field's `lookup` descriptor as it appears on a `ViaBinding.\n * describeFragment()` payload (#650 Task 7 — the first real consumer,\n * `with-shape/introspection/describe.ts`'s `buildDescription`, imports this\n * type directly; `describe.ts` is NOT under `kernel/**`, so it's free to\n * import concrete via/ types the way it already does for\n * `LookupDescriptor`/`MoneyDescriptor`/etc.). `dimension` is OMITTED (not\n * emitted as `''`) for a bare `enumOf()` descriptor — the #650 Task 2\n * `dimension:''` sentinel resolved: no dimension name means no `dimension`\n * key, not a meaningless empty string (T2 carry, #650 Task 7).\n */\nexport interface LookupDescribeFragmentEntry {\n readonly dimension?: string\n readonly backing: LookupBacking\n readonly vocabulary: Vocabulary\n readonly key: string\n readonly altKeys?: readonly string[]\n readonly present?: { readonly label: string; readonly by?: string }\n readonly sortBy?: string\n readonly onDelete: OnDelete\n /** Statically-known closed-vocabulary key set (declared `keys`, or a static table's own keys). Omitted when membership lives only in the backing collection/dictionary (open vocabulary, or closed with no declared `keys`). */\n readonly keys?: readonly string[]\n}\n\n/** The `'lookup'` binding's `describeFragment()` payload shape. */\nexport interface LookupDescribeFragment {\n readonly lookupFields: Record<string, LookupDescribeFragmentEntry>\n}\n\nfunction buildLookupDescribeFragment(cfg: LookupViaConfig): Record<string, unknown> {\n const lookupFields: Record<string, LookupDescribeFragmentEntry> = {}\n for (const [field, desc] of Object.entries(cfg.lookupFields)) {\n lookupFields[field] = {\n ...(desc.dimension !== '' ? { dimension: desc.dimension } : {}),\n backing: desc.backing,\n vocabulary: desc.vocabulary,\n key: desc.key,\n onDelete: desc.onDelete,\n ...(desc.altKeys !== undefined ? { altKeys: desc.altKeys } : {}),\n ...(desc.present !== undefined ? { present: desc.present } : {}),\n ...(desc.sortBy !== undefined ? { sortBy: desc.sortBy } : {}),\n // #657 — static tier: when no `keys` was explicitly declared but a\n // `table` was (the staticDict()-equivalent `lookup(dim, {backing:\n // 'static', table})` shape), the table's own key set IS the\n // statically-known closed-vocabulary set the DescribedField.lookup\n // docblock promises (\"declared `keys`, OR a static table's own\n // keys\"). Reserved/matrix tiers never carry `table`, so this branch\n // never fires for them — their `keys` emission is unchanged.\n ...(desc.keys !== undefined\n ? { keys: desc.keys }\n : desc.backing === 'static' && desc.table !== undefined ? { keys: Object.keys(desc.table) } : {}),\n }\n }\n return { lookupFields }\n}\n\n/**\n * `cfg.getAltIndex(desc)` (matrix tier) reads the backing collection's cache\n * via `querySourceForJoin()` (`buildLookupAltIndex`, registry.ts) — which\n * throws a `.join()`-branded message when that collection was opened\n * `{prefetch:false}` (lazy mode is unsupported for altKey normalization).\n * Normalization must never silently not happen (the banned silent-no-op\n * class, #650 Task 3 review, Important 2) — detect that specific failure\n * and surface a CLEAR, lookup-branded `ValidationError` at the point of\n * failure instead of letting the confusing join-branded one leak onto an\n * unrelated `put()`. Any OTHER error (e.g. `materializeBackingTable`'s own\n * altKey-collision `ValidationError`) propagates unchanged.\n */\nfunction getAltIndexOrThrow(field: string, desc: LookupDescriptor, cfg: LookupViaConfig): MaterializedBacking | undefined {\n try {\n return cfg.getAltIndex?.(desc)\n } catch (err) {\n if (err instanceof Error && err.message.includes('lazy-mode')) {\n throw new ValidationError(\n `lookup: altKeys on \"${field}\" require the backing collection \"${desc.dimension}\" to be ` +\n `prefetch-enabled (lazy mode unsupported); open it without {prefetch:false} or drop altKeys.`,\n )\n }\n throw err\n }\n}\n\n/**\n * `ingest` — altKey candidate values normalize to the canonical key (#650\n * Task 3, spec §3). Pure, sync, idempotent (a canonical key maps to\n * itself); no store read — consults the pre-materialized\n * `cfg.getAltIndex(desc)`. The money `canonicalizeIncomingMoney` precedent\n * (`via/index.ts:108`).\n *\n * A `[].`-wildcard multi-value path (`getAtPath` resolves >1 entries — an\n * array of nested objects, e.g. `'lines[].country'`, the same wildcard\n * convention `runLookupPresent` above already handles) normalizes EVERY\n * element's leaf value, not just a lone scalar (#652 fix — this used to bail\n * out entirely here while `runLookupEnforceWrite` below validated every\n * value unnormalized, so a legitimate altKey in an array position was\n * wrongly refused by closed-vocabulary enforcement). `setAtPathInPlace`\n * can't write into a `[].`-wildcard path, so the array is reconstructed\n * immutably instead, mirroring `runLookupPresent`'s own `field.includes\n * ('[].')` branch. A plain field whose OWN value is a bare top-level array\n * (not a `[].`-wildcard path) gets the SAME element-wise normalization\n * against `backing.altIndex` (#661 fix — `getAtPath` resolves it to one\n * opaque value, so the array itself is `values[0]`; a non-string element is\n * left untouched, mirroring the scalar branch's own `typeof !== 'string'`\n * skip — no parallel coercion invented for this shape).\n */\nfunction runLookupIngest(record: Record<string, unknown>, cfg: LookupViaConfig): Record<string, unknown> {\n const withAltKeys = Object.entries(cfg.lookupFields).filter(([, d]) => (d.altKeys?.length ?? 0) > 0)\n if (withAltKeys.length === 0) return record\n\n let result = record\n for (const [field, desc] of withAltKeys) {\n const backing = getAltIndexOrThrow(field, desc, cfg)\n if (!backing || backing.altIndex.size === 0) continue\n\n if (field.includes('[].')) {\n const [arrayKey, leaf] = field.split('[].')\n if (!leaf || leaf.includes('.')) continue\n const arr = record[arrayKey!]\n if (!Array.isArray(arr)) continue\n let changed = false\n const normalized = arr.map((item) => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return item\n const value = (item as Record<string, unknown>)[leaf]\n if (typeof value !== 'string') return item\n const canonical = backing.altIndex.get(value)\n if (canonical === undefined || canonical === value) return item\n changed = true\n return { ...(item as Record<string, unknown>), [leaf]: canonical }\n })\n if (!changed) continue\n if (result === record) result = { ...record }\n result[arrayKey!] = normalized\n continue\n }\n\n const values = getAtPath(record, field)\n if (values.length !== 1) continue\n const value = values[0]\n\n // `getAtPath`/`setAtPathInPlace` resolve `field` generically, dotted\n // paths included (e.g. 'meta.tags') — do not rewrite this branch to a\n // direct `record[field]` bracket access, which would silently stop\n // normalizing/enforcing a dotted-non-wildcard bare-array field (#661).\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n let changed = false\n const normalized = value.map((el) => {\n if (typeof el !== 'string') return el\n const canonical = backing.altIndex.get(el)\n if (canonical === undefined || canonical === el) return el\n changed = true\n return canonical\n })\n if (!changed) continue\n if (result === record) result = { ...record }\n setAtPathInPlace(result, field, normalized)\n continue\n }\n\n if (typeof value !== 'string') continue\n const canonical = backing.altIndex.get(value)\n if (canonical === undefined || canonical === value) continue\n if (result === record) result = { ...record }\n setAtPathInPlace(result, field, canonical)\n }\n return result\n}\n\n/**\n * `enforceWrite` — closed-vocabulary write refusal (#650 Task 3, spec §3).\n * Runs only for `vocabulary:'closed'` fields; `'open'` (the dictKey/dict\n * default) skips the check entirely, so #649's fix is additive — existing\n * dictKey/staticDict collections are unaffected. `ctx` carries no\n * cross-collection door (`id`/`vault`/`prior`/`emit` only, unchanged) —\n * membership is a vault-built closure on `cfg`, per spec.\n */\nasync function runLookupEnforceWrite(record: Record<string, unknown>, cfg: LookupViaConfig): Promise<void> {\n for (const [field, desc] of Object.entries(cfg.lookupFields)) {\n if (desc.vocabulary !== 'closed') continue\n // Checks every value `getAtPath` returns — for a `[].`-wildcard\n // multi-value path, that's every element's leaf value.\n // `runLookupIngest` above now normalizes each of those elements first\n // (#652), so what lands here for a `[].`-wildcard field is already\n // canonical; this loop's job stays membership, not normalization.\n for (const value of getAtPath(record, field)) {\n // A plain field whose OWN value is a bare top-level array (#661) —\n // `runLookupIngest` above has already normalized its elements'\n // altKeys, so membership-check each element through the SAME\n // `cfg.membership` closure the scalar branch below uses, refusing on\n // the first unknown one. A non-string element is skipped, mirroring\n // the scalar branch's own skip.\n if (Array.isArray(value)) {\n for (const el of value) {\n if (typeof el !== 'string') continue\n const known = cfg.membership ? await cfg.membership(field, el) : true\n if (!known) throw new UnknownLookupKeyError(desc.dimension, field, el)\n }\n continue\n }\n if (typeof value !== 'string') continue\n const known = cfg.membership ? await cfg.membership(field, value) : true\n if (!known) throw new UnknownLookupKeyError(desc.dimension, field, value)\n }\n }\n}\n\n/**\n * `compareForOrder` — exact ordering for a `sortBy`-declared lookup field\n * against the sync snapshot (#650 Task 6, spec §5, conflict resolution 4;\n * matrix-tier coverage added #650 Task 7). Opt-in: undeclared `sortBy`\n * (every dictKey/staticDict alias and every lookup field declared before\n * Task 6) returns `undefined` — falls through to the generic stored-value\n * comparator, byte-identical to today. Static tier reads `descriptor.table`\n * directly; reserved AND matrix (collection) tier both read `cfg.snapshotFor`\n * (the SAME live cache `presentForJoin`'s lookup half reads — see\n * `snapshot.ts`'s file header). The hook has no locale parameter\n * (`via/index.ts:128-129`, unchanged) — closes over the descriptor's own\n * `displayLocale` (the same locale-less-hinge default `runLookupPresent`\n * already uses); a `sortBy` field whose value isn't locale-keyed\n * (`present.by` undefined) never needs one. A `by`-keyed `sortBy` field\n * with NO declared `displayLocale` degrades to comparing the raw canonical\n * keys (silent — `LookupSnapshot.compareKeys` never throws; declare-time\n * warning at `descriptor.ts`'s `lookup()` factory; use\n * `orderBy(field, dir, {by:'label'})` — `resolveOrderLabel` below — for a\n * PER-CALL locale instead).\n */\nfunction compareLookupOrder(field: string, a: unknown, b: unknown, cfg: LookupViaConfig): number | undefined {\n if (typeof a !== 'string' || typeof b !== 'string') return undefined\n const desc = cfg.lookupFields[field]\n if (!desc || desc.sortBy === undefined) return undefined\n const rows = desc.backing === 'static'\n ? (desc.table ? new Map(Object.entries(desc.table)) : undefined)\n : cfg.snapshotFor?.(desc)\n if (!rows) return undefined\n return buildLookupSnapshot(desc.dimension, rows, desc).compareKeys(a, b, desc.displayLocale ?? '')\n}\n\n/**\n * `resolveOrderLabel` — per-key, PER-CALL-locale label resolution for\n * `orderBy(field, dir, { by: 'label' })` (#650 Task 7, spec §6 / seam map\n * Part 10 surprise 6's option (b)) — the channel `compareForOrder` above\n * structurally cannot serve, since `ViaBinding.compareForOrder` carries no\n * locale parameter. Consumed by `kernel/query/builder.ts`'s\n * `buildOrderLabelMaps` as the fallback for lookup fields the legacy dict\n * registries don't bridge (matrix tier; reserved/static tier already\n * resolves via that bridge — `JoinContext.resolveDictSource` — tried\n * FIRST by the caller, see `registry.ts`'s `collectLookupDictCompat` doc\n * comment). Reuses the exact same `cfg.snapshotFor`/`buildLookupSnapshot`\n * machinery as `compareLookupOrder` above, just with the per-call `locale`\n * in place of the descriptor's own `displayLocale` — falling back to\n * `displayLocale` only when the call itself is locale-less, the same\n * hinge order `runLookupPresent` already uses.\n */\nfunction resolveLookupOrderLabel(field: string, key: string, locale: string | undefined, cfg: LookupViaConfig): string | undefined {\n const desc = cfg.lookupFields[field]\n if (!desc) return undefined\n const rows = desc.backing === 'static'\n ? (desc.table ? new Map(Object.entries(desc.table)) : undefined)\n : cfg.snapshotFor?.(desc)\n if (!rows) return undefined\n return buildLookupSnapshot(desc.dimension, rows, desc).label(key, locale ?? desc.displayLocale ?? '')\n}\n\nexport function lookupBinding(cfg: LookupViaConfig): ViaBinding {\n return {\n brand: 'lookup',\n posture: { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: false },\n reservedPrefixes: ['_dict_', '_lookup_'],\n covers: (field) => field in cfg.lookupFields,\n ingest: (record) => runLookupIngest(record, cfg),\n enforceWrite: (record) => runLookupEnforceWrite(record, cfg),\n present: async (record, ctx) => runLookupPresent(record, ctx, cfg),\n compareForOrder: (field, a, b) => compareLookupOrder(field, a, b, cfg),\n resolveOrderLabel: (field, key, locale) => resolveLookupOrderLabel(field, key, locale, cfg),\n describeFragment: () => buildLookupDescribeFragment(cfg),\n }\n}\n\nexport function linkLookupVia(): void {\n installViaBinder('lookup', (c) => lookupBinding(c as LookupViaConfig))\n}\n","/**\n * `lookup()` / `enumOf()` / `dict()` descriptors — the three declaration\n * surfaces for the `'lookup'` via binding (#650 Task 2, phase D of the Via\n * port). One `LookupDescriptor` shape; the tiers are `backing` details:\n *\n * - `lookup(dimension, opts?)` — matrix tier: first-class collection backing\n * (default `backing:'collection'`) — a reference-collection dimension\n * (e.g. `countries`), open vocabulary by default.\n * - `enumOf(keys)` — enum tier: static in-config table, CLOSED\n * vocabulary, no backing store at all (no dimension name — pure inline\n * keys). Exported as `enumOf`; the barrel (`index.ts`) re-exports it as\n * `enum` (`enum` is a reserved word, so it can't be the function's own\n * name).\n * - `dict(dimension, opts?)` — dict tier: reserved `_dict_<dimension>`\n * micro-collection backing (the `vault.dictionary(name)` engine), open\n * vocabulary by default.\n *\n * Each factory calls {@link linkLookupVia} first — the declaration IS the\n * via binding's opt-in unit (#553), same pattern as `money()`/`i18nText()`/\n * `dictKey()`.\n */\n\nimport type { OnMissingPolicy } from '../i18n/policy.js'\nimport { linkLookupVia } from './binding.js'\n\n/** `'closed'` = enum semantics (membership enforced, Task 3); `'open'` permits unknown keys. */\nexport type Vocabulary = 'open' | 'closed'\n\n/** Where a dimension's rows live: static in-config table / reserved micro-collection / first-class collection. */\nexport type LookupBacking = 'static' | 'reserved' | 'collection'\n\n/** Delete-time referential policy for the backing dimension (default `'restrict'`, enforced in Task 5). */\nexport type OnDelete = 'restrict' | 'cascade' | 'nullify'\n\n/** The one descriptor shape every tier compiles to — tiers differ only by `backing`. */\nexport interface LookupDescriptor<Keys extends string = string> {\n readonly _viaBrand: 'lookup'\n /** Dimension/dictionary/target name. Empty for a bare `enumOf()` (no backing store, no name). */\n readonly dimension: string\n /** Canonical key field on the row (default `'id'`). */\n readonly key: string\n /** Candidate keys normalized to `key` on ingest (Task 3). */\n readonly altKeys?: readonly string[]\n readonly vocabulary: Vocabulary\n /** Dressing dimension: which backing-row field supplies `<field>Label`, optionally keyed `by` a sub-field (e.g. locale). */\n readonly present?: { readonly label: string; readonly by?: string }\n /** Field to sort by against the snapshot (Task 6). */\n readonly sortBy?: string\n readonly backing: LookupBacking\n readonly onDelete: OnDelete\n /** Static/enum inline key set. */\n readonly keys?: readonly Keys[]\n /** Static tier only: in-code `key -> { locale -> label }` table. */\n readonly table?: Readonly<Record<string, Readonly<Record<string, string>>>>\n /** Static hybrid hinge (the `staticDict` alias) — locale-less `<field>Label`. */\n readonly displayLocale?: string\n readonly onMissing?: OnMissingPolicy\n readonly substitute?: readonly string[]\n /** Inline display labels (value -> label), the dict-tier sync fallback (mirrors `dictKey`'s `labels`). */\n readonly labels?: Record<string, string>\n}\n\n/**\n * `sortBy`/`present.by` coupling warning (#650 Task 7, T6 minor — task-6-\n * report.md Concern #4). A `by`-keyed `present` (locale-map) field used as\n * `sortBy` needs a declared `displayLocale` for the locale-less\n * `compareForOrder` sort hook (a plain `orderBy(field)`, no `{by:'label'}`)\n * to resolve anything — without one it silently degrades to comparing the\n * raw canonical keys (never throws — `LookupSnapshot.compareKeys`'s\n * contract). `orderBy(field, dir, {by:'label'})` doesn't need this (it\n * carries its own per-call locale via `ViaBinding.resolveOrderLabel`,\n * #650 Task 7) — only a sortBy-driven PLAIN orderBy does. Warn once at\n * declare time rather than degrade silently at query time.\n */\nfunction warnIfSortByNeedsDisplayLocale(\n dimension: string,\n opts?: { sortBy?: string; present?: { by?: string }; displayLocale?: string },\n): void {\n if (opts?.sortBy !== undefined && opts.present?.by !== undefined && opts.displayLocale === undefined) {\n console.warn(\n `[noy-db] lookup(\"${dimension}\"): sortBy \"${opts.sortBy}\" is locale-keyed (present.by is set) but no ` +\n `displayLocale is declared — a locale-less orderBy() will silently sort by the raw stored key instead ` +\n `of the resolved label. Declare displayLocale, or sort via orderBy(field, dir, { by: 'label' }), which ` +\n `resolves at the query's own per-call locale.`,\n )\n }\n}\n\n/**\n * Matrix tier — first-class collection backing (default `backing:'collection'`).\n * `dimension` names the backing collection (e.g. `'countries'`).\n *\n * `backing` may be overridden to construct any tier through this one\n * factory — e.g. `lookup(name, { backing:'static', table, displayLocale })`\n * is the table-bearing static tier `staticDict()` compiles onto. The\n * `table`/`displayLocale`/`keys`/`onMissing`/`substitute`/`labels` options\n * are a superset of the brief's literal opts list: `LookupDescriptor`\n * already carries these fields for exactly this purpose, and without them\n * `backing:'static'` would be unreachable stand-alone through `lookup()`\n * (see task-2-report.md's \"Design decisions\").\n *\n * **`altKeys` caveat (matrix tier only)**: normalizing an altKey candidate\n * to its canonical `key` requires the backing `dimension` collection to be\n * open in EAGER mode (the default; `{ prefetch: false }` — lazy mode — is\n * unsupported). A `put()` on a field with `altKeys` whose backing collection\n * is lazy throws a `ValidationError` naming the field and dimension; open\n * the backing collection without `{ prefetch: false }`, or drop `altKeys`.\n */\nexport function lookup<Keys extends string>(\n dimension: string,\n opts?: {\n key?: string\n altKeys?: readonly string[]\n vocabulary?: Vocabulary\n present?: { label: string; by?: string }\n sortBy?: string\n backing?: LookupBacking\n onDelete?: OnDelete\n keys?: readonly Keys[]\n table?: Readonly<Record<string, Readonly<Record<string, string>>>>\n displayLocale?: string\n onMissing?: OnMissingPolicy\n substitute?: readonly string[]\n labels?: Record<string, string>\n },\n): LookupDescriptor<Keys> {\n linkLookupVia()\n warnIfSortByNeedsDisplayLocale(dimension, opts)\n return {\n _viaBrand: 'lookup',\n dimension,\n key: opts?.key ?? 'id',\n vocabulary: opts?.vocabulary ?? 'open',\n backing: opts?.backing ?? 'collection',\n onDelete: opts?.onDelete ?? 'restrict',\n ...(opts?.altKeys !== undefined ? { altKeys: opts.altKeys } : {}),\n ...(opts?.present !== undefined ? { present: opts.present } : {}),\n ...(opts?.sortBy !== undefined ? { sortBy: opts.sortBy } : {}),\n ...(opts?.keys !== undefined ? { keys: opts.keys } : {}),\n ...(opts?.table !== undefined ? { table: opts.table } : {}),\n ...(opts?.displayLocale !== undefined ? { displayLocale: opts.displayLocale } : {}),\n ...(opts?.onMissing !== undefined ? { onMissing: opts.onMissing } : {}),\n ...(opts?.substitute !== undefined ? { substitute: opts.substitute } : {}),\n ...(opts?.labels !== undefined ? { labels: opts.labels } : {}),\n }\n}\n\n/**\n * Enum tier — static in-config table, CLOSED vocabulary, no backing store.\n * No dimension name (pure inline keys) — `dimension` is `''`.\n */\nexport function enumOf<const Keys extends readonly string[]>(keys: Keys): LookupDescriptor<Keys[number]> {\n linkLookupVia()\n return {\n _viaBrand: 'lookup',\n dimension: '',\n key: 'id',\n vocabulary: 'closed',\n backing: 'static',\n onDelete: 'restrict',\n keys,\n }\n}\n\n/**\n * Dict tier — reserved `_dict_<dimension>` micro-collection backing (the\n * `vault.dictionary(dimension)` engine), open vocabulary by default. The\n * native equivalent of `dictKey()`.\n *\n * Unlike `lookup()`'s matrix tier, `dict()` has no `altKeys` option — its\n * `_dict_<dimension>` backing is the always-synchronous `LookupHandle`\n * write-through cache, never a `vault.collection()` that can be opened\n * `{ prefetch: false }`, so the matrix tier's lazy-mode altKeys restriction\n * does not apply here.\n */\nexport function dict<Keys extends string>(\n dimension: string,\n opts?: {\n keys?: readonly Keys[]\n vocabulary?: Vocabulary\n present?: { label: string; by?: string }\n onDelete?: OnDelete\n onMissing?: OnMissingPolicy\n substitute?: readonly string[]\n },\n): LookupDescriptor<Keys> {\n linkLookupVia()\n return {\n _viaBrand: 'lookup',\n dimension,\n key: 'id',\n vocabulary: opts?.vocabulary ?? 'open',\n backing: 'reserved',\n onDelete: opts?.onDelete ?? 'restrict',\n ...(opts?.keys !== undefined ? { keys: opts.keys } : {}),\n ...(opts?.present !== undefined ? { present: opts.present } : {}),\n ...(opts?.onMissing !== undefined ? { onMissing: opts.onMissing } : {}),\n ...(opts?.substitute !== undefined ? { substitute: opts.substitute } : {}),\n }\n}\n"],"mappings":"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","names":["value","canonical","known"]}
1
+ {"version":3,"sources":["../src/with-commit/numbering/descriptor.ts","../src/via/computed/binding.ts","../src/via/computed/descriptor.ts","../src/via/lookup/binding.ts","../src/via/lookup/descriptor.ts"],"sourcesContent":["/**\n * @category capability\n * Deferred-numbering config descriptor. See\n * docs/superpowers/specs/2026-06-08-sealed-numbering-and-store-clock-design.md.\n */\n\n/** A registered deferred-numbering series. */\nexport interface DeferredNumberingConfig {\n /** Series name — the key passed to `vault.sequence(series)`. */\n readonly series: string\n /** Collection holding the records to number. */\n readonly collection: string\n /** Field on each record where the assigned serial is written. */\n readonly field: string\n /**\n * Minimum wall-clock age (ms) before an entry is eligible at a pass, in\n * addition to the interval commit-wait. Default 0 — the store-clock\n * interval (`storeLatest ≤ now.earliest`) is the correctness mechanism.\n */\n readonly settleWindowMs: number\n}\n\n/**\n * Options for {@link withDeferredNumbering} (#844b — was an inline literal, so\n * unnameable). Same shape as {@link DeferredNumberingConfig} except\n * `settleWindowMs` is optional; the factory defaults it to 0.\n */\nexport interface WithDeferredNumberingOptions {\n /** Series name — the key passed to `vault.sequence(series)`. */\n readonly series: string\n /** Collection holding the records to number. */\n readonly collection: string\n /** Field on each record where the assigned serial is written. */\n readonly field: string\n /** See {@link DeferredNumberingConfig.settleWindowMs}. Default 0. */\n readonly settleWindowMs?: number\n}\n\n/** Declare a deferred-numbering series. Pass the result in `createNoydb({ numbering: [...] })`. */\nexport function withDeferredNumbering(opts: WithDeferredNumberingOptions): DeferredNumberingConfig {\n return {\n series: opts.series,\n collection: opts.collection,\n field: opts.field,\n settleWindowMs: opts.settleWindowMs ?? 0,\n }\n}\n","/**\n * The `computed` via-binding (#638 Task 7) — covers ONLY `mode: 'virtual'`\n * fields. Materialized entries never reach here: they compile into\n * `mergedComputed` (`kernel/collection-config.ts`) and run through the\n * EXISTING stage-5 `evalComputedFields` write path, byte-for-byte unchanged\n * (the behavior lock).\n *\n * A virtual field is computed on READ, inside `present()` — the money-\n * `Formatted`/i18n-`Label` precedent (seam map Part 4) generalized to a\n * user-declared function. It is NEVER stored (no `encodeAtRest`/\n * `decodeAtRest` — the field never appears in `_data` or any `_sealed`\n * slot), and its posture is fixed `queryable: 'none'` — there is no stored/\n * indexed form to query against, regardless of what its sources would\n * otherwise permit (`ViaGraph`'s grain-'virtual' clamp, `kernel/via/graph.ts`,\n * is the belt; this binding's own static posture is the suspenders for a\n * depsless virtual field, which registers no graph edge at all).\n *\n * Export/read redaction for a TAINTED virtual field (sourced from a\n * classified/sealed field) is NOT this binding's job — it stays ignorant of\n * taint, exactly like `evalComputedFields` stays ignorant of it for\n * materialized fields. `kernel/via/taint-binding.ts#taintBinding` (appended\n * to the pipeline by `via/graph-wiring.ts#applyTaintOverlay`, AFTER this\n * binding) overwrites a tainted virtual field's value with the same\n * `EXPORT_REDACTION_MARKER` on every present() — the one enforcement seam\n * every via feature's taint already routes through.\n */\nimport type { ViaBinding, ViaPosture } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { ComputedDescriptor } from './descriptor.js'\n\nexport interface ComputedViaConfig {\n /** field name -> its virtual-mode descriptor. Materialized entries are never present here. */\n readonly virtualFields: ReadonlyMap<string, ComputedDescriptor>\n}\n\nconst VIRTUAL_POSTURE: ViaPosture = { encryptedAtRest: 'envelope', queryable: 'none', exportable: true, forgettable: false }\n\nexport function computedBinding(cfg: ComputedViaConfig): ViaBinding {\n const fields = cfg.virtualFields\n return {\n brand: 'computed',\n posture: VIRTUAL_POSTURE,\n covers: (field) => fields.has(field),\n present: (record) => {\n let r = record\n for (const [field, desc] of fields) {\n if (r === record) r = { ...record }\n r[field] = desc.fn(r)\n }\n return r\n },\n }\n}\n\nexport function linkComputedVia(): void {\n installViaBinder('computed', (cfg) => computedBinding(cfg as ComputedViaConfig))\n}\n","/**\n * computed() — the declaration factory for a field whose value is DERIVED\n * from other fields on the same record (#638 Task 7, spec §6). Composes\n * with `via()` (the locked grammar since phase A: `via(computed(fn, { deps,\n * mode }), money('EUR'))`), grouped by `_viaBrand` like every other via\n * feature (`kernel/via/compose.ts#mergeViaFields`).\n *\n * `mode` picks where the function runs:\n * - `'materialized'` (default) — TODAY's stage-5 write-time eager compute\n * (`with-formula/computed/index.ts#evalComputedFields`), stored like any\n * other field. Byte-for-byte the existing `computed: { field: fn }` sugar.\n * - `'virtual'` — rides the `present` read phase (the money-`Formatted`/\n * i18n-`Label` precedent, seam map Part 4): computed fresh on every\n * read, NEVER stored, `queryable: 'none'`, excluded from export unless\n * its declared `deps` permit (identical taint rule to materialized —\n * see `via/computed/binding.ts`).\n *\n * `deps` names the OTHER fields `fn` reads — feeds `ViaGraph` (Task 1/2) so\n * a source's taint (e.g. a classified field) propagates to this derived\n * field. A depsless entry is fine UNLESS the collection also declares\n * classified fields (`kernel/collection-config.ts#resolveComputedEdges`\n * refuses it — closes the #636 opaque-function leak).\n */\nimport type { ViaDescriptor } from '../../kernel/via/index.js'\nimport { linkComputedVia } from './binding.js'\n\nexport interface ComputedDescriptor extends ViaDescriptor {\n readonly _viaBrand: 'computed'\n readonly fn: (record: Record<string, unknown>) => unknown\n readonly deps?: readonly string[]\n readonly mode: 'materialized' | 'virtual'\n}\n\nexport function computed(\n fn: (record: Record<string, unknown>) => unknown,\n opts?: { readonly deps?: readonly string[]; readonly mode?: 'materialized' | 'virtual' },\n): ComputedDescriptor {\n // Self-link, exactly as `money()` and `lookup()` do (#813). Until this call\n // existed, `computed` was the only via feature whose binder was installed by a\n // DIFFERENT module — `port/with/computed-strategy.ts` — rather than by its own\n // declaration factory. That works whenever the kernel spine and the consumer's\n // `computed` import resolve to one module instance, and fails when they do not:\n // under vitest's `server.deps.inline`, a consumer got the descriptor from one\n // transformed instance while the binder registry consulted at bind time lived in\n // another, producing `VIA_NOT_LINKED` for a descriptor `isComputedDescriptor()`\n // accepted. money/i18n/lookup were immune precisely because they self-link.\n //\n // Constructing a descriptor is the binding's opt-in unit, so linking here makes\n // the guarantee local: whatever instance produced the descriptor also has the\n // binder. `installViaBinder` is idempotent + first-wins, so the eager call in\n // `port/with/computed-strategy.ts` stays harmless.\n linkComputedVia()\n return {\n _viaBrand: 'computed',\n fn,\n ...(opts?.deps !== undefined ? { deps: opts.deps } : {}),\n mode: opts?.mode ?? 'materialized',\n }\n}\n\nexport function isComputedDescriptor(x: unknown): x is ComputedDescriptor {\n return typeof x === 'object' && x !== null && (x as { _viaBrand?: unknown })._viaBrand === 'computed'\n}\n","/**\n * The `'lookup'` `ViaBinding` — wires the lookup engine (present-time label\n * dressing across all three backing tiers) into the kernel's generic Via\n * port. Mirrors `via/i18n/binding.ts`'s #553 static-link pattern; the\n * present-time label-dressing algorithm below is adapted from\n * `via-i18n/binding.ts:253-337` (the same wildcard/array/scalar handling,\n * the same `onMissing`/`substitute` policy engine), generalized to branch on\n * `backing` instead of a static-vs-dynamic descriptor-shape check. For the\n * `'static'` and `'reserved'` tiers this delegates to `cfg.lookupLabelResolver`\n * — the SAME vault-built closure the i18n binding's `dictLabelResolver` uses\n * (static table first, else the `vault.dictionary()` handle) — so a native\n * `dict()`/`lookup(static)` field resolves through the identical label data\n * as its `dictKey()`/`staticDict()` alias (the byte-equivalence lock).\n *\n * `lookup()`/`enumOf()`/`dict()` each call {@link linkLookupVia} first — the\n * same #553 pattern `money()`/`dictKey()` use.\n *\n * `buildClause` (label-predicate queries) is still undeclared — out of scope\n * for #650. `compareForOrder` (#650 Task 6, spec §5; matrix tier added Task\n * 7) resolves a `sortBy`-declared field's ordering via `cfg.snapshotFor`'s\n * sync snapshot; the hook signature is UNCHANGED (`via/index.ts:128-129` — no\n * locale param), so it closes over each descriptor's own `displayLocale`\n * (the same locale-less-hinge default `runLookupPresent`'s\n * `hasStaticDisplay` branch already uses). `resolveOrderLabel` (#650 Task\n * 7) is the PER-CALL-locale sibling `orderBy(..., {by:'label'})` needs —\n * see its own doc comment below. `describeFragment` (#650 Task 7) is the\n * first-ever consumed `ViaBinding.describeFragment` implementation — see\n * `with-shape/introspection/describe.ts`'s `buildDescription`.\n */\nimport type { ViaBinding, ViaReadCtx } from '../../kernel/via/index.js'\nimport { installViaBinder } from '../../kernel/via/index.js'\nimport type { LookupDescriptor, LookupBacking, Vocabulary, OnDelete } from './descriptor.js'\nimport { resolvePolicy, type Layer } from '../i18n/policy.js'\nimport { LocaleNotSpecifiedError, UnknownLookupKeyError, ValidationError } from '../../kernel/errors.js'\nimport { getAtPath, setAtPathInPlace } from '../../kernel/paths.js'\nimport type { MaterializedBacking } from './registry.js'\nimport { buildLookupSnapshot } from './snapshot.js'\n\n/**\n * Config a collection's lookup declarations resolve to — the binding's\n * construction input. `lookupLabelResolver`/`getLookupBacking`/`membership`/\n * `snapshotFor` are vault-built closures (never a `Collection` handle,\n * keyring, or DEK/CEK — the zero-knowledge boundary).\n */\nexport interface LookupViaConfig {\n readonly lookupFields: Record<string, LookupDescriptor>\n readonly lookupLabelResolver?: (dimension: string, key: string, locale: string, fallback?: unknown) => Promise<string | undefined>\n /**\n * The matrix (collection) tier's present-time backing-row source, keyed by the full\n * descriptor (not a bare dimension name) so the closure can resolve by `descriptor.key`,\n * not the backing row's PUT-id (#651 Task 3 — the same descriptor-gaining dispatch Task 7\n * already applied to `snapshotFor`).\n */\n readonly getLookupBacking?: (descriptor: LookupDescriptor) => (key: string) => Promise<Record<string, unknown> | undefined>\n /** Closed-vocabulary write-time membership test (#650 Task 3) — `(field, key) => known?`. */\n readonly membership?: (field: string, key: string) => boolean | Promise<boolean>\n /** Sync per-descriptor altKey index — `ingest`'s normalization source (#650 Task 3). */\n readonly getAltIndex?: (desc: LookupDescriptor) => MaterializedBacking | undefined\n /**\n * Sync materialized `key -> row` rows for a lookup descriptor (#650 Task\n * 6, spec §5; matrix-tier routing added #650 Task 7) — `compareForOrder`\n * and `resolveOrderLabel` below, and `snapshot.ts`'s `presentForJoin`\n * builder, all read this same vault-built closure. Reserved AND\n * collection (matrix) tier route here (the vault wires\n * `dimension -> LookupHandle.snapshotEntries()` / `dimension ->\n * collection.querySourceForJoin().snapshot()` respectively, keyed by\n * `descriptor.key` for the matrix case — see `registry.ts`'s\n * `buildLookupSnapshotRows`); static tier is resolved locally from\n * `descriptor.table` without calling this.\n */\n readonly snapshotFor?: (descriptor: LookupDescriptor) => ReadonlyMap<string, Record<string, unknown>> | undefined\n readonly collectionName: string\n}\n\n/** Enum tier (`backing:'static'`, no `table`) has no label source at all — never dressed. */\nfunction hasLabelSource(desc: LookupDescriptor): boolean {\n return !(desc.backing === 'static' && desc.table === undefined)\n}\n\n/**\n * Resolve one key's label. `'static'`/`'reserved'` both go through\n * `cfg.lookupLabelResolver` (mirrors `dictLabelResolver`'s own static-table-\n * first-else-reserved-handle branching — the SAME closure is reused, see\n * `kernel/vault.ts`). `'collection'` (matrix tier) reads the declared\n * `present.label` off the backing row via `cfg.getLookupBacking`; when\n * `present.by` is set, that field's value is a `{ locale -> label }` map\n * indexed by the effective locale.\n */\nasync function fetchLookupLabel(\n desc: LookupDescriptor,\n key: string,\n effLocale: string,\n fieldFallback: string | readonly string[] | undefined,\n cfg: LookupViaConfig,\n): Promise<string | undefined> {\n if (desc.backing === 'collection') {\n const getRow = cfg.getLookupBacking?.(desc)\n const row = getRow ? await getRow(key) : undefined\n const labelField = desc.present?.label\n if (!row || labelField === undefined) return undefined\n const raw = row[labelField]\n if (desc.present?.by !== undefined) {\n return raw && typeof raw === 'object' ? (raw as Record<string, unknown>)[effLocale] as string | undefined : undefined\n }\n return typeof raw === 'string' ? raw : undefined\n }\n return cfg.lookupLabelResolver?.(desc.dimension, key, effLocale, fieldFallback)\n}\n\n/** `present` — resolve `<field>Label` for every declared lookup field. Adapted from `via-i18n/binding.ts:253-337`. */\nasync function runLookupPresent(\n record: Record<string, unknown>,\n ctx: ViaReadCtx,\n cfg: LookupViaConfig,\n): Promise<Record<string, unknown>> {\n const fields = Object.entries(cfg.lookupFields).filter(([, d]) => hasLabelSource(d))\n if (fields.length === 0) return record\n\n const locale = typeof ctx.locale === 'string' ? ctx.locale : undefined\n const fallback = ctx.fallback as string | readonly string[] | undefined\n const layer = ctx.layer as Layer\n\n // `{ locale: 'raw' }` wants the untouched record — mirrors\n // `via-i18n/binding.ts`'s `locale !== 'raw'` dict-label gate exactly (no\n // synthetic `<field>Label` derivative on a raw read).\n if (locale === 'raw') return record\n\n // Static-display hybrid hinge: a `backing:'static'` field with a declared\n // `displayLocale` resolves its `<field>Label` even under a locale-less\n // read (mirrors staticDict's `hasStaticDisplay` gate).\n const hasStaticDisplay = fields.some(([, d]) => d.backing === 'static' && d.displayLocale !== undefined)\n if (!locale && !hasStaticDisplay) return record\n\n let result = record\n const withLabels = { ...result }\n\n for (const [field, desc] of fields) {\n const policy = desc.onMissing ? resolvePolicy(desc.onMissing, layer) : 'null'\n const fieldFallback = policy === 'substitute' ? (fallback ?? desc.substitute) : fallback\n const effLocale = locale ?? (desc.backing === 'static' ? desc.displayLocale : undefined)\n\n const resolveKey = async (key: string): Promise<string | null> => {\n if (!effLocale) {\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(field, `lookup \"${field}\": no locale active to resolve key \"${key}\".`)\n }\n return null\n }\n const label = await fetchLookupLabel(desc, key, effLocale, fieldFallback, cfg)\n if (label === undefined) {\n if (policy === 'throw') {\n throw new LocaleNotSpecifiedError(field, `lookup \"${field}\": no label for key \"${key}\" in locale \"${effLocale}\".`)\n }\n return null\n }\n return label\n }\n\n if (field.includes('[].')) {\n const parts = field.split('[].')\n const arrayKey = parts[0]!\n const leaf = parts[1]\n if (!leaf || leaf.includes('.')) continue\n const arr = (withLabels as Record<string, unknown>)[arrayKey]\n if (!Array.isArray(arr)) continue\n const labelKey = `${leaf}Label`\n ;(withLabels as Record<string, unknown>)[arrayKey] = await Promise.all(\n arr.map(async (el) => {\n if (!el || typeof el !== 'object' || Array.isArray(el)) return el\n const k = (el as Record<string, unknown>)[leaf]\n if (typeof k !== 'string') return el\n return { ...(el as Record<string, unknown>), [labelKey]: await resolveKey(k) }\n }),\n )\n continue\n }\n\n const val = result[field]\n if (Array.isArray(val)) {\n withLabels[`${field}Label`] = await Promise.all(\n val.map(async (k) => ({ key: k, label: typeof k === 'string' ? await resolveKey(k) : null })),\n )\n } else if (typeof val === 'string') {\n const label = await resolveKey(val)\n if (label !== null) withLabels[`${field}Label`] = label\n }\n }\n\n result = withLabels\n return result\n}\n\n/**\n * One field's `lookup` descriptor as it appears on a `ViaBinding.\n * describeFragment()` payload (#650 Task 7 — the first real consumer,\n * `with-shape/introspection/describe.ts`'s `buildDescription`, imports this\n * type directly; `describe.ts` is NOT under `kernel/**`, so it's free to\n * import concrete via/ types the way it already does for\n * `LookupDescriptor`/`MoneyDescriptor`/etc.). `dimension` is OMITTED (not\n * emitted as `''`) for a bare `enumOf()` descriptor — the #650 Task 2\n * `dimension:''` sentinel resolved: no dimension name means no `dimension`\n * key, not a meaningless empty string (T2 carry, #650 Task 7).\n */\nexport interface LookupDescribeFragmentEntry {\n readonly dimension?: string\n readonly backing: LookupBacking\n readonly vocabulary: Vocabulary\n readonly key: string\n readonly altKeys?: readonly string[]\n readonly present?: { readonly label: string; readonly by?: string }\n readonly sortBy?: string\n readonly onDelete: OnDelete\n /** Statically-known closed-vocabulary key set (declared `keys`, or a static table's own keys). Omitted when membership lives only in the backing collection/dictionary (open vocabulary, or closed with no declared `keys`). */\n readonly keys?: readonly string[]\n}\n\n/** The `'lookup'` binding's `describeFragment()` payload shape. */\nexport interface LookupDescribeFragment {\n readonly lookupFields: Record<string, LookupDescribeFragmentEntry>\n}\n\nfunction buildLookupDescribeFragment(cfg: LookupViaConfig): Record<string, unknown> {\n const lookupFields: Record<string, LookupDescribeFragmentEntry> = {}\n for (const [field, desc] of Object.entries(cfg.lookupFields)) {\n lookupFields[field] = {\n ...(desc.dimension !== '' ? { dimension: desc.dimension } : {}),\n backing: desc.backing,\n vocabulary: desc.vocabulary,\n key: desc.key,\n onDelete: desc.onDelete,\n ...(desc.altKeys !== undefined ? { altKeys: desc.altKeys } : {}),\n ...(desc.present !== undefined ? { present: desc.present } : {}),\n ...(desc.sortBy !== undefined ? { sortBy: desc.sortBy } : {}),\n // #657 — static tier: when no `keys` was explicitly declared but a\n // `table` was (the staticDict()-equivalent `lookup(dim, {backing:\n // 'static', table})` shape), the table's own key set IS the\n // statically-known closed-vocabulary set the DescribedField.lookup\n // docblock promises (\"declared `keys`, OR a static table's own\n // keys\"). Reserved/matrix tiers never carry `table`, so this branch\n // never fires for them — their `keys` emission is unchanged.\n ...(desc.keys !== undefined\n ? { keys: desc.keys }\n : desc.backing === 'static' && desc.table !== undefined ? { keys: Object.keys(desc.table) } : {}),\n }\n }\n return { lookupFields }\n}\n\n/**\n * `cfg.getAltIndex(desc)` (matrix tier) reads the backing collection's cache\n * via `querySourceForJoin()` (`buildLookupAltIndex`, registry.ts) — which\n * throws a `.join()`-branded message when that collection was opened\n * `{prefetch:false}` (lazy mode is unsupported for altKey normalization).\n * Normalization must never silently not happen (the banned silent-no-op\n * class, #650 Task 3 review, Important 2) — detect that specific failure\n * and surface a CLEAR, lookup-branded `ValidationError` at the point of\n * failure instead of letting the confusing join-branded one leak onto an\n * unrelated `put()`. Any OTHER error (e.g. `materializeBackingTable`'s own\n * altKey-collision `ValidationError`) propagates unchanged.\n */\nfunction getAltIndexOrThrow(field: string, desc: LookupDescriptor, cfg: LookupViaConfig): MaterializedBacking | undefined {\n try {\n return cfg.getAltIndex?.(desc)\n } catch (err) {\n if (err instanceof Error && err.message.includes('lazy-mode')) {\n throw new ValidationError(\n `lookup: altKeys on \"${field}\" require the backing collection \"${desc.dimension}\" to be ` +\n `prefetch-enabled (lazy mode unsupported); open it without {prefetch:false} or drop altKeys.`,\n )\n }\n throw err\n }\n}\n\n/**\n * `ingest` — altKey candidate values normalize to the canonical key (#650\n * Task 3, spec §3). Pure, sync, idempotent (a canonical key maps to\n * itself); no store read — consults the pre-materialized\n * `cfg.getAltIndex(desc)`. The money `canonicalizeIncomingMoney` precedent\n * (`via/index.ts:108`).\n *\n * A `[].`-wildcard multi-value path (`getAtPath` resolves >1 entries — an\n * array of nested objects, e.g. `'lines[].country'`, the same wildcard\n * convention `runLookupPresent` above already handles) normalizes EVERY\n * element's leaf value, not just a lone scalar (#652 fix — this used to bail\n * out entirely here while `runLookupEnforceWrite` below validated every\n * value unnormalized, so a legitimate altKey in an array position was\n * wrongly refused by closed-vocabulary enforcement). `setAtPathInPlace`\n * can't write into a `[].`-wildcard path, so the array is reconstructed\n * immutably instead, mirroring `runLookupPresent`'s own `field.includes\n * ('[].')` branch. A plain field whose OWN value is a bare top-level array\n * (not a `[].`-wildcard path) gets the SAME element-wise normalization\n * against `backing.altIndex` (#661 fix — `getAtPath` resolves it to one\n * opaque value, so the array itself is `values[0]`; a non-string element is\n * left untouched, mirroring the scalar branch's own `typeof !== 'string'`\n * skip — no parallel coercion invented for this shape).\n */\nfunction runLookupIngest(record: Record<string, unknown>, cfg: LookupViaConfig): Record<string, unknown> {\n const withAltKeys = Object.entries(cfg.lookupFields).filter(([, d]) => (d.altKeys?.length ?? 0) > 0)\n if (withAltKeys.length === 0) return record\n\n let result = record\n for (const [field, desc] of withAltKeys) {\n const backing = getAltIndexOrThrow(field, desc, cfg)\n if (!backing || backing.altIndex.size === 0) continue\n\n if (field.includes('[].')) {\n const [arrayKey, leaf] = field.split('[].')\n if (!leaf || leaf.includes('.')) continue\n const arr = record[arrayKey!]\n if (!Array.isArray(arr)) continue\n let changed = false\n const normalized = arr.map((item) => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) return item\n const value = (item as Record<string, unknown>)[leaf]\n if (typeof value !== 'string') return item\n const canonical = backing.altIndex.get(value)\n if (canonical === undefined || canonical === value) return item\n changed = true\n return { ...(item as Record<string, unknown>), [leaf]: canonical }\n })\n if (!changed) continue\n if (result === record) result = { ...record }\n result[arrayKey!] = normalized\n continue\n }\n\n const values = getAtPath(record, field)\n if (values.length !== 1) continue\n const value = values[0]\n\n // `getAtPath`/`setAtPathInPlace` resolve `field` generically, dotted\n // paths included (e.g. 'meta.tags') — do not rewrite this branch to a\n // direct `record[field]` bracket access, which would silently stop\n // normalizing/enforcing a dotted-non-wildcard bare-array field (#661).\n if (Array.isArray(value)) {\n if (value.length === 0) continue\n let changed = false\n const normalized = value.map((el) => {\n if (typeof el !== 'string') return el\n const canonical = backing.altIndex.get(el)\n if (canonical === undefined || canonical === el) return el\n changed = true\n return canonical\n })\n if (!changed) continue\n if (result === record) result = { ...record }\n setAtPathInPlace(result, field, normalized)\n continue\n }\n\n if (typeof value !== 'string') continue\n const canonical = backing.altIndex.get(value)\n if (canonical === undefined || canonical === value) continue\n if (result === record) result = { ...record }\n setAtPathInPlace(result, field, canonical)\n }\n return result\n}\n\n/**\n * `enforceWrite` — closed-vocabulary write refusal (#650 Task 3, spec §3).\n * Runs only for `vocabulary:'closed'` fields; `'open'` (the dictKey/dict\n * default) skips the check entirely, so #649's fix is additive — existing\n * dictKey/staticDict collections are unaffected. `ctx` carries no\n * cross-collection door (`id`/`vault`/`prior`/`emit` only, unchanged) —\n * membership is a vault-built closure on `cfg`, per spec.\n */\nasync function runLookupEnforceWrite(record: Record<string, unknown>, cfg: LookupViaConfig): Promise<void> {\n for (const [field, desc] of Object.entries(cfg.lookupFields)) {\n if (desc.vocabulary !== 'closed') continue\n // Checks every value `getAtPath` returns — for a `[].`-wildcard\n // multi-value path, that's every element's leaf value.\n // `runLookupIngest` above now normalizes each of those elements first\n // (#652), so what lands here for a `[].`-wildcard field is already\n // canonical; this loop's job stays membership, not normalization.\n for (const value of getAtPath(record, field)) {\n // A plain field whose OWN value is a bare top-level array (#661) —\n // `runLookupIngest` above has already normalized its elements'\n // altKeys, so membership-check each element through the SAME\n // `cfg.membership` closure the scalar branch below uses, refusing on\n // the first unknown one. A non-string element is skipped, mirroring\n // the scalar branch's own skip.\n if (Array.isArray(value)) {\n for (const el of value) {\n if (typeof el !== 'string') continue\n const known = cfg.membership ? await cfg.membership(field, el) : true\n if (!known) throw new UnknownLookupKeyError(desc.dimension, field, el)\n }\n continue\n }\n if (typeof value !== 'string') continue\n const known = cfg.membership ? await cfg.membership(field, value) : true\n if (!known) throw new UnknownLookupKeyError(desc.dimension, field, value)\n }\n }\n}\n\n/**\n * `compareForOrder` — exact ordering for a `sortBy`-declared lookup field\n * against the sync snapshot (#650 Task 6, spec §5, conflict resolution 4;\n * matrix-tier coverage added #650 Task 7). Opt-in: undeclared `sortBy`\n * (every dictKey/staticDict alias and every lookup field declared before\n * Task 6) returns `undefined` — falls through to the generic stored-value\n * comparator, byte-identical to today. Static tier reads `descriptor.table`\n * directly; reserved AND matrix (collection) tier both read `cfg.snapshotFor`\n * (the SAME live cache `presentForJoin`'s lookup half reads — see\n * `snapshot.ts`'s file header). The hook has no locale parameter\n * (`via/index.ts:128-129`, unchanged) — closes over the descriptor's own\n * `displayLocale` (the same locale-less-hinge default `runLookupPresent`\n * already uses); a `sortBy` field whose value isn't locale-keyed\n * (`present.by` undefined) never needs one. A `by`-keyed `sortBy` field\n * with NO declared `displayLocale` degrades to comparing the raw canonical\n * keys (silent — `LookupSnapshot.compareKeys` never throws; declare-time\n * warning at `descriptor.ts`'s `lookup()` factory; use\n * `orderBy(field, dir, {by:'label'})` — `resolveOrderLabel` below — for a\n * PER-CALL locale instead).\n */\nfunction compareLookupOrder(field: string, a: unknown, b: unknown, cfg: LookupViaConfig): number | undefined {\n if (typeof a !== 'string' || typeof b !== 'string') return undefined\n const desc = cfg.lookupFields[field]\n if (!desc || desc.sortBy === undefined) return undefined\n const rows = desc.backing === 'static'\n ? (desc.table ? new Map(Object.entries(desc.table)) : undefined)\n : cfg.snapshotFor?.(desc)\n if (!rows) return undefined\n return buildLookupSnapshot(desc.dimension, rows, desc).compareKeys(a, b, desc.displayLocale ?? '')\n}\n\n/**\n * `resolveOrderLabel` — per-key, PER-CALL-locale label resolution for\n * `orderBy(field, dir, { by: 'label' })` (#650 Task 7, spec §6 / seam map\n * Part 10 surprise 6's option (b)) — the channel `compareForOrder` above\n * structurally cannot serve, since `ViaBinding.compareForOrder` carries no\n * locale parameter. Consumed by `kernel/query/builder.ts`'s\n * `buildOrderLabelMaps` as the fallback for lookup fields the legacy dict\n * registries don't bridge (matrix tier; reserved/static tier already\n * resolves via that bridge — `JoinContext.resolveDictSource` — tried\n * FIRST by the caller, see `registry.ts`'s `collectLookupDictCompat` doc\n * comment). Reuses the exact same `cfg.snapshotFor`/`buildLookupSnapshot`\n * machinery as `compareLookupOrder` above, just with the per-call `locale`\n * in place of the descriptor's own `displayLocale` — falling back to\n * `displayLocale` only when the call itself is locale-less, the same\n * hinge order `runLookupPresent` already uses.\n */\nfunction resolveLookupOrderLabel(field: string, key: string, locale: string | undefined, cfg: LookupViaConfig): string | undefined {\n const desc = cfg.lookupFields[field]\n if (!desc) return undefined\n const rows = desc.backing === 'static'\n ? (desc.table ? new Map(Object.entries(desc.table)) : undefined)\n : cfg.snapshotFor?.(desc)\n if (!rows) return undefined\n return buildLookupSnapshot(desc.dimension, rows, desc).label(key, locale ?? desc.displayLocale ?? '')\n}\n\nexport function lookupBinding(cfg: LookupViaConfig): ViaBinding {\n return {\n brand: 'lookup',\n posture: { encryptedAtRest: 'envelope', queryable: 'full', exportable: true, forgettable: false },\n reservedPrefixes: ['_dict_', '_lookup_'],\n covers: (field) => field in cfg.lookupFields,\n ingest: (record) => runLookupIngest(record, cfg),\n enforceWrite: (record) => runLookupEnforceWrite(record, cfg),\n present: async (record, ctx) => runLookupPresent(record, ctx, cfg),\n compareForOrder: (field, a, b) => compareLookupOrder(field, a, b, cfg),\n resolveOrderLabel: (field, key, locale) => resolveLookupOrderLabel(field, key, locale, cfg),\n describeFragment: () => buildLookupDescribeFragment(cfg),\n }\n}\n\nexport function linkLookupVia(): void {\n installViaBinder('lookup', (c) => lookupBinding(c as LookupViaConfig))\n}\n","/**\n * `lookup()` / `enumOf()` / `dict()` descriptors — the three declaration\n * surfaces for the `'lookup'` via binding (#650 Task 2, phase D of the Via\n * port). One `LookupDescriptor` shape; the tiers are `backing` details:\n *\n * - `lookup(dimension, opts?)` — matrix tier: first-class collection backing\n * (default `backing:'collection'`) — a reference-collection dimension\n * (e.g. `countries`), open vocabulary by default.\n * - `enumOf(keys)` — enum tier: static in-config table, CLOSED\n * vocabulary, no backing store at all (no dimension name — pure inline\n * keys). Exported as `enumOf`; the barrel (`index.ts`) re-exports it as\n * `enum` (`enum` is a reserved word, so it can't be the function's own\n * name).\n * - `dict(dimension, opts?)` — dict tier: reserved `_dict_<dimension>`\n * micro-collection backing (the `vault.dictionary(name)` engine), open\n * vocabulary by default.\n *\n * Each factory calls {@link linkLookupVia} first — the declaration IS the\n * via binding's opt-in unit (#553), same pattern as `money()`/`i18nText()`/\n * `dictKey()`.\n */\n\nimport type { OnMissingPolicy } from '../i18n/policy.js'\nimport { linkLookupVia } from './binding.js'\n\n/** `'closed'` = enum semantics (membership enforced, Task 3); `'open'` permits unknown keys. */\nexport type Vocabulary = 'open' | 'closed'\n\n/** Where a dimension's rows live: static in-config table / reserved micro-collection / first-class collection. */\nexport type LookupBacking = 'static' | 'reserved' | 'collection'\n\n/** Delete-time referential policy for the backing dimension (default `'restrict'`, enforced in Task 5). */\nexport type OnDelete = 'restrict' | 'cascade' | 'nullify'\n\n/** The one descriptor shape every tier compiles to — tiers differ only by `backing`. */\nexport interface LookupDescriptor<Keys extends string = string> {\n readonly _viaBrand: 'lookup'\n /** Dimension/dictionary/target name. Empty for a bare `enumOf()` (no backing store, no name). */\n readonly dimension: string\n /** Canonical key field on the row (default `'id'`). */\n readonly key: string\n /** Candidate keys normalized to `key` on ingest (Task 3). */\n readonly altKeys?: readonly string[]\n readonly vocabulary: Vocabulary\n /** Dressing dimension: which backing-row field supplies `<field>Label`, optionally keyed `by` a sub-field (e.g. locale). */\n readonly present?: { readonly label: string; readonly by?: string }\n /** Field to sort by against the snapshot (Task 6). */\n readonly sortBy?: string\n readonly backing: LookupBacking\n readonly onDelete: OnDelete\n /** Static/enum inline key set. */\n readonly keys?: readonly Keys[]\n /** Static tier only: in-code `key -> { locale -> label }` table. */\n readonly table?: Readonly<Record<string, Readonly<Record<string, string>>>>\n /** Static hybrid hinge (the `staticDict` alias) — locale-less `<field>Label`. */\n readonly displayLocale?: string\n readonly onMissing?: OnMissingPolicy\n readonly substitute?: readonly string[]\n /** Inline display labels (value -> label), the dict-tier sync fallback (mirrors `dictKey`'s `labels`). */\n readonly labels?: Record<string, string>\n}\n\n/**\n * `sortBy`/`present.by` coupling warning (#650 Task 7, T6 minor — task-6-\n * report.md Concern #4). A `by`-keyed `present` (locale-map) field used as\n * `sortBy` needs a declared `displayLocale` for the locale-less\n * `compareForOrder` sort hook (a plain `orderBy(field)`, no `{by:'label'}`)\n * to resolve anything — without one it silently degrades to comparing the\n * raw canonical keys (never throws — `LookupSnapshot.compareKeys`'s\n * contract). `orderBy(field, dir, {by:'label'})` doesn't need this (it\n * carries its own per-call locale via `ViaBinding.resolveOrderLabel`,\n * #650 Task 7) — only a sortBy-driven PLAIN orderBy does. Warn once at\n * declare time rather than degrade silently at query time.\n */\nfunction warnIfSortByNeedsDisplayLocale(\n dimension: string,\n opts?: { sortBy?: string; present?: { by?: string }; displayLocale?: string },\n): void {\n if (opts?.sortBy !== undefined && opts.present?.by !== undefined && opts.displayLocale === undefined) {\n console.warn(\n `[noy-db] lookup(\"${dimension}\"): sortBy \"${opts.sortBy}\" is locale-keyed (present.by is set) but no ` +\n `displayLocale is declared — a locale-less orderBy() will silently sort by the raw stored key instead ` +\n `of the resolved label. Declare displayLocale, or sort via orderBy(field, dir, { by: 'label' }), which ` +\n `resolves at the query's own per-call locale.`,\n )\n }\n}\n\n/**\n * Matrix tier — first-class collection backing (default `backing:'collection'`).\n * `dimension` names the backing collection (e.g. `'countries'`).\n *\n * `backing` may be overridden to construct any tier through this one\n * factory — e.g. `lookup(name, { backing:'static', table, displayLocale })`\n * is the table-bearing static tier `staticDict()` compiles onto. The\n * `table`/`displayLocale`/`keys`/`onMissing`/`substitute`/`labels` options\n * are a superset of the brief's literal opts list: `LookupDescriptor`\n * already carries these fields for exactly this purpose, and without them\n * `backing:'static'` would be unreachable stand-alone through `lookup()`\n * (see task-2-report.md's \"Design decisions\").\n *\n * **`altKeys` caveat (matrix tier only)**: normalizing an altKey candidate\n * to its canonical `key` requires the backing `dimension` collection to be\n * open in EAGER mode (the default; `{ prefetch: false }` — lazy mode — is\n * unsupported). A `put()` on a field with `altKeys` whose backing collection\n * is lazy throws a `ValidationError` naming the field and dimension; open\n * the backing collection without `{ prefetch: false }`, or drop `altKeys`.\n */\nexport function lookup<Keys extends string>(\n dimension: string,\n opts?: {\n key?: string\n altKeys?: readonly string[]\n vocabulary?: Vocabulary\n present?: { label: string; by?: string }\n sortBy?: string\n backing?: LookupBacking\n onDelete?: OnDelete\n keys?: readonly Keys[]\n table?: Readonly<Record<string, Readonly<Record<string, string>>>>\n displayLocale?: string\n onMissing?: OnMissingPolicy\n substitute?: readonly string[]\n labels?: Record<string, string>\n },\n): LookupDescriptor<Keys> {\n linkLookupVia()\n warnIfSortByNeedsDisplayLocale(dimension, opts)\n return {\n _viaBrand: 'lookup',\n dimension,\n key: opts?.key ?? 'id',\n vocabulary: opts?.vocabulary ?? 'open',\n backing: opts?.backing ?? 'collection',\n onDelete: opts?.onDelete ?? 'restrict',\n ...(opts?.altKeys !== undefined ? { altKeys: opts.altKeys } : {}),\n ...(opts?.present !== undefined ? { present: opts.present } : {}),\n ...(opts?.sortBy !== undefined ? { sortBy: opts.sortBy } : {}),\n ...(opts?.keys !== undefined ? { keys: opts.keys } : {}),\n ...(opts?.table !== undefined ? { table: opts.table } : {}),\n ...(opts?.displayLocale !== undefined ? { displayLocale: opts.displayLocale } : {}),\n ...(opts?.onMissing !== undefined ? { onMissing: opts.onMissing } : {}),\n ...(opts?.substitute !== undefined ? { substitute: opts.substitute } : {}),\n ...(opts?.labels !== undefined ? { labels: opts.labels } : {}),\n }\n}\n\n/**\n * Enum tier — static in-config table, CLOSED vocabulary, no backing store.\n * No dimension name (pure inline keys) — `dimension` is `''`.\n */\nexport function enumOf<const Keys extends readonly string[]>(keys: Keys): LookupDescriptor<Keys[number]> {\n linkLookupVia()\n return {\n _viaBrand: 'lookup',\n dimension: '',\n key: 'id',\n vocabulary: 'closed',\n backing: 'static',\n onDelete: 'restrict',\n keys,\n }\n}\n\n/**\n * Dict tier — reserved `_dict_<dimension>` micro-collection backing (the\n * `vault.dictionary(dimension)` engine), open vocabulary by default. The\n * native equivalent of `dictKey()`.\n *\n * Unlike `lookup()`'s matrix tier, `dict()` has no `altKeys` option — its\n * `_dict_<dimension>` backing is the always-synchronous `LookupHandle`\n * write-through cache, never a `vault.collection()` that can be opened\n * `{ prefetch: false }`, so the matrix tier's lazy-mode altKeys restriction\n * does not apply here.\n */\nexport function dict<Keys extends string>(\n dimension: string,\n opts?: {\n keys?: readonly Keys[]\n vocabulary?: Vocabulary\n present?: { label: string; by?: string }\n onDelete?: OnDelete\n onMissing?: OnMissingPolicy\n substitute?: readonly string[]\n },\n): LookupDescriptor<Keys> {\n linkLookupVia()\n return {\n _viaBrand: 'lookup',\n dimension,\n key: 'id',\n vocabulary: opts?.vocabulary ?? 'open',\n backing: 'reserved',\n onDelete: opts?.onDelete ?? 'restrict',\n ...(opts?.keys !== undefined ? { keys: opts.keys } : {}),\n ...(opts?.present !== undefined ? { present: opts.present } : {}),\n ...(opts?.onMissing !== undefined ? { onMissing: opts.onMissing } : {}),\n ...(opts?.substitute !== undefined ? { substitute: opts.substitute } : {}),\n }\n}\n"],"mappings":"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","names":["value","canonical","known"]}
@@ -30,7 +30,7 @@
30
30
  *
31
31
  * @internal — implementation-sharing only, not part of the public surface.
32
32
  */
33
- import type { EncryptedEnvelope } from './types.js';
33
+ import type { EncryptedEnvelope, StoreCapabilities, TxOp } from './types.js';
34
34
  /** One already-executed write a revert pass needs to unwind. */
35
35
  export interface BestEffortRevertLeg {
36
36
  readonly vaultName: string;
@@ -43,6 +43,14 @@ export interface BestEffortRevertLeg {
43
43
  export interface BestEffortRevertAdapter {
44
44
  put(vaultName: string, collectionName: string, id: string, envelope: EncryptedEnvelope): Promise<void>;
45
45
  delete(vaultName: string, collectionName: string, id: string): Promise<void>;
46
+ /**
47
+ * Optional storage-layer transaction (#886). When the store declares
48
+ * `txAtomic`, the whole revert is submitted as ONE operation instead of a
49
+ * per-leg loop, so a crash mid-revert can no longer leave the vault
50
+ * half-unwound.
51
+ */
52
+ tx?(ops: readonly TxOp[]): Promise<void>;
53
+ readonly capabilities?: StoreCapabilities;
46
54
  }
47
55
  /**
48
56
  * Revert `executed` in reverse order via the raw adapter, best-effort.
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * ```ts
22
22
  * const db = await createNoydb({
23
- * store: jsonFile({ dir: './data' }),
23
+ * store: toFile({ dir: './data' }),
24
24
  * syncPolicy: {
25
25
  * push: { mode: 'debounce', debounceMs: 5_000 },
26
26
  * pull: { mode: 'on-focus' },
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Noydb,
3
3
  createNoydb
4
- } from "./chunk-XOBHRD2M.js";
4
+ } from "./chunk-OW5Y6HA6.js";
5
5
  import "./chunk-NJ4DYYO5.js";
6
6
  import "./chunk-4JQK3L4V.js";
7
7
  import "./chunk-SHEEBRZ4.js";
@@ -26,7 +26,7 @@ import "./chunk-N5PLUFH2.js";
26
26
  import "./chunk-GHVIKOHT.js";
27
27
  import "./chunk-SMY5X46Z.js";
28
28
  import "./chunk-DZFQVICS.js";
29
- import "./chunk-BQ6DHN4I.js";
29
+ import "./chunk-NGSJZFGL.js";
30
30
  import "./chunk-5D2ALSM5.js";
31
31
  import "./chunk-QADFHRQM.js";
32
32
  import "./chunk-AURFOK3D.js";
@@ -50,7 +50,7 @@ import "./chunk-UADPR6F4.js";
50
50
  import "./chunk-6UBZ6I5Z.js";
51
51
  import "./chunk-C25JXSOR.js";
52
52
  import "./chunk-56IIVAEO.js";
53
- import "./chunk-VQNJ7UZL.js";
53
+ import "./chunk-MTJLOC3Y.js";
54
54
  import "./chunk-EZNTORXE.js";
55
55
  import "./chunk-KPXM4WO6.js";
56
56
  import "./chunk-WZ3NCMK6.js";
@@ -79,4 +79,4 @@ export {
79
79
  Noydb,
80
80
  createNoydb
81
81
  };
82
- //# sourceMappingURL=noydb-KXS35UJS.js.map
82
+ //# sourceMappingURL=noydb-MGPOYDOD.js.map
@@ -7,7 +7,7 @@ import {
7
7
  withMetrics,
8
8
  withRetry,
9
9
  wrapStore
10
- } from "../chunk-VSRSQ2CR.js";
10
+ } from "../chunk-EIPVYUEP.js";
11
11
  import "../chunk-PZ5AY32C.js";
12
12
  export {
13
13
  routeStore,
@@ -5,8 +5,8 @@ import {
5
5
  PresenceHandle,
6
6
  SyncEngine,
7
7
  SyncTransaction
8
- } from "../chunk-KBPU5M2E.js";
9
- import "../chunk-VQNJ7UZL.js";
8
+ } from "../chunk-BXHMZ2XW.js";
9
+ import "../chunk-MTJLOC3Y.js";
10
10
  import "../chunk-EZNTORXE.js";
11
11
  import "../chunk-RZPOZPZU.js";
12
12
  import "../chunk-VH6SOCXH.js";
@@ -40,8 +40,8 @@ import {
40
40
  PresenceHandle,
41
41
  SyncEngine,
42
42
  SyncTransaction
43
- } from "../chunk-KBPU5M2E.js";
44
- import "../chunk-VQNJ7UZL.js";
43
+ } from "../chunk-BXHMZ2XW.js";
44
+ import "../chunk-MTJLOC3Y.js";
45
45
  import "../chunk-EZNTORXE.js";
46
46
  import {
47
47
  SYNC_CREDENTIALS_COLLECTION,
@@ -3,7 +3,7 @@ import {
3
3
  TxContext,
4
4
  TxVault,
5
5
  runTransaction
6
- } from "../chunk-BQ6DHN4I.js";
6
+ } from "../chunk-NGSJZFGL.js";
7
7
  import {
8
8
  lazy
9
9
  } from "../chunk-5D2ALSM5.js";
@@ -204,7 +204,7 @@ export interface RoutedNoydbStore extends NoydbStore {
204
204
  * - `hydrate: ['invoices', 'clients']` — copies only named collections.
205
205
  *
206
206
  * Use cases:
207
- * - Shared device: `await store.override('default', memory(), { hydrate: true })`
207
+ * - Shared device: `await store.override('default', toMemory(), { hydrate: true })`
208
208
  * - Restricted network: `store.override('blobs', localFile(...))`
209
209
  */
210
210
  override(route: OverrideTarget, store: NoydbStore, opts?: OverrideOptions): void | Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noy-db/hub",
3
- "version": "0.4.0-pre.7",
3
+ "version": "0.4.0-pre.9",
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>",
@@ -204,14 +204,14 @@
204
204
  "node": ">=22.0.0"
205
205
  },
206
206
  "dependencies": {
207
- "@noy-db/attestation": "0.4.0-pre.7"
207
+ "@noy-db/attestation": "0.4.0-pre.9"
208
208
  },
209
209
  "devDependencies": {
210
210
  "@types/node": "^22.0.0",
211
211
  "esbuild": "^0.25.0",
212
212
  "zod": "^4.0.0",
213
213
  "zod-to-json-schema": "^3.25.2",
214
- "@noy-db/on-shamir": "0.4.0-pre.7"
214
+ "@noy-db/on-shamir": "0.4.0-pre.9"
215
215
  },
216
216
  "peerDependencies": {
217
217
  "zod-to-json-schema": "^3.25.0"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/best-effort-revert.ts","../src/with-commit/tx/transaction.ts"],"sourcesContent":["/**\n * Best-effort reverse-revert — the shape shared by two independent rollback\n * paths that used to duplicate it: satellite fan-out\n * (`with-shape/satellites/fanout.ts`'s `revertAndCompensate`) and\n * multi-record transactions (`with-commit/tx/transaction.ts`'s\n * `revertExecuted`). Both revert a list of already-executed writes on\n * failure by walking them in REVERSE order and, per leg, restoring the\n * captured prior envelope straight through the raw adapter — `put(prior)`\n * when one existed, `delete` when the leg was a fresh insert — bypassing\n * the Collection layer so the revert itself doesn't re-fire\n * encryption/ledger/change-event machinery. Each leg's raw-adapter revert\n * is best-effort: a throw is swallowed so a revert-path failure never masks\n * the original error that triggered the rollback, and the loop moves on to\n * the next leg.\n *\n * An optional per-leg `compensate` callback runs immediately after a\n * successful raw-adapter revert (still inside the same try, so a throwing\n * callback is swallowed the same way as a throwing raw revert). What the\n * callback does is entirely caller-defined — satellites' fan-out fires\n * `_compensateRevertedWrite` only for legs whose write actually landed\n * (#596); with-commit's transaction executor invalidates the Collection\n * cache. Neither concern belongs here: this helper only knows the generic\n * reverse/best-effort/raw-revert shape.\n *\n * Lives in `kernel/` — not a `with-*` service — so both consumers (one a\n * gated service, `with-commit`; one an always-on schema feature,\n * `with-shape/satellites`) can import it without creating a\n * `with-* → with-*` edge, which would force either family to always bundle\n * the other and defeat the opt-in tree-shaking both are built around.\n *\n * @internal — implementation-sharing only, not part of the public surface.\n */\nimport type { EncryptedEnvelope } from './types.js'\n\n/** One already-executed write a revert pass needs to unwind. */\nexport interface BestEffortRevertLeg {\n readonly vaultName: string\n readonly collectionName: string\n readonly id: string\n /** Envelope captured before the write, or `null` if the id didn't exist yet. */\n readonly prior: EncryptedEnvelope | null\n}\n\n/** The subset of `NoydbStore` a raw-adapter revert needs. */\nexport interface BestEffortRevertAdapter {\n put(vaultName: string, collectionName: string, id: string, envelope: EncryptedEnvelope): Promise<void>\n delete(vaultName: string, collectionName: string, id: string): Promise<void>\n}\n\n/**\n * Revert `executed` in reverse order via the raw adapter, best-effort.\n * `compensate`, when supplied, runs once per leg immediately after that\n * leg's own raw revert succeeds.\n */\nexport async function bestEffortRevert<T extends BestEffortRevertLeg>(\n executed: readonly T[],\n adapter: BestEffortRevertAdapter,\n compensate?: (leg: T) => Promise<void> | void,\n): Promise<void> {\n for (const leg of [...executed].reverse()) {\n try {\n if (leg.prior !== null) {\n await adapter.put(leg.vaultName, leg.collectionName, leg.id, leg.prior)\n } else {\n await adapter.delete(leg.vaultName, leg.collectionName, leg.id)\n }\n if (compensate) await compensate(leg)\n } catch {\n // best-effort — a revert-path failure must not mask the original\n // error that triggered the rollback (matches both callers' prior\n // independent implementations).\n }\n }\n}\n","/**\n * Multi-record atomic transactions.\n *\n * Lets an application stage writes across two or more collections (or\n * vaults) and commit them all-or-nothing.\n *\n * ```ts\n * await db.transaction(async (tx) => {\n * const inv = tx.vault('acme').collection<Invoice>('invoices')\n * const pay = tx.vault('acme').collection<Payment>('payments')\n * await inv.put(invoiceId, { ...invoice, status: 'paid' })\n * await pay.put(paymentId, { invoiceId, amount, paidAt })\n * })\n * // If the body throws before returning: nothing persisted.\n * // If the body returns: all puts committed; any CAS mismatch rolls\n * // the batch back and surfaces as ConflictError.\n * ```\n *\n * ## Atomicity semantics\n *\n * Ops are buffered during the body. On body-return the hub:\n *\n * 1. **Pre-flight** — re-reads every touched envelope and enforces\n * any caller-supplied `expectedVersion`. A mismatch throws\n * `ConflictError` with *no* writes performed.\n * 2. **Execute** — calls `Collection.put()` / `.delete()` for each\n * staged op in declaration order. History snapshots, ledger\n * appends, and change events fire as normal per op.\n * 3. **Unwind on failure** — if step 2 throws mid-batch, each\n * already-committed op is reverted via the raw store (restoring\n * the captured prior envelope, or deleting if none existed). The\n * ledger is NOT rewritten — audit history preserves the partial\n * commit and the revert.\n *\n * **Crash window.** Steps 2–3 are not a storage-layer transaction —\n * if the process dies between two executed ops, the on-disk state is\n * partial. True all-or-nothing atomicity requires a store that\n * implements `NoydbStore.tx()` (DynamoDB `TransactWriteItems`,\n * IndexedDB `readwrite` transaction, …). This executor declares\n * that future integration point via the `tx?()` method + the\n * `StoreCapabilities.txAtomic` bit, but does not yet delegate\n * to it — the cascade into `Fork · Stores` tracks the per-adapter\n * wire-up.\n *\n * ## Not covered\n *\n * - Cross-sync-peer atomicity. Transactions commit against the\n * primary store only; the sync engine pushes on its normal\n * schedule. For cross-peer two-phase commit use `SyncTransaction`\n * via `db.transaction(vaultName)`.\n * - Read-your-writes within the body. `tx.collection().get(id)`\n * returns the most-recently-staged value for that id when one\n * exists; if no staged op has touched the id, it reads the current\n * committed state. Version numbers returned by `get` reflect the\n * pre-transaction state (staged puts have no version yet).\n *\n * @module\n */\n\nimport type { Noydb } from '../../kernel/noydb.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { Collection } from '../../kernel/collection.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport {\n AmendmentForbiddenError,\n ConflictError,\n InvariantError,\n ValidationError,\n} from '../../kernel/errors.js'\nimport { generateULID } from '../../with-pod/ulid.js'\nimport type { GuardExecutor as GuardExecutorModule } from '../../with-audit/guards/executor.js'\nimport type { LedgerEntry } from '../history/ledger/entry.js'\nimport type { TransactionInvariant } from './invariants.js'\nimport type { GuardChange, GuardContext, ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport { bestEffortRevert } from '../../kernel/best-effort-revert.js'\n\n/** One op buffered inside a running `TxContext`. @internal */\nexport interface StagedOp {\n type: 'put' | 'delete'\n vaultName: string\n collectionName: string\n id: string\n record?: unknown\n expectedVersion?: number\n /**\n * Optional human-readable tag forwarded to the resulting ledger\n * entry's `reason` field. Set by callers via\n * `tx.vault(v).collection(c).put(id, record, { reason })`.\n */\n reason?: string\n}\n\n/**\n * One executed op (main staged op or recursive side-effect like a\n * derivation output) paired with the envelope captured before the write.\n * `revertExecuted` walks this array in reverse on rollback.\n * @internal\n */\nexport interface ExecutedOp {\n op: StagedOp\n priorEnvelope: EncryptedEnvelope | null\n}\n\n/**\n * Options accepted by `db.transaction({ amendment, reason }, fn)`.\n * Only the amendment variant uses these — a plain `db.transaction(fn)`\n * never sees this shape.\n */\nexport interface AmendmentTxOptions {\n /** Opt into amendment mode. Required to be `true`. */\n readonly amendment: true\n /** Human-readable rationale recorded in the ledger entry. Required. */\n readonly reason: string\n}\n\n/**\n * Transaction handle passed to the user's body. Use\n * `tx.vault(name).collection<T>(name)` to get a per-collection\n * facade; its `put`/`delete`/`get` calls stage ops against the tx.\n */\nexport class TxContext {\n /** Stable id for this transaction; shared by all writes it performs. */\n readonly txId: string = generateULID()\n /** @internal */\n readonly _ops: StagedOp[] = []\n /**\n * @internal — write log built up in Phase 2. Each entry records the\n * envelope captured BEFORE the write so a mid-batch failure can\n * restore prior state via `revertExecuted`. Side-effect writes (e.g.\n * recursive derivation outputs fired inside `Collection.put`) are\n * appended here in execution order so they roll back alongside the\n * main staged ops.\n */\n readonly _executed: ExecutedOp[] = []\n /** @internal */\n readonly _db: Noydb\n /**\n * @internal — true when this TxContext was opened in amendment\n * mode. Toggles the lazy-`beginAmendment` + role-check path on first\n * `tx.vault(name)` and unlocks the post-Phase-2 invariant + audit run.\n */\n readonly _amendment: boolean\n /** @internal — vaults that have already had `beginAmendment` called. */\n readonly _amendmentVaults = new Map<string, Vault>()\n\n /** @internal */\n constructor(db: Noydb, amendment = false) {\n this._db = db\n this._amendment = amendment\n }\n\n /** Scope subsequent `collection()` calls to the named vault. */\n vault(name: string): TxVault {\n const v = this._db.vault(name)\n if (this._amendment && !this._amendmentVaults.has(name)) {\n // Role check is per-vault. The task spec (\"only admin or owner\n // can open an amendment\") is implemented lazy-on-first-touch\n // because the role lives on the vault's keyring, and `tx.vault()`\n // is the first place we know which vault we're addressing. The\n // observable effect is identical to an eager check in the single-\n // vault case the tests exercise; multi-vault amendments check\n // each touched vault as they first appear.\n const role = v.role\n // FR-6: custodian is admin-rank for operational mutations, and an\n // amendment is an operational (data-correcting) act — not an ownership\n // meta-capability — so custodian is allowed alongside owner/admin.\n if (role !== 'admin' && role !== 'owner' && role !== 'custodian') {\n throw new AmendmentForbiddenError(v.userId, role)\n }\n // Amendments require an initialised guard registry — they\n // produce a structured invariant + change-set audit. A vault\n // opened without `guardStrategies` (or via the sync fallback\n // path) has a null registry and cannot run an amendment.\n const reg = v._getGuardRegistry()\n if (reg === null) {\n throw new ValidationError(\n `Vault \"${name}\": amendment mode requires at least one ` +\n `guardStrategy registered via createNoydb({ guardStrategies }). ` +\n `Open the vault with guardStrategies before calling ` +\n `db.transaction({ amendment: true }).`,\n )\n }\n reg.beginAmendment()\n this._amendmentVaults.set(name, v)\n }\n return new TxVault(this, v)\n }\n}\n\n/** Per-vault facade inside a running transaction. */\nexport class TxVault {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault) {\n this._ctx = ctx\n this._vault = vault\n }\n\n /** Scope subsequent op calls to the named collection. */\n collection<T>(name: string): TxCollection<T> {\n const c = this._vault.collection<T>(name)\n return new TxCollection<T>(this._ctx, this._vault, c, name)\n }\n}\n\n/** Per-collection facade inside a running transaction. */\nexport class TxCollection<T> {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n /** @internal */\n readonly _coll: Collection<T>\n /** @internal */\n readonly _name: string\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault, coll: Collection<T>, name: string) {\n this._ctx = ctx\n this._vault = vault\n this._coll = coll\n this._name = name\n }\n\n /**\n * Read the current committed value, or the most-recently-staged\n * value from the same transaction if one exists.\n */\n async get(id: string): Promise<T | null> {\n for (let i = this._ctx._ops.length - 1; i >= 0; i--) {\n const op = this._ctx._ops[i]!\n if (\n op.vaultName === this._vault.name &&\n op.collectionName === this._name &&\n op.id === id\n ) {\n if (op.type === 'delete') return null\n return op.record as T\n }\n }\n return this._coll.get(id)\n }\n\n /**\n * Stage a put. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n put(id: string, record: T, options?: { expectedVersion?: number; reason?: string }): void {\n const op: StagedOp = {\n type: 'put',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n record,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n if (options?.reason !== undefined) op.reason = options.reason\n this._ctx._ops.push(op)\n }\n\n /**\n * Stage a delete. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n delete(id: string, options?: { expectedVersion?: number }): void {\n const op: StagedOp = {\n type: 'delete',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n this._ctx._ops.push(op)\n }\n}\n\n/**\n * Commit plan: pre-flight check + execution + revert plan.\n *\n * @internal — driven by `withTransactions()` (via `tx/active.ts`) for\n * user-facing `db.transaction(...)` calls and by the `amendment` path\n * in `noydb.ts`. `Collection.putManyAtomic` runs its own Phase 2 loop\n * but shares the `_activeTxContext` mechanism (and the `revertExecuted`\n * helper) so nested side-effect derivation writes get registered for\n * revert alongside the bulk-put source ops.\n */\nexport async function runTransaction<T>(\n db: Noydb,\n fn: (tx: TxContext) => Promise<T> | T,\n options?: AmendmentTxOptions,\n txInvariants?: ReadonlyArray<TransactionInvariant>,\n): Promise<T> {\n // ─── Amendment-mode pre-flight ───────────────────────────────\n // `reason` is the only thing we can validate before the body runs;\n // the per-vault role check happens lazily on first `tx.vault(name)`\n // because we don't know which vaults the body will touch ahead of\n // time. Throwing here keeps the failure mode close to the call site\n // so the developer doesn't have to walk an async stack to find the\n // missing-reason mistake.\n if (options?.amendment) {\n if (typeof options.reason !== 'string' || options.reason.trim().length === 0) {\n throw new ValidationError(\n 'db.transaction({ amendment: true }) requires a non-empty `reason` string.',\n )\n }\n }\n\n const ctx = new TxContext(db, options?.amendment === true)\n const bodyResult = await fn(ctx)\n\n if (ctx._ops.length === 0) {\n // Body produced no ops. If amendment mode was active we still\n // need to close any opened windows so a subsequent (unrelated)\n // write doesn't surprise-collect into a stale change-set. Each\n // `beginAmendment` is matched by exactly one `consumeChanges`.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n // Registry is guaranteed non-null here — `tx.vault(name)`\n // threw above if it was null before adding to\n // `_amendmentVaults`.\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n return bodyResult\n }\n\n // Phase 1 — pre-flight: snapshot every touched envelope and enforce\n // any caller-supplied expectedVersion. Same (vault, coll, id) touched\n // more than once in one tx snapshots only the *initial* committed\n // state; the in-order replay in Phase 2 takes care of successor ops.\n const priorEnvelopes = new Map<string, EncryptedEnvelope | null>()\n const store = db._store\n\n // Commit-time changeset invariants need PLAINTEXT prior records\n // for `before`, but `priorEnvelopes` holds ENCRYPTED envelopes. So for\n // ops in a watched scope we additionally decrypt the prior record here,\n // in Phase 1, BEFORE Phase 2 overwrites it. Snapshots only the initial\n // committed state per (vault, coll, id), matching the envelope snapshot.\n const invariants = txInvariants ?? []\n const watchedScopes = new Set(invariants.map(i => i.scope))\n const plainBefore = new Map<string, unknown>()\n\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!priorEnvelopes.has(key)) {\n const env = await store.get(op.vaultName, op.collectionName, op.id)\n priorEnvelopes.set(key, env)\n }\n if (watchedScopes.has(op.collectionName) && !plainBefore.has(key)) {\n const prior = await db\n .vault(op.vaultName)\n .collection(op.collectionName)\n .get(op.id)\n plainBefore.set(key, prior ?? null)\n }\n if (op.expectedVersion !== undefined) {\n const env = priorEnvelopes.get(key) ?? null\n const actual = env?._v ?? 0\n if (actual !== op.expectedVersion) {\n throw new ConflictError(\n actual,\n `Transaction pre-flight: ${op.vaultName}/${op.collectionName}/${op.id} ` +\n `expected v${op.expectedVersion}, found v${actual}`,\n )\n }\n }\n }\n\n // Phase 2 — execute via the Collection layer so history snapshots,\n // ledger entries, and change events fire normally. We capture each\n // successful op so a mid-batch throw can revert in Phase 3.\n //\n // `_activeTxContext` is published on the Noydb instance for the\n // duration of Phase 2 so recursive writes triggered inside\n // `Collection.put` (today: eager derivation outputs) can register\n // their own envelopes onto `ctx._executed` and roll back alongside\n // the main staged ops. The `finally` clears it before the\n // amendment commit phase runs.\n db._setActiveTxContext(ctx)\n try {\n try {\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n const key = keyOf(op)\n const prior = priorEnvelopes.get(key) ?? null\n // Record the revert plan BEFORE the call so a mid-`coll.put` throw\n // (e.g. strict-mode derivation failure firing after `store.put`\n // has already committed the envelope) still has its source write\n // reverted. `revertExecuted` is best-effort: putting prior back is\n // idempotent when the failing op never actually wrote, and\n // `_invalidateCacheEntry` is a no-op when the collection isn't\n // hydrated.\n ctx._executed.push({ op, priorEnvelope: prior })\n if (op.type === 'put') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await coll.put(op.id, op.record as any, op.reason !== undefined ? { reason: op.reason } : undefined)\n } else {\n await coll.delete(op.id)\n }\n }\n } catch (err) {\n // Phase 3 — best-effort revert. See helper docstring.\n await revertExecuted(ctx._executed, store, db)\n // Drain amendment windows so the next transaction starts clean.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n throw err\n }\n } finally {\n db._clearActiveTxContext(ctx)\n }\n\n // ─── Amendment commit phase (only if amendment === true) ────\n // Body succeeded — now run each touched vault's invariants over the\n // collected change-set, then append a structured ledger entry. If\n // any invariant throws, treat it exactly like a mid-Phase-2 failure:\n // revert every executed op and re-throw the InvariantError.\n if (ctx._amendment) {\n // Lazy-load GuardExecutor at the dispatch site — keeps the floor\n // bundle free of the guards service when amendments aren't used.\n // Mirrors the deferred-load pattern from elsewhere in this module.\n const { GuardExecutor } = (await import('../../with-audit/guards/executor.js')) as {\n GuardExecutor: typeof GuardExecutorModule\n }\n try {\n for (const [vaultName, v] of ctx._amendmentVaults) {\n const registry = v._getGuardRegistry()\n // Registry is guaranteed non-null at this point — the\n // `tx.vault(name)` path that populates `_amendmentVaults`\n // throws if the registry is null. The defensive check here\n // is for TypeScript's narrowing.\n if (registry === null) continue\n const changesByCollection = registry.consumeChanges()\n const meta = registry.consumeMeta()\n if (changesByCollection.size === 0) continue\n\n const readOnlyVault = v._getReadOnlyFacade()\n if (readOnlyVault === null) continue\n\n // Build the invariant ctx once per vault — it's the same shape\n // every guard sees on the normal `check` path, just with a\n // synthetic `existing: null` (invariants get the full change\n // set in their first parameter; `existing` is a per-record\n // concept that doesn't apply here).\n const invariantsPassed: string[] = []\n for (const [collection, changes] of changesByCollection) {\n const guards = registry.guardsFor(collection).filter(g => g.amendment !== undefined)\n for (const guard of guards) {\n await GuardExecutor.runInvariant(guard, changes, {\n existing: null,\n vault: readOnlyVault,\n userId: v.userId,\n role: v.role,\n })\n }\n if (guards.length > 0) invariantsPassed.push(collection)\n }\n\n // Append the audit ledger entry. Silent no-op when the\n // history strategy isn't configured — the records still\n // committed, only the multi-record summary is unavailable.\n const ledger = v._getLedgerOrNull()\n if (ledger) {\n const role = v.role as 'admin' | 'owner'\n const amendment: NonNullable<LedgerEntry['amendment']> = {\n reason: options!.reason,\n role,\n changes: meta,\n invariantsPassed,\n }\n await ledger.append({\n op: 'amendment',\n collection: '',\n id: '',\n version: 0,\n actor: v.userId,\n // No payload to hash — the per-record entries already\n // captured `payloadHash` at their own append time. We use\n // a sha256 of the canonical reason string so the field is\n // populated with something deterministic and non-empty.\n payloadHash: '',\n amendment,\n })\n }\n void vaultName\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n // ─── Commit-time changeset invariant phase ───────────\n // Runs for BOTH ordinary and amendment transactions (placed after the\n // amendment phase so an amendment commit is still subject to these\n // set-level constraints). Assemble the changeset from the executed\n // staged ops, deduped to the LAST write per (vault, coll, id) while\n // preserving write order, then group `GuardChange` by collection\n // (scope) and run each matching invariant. A throw mirrors the\n // amendment-phase failure mode exactly: revert every executed op and\n // re-throw as `InvariantError`.\n if (invariants.length > 0) {\n // Dedup ctx._ops to the last write per key, preserving first-seen\n // (write) order so the changeset is stable and order-meaningful.\n const lastOp = new Map<string, StagedOp>()\n const order: string[] = []\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!lastOp.has(key)) order.push(key)\n lastOp.set(key, op)\n }\n\n // Group {before, after} pairs by collection name (the invariant\n // scope). `before` is the plaintext prior captured in Phase 1 (null\n // for inserts / unwatched — only watched scopes were captured, and\n // every grouped key belongs to a watched scope). `after` is the\n // written record, null for a delete.\n const changesByScope = new Map<string, GuardChange<unknown>[]>()\n // Parallel map: scope → the vault name of its (last-seen) op, used to\n // build the per-invariant read-only ctx (facade + userId + role).\n const scopeVault = new Map<string, string>()\n for (const key of order) {\n const op = lastOp.get(key)!\n if (!watchedScopes.has(op.collectionName)) continue\n const before = plainBefore.get(key) ?? null\n const after = op.type === 'delete' ? null : (op.record ?? null)\n const change = { before, after } as GuardChange<unknown>\n const arr = changesByScope.get(op.collectionName)\n if (arr) arr.push(change)\n else changesByScope.set(op.collectionName, [change])\n\n // Stash the vault name alongside so we can build a per-vault ctx.\n // (All ops in a scope group could span vaults; we resolve the\n // vault per change below via a parallel map keyed the same way.)\n scopeVault.set(op.collectionName, op.vaultName)\n }\n\n try {\n for (const inv of invariants) {\n const changes = changesByScope.get(inv.scope)\n if (changes === undefined || changes.length === 0) continue\n const vaultName = scopeVault.get(inv.scope)!\n const v = db.vault(vaultName)\n // Prefer the real read-only facade so the invariant can read\n // sibling collections; fall back to a minimal read-only stub.\n const facade: ReadOnlyVaultFacade =\n v._getReadOnlyFacade() ?? {\n collection<R = unknown>(name: string) {\n const c = v.collection<R>(name)\n return {\n get: (id: string) => c.get(id),\n list: () => c.list(),\n query: () => c.query(),\n }\n },\n }\n const ctxForInv: GuardContext<unknown> = {\n existing: null,\n vault: facade,\n userId: v.userId,\n role: v.role,\n }\n await inv.check(changes, ctxForInv)\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n return bodyResult\n}\n\n/**\n * Phase 3 helper — restore captured prior envelopes via the raw store\n * to avoid re-firing Collection-level side effects (we don't want a\n * cascade of change events undoing themselves). The ledger is left\n * as-is: each committed op appended an entry; the revert is\n * deliberately NOT recorded as a compensating entry because the\n * caller-facing contract is \"atomic or not at all,\" not \"every write\n * visible in the audit trail.\" Auditors who need the intermediate\n * state can still reconstruct it by walking the ledger through the\n * failed-tx timestamp.\n *\n * Delegates the reverse/best-effort/raw-revert shape to the shared\n * `bestEffortRevert` helper (`kernel/best-effort-revert.ts`); the cache\n * invalidation below rides along as that helper's per-leg `compensate`\n * callback, gated on `db` exactly as before.\n *\n * @internal — shared between `runTransaction` and\n * `Collection.putManyAtomic`. Both register source ops + nested\n * derivation side-effect ops onto `_executed`; this helper unwinds the\n * combined list in reverse on rollback.\n */\nexport async function revertExecuted(\n executed: ReadonlyArray<ExecutedOp>,\n store: Noydb['_store'],\n db?: Noydb,\n): Promise<void> {\n const legs = executed.map(({ op, priorEnvelope }) => ({\n vaultName: op.vaultName,\n collectionName: op.collectionName,\n id: op.id,\n prior: priorEnvelope,\n }))\n await bestEffortRevert(\n legs,\n store,\n db\n ? async (leg) => {\n // Sync the Collection-layer cache with what we just wrote at\n // the raw store. Without this, eager-mode `get` would still\n // return the rolled-back record from its in-memory map. The\n // Collection's `_invalidateCacheEntry` is a no-op when the\n // collection hasn't yet been hydrated.\n const coll = db.vault(leg.vaultName).collection(leg.collectionName)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (coll as any)._invalidateCacheEntry(leg.id)\n }\n : undefined,\n )\n}\n\nfunction keyOf(op: StagedOp): string {\n return `${op.vaultName}\\x00${op.collectionName}\\x00${op.id}`\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/sync-policy.ts"],"sourcesContent":["/**\n * Sync scheduling policy.\n *\n * ## What it controls\n *\n * A {@link SyncPolicy} has two halves:\n * - **push** ({@link PushPolicy}) — when dirty local writes are sent to the remote.\n * - **pull** ({@link PullPolicy}) — when the remote is polled for new data.\n *\n * ## Choosing a policy\n *\n * The right policy depends on the backend's operational characteristics:\n *\n * | Backend type | Recommended policy |\n * |---|---|\n * | Per-record (DynamoDB, S3, IDB) | {@link INDEXED_STORE_POLICY} — `on-change` push, `manual` pull |\n * | Bundle (Drive, WebDAV, Git) | {@link POD_STORE_POLICY} — `debounce` push, `interval` pull |\n *\n * Consumers can override via `createNoydb({ syncPolicy: { ... } })`:\n *\n * ```ts\n * const db = await createNoydb({\n * store: jsonFile({ dir: './data' }),\n * syncPolicy: {\n * push: { mode: 'debounce', debounceMs: 5_000 },\n * pull: { mode: 'on-focus' },\n * },\n * })\n * ```\n *\n * ## Scheduler lifecycle\n *\n * {@link SyncScheduler} owns all timers, debounce logic, and browser lifecycle\n * hooks (`visibilitychange`, `pagehide`, `beforeExit`). Call `scheduler.start()`\n * after opening a vault and `scheduler.stop()` when closing it. The scheduler\n * delegates actual push/pull work to {@link SyncSchedulerCallbacks} provided\n * by the {@link SyncEngine}.\n *\n * @module\n */\n\n// ─── Policy types ───────────────────────────────────────────────────────\n\n/**\n * When push operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.push()` calls.\n * - `'on-change'` — immediately after every local write (respecting `minIntervalMs`).\n * - `'debounce'` — after `debounceMs` of inactivity following a write.\n * - `'interval'` — on a fixed timer regardless of writes.\n */\nexport type PushMode = 'manual' | 'on-change' | 'debounce' | 'interval'\n\n/**\n * When pull operations are triggered automatically.\n *\n * - `'manual'` — only on explicit `sync.pull()` calls.\n * - `'interval'` — on a fixed `intervalMs` timer.\n * - `'on-focus'` — when the browser tab regains visibility.\n */\nexport type PullMode = 'manual' | 'interval' | 'on-focus'\n\n/**\n * Push half of a sync policy. Controls the trigger mode and timing guards\n * for outbound sync operations.\n */\nexport interface PushPolicy {\n /** Push trigger mode. */\n readonly mode: PushMode\n /** Debounce delay in ms. Only used when `mode: 'debounce'`. Default: 30_000. */\n readonly debounceMs?: number\n /** Interval in ms between automatic pushes. Used by `'interval'` and as floor for `'debounce'`. */\n readonly intervalMs?: number\n /**\n * Hard floor between pushes regardless of mode. Prevents burst writes\n * from hammering the remote. Default: 0 (no floor).\n */\n readonly minIntervalMs?: number\n /**\n * Force a push on page unload (`pagehide` / `visibilitychange → hidden`)\n * in browsers, `beforeExit` in Node. Default: true for non-manual modes.\n */\n readonly onUnload?: boolean\n}\n\n/**\n * Pull half of a sync policy. Controls when and how often inbound sync\n * operations are triggered.\n */\nexport interface PullPolicy {\n /** Pull trigger mode. */\n readonly mode: PullMode\n /** Interval in ms between automatic pulls. Used by `'interval'` mode. Default: 60_000. */\n readonly intervalMs?: number\n}\n\n/**\n * Combined push + pull sync scheduling policy for a vault.\n *\n * Pass via `createNoydb({ syncPolicy })` to override the default policy\n * derived from the active store type. Pre-built defaults are available\n * as `INDEXED_STORE_POLICY` and `POD_STORE_POLICY`.\n */\nexport interface SyncPolicy {\n readonly push: PushPolicy\n readonly pull: PullPolicy\n}\n\n// ─── Default policies by store category ─────────────────────────────────\n\n/** Default for per-record stores (DynamoDB, S3, file, IDB). */\nexport const INDEXED_STORE_POLICY: SyncPolicy = {\n push: { mode: 'on-change', minIntervalMs: 0, onUnload: true },\n pull: { mode: 'manual' },\n}\n\n/** Default for bundle stores (Drive, WebDAV, Git). */\nexport const POD_STORE_POLICY: SyncPolicy = {\n push: { mode: 'debounce', debounceMs: 30_000, minIntervalMs: 120_000, onUnload: true },\n pull: { mode: 'interval', intervalMs: 60_000 },\n}\n\n/** @deprecated Use `POD_STORE_POLICY`. */\nexport const BUNDLE_STORE_POLICY = POD_STORE_POLICY\n\n// ─── Sync scheduler ─────────────────────────────────────────────────────\n\n/**\n * Current operational state of the `SyncScheduler`.\n *\n * - `'idle'` — no pending or active sync operations.\n * - `'pending'` — local writes are queued, waiting for debounce/interval to fire.\n * - `'pushing'` — push in progress.\n * - `'pulling'` — pull in progress.\n * - `'error'` — last sync operation failed; `lastError` holds the cause.\n */\nexport type SyncSchedulerState = 'idle' | 'pending' | 'pushing' | 'pulling' | 'error'\n\n/**\n * Snapshot of the sync scheduler's state, returned by `SyncScheduler.status`.\n * Safe to expose in a reactive UI status indicator.\n */\nexport interface SyncSchedulerStatus {\n readonly state: SyncSchedulerState\n readonly lastPushAt: string | null\n readonly lastPullAt: string | null\n readonly lastError: Error | null\n readonly pendingWrites: number\n}\n\n/**\n * Callbacks injected into `SyncScheduler` by the SyncEngine.\n *\n * The scheduler owns timers and lifecycle hooks; it delegates actual push/pull\n * work to these callbacks to stay decoupled from the sync implementation.\n */\nexport interface SyncSchedulerCallbacks {\n push(): Promise<void>\n pull(): Promise<void>\n getDirtyCount(): number\n}\n\n/**\n * Manages sync timing according to a `SyncPolicy`.\n *\n * The scheduler owns all timers and lifecycle hooks. It delegates actual\n * push/pull work to callbacks provided by the SyncEngine.\n */\nexport class SyncScheduler {\n private readonly policy: SyncPolicy\n private readonly callbacks: SyncSchedulerCallbacks\n\n private _state: SyncSchedulerState = 'idle'\n private _lastPushAt: string | null = null\n private _lastPullAt: string | null = null\n private _lastError: Error | null = null\n private _lastPushTime = 0 // monotonic ms for minIntervalMs enforcement\n\n // Timers\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n private pushIntervalTimer: ReturnType<typeof setInterval> | null = null\n private pullIntervalTimer: ReturnType<typeof setInterval> | null = null\n\n // Bound handlers for cleanup\n private readonly boundOnVisibilityChange: (() => void) | null = null\n private readonly boundOnBeforeExit: (() => void) | null = null\n private readonly boundOnPageHide: (() => void) | null = null\n\n private started = false\n\n constructor(policy: SyncPolicy, callbacks: SyncSchedulerCallbacks) {\n this.policy = policy\n this.callbacks = callbacks\n\n // Pre-bind handlers\n if (this.shouldRegisterUnload()) {\n this.boundOnVisibilityChange = this.handleVisibilityChange.bind(this)\n this.boundOnPageHide = this.handlePageHide.bind(this)\n this.boundOnBeforeExit = this.handleBeforeExit.bind(this)\n }\n }\n\n /** Current scheduler status snapshot. */\n get status(): SyncSchedulerStatus {\n return {\n state: this._state,\n lastPushAt: this._lastPushAt,\n lastPullAt: this._lastPullAt,\n lastError: this._lastError,\n pendingWrites: this.callbacks.getDirtyCount(),\n }\n }\n\n /** Start the scheduler — registers timers, event listeners. */\n start(): void {\n if (this.started) return\n this.started = true\n\n // Push: interval mode\n if (this.policy.push.mode === 'interval' && this.policy.push.intervalMs) {\n this.pushIntervalTimer = setInterval(() => {\n void this.executePush()\n }, this.policy.push.intervalMs)\n }\n\n // Pull: interval mode\n if (this.policy.pull.mode === 'interval' && this.policy.pull.intervalMs) {\n this.pullIntervalTimer = setInterval(() => {\n void this.executePull()\n }, this.policy.pull.intervalMs)\n }\n\n // Pull: on-focus mode\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (this.shouldRegisterUnload()) {\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.addEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.addEventListener === 'function' && this.boundOnPageHide) {\n globalThis.addEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.on('beforeExit', this.boundOnBeforeExit)\n }\n }\n }\n\n /** Stop the scheduler — clears timers, removes event listeners. */\n stop(): void {\n if (!this.started) return\n this.started = false\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n if (this.pushIntervalTimer) {\n clearInterval(this.pushIntervalTimer)\n this.pushIntervalTimer = null\n }\n if (this.pullIntervalTimer) {\n clearInterval(this.pullIntervalTimer)\n this.pullIntervalTimer = null\n }\n\n // Focus pull\n if (this.policy.pull.mode === 'on-focus' && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.handleFocusPull)\n }\n\n // Unload hooks\n if (typeof document !== 'undefined' && this.boundOnVisibilityChange) {\n document.removeEventListener('visibilitychange', this.boundOnVisibilityChange)\n }\n if (typeof globalThis.removeEventListener === 'function' && this.boundOnPageHide) {\n globalThis.removeEventListener('pagehide', this.boundOnPageHide)\n }\n if (typeof process !== 'undefined' && this.boundOnBeforeExit) {\n process.removeListener('beforeExit', this.boundOnBeforeExit)\n }\n }\n\n /**\n * Notify the scheduler that a local write occurred.\n * For `on-change` mode: triggers immediate push (respecting minIntervalMs).\n * For `debounce` mode: resets the debounce timer.\n * For `manual` / `interval`: no-op.\n */\n notifyChange(): void {\n if (!this.started) return\n\n if (this.policy.push.mode === 'on-change') {\n void this.executePush()\n } else if (this.policy.push.mode === 'debounce') {\n this.resetDebounce()\n }\n }\n\n /** Force an immediate push, bypassing the scheduler. */\n async forcePush(): Promise<void> {\n await this.executePush()\n }\n\n /** Force an immediate pull, bypassing the scheduler. */\n async forcePull(): Promise<void> {\n await this.executePull()\n }\n\n // ─── Internal ─────────────────────────────────────────────────────\n\n private async executePush(): Promise<void> {\n if (this._state === 'pushing') return // already in progress\n\n // minIntervalMs enforcement\n const minInterval = this.policy.push.minIntervalMs ?? 0\n if (minInterval > 0) {\n const elapsed = Date.now() - this._lastPushTime\n if (elapsed < minInterval) {\n // Schedule for later if debounce mode\n if (this.policy.push.mode === 'debounce') {\n this.scheduleDebounce(minInterval - elapsed)\n }\n return\n }\n }\n\n // Nothing to push\n if (this.callbacks.getDirtyCount() === 0) {\n this._state = 'idle'\n return\n }\n\n this._state = 'pushing'\n try {\n await this.callbacks.push()\n this._lastPushAt = new Date().toISOString()\n this._lastPushTime = Date.now()\n this._lastError = null\n this._state = this.callbacks.getDirtyCount() > 0 ? 'pending' : 'idle'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n }\n }\n\n private async executePull(): Promise<void> {\n if (this._state === 'pulling') return\n\n const previousState = this._state\n this._state = 'pulling'\n try {\n await this.callbacks.pull()\n this._lastPullAt = new Date().toISOString()\n this._lastError = null\n this._state = previousState === 'pending' ? 'pending' : 'idle'\n } catch (err) {\n this._lastError = err instanceof Error ? err : new Error(String(err))\n this._state = 'error'\n }\n }\n\n private resetDebounce(): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n const ms = this.policy.push.debounceMs ?? 30_000\n this._state = 'pending'\n this.scheduleDebounce(ms)\n }\n\n private scheduleDebounce(ms: number): void {\n if (this.debounceTimer) clearTimeout(this.debounceTimer)\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null\n void this.executePush()\n }, ms)\n }\n\n private shouldRegisterUnload(): boolean {\n const onUnload = this.policy.push.onUnload\n if (onUnload !== undefined) return onUnload\n return this.policy.push.mode !== 'manual'\n }\n\n // ─── Event handlers ───────────────────────────────────────────────\n\n private handleVisibilityChange(): void {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n this.fireUnloadPush()\n }\n }\n\n private handlePageHide(): void {\n this.fireUnloadPush()\n }\n\n private handleBeforeExit(): void {\n this.fireUnloadPush()\n }\n\n private handleFocusPull = (): void => {\n if (typeof document !== 'undefined' && document.visibilityState === 'visible') {\n void this.executePull()\n }\n }\n\n private fireUnloadPush(): void {\n if (this.callbacks.getDirtyCount() === 0) return\n // Best-effort synchronous-ish push on unload\n void this.callbacks.push().catch(() => {})\n }\n}\n"],"mappings":"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","names":[]}