attaform 0.16.4 → 0.17.0

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.
Files changed (84) hide show
  1. package/README.md +4 -2
  2. package/dist/chunks/devtools.cjs +19 -12
  3. package/dist/chunks/devtools.cjs.map +1 -1
  4. package/dist/chunks/devtools.mjs +19 -12
  5. package/dist/chunks/devtools.mjs.map +1 -1
  6. package/dist/chunks/indexeddb.cjs +1 -1
  7. package/dist/chunks/indexeddb.mjs +1 -1
  8. package/dist/chunks/local-storage.cjs +1 -1
  9. package/dist/chunks/local-storage.mjs +1 -1
  10. package/dist/chunks/session-storage.cjs +1 -1
  11. package/dist/chunks/session-storage.mjs +1 -1
  12. package/dist/index.cjs +26 -7
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +51 -8
  15. package/dist/index.d.mts +51 -8
  16. package/dist/index.d.ts +51 -8
  17. package/dist/index.mjs +28 -9
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/nuxt.d.cts +1 -1
  20. package/dist/nuxt.d.mts +1 -1
  21. package/dist/nuxt.d.ts +1 -1
  22. package/dist/runtime/plugins/attaform.cjs +3 -3
  23. package/dist/runtime/plugins/attaform.cjs.map +1 -1
  24. package/dist/runtime/plugins/attaform.mjs +3 -3
  25. package/dist/runtime/plugins/attaform.mjs.map +1 -1
  26. package/dist/shared/{attaform.Dd_pWnmn.cjs → attaform.0Wg7UEeX.cjs} +51 -10
  27. package/dist/shared/attaform.0Wg7UEeX.cjs.map +1 -0
  28. package/dist/shared/attaform.AOgGyRoI.d.cts +65 -0
  29. package/dist/shared/{attaform.CCQkY4Ta.d.ts → attaform.B0zue7zt.d.ts} +1 -1
  30. package/dist/shared/{attaform.CIwZtbGV.cjs → attaform.BBM2muQ9.cjs} +2 -2
  31. package/dist/shared/{attaform.CIwZtbGV.cjs.map → attaform.BBM2muQ9.cjs.map} +1 -1
  32. package/dist/shared/{attaform.fegmBJaq.cjs → attaform.BFumZXY2.cjs} +1422 -389
  33. package/dist/shared/attaform.BFumZXY2.cjs.map +1 -0
  34. package/dist/shared/attaform.BQ-iGGWd.d.mts +65 -0
  35. package/dist/shared/{attaform.DyV1O4tI.mjs → attaform.BT55rDNN.mjs} +1423 -391
  36. package/dist/shared/attaform.BT55rDNN.mjs.map +1 -0
  37. package/dist/shared/{attaform.CU3JperC.d.cts → attaform.BYbsV2Wv.d.cts} +574 -132
  38. package/dist/shared/{attaform.CU3JperC.d.mts → attaform.BYbsV2Wv.d.mts} +574 -132
  39. package/dist/shared/{attaform.CU3JperC.d.ts → attaform.BYbsV2Wv.d.ts} +574 -132
  40. package/dist/shared/{attaform.keLBaHB6.cjs → attaform.C6_zOf8x.cjs} +228 -113
  41. package/dist/shared/attaform.C6_zOf8x.cjs.map +1 -0
  42. package/dist/shared/{attaform.CJttVxRj.cjs → attaform.C8LVFVVe.cjs} +2 -2
  43. package/dist/shared/{attaform.CJttVxRj.cjs.map → attaform.C8LVFVVe.cjs.map} +1 -1
  44. package/dist/shared/{attaform.BfMxsfmE.mjs → attaform.CIEQgJnM.mjs} +143 -78
  45. package/dist/shared/attaform.CIEQgJnM.mjs.map +1 -0
  46. package/dist/shared/attaform.CX9v2M8k.d.ts +65 -0
  47. package/dist/shared/{attaform.g7rfuXdz.mjs → attaform.Cj0pCNVn.mjs} +228 -113
  48. package/dist/shared/attaform.Cj0pCNVn.mjs.map +1 -0
  49. package/dist/shared/{attaform.CMRmwGDt.d.cts → attaform.ClfCi1i2.d.mts} +1 -1
  50. package/dist/shared/{attaform.UA19EF3J.mjs → attaform.D6Q5ZP8L.mjs} +51 -10
  51. package/dist/shared/attaform.D6Q5ZP8L.mjs.map +1 -0
  52. package/dist/shared/{attaform.CXMOheyZ.d.mts → attaform.D7lomopc.d.cts} +1 -1
  53. package/dist/shared/{attaform.rIRYSUI1.cjs → attaform.Dee2rU1P.cjs} +145 -77
  54. package/dist/shared/attaform.Dee2rU1P.cjs.map +1 -0
  55. package/dist/shared/{attaform.CINUMjPq.mjs → attaform.Vo-Kft0t.mjs} +2 -2
  56. package/dist/shared/{attaform.CINUMjPq.mjs.map → attaform.Vo-Kft0t.mjs.map} +1 -1
  57. package/dist/shared/{attaform.DZRj9s0s.mjs → attaform.h1sq3BFu.mjs} +2 -2
  58. package/dist/shared/{attaform.DZRj9s0s.mjs.map → attaform.h1sq3BFu.mjs.map} +1 -1
  59. package/dist/zod-v3.cjs +3 -3
  60. package/dist/zod-v3.d.cts +5 -5
  61. package/dist/zod-v3.d.mts +5 -5
  62. package/dist/zod-v3.d.ts +5 -5
  63. package/dist/zod-v3.mjs +3 -3
  64. package/dist/zod-v4.cjs +3 -3
  65. package/dist/zod-v4.d.cts +16 -42
  66. package/dist/zod-v4.d.mts +16 -42
  67. package/dist/zod-v4.d.ts +16 -42
  68. package/dist/zod-v4.mjs +3 -3
  69. package/dist/zod.cjs +4 -4
  70. package/dist/zod.cjs.map +1 -1
  71. package/dist/zod.d.cts +6 -5
  72. package/dist/zod.d.mts +6 -5
  73. package/dist/zod.d.ts +6 -5
  74. package/dist/zod.mjs +5 -5
  75. package/dist/zod.mjs.map +1 -1
  76. package/package.json +3 -8
  77. package/dist/shared/attaform.BfMxsfmE.mjs.map +0 -1
  78. package/dist/shared/attaform.Dd_pWnmn.cjs.map +0 -1
  79. package/dist/shared/attaform.DyV1O4tI.mjs.map +0 -1
  80. package/dist/shared/attaform.UA19EF3J.mjs.map +0 -1
  81. package/dist/shared/attaform.fegmBJaq.cjs.map +0 -1
  82. package/dist/shared/attaform.g7rfuXdz.mjs.map +0 -1
  83. package/dist/shared/attaform.keLBaHB6.cjs.map +0 -1
  84. package/dist/shared/attaform.rIRYSUI1.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const plugin = require('./attaform.rIRYSUI1.cjs');
