diff --git a/web/src/lib/arraypolicy.ts b/web/src/lib/arraypolicy.ts new file mode 100644 index 0000000..23b614a --- /dev/null +++ b/web/src/lib/arraypolicy.ts @@ -0,0 +1,54 @@ +// Pure helpers for array-valued local state: parse the declared initial value +// and enforce the declared sizing policy (dynamic / capped / fixed). Shared by +// localstate.ts (write path) and logic.ts (node handlers + migration). + +import type { StateVar } from './types'; + +export type ArrVal = number | ArrVal[]; +export const ARRAY_MAX = 1_000_000; + +// zeroFill builds a length-n array of zeros (flat; fixed nested init must come +// from an explicit `initial` literal). +function zeroFill(n: number): ArrVal[] { + return new Array(Math.max(0, n)).fill(0); +} + +// parseInitialArray returns the starting contents of an array local. +export function parseInitialArray(sv: StateVar): ArrVal[] { + const cap = sv.capacity ?? 0; + const raw = (sv.initial ?? '').trim(); + let parsed: ArrVal[] | null = null; + if (raw) { + try { + const j = JSON.parse(raw); + if (Array.isArray(j)) parsed = j as ArrVal[]; + } catch { parsed = null; } + } + if (sv.sizing === 'fixed') { + if (!parsed) return zeroFill(cap); + // truncate / zero-pad to capacity + const out = parsed.slice(0, cap); + while (out.length < cap) out.push(0); + return out; + } + return parsed ?? []; +} + +// applySizing returns arr clamped to the declared policy. +// dynamic → unchanged (but globally capped at ARRAY_MAX, dropping oldest) +// capped → keep at most capacity elements, dropping oldest (ring/FIFO) +// fixed → exactly capacity elements (truncate / zero-pad); never grow/shrink +export function applySizing(arr: ArrVal[], sv: StateVar): ArrVal[] { + const cap = sv.capacity ?? 0; + switch (sv.sizing) { + case 'fixed': { + const out = arr.slice(0, cap); + while (out.length < cap) out.push(0); + return out; + } + case 'capped': + return arr.length > cap ? arr.slice(arr.length - cap) : arr; + default: + return arr.length > ARRAY_MAX ? arr.slice(arr.length - ARRAY_MAX) : arr; + } +}