/** * Write-side and read-side normalization for money fields. * * - **Write** ({@link quantizeMoneyFields}): user input (`number | string`, * or `{ amount, currency }` in multi mode) → the canonical *stored* * form — a scaled-integer **digit string** (`'12345'`), or * `{ amount: '12345', currency: 'EUR' }` in multi mode. A JSON number * would truncate past 2^53, so the integer is always stored as a string. * - **Read** ({@link decodeMoneyFields}): stored form → an exact decimal * string (`'123.45'`) plus, when formatting is requested, the virtual * `Formatted` (locale currency string) and `Number` * (convenience JS number, explicitly lossy past 2^53). * * Both return a shallow clone; neither mutates the input record. */ import { type MoneyDescriptor } from './descriptor.js'; /** * Canonicalize a STORED-form record's money fields for an internal * callback boundary. Gate handlers (guard `check` / * `frozenFields` / `onDelete`, period guard, amendment invariants) and * derivation `derive(source, ctx)` callbacks are user-facing: they must * see the same decoded canonical decimal that `get()` returns — the * scaled-int storage form never escapes. Decoded with `'raw'` (no * `Formatted`/`Number` virtuals): these boundaries carry * no locale, and fabricating one would re-create a two-read-paths * skew inside comparisons. */ export declare function canonicalizeStoredMoney(record: unknown, moneyFields: Record | undefined): unknown; /** * Canonicalize an INCOMING record's money fields at the top of the * write pipeline. Raw user input (pre-quantize) may hold a * number (`10000`), a major-unit string (`'10000.00'`), or a spread of * an already-decoded read. Quantize→decode folds all three to the * canonical decimal string, so gate handlers, computed-field * callbacks, and schema validation all see the `get()` shape — and * freeze-style guards comparing `incoming[f]` vs `existing[f]` see * equal values for an unchanged field. Best-effort: input that fails * to quantize passes through unchanged — the write path quantizes * again after validation and surfaces the real * `MoneyPrecisionError`/`TypeError`. */ export declare function canonicalizeIncomingMoney(record: unknown, moneyFields: Record | undefined): unknown; /** * Convert money fields in `record` from user input to their canonical * stored form. Returns a shallow clone (deep along declared nested * paths). A nested path whose declared shape disagrees with the * data throws: writing through would store an un-quantized amount. */ export declare function quantizeMoneyFields>(record: T, moneyFields: Record): T; /** * The stored scaled-integer value of a money field as a `BigInt`, for * exact numeric comparison (e.g. `orderBy` / sorting), or `null` when the * stored value is missing/malformed. Mirrors the stored form `decodeValue` * reads: a bare scaled-int (fixed mode) or `{ amount, currency }` * (multi-currency). Comparison is in scaled space and is exact within a * single currency/scale (the `where` / `sum` BigInt model); across * currencies of different scales the raw scaled comparison is best-effort. */ export declare function moneyScaledValue(stored: unknown, desc: MoneyDescriptor): bigint | null; /** * The `ViaBinding.canonicalizeIndexKey` implementation for money (#672) — * bucket an eager index's money field entries by the BigInt-normalized * scaled-int string, not the raw stored bytes, so a pre-declaration / * non-canonical value (e.g. `'0100'`) lands under the SAME key a canonical * write produces (`'100'`). Only FIXED-mode declared money fields * participate — multi-mode stores `{ amount, currency }`, which has no * single bucketable scalar (mirrors `moneyIndexProbe`'s fixed-only gate, * `via/money/where.ts`). `undefined` when `field` isn't a declared * fixed-mode money field, or the stored value doesn't parse — in both * cases the caller falls back to the raw stringified bucket, which is * exactly what the scan (`evaluateMoneyClause`) also treats as a * non-match, preserving fast-path/scan parity. */ export declare function canonicalizeMoneyIndexKey(field: string, rawValue: unknown, moneyFields: Record): string | undefined; /** * money's `presentLate` hook (#669) — for each field in `virtualMoney` (money ∩ * virtual-mode computed on the SAME field), quantize the computed fn's fresh * MAJOR-UNITS output to the descriptor's scale (with its declared rounding) * and present it EXACTLY like a stored money field: the exact decimal string * (via {@link formatScaledInt}), plus `Formatted`/`Number` when * `locale !== 'raw'`. NEVER the scaled-int decode {@link decodeMoneyFields} * runs for a genuinely-stored value — that would misread a virtual field's * raw major-unit `21` as 21 SCALED units (`'0.21'`, the #665 corruption this * must not reintroduce). Runs AFTER computed's `present()` (the pipeline's * `presentLate` fold point, `kernel/via/pipeline.ts`), so `record[field]` * already holds the fn's fresh output by the time this runs. * * Absent/null value → left untouched (nothing to dress). Unparseable value * (fails `parseToScaledInt` — e.g. excess precision with no declared * rounding) → left RAW, no throw: read-time dressing must never brick a * read. Fixed-mode fields only — a virtual field's computed output has no * natural `{ amount, currency }` shape to parse for multi-currency mode. */ export declare function presentVirtualMoneyFields>(record: T, moneyFields: Record, virtualMoney: ReadonlySet, locale: string | undefined): T; /** * Convert money fields in `record` from stored form to the read shape: * an exact decimal string, plus `Formatted` / `Number` * virtuals when `locale !== 'raw'`. Returns a shallow clone (deep along * declared nested paths; virtuals land as siblings inside the * nested container, e.g. each `lineItems[]` element gains * `amountFormatted`, except for values held directly in arrays where a * scalar has no sibling slot). The decode walk is LENIENT: stored data * predating a declaration change must stay readable. */ export declare function decodeMoneyFields>(record: T, moneyFields: Record, locale: string | undefined): T;