3
+ const plugin = require('./attaform.Dee2rU1P.cjs');
4
4
 
5
5
  const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
6
6
  function normalizeSegment(raw) {
@@ -98,4 +98,4 @@ exports.canonicalizePath = canonicalizePath;
98
98
  exports.isPathPrefix = isPathPrefix;
99
99
  exports.parseDottedPath = parseDottedPath;
100
100
  exports.segmentsForPathKey = segmentsForPathKey;
101
- //# sourceMappingURL=attaform.CIwZtbGV.cjs.map
101
+ //# sourceMappingURL=attaform.BBM2muQ9.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"attaform.CIwZtbGV.cjs","sources":["../../src/runtime/core/paths.ts"],"sourcesContent":["import { InvalidPathError } from './errors'\n\n/**\n * Path primitives for advanced integrations. The form library accepts\n * paths in dotted-string form (`'user.email'`) at every public API.\n * These primitives are exposed for adapter authors who need to\n * canonicalise user-provided paths.\n */\n\ndeclare const pathKeyBrand: unique symbol\n\n/**\n * Branded string identifier for a canonicalised path. Useful as a\n * `Map` key — two paths that resolve to the same canonical form\n * produce the same `PathKey`. Treat as opaque; don't try to parse.\n */\nexport type PathKey = string & { readonly [pathKeyBrand]: 'PathKey' }\n\n/** A single path segment — a property name or array index. */\nexport type Segment = string | number\n/** A structured path as a read-only sequence of segments. */\nexport type Path = readonly Segment[]\n\n/** Tests an integer-like string without leading zeros. `'0'` | `'1'` | `'42'` pass; `'01'`, `'-1'`, `'1.5'` do not. */\nconst INTEGER_SEGMENT = /^(?:0|[1-9]\\d*)$/\n\nfunction normalizeSegment(raw: Segment): Segment {\n if (typeof raw === 'number') {\n if (!Number.isInteger(raw) || raw < 0) {\n throw new InvalidPathError(\n `Path segments must be non-negative integers when numeric; got ${String(raw)}`\n )\n }\n return raw\n }\n // Integer-looking strings normalise to numbers so that dotted-form\n // `'items.0.name'` and array-form `['items', 0, 'name']` yield the same\n // canonical path (and PathKey).\n if (INTEGER_SEGMENT.test(raw)) return Number(raw)\n return raw\n}\n\n/**\n * Parse a dotted-string path into structured segments.\n *\n * ```ts\n * parseDottedPath('user.address.line1') // ['user', 'address', 'line1']\n * parseDottedPath('items.0.name') // ['items', 0, 'name']\n * parseDottedPath('') // [''] (the empty-string key)\n * ```\n *\n * The empty-string input `''` is the **literal empty-key path**, not\n * the root. Use the array form `[]` for root. Form-level errors\n * (root `.refine()`) live at the empty-string path bucket so\n * `errors('')` returns them without sweeping every field error too.\n *\n * Throws `InvalidPathError` for paths with empty INTERNAL segments\n * (`'a..b'`, leading or trailing dots). For keys containing literal\n * dots, pass an array form (`['user.name']`) instead.\n */\nexport function parseDottedPath(path: string): Segment[] {\n if (path.length === 0) return ['']\n const rawSegments = path.split('.')\n const segments: Segment[] = []\n for (const raw of rawSegments) {\n if (raw.length === 0) {\n throw new InvalidPathError(\n `Path '${path}' has an empty segment; use the array form for empty keys.`\n )\n }\n segments.push(normalizeSegment(raw))\n }\n return segments\n}\n\n/**\n * Bounded FIFO cache for canonicalizePath on dotted-string inputs.\n * Real forms re-canonicalise a small working-set of paths thousands\n * of times per session (every keystroke on a registered field, every\n * validate, every getValue), so a small cache amortises the parse +\n * stringify cost across repeat calls without pinning memory as apps\n * accumulate fields.\n *\n * Eviction is FIFO (oldest insertion wins), not LRU. The 128-entry\n * cap is generous relative to a typical form's working set\n * (playground: ~15 paths; the entire test suite: 45 unique register\n * patterns) — overflow doesn't fire in practice. On the rare overflow\n * a re-canonicalisation hit is still O(segments) and lands back in\n * the cache. Bumping recency on every hit (`delete` + `set`) costs\n * two Map operations per cache hit, in the hottest read-side loop in\n * the library, with no observable benefit at this cap — so we don't.\n *\n * Array inputs are not cached: callers in the runtime (unset-walker's\n * recursive `[...segments, i]`, devtools' inspector `payload.path.slice(...)`)\n * overwhelmingly pass freshly-allocated arrays per call, so a\n * WeakMap-keyed cache would miss on every call and pay the\n * lookup-then-set cost without benefit.\n */\nconst CANONICAL_STRING_CACHE_MAX = 128\nconst canonicalStringCache = new Map<string, { segments: readonly Segment[]; key: PathKey }>()\n\n/**\n * Inverse cache: PathKey → segments. Populated by `canonicalizePath`\n * (string and array branches) so any consumer holding a PathKey\n * produced through the canonical pipeline can recover its structured\n * segments without `JSON.parse`. Callers reach this through\n * `segmentsForPathKey` below.\n *\n * The store-side data structures keyed by PathKey (form-store error\n * maps, blank-paths set, variant-memory map, persistence opt-in\n * registry) all source their keys from `canonicalizePath`, so reads\n * are dominantly cache hits. Cold paths (PathKeys round-tripped from\n * a persisted payload that came from disk) still hit a single\n * `JSON.parse` on first lookup, then warm the cache.\n *\n * Bounded FIFO at 4096 entries — generous relative to a typical form's\n * working set (~tens to ~hundreds of paths per form) but small enough\n * that long-running multi-form apps don't accumulate unbounded\n * references. Eviction only fires on net-new entries; idempotent\n * overwrites (same key, same segments) don't count toward the cap.\n */\nconst PATHKEY_TO_SEGMENTS_MAX = 4096\nconst pathKeyToSegments = new Map<PathKey, readonly Segment[]>()\n\nfunction rememberSegmentsForPathKey(key: PathKey, segments: readonly Segment[]): void {\n if (!pathKeyToSegments.has(key) && pathKeyToSegments.size >= PATHKEY_TO_SEGMENTS_MAX) {\n const oldest = pathKeyToSegments.keys().next().value\n if (oldest !== undefined) pathKeyToSegments.delete(oldest)\n }\n pathKeyToSegments.set(key, segments)\n}\n\n/**\n * Recover the structured `Segment[]` for a `PathKey` produced by\n * `canonicalizePath`. O(1) on the hot path (cache hit); cold keys\n * fall back to `JSON.parse(key)` plus segment normalization, then\n * warm the cache so subsequent lookups hit.\n *\n * Returns `null` for malformed PathKeys (non-JSON, non-array, or\n * containing values that aren't strings/numbers). Keys produced by\n * `canonicalizePath` never trip this — corrupt persistence payloads\n * (or test fixtures crafting raw strings) are the only realistic\n * sources.\n */\nexport function segmentsForPathKey(key: PathKey): readonly Segment[] | null {\n const cached = pathKeyToSegments.get(key)\n if (cached !== undefined) return cached\n let parsed: unknown\n try {\n parsed = JSON.parse(key)\n } catch {\n return null\n }\n if (!Array.isArray(parsed)) return null\n const segments: Segment[] = []\n for (const raw of parsed) {\n if (typeof raw !== 'string' && typeof raw !== 'number') return null\n segments.push(normalizeSegment(raw))\n }\n rememberSegmentsForPathKey(key, segments)\n return segments\n}\n\n/**\n * Canonicalise a path into structured segments plus a stable string\n * key. Accepts either dotted-string or array form; integer-looking\n * segments normalise to numbers.\n *\n * ```ts\n * canonicalizePath('items.0.name')\n * // { segments: ['items', 0, 'name'], key: '[\"items\",0,\"name\"]' as PathKey }\n *\n * canonicalizePath(['items', 0, 'name'])\n * // → same result\n * ```\n *\n * The returned `key` is suitable as a `Map`/`Set` key — equal paths\n * produce equal keys regardless of input form.\n */\nexport function canonicalizePath(input: string | Path): {\n segments: readonly Segment[]\n key: PathKey\n} {\n if (typeof input === 'string') {\n const cached = canonicalStringCache.get(input)\n if (cached !== undefined) return cached\n // `parseDottedPath` already normalises each segment; the previous\n // `.map(normalizeSegment)` second pass was a no-op. We drop it here.\n const segments: readonly Segment[] = parseDottedPath(input)\n const key = JSON.stringify(segments) as PathKey\n const entry = { segments, key }\n if (canonicalStringCache.size >= CANONICAL_STRING_CACHE_MAX) {\n const oldest = canonicalStringCache.keys().next().value\n if (oldest !== undefined) canonicalStringCache.delete(oldest)\n }\n canonicalStringCache.set(input, entry)\n rememberSegmentsForPathKey(key, segments)\n return entry\n }\n const segments = Array.from(input).map(normalizeSegment)\n const key = JSON.stringify(segments) as PathKey\n rememberSegmentsForPathKey(key, segments)\n return { segments, key }\n}\n\n/**\n * The root path — an empty segment tuple. Pass to APIs that accept\n * a `Path` to address the form value as a whole.\n */\nexport const ROOT_PATH: Path = Object.freeze([])\n/** Stable string key for the root path. */\nexport const ROOT_PATH_KEY = '[]' as PathKey\n\n/**\n * The form-level path — a one-segment path with the empty-string\n * key. Conventionally home for errors that don't belong to any\n * specific field: root `.refine()` messages, server-emitted form\n * errors, capacity / availability / cross-field-summary banners.\n *\n * Distinct from `ROOT_PATH` (`[]`): the root is the whole-form\n * subtree address (e.g. `errors([])` returns every error). The\n * form-level path is a sibling-of-no-field path that aggregation\n * walks DON'T sweep into per-field reads — `errors('field')`\n * returns only the field's errors, never the form-level bucket,\n * and `errors('')` returns only the form-level bucket, never the\n * field errors.\n */\nexport const FORM_ERRORS_PATH: Path = Object.freeze([''])\n/** Stable string key for the form-level errors path. */\nexport const FORM_ERRORS_PATH_KEY = '[\"\"]' as PathKey\n\n/**\n * `true` when `path` starts with every segment of `prefix` (in order).\n * The empty `prefix` matches every path — ROOT prefix is universal.\n *\n * Walks segments rather than `PathKey` strings because the data this\n * helper operates on (e.g. `meta.errors[].path`) carries segment\n * arrays directly.\n *\n * ```ts\n * isPathPrefix(['cargo'], ['cargo', 'items', 0, 'sku']) // true\n * isPathPrefix(['cargo', 'items'], ['cargo']) // false (path shorter)\n * isPathPrefix([], ['anything']) // true (root prefix)\n * ```\n */\nexport function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean {\n if (path.length < prefix.length) return false\n for (let i = 0; i < prefix.length; i++) {\n if (path[i] !== prefix[i]) return false\n }\n return true\n}\n"],"names":["InvalidPathError","segments","key"],"mappings":"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"}
1
+ {"version":3,"file":"attaform.BBM2muQ9.cjs","sources":["../../src/runtime/core/paths.ts"],"sourcesContent":["import { InvalidPathError } from './errors'\n\n/**\n * Path primitives for advanced integrations. The form library accepts\n * paths in dotted-string form (`'user.email'`) at every public API.\n * These primitives are exposed for adapter authors who need to\n * canonicalise user-provided paths.\n */\n\ndeclare const pathKeyBrand: unique symbol\n\n/**\n * Branded string identifier for a canonicalised path. Useful as a\n * `Map` key — two paths that resolve to the same canonical form\n * produce the same `PathKey`. Treat as opaque; don't try to parse.\n */\nexport type PathKey = string & { readonly [pathKeyBrand]: 'PathKey' }\n\n/** A single path segment — a property name or array index. */\nexport type Segment = string | number\n/** A structured path as a read-only sequence of segments. */\nexport type Path = readonly Segment[]\n\n/** Tests an integer-like string without leading zeros. `'0'` | `'1'` | `'42'` pass; `'01'`, `'-1'`, `'1.5'` do not. */\nconst INTEGER_SEGMENT = /^(?:0|[1-9]\\d*)$/\n\nfunction normalizeSegment(raw: Segment): Segment {\n if (typeof raw === 'number') {\n if (!Number.isInteger(raw) || raw < 0) {\n throw new InvalidPathError(\n `Path segments must be non-negative integers when numeric; got ${String(raw)}`\n )\n }\n return raw\n }\n // Integer-looking strings normalise to numbers so that dotted-form\n // `'items.0.name'` and array-form `['items', 0, 'name']` yield the same\n // canonical path (and PathKey).\n if (INTEGER_SEGMENT.test(raw)) return Number(raw)\n return raw\n}\n\n/**\n * Parse a dotted-string path into structured segments.\n *\n * ```ts\n * parseDottedPath('user.address.line1') // ['user', 'address', 'line1']\n * parseDottedPath('items.0.name') // ['items', 0, 'name']\n * parseDottedPath('') // [''] (the empty-string key)\n * ```\n *\n * The empty-string input `''` is the **literal empty-key path**, not\n * the root. Use the array form `[]` for root. Form-level errors\n * (root `.refine()`) live at the empty-string path bucket so\n * `errors('')` returns them without sweeping every field error too.\n *\n * Throws `InvalidPathError` for paths with empty INTERNAL segments\n * (`'a..b'`, leading or trailing dots). For keys containing literal\n * dots, pass an array form (`['user.name']`) instead.\n */\nexport function parseDottedPath(path: string): Segment[] {\n if (path.length === 0) return ['']\n const rawSegments = path.split('.')\n const segments: Segment[] = []\n for (const raw of rawSegments) {\n if (raw.length === 0) {\n throw new InvalidPathError(\n `Path '${path}' has an empty segment; use the array form for empty keys.`\n )\n }\n segments.push(normalizeSegment(raw))\n }\n return segments\n}\n\n/**\n * Bounded FIFO cache for canonicalizePath on dotted-string inputs.\n * Real forms re-canonicalise a small working-set of paths thousands\n * of times per session (every keystroke on a registered field, every\n * validate, every getValue), so a small cache amortises the parse +\n * stringify cost across repeat calls without pinning memory as apps\n * accumulate fields.\n *\n * Eviction is FIFO (oldest insertion wins), not LRU. The 128-entry\n * cap is generous relative to a typical form's working set\n * (playground: ~15 paths; the entire test suite: 45 unique register\n * patterns) — overflow doesn't fire in practice. On the rare overflow\n * a re-canonicalisation hit is still O(segments) and lands back in\n * the cache. Bumping recency on every hit (`delete` + `set`) costs\n * two Map operations per cache hit, in the hottest read-side loop in\n * the library, with no observable benefit at this cap — so we don't.\n *\n * Array inputs are not cached: callers in the runtime (unset-walker's\n * recursive `[...segments, i]`, devtools' inspector `payload.path.slice(...)`)\n * overwhelmingly pass freshly-allocated arrays per call, so a\n * WeakMap-keyed cache would miss on every call and pay the\n * lookup-then-set cost without benefit.\n */\nconst CANONICAL_STRING_CACHE_MAX = 128\nconst canonicalStringCache = new Map<string, { segments: readonly Segment[]; key: PathKey }>()\n\n/**\n * Inverse cache: PathKey → segments. Populated by `canonicalizePath`\n * (string and array branches) so any consumer holding a PathKey\n * produced through the canonical pipeline can recover its structured\n * segments without `JSON.parse`. Callers reach this through\n * `segmentsForPathKey` below.\n *\n * The store-side data structures keyed by PathKey (form-store error\n * maps, blank-paths set, variant-memory map, persistence opt-in\n * registry) all source their keys from `canonicalizePath`, so reads\n * are dominantly cache hits. Cold paths (PathKeys round-tripped from\n * a persisted payload that came from disk) still hit a single\n * `JSON.parse` on first lookup, then warm the cache.\n *\n * Bounded FIFO at 4096 entries — generous relative to a typical form's\n * working set (~tens to ~hundreds of paths per form) but small enough\n * that long-running multi-form apps don't accumulate unbounded\n * references. Eviction only fires on net-new entries; idempotent\n * overwrites (same key, same segments) don't count toward the cap.\n */\nconst PATHKEY_TO_SEGMENTS_MAX = 4096\nconst pathKeyToSegments = new Map<PathKey, readonly Segment[]>()\n\nfunction rememberSegmentsForPathKey(key: PathKey, segments: readonly Segment[]): void {\n if (!pathKeyToSegments.has(key) && pathKeyToSegments.size >= PATHKEY_TO_SEGMENTS_MAX) {\n const oldest = pathKeyToSegments.keys().next().value\n if (oldest !== undefined) pathKeyToSegments.delete(oldest)\n }\n pathKeyToSegments.set(key, segments)\n}\n\n/**\n * Recover the structured `Segment[]` for a `PathKey` produced by\n * `canonicalizePath`. O(1) on the hot path (cache hit); cold keys\n * fall back to `JSON.parse(key)` plus segment normalization, then\n * warm the cache so subsequent lookups hit.\n *\n * Returns `null` for malformed PathKeys (non-JSON, non-array, or\n * containing values that aren't strings/numbers). Keys produced by\n * `canonicalizePath` never trip this — corrupt persistence payloads\n * (or test fixtures crafting raw strings) are the only realistic\n * sources.\n */\nexport function segmentsForPathKey(key: PathKey): readonly Segment[] | null {\n const cached = pathKeyToSegments.get(key)\n if (cached !== undefined) return cached\n let parsed: unknown\n try {\n parsed = JSON.parse(key)\n } catch {\n return null\n }\n if (!Array.isArray(parsed)) return null\n const segments: Segment[] = []\n for (const raw of parsed) {\n if (typeof raw !== 'string' && typeof raw !== 'number') return null\n segments.push(normalizeSegment(raw))\n }\n rememberSegmentsForPathKey(key, segments)\n return segments\n}\n\n/**\n * Canonicalise a path into structured segments plus a stable string\n * key. Accepts either dotted-string or array form; integer-looking\n * segments normalise to numbers.\n *\n * ```ts\n * canonicalizePath('items.0.name')\n * // { segments: ['items', 0, 'name'], key: '[\"items\",0,\"name\"]' as PathKey }\n *\n * canonicalizePath(['items', 0, 'name'])\n * // → same result\n * ```\n *\n * The returned `key` is suitable as a `Map`/`Set` key — equal paths\n * produce equal keys regardless of input form.\n */\nexport function canonicalizePath(input: string | Path): {\n segments: readonly Segment[]\n key: PathKey\n} {\n if (typeof input === 'string') {\n const cached = canonicalStringCache.get(input)\n if (cached !== undefined) return cached\n // `parseDottedPath` already normalises each segment; the previous\n // `.map(normalizeSegment)` second pass was a no-op. We drop it here.\n const segments: readonly Segment[] = parseDottedPath(input)\n const key = JSON.stringify(segments) as PathKey\n const entry = { segments, key }\n if (canonicalStringCache.size >= CANONICAL_STRING_CACHE_MAX) {\n const oldest = canonicalStringCache.keys().next().value\n if (oldest !== undefined) canonicalStringCache.delete(oldest)\n }\n canonicalStringCache.set(input, entry)\n rememberSegmentsForPathKey(key, segments)\n return entry\n }\n const segments = Array.from(input).map(normalizeSegment)\n const key = JSON.stringify(segments) as PathKey\n rememberSegmentsForPathKey(key, segments)\n return { segments, key }\n}\n\n/**\n * The root path — an empty segment tuple. Pass to APIs that accept\n * a `Path` to address the form value as a whole.\n */\nexport const ROOT_PATH: Path = Object.freeze([])\n/** Stable string key for the root path. */\nexport const ROOT_PATH_KEY = '[]' as PathKey\n\n/**\n * The form-level path — a one-segment path with the empty-string\n * key. Conventionally home for errors that don't belong to any\n * specific field: root `.refine()` messages, server-emitted form\n * errors, capacity / availability / cross-field-summary banners.\n *\n * Distinct from `ROOT_PATH` (`[]`): the root is the whole-form\n * subtree address (e.g. `errors([])` returns every error). The\n * form-level path is a sibling-of-no-field path that aggregation\n * walks DON'T sweep into per-field reads — `errors('field')`\n * returns only the field's errors, never the form-level bucket,\n * and `errors('')` returns only the form-level bucket, never the\n * field errors.\n */\nexport const FORM_ERRORS_PATH: Path = Object.freeze([''])\n/** Stable string key for the form-level errors path. */\nexport const FORM_ERRORS_PATH_KEY = '[\"\"]' as PathKey\n\n/**\n * `true` when `path` starts with every segment of `prefix` (in order).\n * The empty `prefix` matches every path — ROOT prefix is universal.\n *\n * Walks segments rather than `PathKey` strings because the data this\n * helper operates on (e.g. `meta.errors[].path`) carries segment\n * arrays directly.\n *\n * ```ts\n * isPathPrefix(['cargo'], ['cargo', 'items', 0, 'sku']) // true\n * isPathPrefix(['cargo', 'items'], ['cargo']) // false (path shorter)\n * isPathPrefix([], ['anything']) // true (root prefix)\n * ```\n */\nexport function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean {\n if (path.length < prefix.length) return false\n for (let i = 0; i < prefix.length; i++) {\n if (path[i] !== prefix[i]) return false\n }\n return true\n}\n"],"names":["InvalidPathError","segments","key"],"mappings":"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"}