attaform 0.15.1 → 0.16.1

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 (92) hide show
  1. package/README.md +24 -12
  2. package/dist/chunks/devtools.cjs +4 -4
  3. package/dist/chunks/devtools.cjs.map +1 -1
  4. package/dist/chunks/devtools.mjs +2 -2
  5. package/dist/chunks/indexeddb.cjs +4 -4
  6. package/dist/chunks/indexeddb.cjs.map +1 -1
  7. package/dist/chunks/indexeddb.mjs +1 -1
  8. package/dist/chunks/local-storage.cjs +2 -2
  9. package/dist/chunks/local-storage.cjs.map +1 -1
  10. package/dist/chunks/local-storage.mjs +1 -1
  11. package/dist/chunks/session-storage.cjs +2 -2
  12. package/dist/chunks/session-storage.cjs.map +1 -1
  13. package/dist/chunks/session-storage.mjs +1 -1
  14. package/dist/index.cjs +23 -22
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +75 -70
  17. package/dist/index.d.mts +75 -70
  18. package/dist/index.d.ts +75 -70
  19. package/dist/index.mjs +6 -6
  20. package/dist/nuxt.cjs +5 -11
  21. package/dist/nuxt.cjs.map +1 -1
  22. package/dist/nuxt.d.cts +8 -0
  23. package/dist/nuxt.d.mts +8 -0
  24. package/dist/nuxt.d.ts +8 -0
  25. package/dist/nuxt.mjs +6 -12
  26. package/dist/nuxt.mjs.map +1 -1
  27. package/dist/runtime/plugins/attaform.cjs +3 -2
  28. package/dist/runtime/plugins/attaform.cjs.map +1 -1
  29. package/dist/runtime/plugins/attaform.mjs +2 -1
  30. package/dist/runtime/plugins/attaform.mjs.map +1 -1
  31. package/dist/shared/{attaform.BwaYWtMs.d.cts → attaform.B7rzpK1U.d.cts} +34 -5
  32. package/dist/shared/{attaform.BwaYWtMs.d.mts → attaform.B7rzpK1U.d.mts} +34 -5
  33. package/dist/shared/{attaform.BwaYWtMs.d.ts → attaform.B7rzpK1U.d.ts} +34 -5
  34. package/dist/shared/attaform.BAuJTWuT.d.mts +84 -0
  35. package/dist/shared/{attaform.CRk8NhlD.mjs → attaform.BfMxsfmE.mjs} +428 -49
  36. package/dist/shared/attaform.BfMxsfmE.mjs.map +1 -0
  37. package/dist/shared/attaform.Bp1c-uGF.cjs +1561 -0
  38. package/dist/shared/attaform.Bp1c-uGF.cjs.map +1 -0
  39. package/dist/shared/{attaform.CDJVeoJU.cjs → attaform.C9Ph2SMx.cjs} +49 -42
  40. package/dist/shared/{attaform.qxyip_aN.mjs.map → attaform.C9Ph2SMx.cjs.map} +1 -1
  41. package/dist/shared/attaform.CINUMjPq.mjs +29 -0
  42. package/dist/shared/attaform.CINUMjPq.mjs.map +1 -0
  43. package/dist/shared/attaform.CJttVxRj.cjs +32 -0
  44. package/dist/shared/attaform.CJttVxRj.cjs.map +1 -0
  45. package/dist/shared/attaform.CvOXSpCb.mjs +1908 -0
  46. package/dist/shared/attaform.CvOXSpCb.mjs.map +1 -0
  47. package/dist/shared/{attaform.qxyip_aN.mjs → attaform.DILbdvfo.mjs} +12 -5
  48. package/dist/shared/{attaform.CDJVeoJU.cjs.map → attaform.DILbdvfo.mjs.map} +1 -1
  49. package/dist/shared/attaform.DdnithOf.mjs +1555 -0
  50. package/dist/shared/attaform.DdnithOf.mjs.map +1 -0
  51. package/dist/shared/attaform.DfrYByDj.cjs +1916 -0
  52. package/dist/shared/attaform.DfrYByDj.cjs.map +1 -0
  53. package/dist/shared/{attaform.BOi138GE.cjs → attaform.c_NzdRyc.cjs} +4 -4
  54. package/dist/shared/{attaform.BOi138GE.cjs.map → attaform.c_NzdRyc.cjs.map} +1 -1
  55. package/dist/shared/{attaform.DXye3JKf.mjs → attaform.jrxE_xZw.mjs} +2 -2
  56. package/dist/shared/{attaform.DXye3JKf.mjs.map → attaform.jrxE_xZw.mjs.map} +1 -1
  57. package/dist/shared/attaform.ls_7jBYc.d.ts +84 -0
  58. package/dist/shared/{attaform.BgYBU8gV.cjs → attaform.rIRYSUI1.cjs} +461 -61
  59. package/dist/shared/attaform.rIRYSUI1.cjs.map +1 -0
  60. package/dist/shared/attaform.xIcmqscx.d.cts +84 -0
  61. package/dist/vite.cjs +62 -9
  62. package/dist/vite.cjs.map +1 -1
  63. package/dist/vite.d.cts +23 -32
  64. package/dist/vite.d.mts +23 -32
  65. package/dist/vite.d.ts +23 -32
  66. package/dist/vite.mjs +62 -9
  67. package/dist/vite.mjs.map +1 -1
  68. package/dist/zod-v3.cjs +9 -1553
  69. package/dist/zod-v3.cjs.map +1 -1
  70. package/dist/zod-v3.mjs +3 -1553
  71. package/dist/zod-v3.mjs.map +1 -1
  72. package/dist/zod-v4.cjs +21 -0
  73. package/dist/zod-v4.cjs.map +1 -0
  74. package/dist/zod-v4.d.cts +104 -0
  75. package/dist/zod-v4.d.mts +104 -0
  76. package/dist/zod-v4.d.ts +104 -0
  77. package/dist/zod-v4.mjs +4 -0
  78. package/dist/zod-v4.mjs.map +1 -0
  79. package/dist/zod.cjs +19 -1900
  80. package/dist/zod.cjs.map +1 -1
  81. package/dist/zod.d.cts +27 -155
  82. package/dist/zod.d.mts +27 -155
  83. package/dist/zod.d.ts +27 -155
  84. package/dist/zod.mjs +19 -1896
  85. package/dist/zod.mjs.map +1 -1
  86. package/package.json +6 -2
  87. package/dist/shared/attaform.BgYBU8gV.cjs.map +0 -1
  88. package/dist/shared/attaform.CRk8NhlD.mjs.map +0 -1
  89. package/dist/shared/attaform.RypIkgVy.cjs +0 -417
  90. package/dist/shared/attaform.RypIkgVy.cjs.map +0 -1
  91. package/dist/shared/attaform.a99dQV7Q.mjs +0 -392
  92. package/dist/shared/attaform.a99dQV7Q.mjs.map +0 -1
package/README.md CHANGED
@@ -14,25 +14,23 @@ A type-safe, schema-driven form library for Vue 3 and Nuxt with first-class Zod
14
14
  npm install attaform zod
15
15
  ```
16
16
 
17
- **Nuxt 3 / 4** install the module:
17
+ That's it for client-side rendering. Forms render and validate the moment you call `useForm` — the registry self-installs on first use.
18
18
 
19
- ```ts
20
- // nuxt.config.ts
21
- export default defineNuxtConfig({
22
- modules: ['attaform/nuxt'],
23
- })
24
- ```
19
+ ### Going further
25
20
 
26
- **Bare Vue 3** — install the plugin and the Vite plugin:
21
+ **Nuxt 3 / 4** — install the Vue plugin via a Nuxt plugin:
27
22
 
28
23
  ```ts
29
- // main.ts
30
- import { createApp } from 'vue'
24
+ // plugins/attaform.ts
31
25
  import { createAttaform } from 'attaform'
32
26
 
33
- createApp(App).use(createAttaform()).mount('#app')
27
+ export default defineNuxtPlugin((nuxtApp) => {
28
+ nuxtApp.vueApp.use(createAttaform())
29
+ })
34
30
  ```
35
31
 
32
+ **Bare Vue + SSR** — add the Vite plugin so server-rendered HTML matches the hydrated client (the plugin injects `:value` / `:checked` bindings at compile time):
33
+
36
34
  ```ts
37
35
  // vite.config.ts
38
36
  import vue from '@vitejs/plugin-vue'
@@ -43,6 +41,20 @@ export default defineConfig({
43
41
  })
44
42
  ```
45
43
 
44
+ The Vite plugin also rewrites `attaform/zod` imports at build time to `attaform/zod-v3` or `attaform/zod-v4` — your bundle ships only the adapter you actually use.
45
+
46
+ **App-wide options** — install the Vue plugin if you want to set defaults or disable devtools:
47
+
48
+ ```ts
49
+ // main.ts
50
+ import { createApp } from 'vue'
51
+ import { createAttaform } from 'attaform'
52
+
53
+ createApp(App)
54
+ .use(createAttaform({ defaults: { debounceMs: 100 } }))
55
+ .mount('#app')
56
+ ```
57
+
46
58
  ### Recommended tsconfig
47
59
 
48
60
  We pair well with `noUncheckedIndexedAccess: true`:
@@ -63,7 +75,7 @@ It catches stale `form.values.contacts[N]` reads at compile time. Nuxt 3 / 4 set
63
75
  ```vue
64
76
  <script setup lang="ts">
65
77
  import { z } from 'zod'
66
- import { useForm } from 'attaform/zod' // zod v4; use /zod-v3 for v3
78
+ import { useForm } from 'attaform/zod' // auto-detects Zod major
67
79
 
68
80
  const schema = z.object({
69
81
  email: z.email(),
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const paths = require('../shared/attaform.BOi138GE.cjs');
4
- const sensitiveNames = require('../shared/attaform.RypIkgVy.cjs');
3
+ const paths = require('../shared/attaform.c_NzdRyc.cjs');
4
+ const plugin = require('../shared/attaform.rIRYSUI1.cjs');
5
5
 
6
6
  const INSPECTOR_ID = "attaform";
7
7
  const TIMELINE_LAYER_ID = "attaform:events";
@@ -22,7 +22,7 @@ function redactImpl(value, inSensitiveSubtree) {
22
22
  }
23
23
  const out = {};
24
24
  for (const key of Object.keys(value)) {
25
- const childSensitive = inSensitiveSubtree || sensitiveNames.segmentMatchesSensitive(key);
25
+ const childSensitive = inSensitiveSubtree || plugin.segmentMatchesSensitive(key);
26
26
  out[key] = redactImpl(value[key], childSensitive);
27
27
  }
28
28
  return out;
@@ -165,7 +165,7 @@ function wire(api, app, registry) {
165
165
  if (section !== "Form value") return;
166
166
  const segments = payload.path.slice(2);
167
167
  const { segments: canonicalPath, key: canonicalKey } = paths.canonicalizePath(segments);
168
- if (sensitiveNames.isSensitivePath([...canonicalPath])) return;
168
+ if (plugin.isSensitivePath([...canonicalPath])) return;
169
169
  state.setValueAtPath(canonicalPath, payload.state.value, {
170
170
  persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey)
171
171
  });
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.cjs","sources":["../../src/runtime/core/devtools.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormStore } from './create-form-store'\nimport type { AttaformRegistry } from './registry'\nimport type { GenericForm } from '../types/types-core'\nimport type { FormKey } from '../types/types-api'\nimport { canonicalizePath } from './paths'\nimport { isSensitivePath, segmentMatchesSensitive } from './persistence/sensitive-names'\n\n/**\n * Vue DevTools plugin wiring for attaform. Lazy-imported by\n * `createAttaform` under dev-mode guards so the production\n * bundle tree-shakes it out entirely.\n *\n * Registers:\n * - An inspector (per-app) that lists every registered form, with\n * nodes for form value / errors / aggregates / history.\n * - A timeline layer that emits events on submit start/success/\n * failure, reset, undo, redo, and form mutations.\n * - State editing — modifying a leaf inside the inspector tree\n * pushes through `state.setValueAtPath`, mutating the form.\n *\n * Tolerant of missing `@vue/devtools-api` — the peer dep is marked\n * optional. If the import fails, `setupAttaformDevtools` silently\n * no-ops so production builds / users without DevTools installed\n * don't see errors.\n */\n\nconst INSPECTOR_ID = 'attaform'\nconst TIMELINE_LAYER_ID = 'attaform:events'\n\nconst REDACTED = '[redacted]'\n\n/**\n * Walk `value` and replace any leaf whose enclosing path matches the\n * sensitive-name heuristic with the string `'[redacted]'`. Returns a\n * new tree (no mutation of the input). Object keys + array indices\n * are preserved; only the leaf payloads change.\n *\n * Applied to BOTH the DevTools timeline events and the inspector\n * `Form value` panel — leaks via either surface are treatable as\n * \"any developer with the panel open during user testing can read\n * a customer's password,\" which is exactly the failure mode the\n * sensitive-name guard exists to prevent on the storage side.\n *\n * Leaves whose path doesn't match a pattern pass through untouched.\n * `acknowledgeSensitive: true` on persistence does NOT bypass this —\n * if the consumer opted into persisting the value, they still\n * shouldn't see it in DevTools timelines that grow unbounded.\n *\n * Implementation note: tracks an `inSensitiveSubtree` flag through\n * the recursion instead of allocating a fresh path array per node\n * + calling `isSensitivePath` per leaf. Once any ancestor segment\n * matches the heuristic, the flag stays set for every descendant —\n * the leaf simply returns `REDACTED` without re-scanning the path.\n * For a 100-leaf form: ~100 path allocations + ~100 full-path regex\n * sweeps → 0 path allocations + ~100 single-segment regex sweeps,\n * with whole-subtree short-circuit when sensitive ancestors are\n * found early.\n */\nfunction redactSensitiveLeaves(value: unknown): unknown {\n return redactImpl(value, false)\n}\n\nfunction redactImpl(value: unknown, inSensitiveSubtree: boolean): unknown {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') {\n return inSensitiveSubtree ? REDACTED : value\n }\n if (Array.isArray(value)) {\n // Numeric segments never match the sensitive-name heuristic\n // (segmentMatchesSensitive rejects non-string segments), so the\n // flag passes through unchanged when descending into arrays.\n return value.map((item) => redactImpl(item, inSensitiveSubtree))\n }\n // Non-plain object (Map / Set / Date / class instance) — redact\n // wholesale if we're already in a sensitive subtree; otherwise pass\n // through. DevTools rendering of these is already heuristic, so we\n // don't try to descend into them.\n if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\n return inSensitiveSubtree ? REDACTED : value\n }\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const childSensitive = inSensitiveSubtree || segmentMatchesSensitive(key)\n out[key] = redactImpl((value as Record<string, unknown>)[key], childSensitive)\n }\n return out\n}\n\ntype UnsafeDevtoolsApi = {\n addInspector(opts: { id: string; label: string; icon?: string; app: App }): void\n addTimelineLayer(opts: { id: string; label: string; color: number }): void\n sendInspectorTree(inspectorId: string): void\n sendInspectorState(inspectorId: string): void\n addTimelineEvent(payload: {\n layerId: string\n event: {\n time: number\n title: string\n subtitle?: string\n data?: Record<string, unknown>\n groupId?: string | number\n }\n }): void\n on: {\n getInspectorTree(\n handler: (payload: {\n inspectorId: string\n filter: string\n rootNodes: Array<{ id: string; label: string; tags?: unknown[] }>\n }) => void\n ): void\n getInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n state: Record<string, Array<{ key: string; value: unknown; editable?: boolean }>>\n }) => void\n ): void\n editInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n path: string[]\n state: { value: unknown; newKey?: string | null; remove?: boolean }\n }) => void\n ): void\n }\n}\n\ntype SetupDevtoolsPluginFn = (\n descriptor: {\n id: string\n label: string\n packageName?: string\n homepage?: string\n componentStateTypes?: string[]\n app: App\n },\n setup: (api: UnsafeDevtoolsApi) => void\n) => void\n\n/**\n * Install the DevTools plugin for the given Vue app + registry. Safe\n * to call in production — if `@vue/devtools-api` isn't installed, the\n * dynamic import fails and we log nothing. Returns `true` when\n * DevTools was wired successfully, `false` otherwise — useful for\n * tests.\n */\nexport async function setupAttaformDevtools(\n app: App,\n registry: AttaformRegistry\n): Promise<boolean> {\n let mod: { setupDevtoolsPlugin?: SetupDevtoolsPluginFn }\n try {\n mod = (await import('@vue/devtools-api')) as {\n setupDevtoolsPlugin?: SetupDevtoolsPluginFn\n }\n } catch {\n // Peer dep not installed — silently skip. Production builds pass\n // `{ devtools: false }` explicitly, but this catch covers the\n // \"dev without the peer dep\" case without a noisy warning.\n return false\n }\n const setupDevtoolsPlugin = mod.setupDevtoolsPlugin\n if (typeof setupDevtoolsPlugin !== 'function') return false\n\n setupDevtoolsPlugin(\n {\n id: INSPECTOR_ID,\n label: 'Attaform',\n packageName: 'attaform',\n homepage: 'https://github.com/attaform/attaform',\n app,\n componentStateTypes: ['Attaform form'],\n },\n (api) => wire(api, app, registry)\n )\n return true\n}\n\nfunction wire(api: UnsafeDevtoolsApi, app: App, registry: AttaformRegistry): void {\n // Per-form subscriber bookkeeping — we keep the unsubscribers so\n // the registry's eviction path can detach them when a form is\n // disposed. Using a Map keyed by FormKey mirrors the registry.\n const subscriberUnsubs = new Map<FormKey, () => void>()\n\n api.addInspector({ id: INSPECTOR_ID, label: 'Attaform', app })\n api.addTimelineLayer({ id: TIMELINE_LAYER_ID, label: 'Attaform', color: 0x5b8def })\n\n function refreshTree(): void {\n api.sendInspectorTree(INSPECTOR_ID)\n }\n\n function refreshState(): void {\n api.sendInspectorState(INSPECTOR_ID)\n }\n\n function subscribeForm(state: FormStore<GenericForm>): void {\n if (subscriberUnsubs.has(state.formKey)) return\n const unsubChange = state.onFormChange(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'form.change',\n subtitle: state.formKey,\n // Redact sensitive-named leaves before they land in the\n // timeline event log — events accumulate for the whole\n // session and a screen-share / paired-debugging session\n // would otherwise expose any password / token / etc. the\n // user typed since DevTools was opened.\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubSubmit = state.onSubmitSuccess(() => {\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'submit.success',\n subtitle: state.formKey,\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubReset = state.onReset(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'reset',\n subtitle: state.formKey,\n },\n })\n })\n subscriberUnsubs.set(state.formKey, () => {\n unsubChange()\n unsubSubmit()\n unsubReset()\n })\n }\n\n // Subscribe all currently-registered forms + register as they're\n // added. The registry's `forms` Map is shallowReactive — we poll\n // once per render on refresh; for live change detection, each\n // useForm call that adds a new form triggers a tree/state refresh\n // via the form's own onFormChange emission on the first\n // applyFormReplacement.\n function syncForms(): void {\n for (const [, state] of registry.forms) {\n subscribeForm(state)\n }\n // Drop subscribers for forms that were evicted.\n for (const [formKey, unsub] of subscriberUnsubs) {\n if (!registry.forms.has(formKey)) {\n unsub()\n subscriberUnsubs.delete(formKey)\n }\n }\n }\n\n api.on.getInspectorTree((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n syncForms()\n payload.rootNodes = [...registry.forms.keys()].map((key) => ({\n id: `form:${key}`,\n label: key,\n tags: [],\n }))\n })\n\n api.on.getInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // Redact sensitive-named leaves in the inspector panel for the\n // same reason as the timeline events: a screen-share with an\n // open DevTools panel shouldn't expose passwords / tokens.\n // Editing stays enabled at the section level — the editInspector\n // handler refuses sensitive-path edits at write time so a dev\n // can't accidentally write the literal string `'[redacted]'` over\n // a real value.\n payload.state['Form value'] = [\n { key: 'form', value: redactSensitiveLeaves(state.form.value), editable: true },\n ]\n // Schema-driven and user-injected errors land in separate inspector\n // sections so devs can see the source distinction at a glance — a\n // user-injected entry surviving a successful submit, or a schema\n // entry that should have cleared after a value fix, are immediately\n // visible without cross-referencing call sites.\n payload.state['Schema Errors'] = [\n ...[...state.schemaErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['User Errors'] = [\n ...[...state.userErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['Aggregates'] = [\n { key: 'submitting', value: state.submitting.value },\n { key: 'submitCount', value: state.submitCount.value },\n { key: 'submitError', value: state.submitError.value },\n { key: 'activeValidations', value: state.activeValidations.value },\n ]\n })\n\n api.on.editInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // payload.path is `['Form value', 'form', ...pathSegments]` — the\n // first two segments are the inspector section + key, the rest is\n // the target form path the user edited. Pass the segment array\n // directly to `canonicalizePath`: join('.') would collapse a\n // literal-dot field key (`{\"user.email\": ...}`) into two segments,\n // writing to the wrong leaf.\n if (payload.path.length < 3) return\n const section = payload.path[0]\n if (section !== 'Form value') return\n const segments = payload.path.slice(2)\n const { segments: canonicalPath, key: canonicalKey } = canonicalizePath(segments)\n // Refuse edits on sensitive-named paths. The inspector renders\n // them as `'[redacted]'`, so a dev who confirms the field would\n // overwrite the real value with the literal masked string. Edits\n // to sensitive paths must go through the bound input element.\n if (isSensitivePath([...canonicalPath])) return\n // A devtools edit on a path that any element has opted in to should\n // persist (matches the user's expectation: editing via the inspector\n // should be indistinguishable from typing into the bound input).\n // No opt-in for this path → no write.\n state.setValueAtPath(canonicalPath, payload.state.value, {\n persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey),\n })\n refreshState()\n })\n\n // Initial sync so existing forms show up.\n syncForms()\n refreshTree()\n}\n"],"names":["segmentMatchesSensitive","canonicalizePath","isSensitivePath"],"mappings":"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"}
1
+ {"version":3,"file":"devtools.cjs","sources":["../../src/runtime/core/devtools.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormStore } from './create-form-store'\nimport type { AttaformRegistry } from './registry'\nimport type { GenericForm } from '../types/types-core'\nimport type { FormKey } from '../types/types-api'\nimport { canonicalizePath } from './paths'\nimport { isSensitivePath, segmentMatchesSensitive } from './persistence/sensitive-names'\n\n/**\n * Vue DevTools plugin wiring for attaform. Lazy-imported by\n * `createAttaform` under dev-mode guards so the production\n * bundle tree-shakes it out entirely.\n *\n * Registers:\n * - An inspector (per-app) that lists every registered form, with\n * nodes for form value / errors / aggregates / history.\n * - A timeline layer that emits events on submit start/success/\n * failure, reset, undo, redo, and form mutations.\n * - State editing — modifying a leaf inside the inspector tree\n * pushes through `state.setValueAtPath`, mutating the form.\n *\n * Tolerant of missing `@vue/devtools-api` — the peer dep is marked\n * optional. If the import fails, `setupAttaformDevtools` silently\n * no-ops so production builds / users without DevTools installed\n * don't see errors.\n */\n\nconst INSPECTOR_ID = 'attaform'\nconst TIMELINE_LAYER_ID = 'attaform:events'\n\nconst REDACTED = '[redacted]'\n\n/**\n * Walk `value` and replace any leaf whose enclosing path matches the\n * sensitive-name heuristic with the string `'[redacted]'`. Returns a\n * new tree (no mutation of the input). Object keys + array indices\n * are preserved; only the leaf payloads change.\n *\n * Applied to BOTH the DevTools timeline events and the inspector\n * `Form value` panel — leaks via either surface are treatable as\n * \"any developer with the panel open during user testing can read\n * a customer's password,\" which is exactly the failure mode the\n * sensitive-name guard exists to prevent on the storage side.\n *\n * Leaves whose path doesn't match a pattern pass through untouched.\n * `acknowledgeSensitive: true` on persistence does NOT bypass this —\n * if the consumer opted into persisting the value, they still\n * shouldn't see it in DevTools timelines that grow unbounded.\n *\n * Implementation note: tracks an `inSensitiveSubtree` flag through\n * the recursion instead of allocating a fresh path array per node\n * + calling `isSensitivePath` per leaf. Once any ancestor segment\n * matches the heuristic, the flag stays set for every descendant —\n * the leaf simply returns `REDACTED` without re-scanning the path.\n * For a 100-leaf form: ~100 path allocations + ~100 full-path regex\n * sweeps → 0 path allocations + ~100 single-segment regex sweeps,\n * with whole-subtree short-circuit when sensitive ancestors are\n * found early.\n */\nfunction redactSensitiveLeaves(value: unknown): unknown {\n return redactImpl(value, false)\n}\n\nfunction redactImpl(value: unknown, inSensitiveSubtree: boolean): unknown {\n if (value === null || value === undefined) return value\n if (typeof value !== 'object') {\n return inSensitiveSubtree ? REDACTED : value\n }\n if (Array.isArray(value)) {\n // Numeric segments never match the sensitive-name heuristic\n // (segmentMatchesSensitive rejects non-string segments), so the\n // flag passes through unchanged when descending into arrays.\n return value.map((item) => redactImpl(item, inSensitiveSubtree))\n }\n // Non-plain object (Map / Set / Date / class instance) — redact\n // wholesale if we're already in a sensitive subtree; otherwise pass\n // through. DevTools rendering of these is already heuristic, so we\n // don't try to descend into them.\n if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\n return inSensitiveSubtree ? REDACTED : value\n }\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as Record<string, unknown>)) {\n const childSensitive = inSensitiveSubtree || segmentMatchesSensitive(key)\n out[key] = redactImpl((value as Record<string, unknown>)[key], childSensitive)\n }\n return out\n}\n\ntype UnsafeDevtoolsApi = {\n addInspector(opts: { id: string; label: string; icon?: string; app: App }): void\n addTimelineLayer(opts: { id: string; label: string; color: number }): void\n sendInspectorTree(inspectorId: string): void\n sendInspectorState(inspectorId: string): void\n addTimelineEvent(payload: {\n layerId: string\n event: {\n time: number\n title: string\n subtitle?: string\n data?: Record<string, unknown>\n groupId?: string | number\n }\n }): void\n on: {\n getInspectorTree(\n handler: (payload: {\n inspectorId: string\n filter: string\n rootNodes: Array<{ id: string; label: string; tags?: unknown[] }>\n }) => void\n ): void\n getInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n state: Record<string, Array<{ key: string; value: unknown; editable?: boolean }>>\n }) => void\n ): void\n editInspectorState(\n handler: (payload: {\n inspectorId: string\n nodeId: string\n path: string[]\n state: { value: unknown; newKey?: string | null; remove?: boolean }\n }) => void\n ): void\n }\n}\n\ntype SetupDevtoolsPluginFn = (\n descriptor: {\n id: string\n label: string\n packageName?: string\n homepage?: string\n componentStateTypes?: string[]\n app: App\n },\n setup: (api: UnsafeDevtoolsApi) => void\n) => void\n\n/**\n * Install the DevTools plugin for the given Vue app + registry. Safe\n * to call in production — if `@vue/devtools-api` isn't installed, the\n * dynamic import fails and we log nothing. Returns `true` when\n * DevTools was wired successfully, `false` otherwise — useful for\n * tests.\n */\nexport async function setupAttaformDevtools(\n app: App,\n registry: AttaformRegistry\n): Promise<boolean> {\n let mod: { setupDevtoolsPlugin?: SetupDevtoolsPluginFn }\n try {\n mod = (await import('@vue/devtools-api')) as {\n setupDevtoolsPlugin?: SetupDevtoolsPluginFn\n }\n } catch {\n // Peer dep not installed — silently skip. Production builds pass\n // `{ devtools: false }` explicitly, but this catch covers the\n // \"dev without the peer dep\" case without a noisy warning.\n return false\n }\n const setupDevtoolsPlugin = mod.setupDevtoolsPlugin\n if (typeof setupDevtoolsPlugin !== 'function') return false\n\n setupDevtoolsPlugin(\n {\n id: INSPECTOR_ID,\n label: 'Attaform',\n packageName: 'attaform',\n homepage: 'https://github.com/attaform/attaform',\n app,\n componentStateTypes: ['Attaform form'],\n },\n (api) => wire(api, app, registry)\n )\n return true\n}\n\nfunction wire(api: UnsafeDevtoolsApi, app: App, registry: AttaformRegistry): void {\n // Per-form subscriber bookkeeping — we keep the unsubscribers so\n // the registry's eviction path can detach them when a form is\n // disposed. Using a Map keyed by FormKey mirrors the registry.\n const subscriberUnsubs = new Map<FormKey, () => void>()\n\n api.addInspector({ id: INSPECTOR_ID, label: 'Attaform', app })\n api.addTimelineLayer({ id: TIMELINE_LAYER_ID, label: 'Attaform', color: 0x5b8def })\n\n function refreshTree(): void {\n api.sendInspectorTree(INSPECTOR_ID)\n }\n\n function refreshState(): void {\n api.sendInspectorState(INSPECTOR_ID)\n }\n\n function subscribeForm(state: FormStore<GenericForm>): void {\n if (subscriberUnsubs.has(state.formKey)) return\n const unsubChange = state.onFormChange(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'form.change',\n subtitle: state.formKey,\n // Redact sensitive-named leaves before they land in the\n // timeline event log — events accumulate for the whole\n // session and a screen-share / paired-debugging session\n // would otherwise expose any password / token / etc. the\n // user typed since DevTools was opened.\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubSubmit = state.onSubmitSuccess(() => {\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'submit.success',\n subtitle: state.formKey,\n data: { form: redactSensitiveLeaves(state.form.value) as Record<string, unknown> },\n },\n })\n })\n const unsubReset = state.onReset(() => {\n refreshState()\n api.addTimelineEvent({\n layerId: TIMELINE_LAYER_ID,\n event: {\n time: Date.now(),\n title: 'reset',\n subtitle: state.formKey,\n },\n })\n })\n subscriberUnsubs.set(state.formKey, () => {\n unsubChange()\n unsubSubmit()\n unsubReset()\n })\n }\n\n // Subscribe all currently-registered forms + register as they're\n // added. The registry's `forms` Map is shallowReactive — we poll\n // once per render on refresh; for live change detection, each\n // useForm call that adds a new form triggers a tree/state refresh\n // via the form's own onFormChange emission on the first\n // applyFormReplacement.\n function syncForms(): void {\n for (const [, state] of registry.forms) {\n subscribeForm(state)\n }\n // Drop subscribers for forms that were evicted.\n for (const [formKey, unsub] of subscriberUnsubs) {\n if (!registry.forms.has(formKey)) {\n unsub()\n subscriberUnsubs.delete(formKey)\n }\n }\n }\n\n api.on.getInspectorTree((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n syncForms()\n payload.rootNodes = [...registry.forms.keys()].map((key) => ({\n id: `form:${key}`,\n label: key,\n tags: [],\n }))\n })\n\n api.on.getInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // Redact sensitive-named leaves in the inspector panel for the\n // same reason as the timeline events: a screen-share with an\n // open DevTools panel shouldn't expose passwords / tokens.\n // Editing stays enabled at the section level — the editInspector\n // handler refuses sensitive-path edits at write time so a dev\n // can't accidentally write the literal string `'[redacted]'` over\n // a real value.\n payload.state['Form value'] = [\n { key: 'form', value: redactSensitiveLeaves(state.form.value), editable: true },\n ]\n // Schema-driven and user-injected errors land in separate inspector\n // sections so devs can see the source distinction at a glance — a\n // user-injected entry surviving a successful submit, or a schema\n // entry that should have cleared after a value fix, are immediately\n // visible without cross-referencing call sites.\n payload.state['Schema Errors'] = [\n ...[...state.schemaErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['User Errors'] = [\n ...[...state.userErrors.entries()].map(([k, v]) => ({\n key: String(k),\n value: v as unknown,\n })),\n ]\n payload.state['Aggregates'] = [\n { key: 'submitting', value: state.submitting.value },\n { key: 'submitCount', value: state.submitCount.value },\n { key: 'submitError', value: state.submitError.value },\n { key: 'activeValidations', value: state.activeValidations.value },\n ]\n })\n\n api.on.editInspectorState((payload) => {\n if (payload.inspectorId !== INSPECTOR_ID) return\n if (!payload.nodeId.startsWith('form:')) return\n const formKey = payload.nodeId.slice('form:'.length)\n const state = registry.forms.get(formKey)\n if (state === undefined) return\n // payload.path is `['Form value', 'form', ...pathSegments]` — the\n // first two segments are the inspector section + key, the rest is\n // the target form path the user edited. Pass the segment array\n // directly to `canonicalizePath`: join('.') would collapse a\n // literal-dot field key (`{\"user.email\": ...}`) into two segments,\n // writing to the wrong leaf.\n if (payload.path.length < 3) return\n const section = payload.path[0]\n if (section !== 'Form value') return\n const segments = payload.path.slice(2)\n const { segments: canonicalPath, key: canonicalKey } = canonicalizePath(segments)\n // Refuse edits on sensitive-named paths. The inspector renders\n // them as `'[redacted]'`, so a dev who confirms the field would\n // overwrite the real value with the literal masked string. Edits\n // to sensitive paths must go through the bound input element.\n if (isSensitivePath([...canonicalPath])) return\n // A devtools edit on a path that any element has opted in to should\n // persist (matches the user's expectation: editing via the inspector\n // should be indistinguishable from typing into the bound input).\n // No opt-in for this path → no write.\n state.setValueAtPath(canonicalPath, payload.state.value, {\n persist: state.persistOptIns.hasAnyOptInForPath(canonicalKey),\n })\n refreshState()\n })\n\n // Initial sync so existing forms show up.\n syncForms()\n refreshTree()\n}\n"],"names":["segmentMatchesSensitive","canonicalizePath","isSensitivePath"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { c as canonicalizePath } from '../shared/attaform.DXye3JKf.mjs';
2
- import { i as isSensitivePath, s as segmentMatchesSensitive } from '../shared/attaform.a99dQV7Q.mjs';
1
+ import { c as canonicalizePath } from '../shared/attaform.jrxE_xZw.mjs';
2
+ import { i as isSensitivePath, s as segmentMatchesSensitive } from '../shared/attaform.BfMxsfmE.mjs';
3
3
 
4
4
  const INSPECTOR_ID = "attaform";
5
5
  const TIMELINE_LAYER_ID = "attaform:events";
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const sensitiveNames = require('../shared/attaform.RypIkgVy.cjs');
3
+ const plugin = require('../shared/attaform.rIRYSUI1.cjs');
4
4
 
5
5
  const DB_NAME = "attaform";
6
6
  const STORE_NAME = "kv";
@@ -24,7 +24,7 @@ function openDb() {
24
24
  };
25
25
  request.onsuccess = () => resolve(request.result);
26
26
  request.onerror = () => {
27
- if (sensitiveNames.__DEV__ && !warnedOnOpenFailure) {
27
+ if (plugin.__DEV__ && !warnedOnOpenFailure) {
28
28
  warnedOnOpenFailure = true;
29
29
  console.warn(
30
30
  "[attaform] IndexedDB open failed; persistence disabled. Common causes: private-mode disabled IDB, browser quota policy.",
@@ -34,7 +34,7 @@ function openDb() {
34
34
  resolve(null);
35
35
  };
36
36
  request.onblocked = () => {
37
- if (sensitiveNames.__DEV__ && !warnedOnOpenFailure) {
37
+ if (plugin.__DEV__ && !warnedOnOpenFailure) {
38
38
  warnedOnOpenFailure = true;
39
39
  console.warn(
40
40
  "[attaform] IndexedDB open blocked (another tab holds an older version); persistence disabled until the conflict resolves."
@@ -77,7 +77,7 @@ function runWriteOp(fn) {
77
77
  fn(tx.objectStore(STORE_NAME));
78
78
  tx.oncomplete = () => resolve();
79
79
  tx.onabort = () => {
80
- if (sensitiveNames.__DEV__ && !warnedOnWriteFailure) {
80
+ if (plugin.__DEV__ && !warnedOnWriteFailure) {
81
81
  warnedOnWriteFailure = true;
82
82
  console.warn(
83
83
  "[attaform] IndexedDB transaction aborted; subsequent writes will silently no-op. Common cause: storage quota exceeded.",
@@ -1 +1 @@
1
- {"version":3,"file":"indexeddb.cjs","sources":["../../src/runtime/core/persistence/indexeddb.ts"],"sourcesContent":["import { __DEV__ } from '../dev'\nimport type { FormStorage } from '../../types/types-api'\n\n/**\n * Zero-dependency IndexedDB adapter. A single shared DB\n * (`attaform`) with a single object store (`kv`). Entries are\n * structured-cloned on write, so `Date` / `Map` / `Set` / typed\n * arrays / nested arrays round-trip without JSON flattening.\n *\n * Size budget: ≤1 KB gzip (verified via size-limit). Consumers who\n * want richer IDB features (indexes, cursors, transactions) should\n * roll their own `FormStorage`.\n *\n * Open failures (private mode, blocked DB, unsupported environment)\n * resolve `dbPromise` to `null`; subsequent reads/writes silently\n * no-op so the form stays usable. A one-shot dev warning surfaces\n * the degradation so the developer notices.\n */\n\nconst DB_NAME = 'attaform'\nconst STORE_NAME = 'kv'\nconst DB_VERSION = 1\n\nlet dbPromise: Promise<IDBDatabase | null> | null = null\n// One-shot dev-warn flags for adapter-level failures. Module-scoped\n// so the warning only fires once per process — re-opening the DB\n// after `__resetIndexedDbForTests` clears `dbPromise` but leaves\n// these flags alone (tests that want a fresh warn-state should\n// reset them via the test hook below).\nlet warnedOnOpenFailure = false\nlet warnedOnWriteFailure = false\n\nfunction openDb(): Promise<IDBDatabase | null> {\n if (dbPromise !== null) return dbPromise\n if (typeof indexedDB === 'undefined') {\n dbPromise = Promise.resolve(null)\n return dbPromise\n }\n dbPromise = new Promise<IDBDatabase | null>((resolve) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => {\n // Drop to null so subsequent calls don't retry a broken open —\n // the form just runs without persistence.\n if (__DEV__ && !warnedOnOpenFailure) {\n warnedOnOpenFailure = true\n console.warn(\n '[attaform] IndexedDB open failed; persistence disabled. ' +\n 'Common causes: private-mode disabled IDB, browser quota policy.',\n request.error\n )\n }\n resolve(null)\n }\n request.onblocked = () => {\n if (__DEV__ && !warnedOnOpenFailure) {\n warnedOnOpenFailure = true\n console.warn(\n '[attaform] IndexedDB open blocked (another tab holds an older version); persistence disabled until the conflict resolves.'\n )\n }\n // `onblocked` is transient — the holding tab can close at any\n // moment. Clear the cache so the next persistence call retries\n // the open instead of permanently no-opping.\n dbPromise = null\n resolve(null)\n }\n })\n return dbPromise\n}\n\n/**\n * Read path: resolve on `request.onsuccess` with the fetched value.\n * `readonly` transactions are atomic by nature — there's no commit\n * phase to worry about, so waiting for the transaction's\n * `oncomplete` would be redundant.\n */\nfunction runReadOp<T>(fn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T | undefined> {\n return openDb().then(\n (db) =>\n new Promise<T | undefined>((resolve) => {\n if (db === null) return resolve(undefined)\n let tx: IDBTransaction\n try {\n tx = db.transaction(STORE_NAME, 'readonly')\n } catch {\n return resolve(undefined)\n }\n const store = tx.objectStore(STORE_NAME)\n const request = fn(store)\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => resolve(undefined)\n tx.onerror = () => resolve(undefined)\n })\n )\n}\n\n/**\n * Write path: resolve on `tx.oncomplete` (the spec-defined commit\n * signal), NOT on `request.onsuccess` (which fires per-request,\n * before the transaction commits).\n *\n * Resolving on `onsuccess` means a tab close, power loss, or\n * browser crash between the request succeeding and the transaction\n * committing silently loses the write — the Promise already\n * resolved successfully. Aborts (quota exceeded, version change,\n * constraint violation) likewise roll back after `onsuccess` fired.\n *\n * Resolving on `oncomplete` makes durability a precondition for\n * the Promise settlement; `onabort` catches the failure cases so\n * the Promise resolves to `undefined` instead of hanging.\n */\nfunction runWriteOp(fn: (store: IDBObjectStore) => void): Promise<void> {\n return openDb().then(\n (db) =>\n new Promise<void>((resolve) => {\n if (db === null) return resolve()\n let tx: IDBTransaction\n try {\n tx = db.transaction(STORE_NAME, 'readwrite')\n } catch {\n return resolve()\n }\n fn(tx.objectStore(STORE_NAME))\n tx.oncomplete = () => resolve()\n tx.onabort = () => {\n // QuotaExceededError, version-change, constraint violation\n // — all surface as a transaction abort. One-shot dev warn so\n // the developer notices instead of silently losing writes.\n if (__DEV__ && !warnedOnWriteFailure) {\n warnedOnWriteFailure = true\n console.warn(\n '[attaform] IndexedDB transaction aborted; subsequent writes will silently no-op. ' +\n 'Common cause: storage quota exceeded.',\n tx.error\n )\n }\n resolve()\n }\n tx.onerror = () => resolve()\n })\n )\n}\n\nexport function createIndexedDbAdapter(): FormStorage {\n return {\n async getItem(key) {\n return await runReadOp<unknown>((store) => store.get(key) as IDBRequest<unknown>)\n },\n async setItem(key, value) {\n await runWriteOp((store) => void store.put(value, key))\n },\n async removeItem(key) {\n await runWriteOp((store) => void store.delete(key))\n },\n async listKeys(prefix) {\n // `IDBKeyRange.bound(prefix, prefix + '￿')` would skip attaform\n // keys that contain the U+FFFF code unit; safer to fetch all\n // keys and filter in-process. The attaform-managed key namespace is\n // tiny in practice, so the cost is negligible.\n const all = await runReadOp<IDBValidKey[]>(\n (store) => store.getAllKeys() as IDBRequest<IDBValidKey[]>\n )\n if (all === undefined) return []\n const out: string[] = []\n for (const k of all) {\n if (typeof k === 'string' && k.startsWith(prefix)) out.push(k)\n }\n return out\n },\n }\n}\n\n/**\n * Test hook: reset the cached DB promise so a subsequent call re-\n * opens the database. Production code should never call this —\n * exposed for `fake-indexeddb`-backed tests that tear down the\n * in-memory DB between cases.\n */\nexport function __resetIndexedDbForTests(): void {\n dbPromise = null\n warnedOnOpenFailure = false\n warnedOnWriteFailure = false\n}\n"],"names":["__DEV__"],"mappings":"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"}
1
+ {"version":3,"file":"indexeddb.cjs","sources":["../../src/runtime/core/persistence/indexeddb.ts"],"sourcesContent":["import { __DEV__ } from '../dev'\nimport type { FormStorage } from '../../types/types-api'\n\n/**\n * Zero-dependency IndexedDB adapter. A single shared DB\n * (`attaform`) with a single object store (`kv`). Entries are\n * structured-cloned on write, so `Date` / `Map` / `Set` / typed\n * arrays / nested arrays round-trip without JSON flattening.\n *\n * Size budget: ≤1 KB gzip (verified via size-limit). Consumers who\n * want richer IDB features (indexes, cursors, transactions) should\n * roll their own `FormStorage`.\n *\n * Open failures (private mode, blocked DB, unsupported environment)\n * resolve `dbPromise` to `null`; subsequent reads/writes silently\n * no-op so the form stays usable. A one-shot dev warning surfaces\n * the degradation so the developer notices.\n */\n\nconst DB_NAME = 'attaform'\nconst STORE_NAME = 'kv'\nconst DB_VERSION = 1\n\nlet dbPromise: Promise<IDBDatabase | null> | null = null\n// One-shot dev-warn flags for adapter-level failures. Module-scoped\n// so the warning only fires once per process — re-opening the DB\n// after `__resetIndexedDbForTests` clears `dbPromise` but leaves\n// these flags alone (tests that want a fresh warn-state should\n// reset them via the test hook below).\nlet warnedOnOpenFailure = false\nlet warnedOnWriteFailure = false\n\nfunction openDb(): Promise<IDBDatabase | null> {\n if (dbPromise !== null) return dbPromise\n if (typeof indexedDB === 'undefined') {\n dbPromise = Promise.resolve(null)\n return dbPromise\n }\n dbPromise = new Promise<IDBDatabase | null>((resolve) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION)\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME)\n }\n }\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => {\n // Drop to null so subsequent calls don't retry a broken open —\n // the form just runs without persistence.\n if (__DEV__ && !warnedOnOpenFailure) {\n warnedOnOpenFailure = true\n console.warn(\n '[attaform] IndexedDB open failed; persistence disabled. ' +\n 'Common causes: private-mode disabled IDB, browser quota policy.',\n request.error\n )\n }\n resolve(null)\n }\n request.onblocked = () => {\n if (__DEV__ && !warnedOnOpenFailure) {\n warnedOnOpenFailure = true\n console.warn(\n '[attaform] IndexedDB open blocked (another tab holds an older version); persistence disabled until the conflict resolves.'\n )\n }\n // `onblocked` is transient — the holding tab can close at any\n // moment. Clear the cache so the next persistence call retries\n // the open instead of permanently no-opping.\n dbPromise = null\n resolve(null)\n }\n })\n return dbPromise\n}\n\n/**\n * Read path: resolve on `request.onsuccess` with the fetched value.\n * `readonly` transactions are atomic by nature — there's no commit\n * phase to worry about, so waiting for the transaction's\n * `oncomplete` would be redundant.\n */\nfunction runReadOp<T>(fn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T | undefined> {\n return openDb().then(\n (db) =>\n new Promise<T | undefined>((resolve) => {\n if (db === null) return resolve(undefined)\n let tx: IDBTransaction\n try {\n tx = db.transaction(STORE_NAME, 'readonly')\n } catch {\n return resolve(undefined)\n }\n const store = tx.objectStore(STORE_NAME)\n const request = fn(store)\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => resolve(undefined)\n tx.onerror = () => resolve(undefined)\n })\n )\n}\n\n/**\n * Write path: resolve on `tx.oncomplete` (the spec-defined commit\n * signal), NOT on `request.onsuccess` (which fires per-request,\n * before the transaction commits).\n *\n * Resolving on `onsuccess` means a tab close, power loss, or\n * browser crash between the request succeeding and the transaction\n * committing silently loses the write — the Promise already\n * resolved successfully. Aborts (quota exceeded, version change,\n * constraint violation) likewise roll back after `onsuccess` fired.\n *\n * Resolving on `oncomplete` makes durability a precondition for\n * the Promise settlement; `onabort` catches the failure cases so\n * the Promise resolves to `undefined` instead of hanging.\n */\nfunction runWriteOp(fn: (store: IDBObjectStore) => void): Promise<void> {\n return openDb().then(\n (db) =>\n new Promise<void>((resolve) => {\n if (db === null) return resolve()\n let tx: IDBTransaction\n try {\n tx = db.transaction(STORE_NAME, 'readwrite')\n } catch {\n return resolve()\n }\n fn(tx.objectStore(STORE_NAME))\n tx.oncomplete = () => resolve()\n tx.onabort = () => {\n // QuotaExceededError, version-change, constraint violation\n // — all surface as a transaction abort. One-shot dev warn so\n // the developer notices instead of silently losing writes.\n if (__DEV__ && !warnedOnWriteFailure) {\n warnedOnWriteFailure = true\n console.warn(\n '[attaform] IndexedDB transaction aborted; subsequent writes will silently no-op. ' +\n 'Common cause: storage quota exceeded.',\n tx.error\n )\n }\n resolve()\n }\n tx.onerror = () => resolve()\n })\n )\n}\n\nexport function createIndexedDbAdapter(): FormStorage {\n return {\n async getItem(key) {\n return await runReadOp<unknown>((store) => store.get(key) as IDBRequest<unknown>)\n },\n async setItem(key, value) {\n await runWriteOp((store) => void store.put(value, key))\n },\n async removeItem(key) {\n await runWriteOp((store) => void store.delete(key))\n },\n async listKeys(prefix) {\n // `IDBKeyRange.bound(prefix, prefix + '￿')` would skip attaform\n // keys that contain the U+FFFF code unit; safer to fetch all\n // keys and filter in-process. The attaform-managed key namespace is\n // tiny in practice, so the cost is negligible.\n const all = await runReadOp<IDBValidKey[]>(\n (store) => store.getAllKeys() as IDBRequest<IDBValidKey[]>\n )\n if (all === undefined) return []\n const out: string[] = []\n for (const k of all) {\n if (typeof k === 'string' && k.startsWith(prefix)) out.push(k)\n }\n return out\n },\n }\n}\n\n/**\n * Test hook: reset the cached DB promise so a subsequent call re-\n * opens the database. Production code should never call this —\n * exposed for `fake-indexeddb`-backed tests that tear down the\n * in-memory DB between cases.\n */\nexport function __resetIndexedDbForTests(): void {\n dbPromise = null\n warnedOnOpenFailure = false\n warnedOnWriteFailure = false\n}\n"],"names":["__DEV__"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { _ as __DEV__ } from '../shared/attaform.a99dQV7Q.mjs';
1
+ import { _ as __DEV__ } from '../shared/attaform.BfMxsfmE.mjs';
2
2
 
3
3
  const DB_NAME = "attaform";
4
4
  const STORE_NAME = "kv";
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const sensitiveNames = require('../shared/attaform.RypIkgVy.cjs');
3
+ const plugin = require('../shared/attaform.rIRYSUI1.cjs');
4
4
 
5
5
  function createLocalStorageAdapter() {
6
6
  const available = typeof localStorage !== "undefined";
@@ -21,7 +21,7 @@ function createLocalStorageAdapter() {
21
21
  try {
22
22
  localStorage.setItem(key, JSON.stringify(value));
23
23
  } catch (err) {
24
- if (sensitiveNames.__DEV__ && !warnedOnFailure) {
24
+ if (plugin.__DEV__ && !warnedOnFailure) {
25
25
  warnedOnFailure = true;
26
26
  console.warn(
27
27
  '[attaform] localStorage write failed; subsequent writes will silently no-op for this form. Common causes: quota exceeded, private-mode storage lock. Switch to `persist: "indexeddb"` for larger payloads.',
@@ -1 +1 @@
1
- {"version":3,"file":"local-storage.cjs","sources":["../../src/runtime/core/persistence/local-storage.ts"],"sourcesContent":["import { __DEV__ } from '../dev'\nimport type { FormStorage } from '../../types/types-api'\n\n/**\n * `localStorage` adapter for `FormStorage`. Wraps the sync Web Storage\n * API in `async` functions — the extra microtask is negligible and the\n * uniform Promise contract means every caller handles backends the same\n * way.\n *\n * Serialises payloads with `JSON.stringify`. Callers pass plain data;\n * non-JSON values (`Date` / `Map` / `Set` / typed arrays) round-trip as\n * strings / objects. Switch to the `indexeddb` backend if you need\n * structured-clone fidelity.\n *\n * Missing / unavailable `localStorage` (Node, Safari private mode in\n * older versions, disabled by the user) is handled by a `typeof` gate;\n * every method becomes a no-op so the form stays usable.\n *\n * On the FIRST `setItem` failure (quota exceeded, security error in\n * private mode), the adapter logs a one-shot dev warning so the\n * developer notices instead of silently losing data. Subsequent\n * failures stay silent — bouncing a warning per keystroke would be\n * worse than the original silent-fail behavior.\n */\nexport function createLocalStorageAdapter(): FormStorage {\n const available = typeof localStorage !== 'undefined'\n // Per-adapter flag: trips on the first setItem failure and stays\n // tripped for the lifetime of the adapter instance.\n let warnedOnFailure = false\n return {\n getItem(key) {\n if (!available) return Promise.resolve(undefined)\n try {\n const raw = localStorage.getItem(key)\n if (raw === null) return Promise.resolve(undefined)\n return Promise.resolve(JSON.parse(raw) as unknown)\n } catch {\n // JSON.parse failure or SecurityError — drop the stale entry so\n // the next write can replace it. Caller handles undefined as\n // \"no persisted state\".\n return Promise.resolve(undefined)\n }\n },\n setItem(key, value) {\n if (!available) return Promise.resolve()\n try {\n localStorage.setItem(key, JSON.stringify(value))\n } catch (err) {\n // Quota-exceeded or SecurityError — swallow at runtime. In dev,\n // surface the first failure so the developer knows persistence\n // has degraded. Silent on subsequent failures (debounced writes\n // would otherwise spam the console once per keystroke).\n if (__DEV__ && !warnedOnFailure) {\n warnedOnFailure = true\n console.warn(\n '[attaform] localStorage write failed; subsequent writes will silently no-op for this form. ' +\n 'Common causes: quota exceeded, private-mode storage lock. ' +\n 'Switch to `persist: \"indexeddb\"` for larger payloads.',\n err\n )\n }\n }\n return Promise.resolve()\n },\n removeItem(key) {\n if (!available) return Promise.resolve()\n try {\n localStorage.removeItem(key)\n } catch {\n // Private-mode write guards occasionally throw here too.\n }\n return Promise.resolve()\n },\n listKeys(prefix) {\n if (!available) return Promise.resolve([])\n const out: string[] = []\n try {\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k !== null && k.startsWith(prefix) === true) out.push(k)\n }\n } catch {\n // SecurityError under private-mode locks.\n }\n return Promise.resolve(out)\n },\n }\n}\n"],"names":["__DEV__"],"mappings":";;;;AAwBO,SAAS,yBAAA,GAAyC;AACvD,EAAA,MAAM,SAAA,GAAY,OAAO,YAAA,KAAiB,WAAA;AAG1C,EAAA,IAAI,eAAA,GAAkB,KAAA;AACtB,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAK;AACX,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAChD,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AACpC,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,OAAO,OAAA,CAAQ,QAAQ,KAAA,CAAS,CAAA;AAClD,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAY,CAAA;AAAA,MACnD,CAAA,CAAA,MAAQ;AAIN,QAAA,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAAA,MAClC;AAAA,IACF,CAAA;AAAA,IACA,OAAA,CAAQ,KAAK,KAAA,EAAO;AAClB,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,MACjD,SAAS,GAAA,EAAK;AAKZ,QAAA,IAAIA,sBAAA,IAAW,CAAC,eAAA,EAAiB;AAC/B,UAAA,eAAA,GAAkB,IAAA;AAClB,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,4MAAA;AAAA,YAGA;AAAA,WACF;AAAA,QACF;AAAA,MACF;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,WAAW,GAAA,EAAK;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,WAAW,GAAG,CAAA;AAAA,MAC7B,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,SAAS,MAAA,EAAQ;AACf,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AACzC,MAAA,MAAM,MAAgB,EAAC;AACvB,MAAA,IAAI;AACF,QAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK;AAC5C,UAAA,MAAM,CAAA,GAAI,YAAA,CAAa,GAAA,CAAI,CAAC,CAAA;AAC5B,UAAA,IAAI,CAAA,KAAM,QAAQ,CAAA,CAAE,UAAA,CAAW,MAAM,CAAA,KAAM,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA;AAAA,QAC7D;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAAA,IAC5B;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"local-storage.cjs","sources":["../../src/runtime/core/persistence/local-storage.ts"],"sourcesContent":["import { __DEV__ } from '../dev'\nimport type { FormStorage } from '../../types/types-api'\n\n/**\n * `localStorage` adapter for `FormStorage`. Wraps the sync Web Storage\n * API in `async` functions — the extra microtask is negligible and the\n * uniform Promise contract means every caller handles backends the same\n * way.\n *\n * Serialises payloads with `JSON.stringify`. Callers pass plain data;\n * non-JSON values (`Date` / `Map` / `Set` / typed arrays) round-trip as\n * strings / objects. Switch to the `indexeddb` backend if you need\n * structured-clone fidelity.\n *\n * Missing / unavailable `localStorage` (Node, Safari private mode in\n * older versions, disabled by the user) is handled by a `typeof` gate;\n * every method becomes a no-op so the form stays usable.\n *\n * On the FIRST `setItem` failure (quota exceeded, security error in\n * private mode), the adapter logs a one-shot dev warning so the\n * developer notices instead of silently losing data. Subsequent\n * failures stay silent — bouncing a warning per keystroke would be\n * worse than the original silent-fail behavior.\n */\nexport function createLocalStorageAdapter(): FormStorage {\n const available = typeof localStorage !== 'undefined'\n // Per-adapter flag: trips on the first setItem failure and stays\n // tripped for the lifetime of the adapter instance.\n let warnedOnFailure = false\n return {\n getItem(key) {\n if (!available) return Promise.resolve(undefined)\n try {\n const raw = localStorage.getItem(key)\n if (raw === null) return Promise.resolve(undefined)\n return Promise.resolve(JSON.parse(raw) as unknown)\n } catch {\n // JSON.parse failure or SecurityError — drop the stale entry so\n // the next write can replace it. Caller handles undefined as\n // \"no persisted state\".\n return Promise.resolve(undefined)\n }\n },\n setItem(key, value) {\n if (!available) return Promise.resolve()\n try {\n localStorage.setItem(key, JSON.stringify(value))\n } catch (err) {\n // Quota-exceeded or SecurityError — swallow at runtime. In dev,\n // surface the first failure so the developer knows persistence\n // has degraded. Silent on subsequent failures (debounced writes\n // would otherwise spam the console once per keystroke).\n if (__DEV__ && !warnedOnFailure) {\n warnedOnFailure = true\n console.warn(\n '[attaform] localStorage write failed; subsequent writes will silently no-op for this form. ' +\n 'Common causes: quota exceeded, private-mode storage lock. ' +\n 'Switch to `persist: \"indexeddb\"` for larger payloads.',\n err\n )\n }\n }\n return Promise.resolve()\n },\n removeItem(key) {\n if (!available) return Promise.resolve()\n try {\n localStorage.removeItem(key)\n } catch {\n // Private-mode write guards occasionally throw here too.\n }\n return Promise.resolve()\n },\n listKeys(prefix) {\n if (!available) return Promise.resolve([])\n const out: string[] = []\n try {\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i)\n if (k !== null && k.startsWith(prefix) === true) out.push(k)\n }\n } catch {\n // SecurityError under private-mode locks.\n }\n return Promise.resolve(out)\n },\n }\n}\n"],"names":["__DEV__"],"mappings":";;;;AAwBO,SAAS,yBAAA,GAAyC;AACvD,EAAA,MAAM,SAAA,GAAY,OAAO,YAAA,KAAiB,WAAA;AAG1C,EAAA,IAAI,eAAA,GAAkB,KAAA;AACtB,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAK;AACX,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAChD,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AACpC,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,OAAO,OAAA,CAAQ,QAAQ,KAAA,CAAS,CAAA;AAClD,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAY,CAAA;AAAA,MACnD,CAAA,CAAA,MAAQ;AAIN,QAAA,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAAA,MAClC;AAAA,IACF,CAAA;AAAA,IACA,OAAA,CAAQ,KAAK,KAAA,EAAO;AAClB,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,MACjD,SAAS,GAAA,EAAK;AAKZ,QAAA,IAAIA,cAAA,IAAW,CAAC,eAAA,EAAiB;AAC/B,UAAA,eAAA,GAAkB,IAAA;AAClB,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,4MAAA;AAAA,YAGA;AAAA,WACF;AAAA,QACF;AAAA,MACF;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,WAAW,GAAA,EAAK;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,WAAW,GAAG,CAAA;AAAA,MAC7B,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,SAAS,MAAA,EAAQ;AACf,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AACzC,MAAA,MAAM,MAAgB,EAAC;AACvB,MAAA,IAAI;AACF,QAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK;AAC5C,UAAA,MAAM,CAAA,GAAI,YAAA,CAAa,GAAA,CAAI,CAAC,CAAA;AAC5B,UAAA,IAAI,CAAA,KAAM,QAAQ,CAAA,CAAE,UAAA,CAAW,MAAM,CAAA,KAAM,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA;AAAA,QAC7D;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAAA,IAC5B;AAAA,GACF;AACF;;;;"}
@@ -1,4 +1,4 @@
1
- import { _ as __DEV__ } from '../shared/attaform.a99dQV7Q.mjs';
1
+ import { _ as __DEV__ } from '../shared/attaform.BfMxsfmE.mjs';
2
2
 
3
3
  function createLocalStorageAdapter() {
4
4
  const available = typeof localStorage !== "undefined";
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const sensitiveNames = require('../shared/attaform.RypIkgVy.cjs');
3
+ const plugin = require('../shared/attaform.rIRYSUI1.cjs');
4
4
 
5
5
  function createSessionStorageAdapter() {
6
6
  const available = typeof sessionStorage !== "undefined";
@@ -21,7 +21,7 @@ function createSessionStorageAdapter() {
21
21
  try {
22
22
  sessionStorage.setItem(key, JSON.stringify(value));
23
23
  } catch (err) {
24
- if (sensitiveNames.__DEV__ && !warnedOnFailure) {
24
+ if (plugin.__DEV__ && !warnedOnFailure) {
25
25
  warnedOnFailure = true;
26
26
  console.warn(
27
27
  "[attaform] sessionStorage write failed; subsequent writes will silently no-op for this form. Common causes: quota exceeded, private-mode storage lock.",
@@ -1 +1 @@
1
- {"version":3,"file":"session-storage.cjs","sources":["../../src/runtime/core/persistence/session-storage.ts"],"sourcesContent":["import { __DEV__ } from '../dev'\nimport type { FormStorage } from '../../types/types-api'\n\n/**\n * `sessionStorage` adapter — identical shape to the `localStorage`\n * adapter, different backing store. Tab-scoped: closing the tab\n * drops the entry. Useful for multi-step flows where the user\n * shouldn't see last-session state on a fresh open.\n *\n * Same one-shot dev-warn semantic on setItem failure as the\n * localStorage adapter — see that file's header for rationale.\n */\nexport function createSessionStorageAdapter(): FormStorage {\n const available = typeof sessionStorage !== 'undefined'\n let warnedOnFailure = false\n return {\n getItem(key) {\n if (!available) return Promise.resolve(undefined)\n try {\n const raw = sessionStorage.getItem(key)\n if (raw === null) return Promise.resolve(undefined)\n return Promise.resolve(JSON.parse(raw) as unknown)\n } catch {\n return Promise.resolve(undefined)\n }\n },\n setItem(key, value) {\n if (!available) return Promise.resolve()\n try {\n sessionStorage.setItem(key, JSON.stringify(value))\n } catch (err) {\n if (__DEV__ && !warnedOnFailure) {\n warnedOnFailure = true\n console.warn(\n '[attaform] sessionStorage write failed; subsequent writes will silently no-op for this form. ' +\n 'Common causes: quota exceeded, private-mode storage lock.',\n err\n )\n }\n }\n return Promise.resolve()\n },\n removeItem(key) {\n if (!available) return Promise.resolve()\n try {\n sessionStorage.removeItem(key)\n } catch {\n // SecurityError in private mode — swallow.\n }\n return Promise.resolve()\n },\n listKeys(prefix) {\n if (!available) return Promise.resolve([])\n const out: string[] = []\n try {\n for (let i = 0; i < sessionStorage.length; i++) {\n const k = sessionStorage.key(i)\n if (k !== null && k.startsWith(prefix) === true) out.push(k)\n }\n } catch {\n // SecurityError under private-mode locks.\n }\n return Promise.resolve(out)\n },\n }\n}\n"],"names":["__DEV__"],"mappings":";;;;AAYO,SAAS,2BAAA,GAA2C;AACzD,EAAA,MAAM,SAAA,GAAY,OAAO,cAAA,KAAmB,WAAA;AAC5C,EAAA,IAAI,eAAA,GAAkB,KAAA;AACtB,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAK;AACX,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAChD,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA;AACtC,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,OAAO,OAAA,CAAQ,QAAQ,KAAA,CAAS,CAAA;AAClD,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAY,CAAA;AAAA,MACnD,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAAA,MAClC;AAAA,IACF,CAAA;AAAA,IACA,OAAA,CAAQ,KAAK,KAAA,EAAO;AAClB,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,MACnD,SAAS,GAAA,EAAK;AACZ,QAAA,IAAIA,sBAAA,IAAW,CAAC,eAAA,EAAiB;AAC/B,UAAA,eAAA,GAAkB,IAAA;AAClB,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,wJAAA;AAAA,YAEA;AAAA,WACF;AAAA,QACF;AAAA,MACF;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,WAAW,GAAA,EAAK;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,WAAW,GAAG,CAAA;AAAA,MAC/B,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,SAAS,MAAA,EAAQ;AACf,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AACzC,MAAA,MAAM,MAAgB,EAAC;AACvB,MAAA,IAAI;AACF,QAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,cAAA,CAAe,QAAQ,CAAA,EAAA,EAAK;AAC9C,UAAA,MAAM,CAAA,GAAI,cAAA,CAAe,GAAA,CAAI,CAAC,CAAA;AAC9B,UAAA,IAAI,CAAA,KAAM,QAAQ,CAAA,CAAE,UAAA,CAAW,MAAM,CAAA,KAAM,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA;AAAA,QAC7D;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAAA,IAC5B;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"session-storage.cjs","sources":["../../src/runtime/core/persistence/session-storage.ts"],"sourcesContent":["import { __DEV__ } from '../dev'\nimport type { FormStorage } from '../../types/types-api'\n\n/**\n * `sessionStorage` adapter — identical shape to the `localStorage`\n * adapter, different backing store. Tab-scoped: closing the tab\n * drops the entry. Useful for multi-step flows where the user\n * shouldn't see last-session state on a fresh open.\n *\n * Same one-shot dev-warn semantic on setItem failure as the\n * localStorage adapter — see that file's header for rationale.\n */\nexport function createSessionStorageAdapter(): FormStorage {\n const available = typeof sessionStorage !== 'undefined'\n let warnedOnFailure = false\n return {\n getItem(key) {\n if (!available) return Promise.resolve(undefined)\n try {\n const raw = sessionStorage.getItem(key)\n if (raw === null) return Promise.resolve(undefined)\n return Promise.resolve(JSON.parse(raw) as unknown)\n } catch {\n return Promise.resolve(undefined)\n }\n },\n setItem(key, value) {\n if (!available) return Promise.resolve()\n try {\n sessionStorage.setItem(key, JSON.stringify(value))\n } catch (err) {\n if (__DEV__ && !warnedOnFailure) {\n warnedOnFailure = true\n console.warn(\n '[attaform] sessionStorage write failed; subsequent writes will silently no-op for this form. ' +\n 'Common causes: quota exceeded, private-mode storage lock.',\n err\n )\n }\n }\n return Promise.resolve()\n },\n removeItem(key) {\n if (!available) return Promise.resolve()\n try {\n sessionStorage.removeItem(key)\n } catch {\n // SecurityError in private mode — swallow.\n }\n return Promise.resolve()\n },\n listKeys(prefix) {\n if (!available) return Promise.resolve([])\n const out: string[] = []\n try {\n for (let i = 0; i < sessionStorage.length; i++) {\n const k = sessionStorage.key(i)\n if (k !== null && k.startsWith(prefix) === true) out.push(k)\n }\n } catch {\n // SecurityError under private-mode locks.\n }\n return Promise.resolve(out)\n },\n }\n}\n"],"names":["__DEV__"],"mappings":";;;;AAYO,SAAS,2BAAA,GAA2C;AACzD,EAAA,MAAM,SAAA,GAAY,OAAO,cAAA,KAAmB,WAAA;AAC5C,EAAA,IAAI,eAAA,GAAkB,KAAA;AACtB,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAK;AACX,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAChD,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAM,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA;AACtC,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,OAAO,OAAA,CAAQ,QAAQ,KAAA,CAAS,CAAA;AAClD,QAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAY,CAAA;AAAA,MACnD,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,OAAA,CAAQ,QAAQ,MAAS,CAAA;AAAA,MAClC;AAAA,IACF,CAAA;AAAA,IACA,OAAA,CAAQ,KAAK,KAAA,EAAO;AAClB,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,OAAA,CAAQ,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,MACnD,SAAS,GAAA,EAAK;AACZ,QAAA,IAAIA,cAAA,IAAW,CAAC,eAAA,EAAiB;AAC/B,UAAA,eAAA,GAAkB,IAAA;AAClB,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,wJAAA;AAAA,YAEA;AAAA,WACF;AAAA,QACF;AAAA,MACF;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,WAAW,GAAA,EAAK;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACvC,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,WAAW,GAAG,CAAA;AAAA,MAC/B,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,IACzB,CAAA;AAAA,IACA,SAAS,MAAA,EAAQ;AACf,MAAA,IAAI,CAAC,SAAA,EAAW,OAAO,OAAA,CAAQ,OAAA,CAAQ,EAAE,CAAA;AACzC,MAAA,MAAM,MAAgB,EAAC;AACvB,MAAA,IAAI;AACF,QAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,cAAA,CAAe,QAAQ,CAAA,EAAA,EAAK;AAC9C,UAAA,MAAM,CAAA,GAAI,cAAA,CAAe,GAAA,CAAI,CAAC,CAAA;AAC9B,UAAA,IAAI,CAAA,KAAM,QAAQ,CAAA,CAAE,UAAA,CAAW,MAAM,CAAA,KAAM,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA;AAAA,QAC7D;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,OAAO,OAAA,CAAQ,QAAQ,GAAG,CAAA;AAAA,IAC5B;AAAA,GACF;AACF;;;;"}
@@ -1,4 +1,4 @@
1
- import { _ as __DEV__ } from '../shared/attaform.a99dQV7Q.mjs';
1
+ import { _ as __DEV__ } from '../shared/attaform.BfMxsfmE.mjs';
2
2
 
3
3
  function createSessionStorageAdapter() {
4
4
  const available = typeof sessionStorage !== "undefined";
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const serialize = require('./shared/attaform.BgYBU8gV.cjs');
4
- const sensitiveNames = require('./shared/attaform.RypIkgVy.cjs');
5
- const useFormContext = require('./shared/attaform.CDJVeoJU.cjs');
6
- const paths = require('./shared/attaform.BOi138GE.cjs');
3
+ const plugin = require('./shared/attaform.rIRYSUI1.cjs');
4
+ const serialize = require('./shared/attaform.CJttVxRj.cjs');
5
+ const useFormContext = require('./shared/attaform.C9Ph2SMx.cjs');
6
+ const paths = require('./shared/attaform.c_NzdRyc.cjs');
7
7
 
8
8
  function escapeForInlineScript(json) {
9
9
  return json.replace(/[<>&\u2028\u2029]/g, (char) => {
@@ -62,7 +62,7 @@ function parseApiErrors(payload, options) {
62
62
  try {
63
63
  segments = paths.canonicalizePath(key).segments;
64
64
  } catch (err) {
65
- if (err instanceof sensitiveNames.InvalidPathError) continue;
65
+ if (err instanceof plugin.InvalidPathError) continue;
66
66
  throw err;
67
67
  }
68
68
  if (segments.length > maxPathDepth) continue;
@@ -137,25 +137,26 @@ function isDetailsRecord(value) {
137
137
  return true;
138
138
  }
139
139
 
140
- exports.assignKey = serialize.assignKey;
141
- exports.createAttaform = serialize.createAttaform;
140
+ exports.AnonPersistError = plugin.AnonPersistError;
141
+ exports.AttaformError = plugin.AttaformError;
142
+ exports.InvalidPathError = plugin.InvalidPathError;
143
+ exports.InvalidUseFormConfigError = plugin.InvalidUseFormConfigError;
144
+ exports.OutsideSetupError = plugin.OutsideSetupError;
145
+ exports.RegistryNotInstalledError = plugin.RegistryNotInstalledError;
146
+ exports.ReservedFormKeyError = plugin.ReservedFormKeyError;
147
+ exports.SensitivePersistFieldError = plugin.SensitivePersistFieldError;
148
+ exports.SubmitErrorHandlerError = plugin.SubmitErrorHandlerError;
149
+ exports.assignKey = plugin.assignKey;
150
+ exports.createAttaform = plugin.createAttaform;
151
+ exports.createRegistry = plugin.createRegistry;
152
+ exports.getRegistryFromApp = plugin.getRegistryFromApp;
153
+ exports.isRegisterValue = plugin.isRegisterValue;
154
+ exports.kAttaformRegistry = plugin.kAttaformRegistry;
155
+ exports.useRegister = plugin.useRegister;
156
+ exports.useRegistry = plugin.useRegistry;
157
+ exports.vRegister = plugin.vRegister;
142
158
  exports.hydrateAttaformState = serialize.hydrateAttaformState;
143
- exports.isRegisterValue = serialize.isRegisterValue;
144
159
  exports.renderAttaformState = serialize.renderAttaformState;
145
- exports.vRegister = serialize.vRegister;
146
- exports.AnonPersistError = sensitiveNames.AnonPersistError;
147
- exports.AttaformError = sensitiveNames.AttaformError;
148
- exports.InvalidPathError = sensitiveNames.InvalidPathError;
149
- exports.OutsideSetupError = sensitiveNames.OutsideSetupError;
150
- exports.RegistryNotInstalledError = sensitiveNames.RegistryNotInstalledError;
151
- exports.ReservedFormKeyError = sensitiveNames.ReservedFormKeyError;
152
- exports.SensitivePersistFieldError = sensitiveNames.SensitivePersistFieldError;
153
- exports.SubmitErrorHandlerError = sensitiveNames.SubmitErrorHandlerError;
154
- exports.createRegistry = sensitiveNames.createRegistry;
155
- exports.getRegistryFromApp = sensitiveNames.getRegistryFromApp;
156
- exports.kAttaformRegistry = sensitiveNames.kAttaformRegistry;
157
- exports.useRegister = sensitiveNames.useRegister;
158
- exports.useRegistry = sensitiveNames.useRegistry;
159
160
  exports.AttaformErrorCode = useFormContext.AttaformErrorCode;
160
161
  exports.defaultCoercionRules = useFormContext.defaultCoercionRules;
161
162
  exports.defineCoercion = useFormContext.defineCoercion;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/runtime/core/serialize-script.ts","../src/runtime/core/parse-api-errors.ts"],"sourcesContent":["/**\n * Escape a JSON string so it's safe to embed inside an inline\n * `<script>` tag during SSR. Plain `JSON.stringify` is not safe — a\n * form value containing the literal substring `</script>` would\n * break out of the script tag.\n *\n * ```ts\n * const payload = escapeForInlineScript(JSON.stringify(renderAttaformState(app)))\n * // `<script>window.__ATTAFORM_STATE__ = ${payload}</script>` is safe.\n * ```\n *\n * Output remains valid JSON — `JSON.parse` round-trips back to the\n * original value on the client.\n */\nexport function escapeForInlineScript(json: string): string {\n return json.replace(/[<>&\\u2028\\u2029]/g, (char) => {\n switch (char) {\n case '<':\n return '\\\\u003c'\n case '>':\n return '\\\\u003e'\n case '&':\n return '\\\\u0026'\n case '\\u2028':\n return '\\\\u2028'\n case '\\u2029':\n return '\\\\u2029'\n default:\n return char\n }\n })\n}\n","import type {\n ApiErrorDetails,\n ApiErrorEntry,\n ApiErrorEnvelope,\n FormKey,\n ValidationError,\n} from '../types/types-api'\nimport { InvalidPathError } from './errors'\nimport { canonicalizePath } from './paths'\n\n/**\n * Result of `parseApiErrors`. Branch on `ok` to handle the two cases:\n *\n * ```ts\n * const result = parseApiErrors(payload, { formKey: form.key })\n * if (result.ok) {\n * form.setFieldErrors(result.errors)\n * } else {\n * console.warn('Bad error payload:', result.rejected)\n * }\n * ```\n *\n * `ok: true` means the payload was recognised — `errors` may still be\n * empty if the payload was valid but had no actual errors.\n * `ok: false` means the payload didn't match a known shape; `rejected`\n * carries a one-line description of why.\n */\nexport type ParseApiErrorsResult = {\n /** `true` when the payload was recognised; `false` when the shape was unfamiliar. */\n readonly ok: boolean\n /** Errors extracted from the payload. May be empty even when `ok: true`. */\n readonly errors: ValidationError[]\n /** When `ok: false`, a one-line description of why the payload was rejected. */\n readonly rejected?: string\n}\n\n/**\n * Options for `parseApiErrors`. The size caps protect against\n * misbehaving or hostile servers — exceeding any cap causes the\n * parser to reject the payload wholesale rather than partially apply.\n */\nexport type ParseApiErrorsOptions = {\n /**\n * The form's identifier — pass `form.key`. Stamped on every\n * produced `ValidationError` so errors route to the right form.\n */\n readonly formKey: FormKey\n /**\n * Code stamped on `ValidationError`s synthesized from bare-string\n * entries (the Rails / DRF / Laravel `{ field: [\"msg\"] }` shape).\n * Default `'api:unknown'`. Pick something more specific\n * (`'api:server-validation'`, `'myapp:legacy'`, …) when you know\n * the source.\n *\n * Structured `{ message, code }` entries forward their `code`\n * verbatim and ignore this option.\n */\n readonly defaultCode?: string\n /**\n * Maximum number of distinct keys to accept. Default `1000`.\n * Raise for trusted backends that legitimately produce more.\n */\n readonly maxEntries?: number\n /**\n * Maximum number of path segments per key. Default `32`. Keys\n * deeper than this are dropped (the rest of the payload still\n * applies if it stays under the other caps).\n */\n readonly maxPathDepth?: number\n /**\n * Maximum total path segments summed across every accepted key.\n * Default `10000`. Bounds the worst-case traversal cost.\n */\n readonly maxTotalSegments?: number\n}\n\n/**\n * Default size caps + default fallback code used by `parseApiErrors`.\n * Conservative; pass larger values (or a more specific code) via the\n * options bag for trusted-backend integrations.\n */\nexport const PARSE_API_ERRORS_DEFAULTS = {\n maxEntries: 1000,\n maxPathDepth: 32,\n maxTotalSegments: 10000,\n defaultCode: 'api:unknown',\n} as const\n\n/**\n * Normalise a server-side validation error payload into\n * `ValidationError[]`. Pair with `form.setFieldErrors` /\n * `form.addFieldErrors` to surface server errors on the form:\n *\n * ```ts\n * const response = await fetch('/api/signup', { … })\n * if (!response.ok) {\n * const payload = await response.json()\n * const result = parseApiErrors(payload, { formKey: form.key })\n * if (result.ok) form.setFieldErrors(result.errors)\n * }\n * ```\n *\n * Recognised payload shapes:\n *\n * - Wrapped envelope:\n * `{ error: { details: { email: { message: 'taken', code: 'api:duplicate-email' } } } }`\n * - Unwrapped envelope:\n * `{ details: { email: { message: 'taken', code: 'api:duplicate-email' } } }`\n * - Raw details record:\n * `{ email: { message: 'taken', code: 'api:duplicate-email' } }`\n * - **Bare-string Rails / DRF / Laravel shape:**\n * `{ email: ['Email already taken.'], username: 'too short' }`\n * - `null` / `undefined` — returns `{ ok: true, errors: [] }`\n *\n * Two entry shapes are accepted:\n *\n * 1. **Structured** — `{ message: string, code: string }`. The `code`\n * is forwarded verbatim onto the produced `ValidationError`.\n * 2. **Bare-string** — a plain string. Synthesized into\n * `{ message: <string>, code: <defaultCode> }` where `defaultCode`\n * comes from `options.defaultCode` (default `'api:unknown'`).\n * Useful for the Rails / Django REST Framework / FastAPI / Laravel\n * JSON shape that doesn't carry a per-field code.\n *\n * Each detail key's value can be a single entry, an array, or a mix\n * of structured and bare-string entries; arrays expand into one\n * `ValidationError` per entry. Pick a prefix on the server (`api:`,\n * `auth:`, etc.) and stay consistent so error renderers can branch\n * on `code` — or rely on `defaultCode` when the wire shape is\n * message-only.\n *\n * Dotted keys (`\"address.line1\"`) are split into structured paths\n * automatically. Use a custom server response shape outside these\n * patterns? Build the `ValidationError[]` array yourself and pass\n * it to `setFieldErrors` directly — `parseApiErrors` is just a\n * convenience for the common shapes.\n */\nexport function parseApiErrors(\n payload: ApiErrorEnvelope | ApiErrorDetails | null | undefined | unknown,\n options: ParseApiErrorsOptions\n): ParseApiErrorsResult {\n const maxEntries = options.maxEntries ?? PARSE_API_ERRORS_DEFAULTS.maxEntries\n const maxPathDepth = options.maxPathDepth ?? PARSE_API_ERRORS_DEFAULTS.maxPathDepth\n const maxTotalSegments = options.maxTotalSegments ?? PARSE_API_ERRORS_DEFAULTS.maxTotalSegments\n const defaultCode = options.defaultCode ?? PARSE_API_ERRORS_DEFAULTS.defaultCode\n\n if (payload === null || payload === undefined) {\n return { ok: true, errors: [] }\n }\n if (typeof payload !== 'object') {\n return { ok: false, errors: [], rejected: `payload was ${typeof payload}, expected object` }\n }\n\n const extraction = extractDetails(payload as Record<string, unknown>)\n if (!extraction.ok) {\n return { ok: false, errors: [], rejected: extraction.reason }\n }\n\n const { details } = extraction\n const entryCount = Object.keys(details).length\n // Enforce the guardrails before we spend time walking the payload.\n // Rejecting wholesale (not partial-applying) keeps the failure visible\n // so consumers can tune the caps or investigate the server payload.\n if (entryCount > maxEntries) {\n return {\n ok: false,\n errors: [],\n rejected: `payload has ${entryCount} entries, exceeds maxEntries=${maxEntries}`,\n }\n }\n\n const errors: ValidationError[] = []\n let totalSegments = 0\n for (const [key, value] of Object.entries(details)) {\n const entryList: ReadonlyArray<string | ApiErrorEntry> = Array.isArray(value) ? value : [value]\n // `canonicalizePath` throws `InvalidPathError` for dotted strings with\n // empty segments (e.g. `'. '`, `'a..b'`). A misbehaving server can\n // genuinely emit such a key; the hydrator is a normaliser, not a\n // validator, so we drop offending keys rather than let the exception\n // escape. Well-formed keys continue as normal.\n let segments: readonly (string | number)[]\n try {\n segments = canonicalizePath(key).segments\n } catch (err) {\n if (err instanceof InvalidPathError) continue\n throw err\n }\n // Per-path depth cap. We drop the offending key (rather than\n // rejecting the whole payload) because a single stray deep path\n // in an otherwise legitimate error set is still worth surfacing\n // the rest. Consumers who want strict rejection can post-filter\n // on `result.errors.length < details entryCount`.\n if (segments.length > maxPathDepth) continue\n // Total-segment cap. Enforced wholesale (not per-key) so a payload\n // that passes the per-key gate but stacks into a pathological\n // total still fails visibly. Mirrors `maxEntries` strictness.\n totalSegments += segments.length\n if (totalSegments > maxTotalSegments) {\n return {\n ok: false,\n errors: [],\n rejected: `payload total path segments exceeds maxTotalSegments=${maxTotalSegments}`,\n }\n }\n for (const entry of entryList) {\n // Bare-string entries (Rails / DRF / Laravel shape) synthesize a\n // `code` from `options.defaultCode`; structured `{ message, code }`\n // entries forward `code` verbatim. Empty messages drop silently\n // (`{ message: '' }` or `''`) — same recoverable-malformed-server\n // policy as before.\n const message = typeof entry === 'string' ? entry : entry.message\n const code = typeof entry === 'string' ? defaultCode : entry.code\n if (message.length === 0) continue\n errors.push({\n message,\n path: Array.from(segments),\n formKey: options.formKey,\n code,\n })\n }\n }\n return { ok: true, errors }\n}\n\ntype ExtractResult = { ok: true; details: ApiErrorDetails } | { ok: false; reason: string }\n\nfunction extractDetails(payload: Record<string, unknown>): ExtractResult {\n const wrappedError = payload['error']\n if (wrappedError !== null && wrappedError !== undefined && typeof wrappedError === 'object') {\n const inner = (wrappedError as { details?: unknown }).details\n if (inner === undefined) {\n // A wrapped envelope without details is considered \"no errors\" — valid shape.\n return { ok: true, details: {} }\n }\n if (isDetailsRecord(inner)) return { ok: true, details: inner }\n return {\n ok: false,\n reason: 'error.details entries must be strings or { message, code } objects',\n }\n }\n\n // `{ error: 'oops' }` / `{ error: 42 }` is a malformed wrapped envelope —\n // the server meant an error object but sent a scalar. Without this guard\n // the payload would fall through to the raw-details branch below, where\n // `{ error: 'oops' }` satisfies `isDetailsRecord` and silently produces\n // a phantom `ValidationError` at path `['error']`.\n if (wrappedError !== null && wrappedError !== undefined && typeof wrappedError !== 'object') {\n return {\n ok: false,\n reason: `payload.error was ${typeof wrappedError}, expected an object with { details }`,\n }\n }\n\n if ('details' in payload) {\n const inner = payload['details']\n if (inner === undefined) return { ok: true, details: {} }\n if (isDetailsRecord(inner)) return { ok: true, details: inner }\n return { ok: false, reason: 'details entries must be strings or { message, code } objects' }\n }\n\n if (isDetailsRecord(payload)) return { ok: true, details: payload }\n\n // Heuristic: if the payload has keys but none of them look like details,\n // it's probably a completely different shape. Reject.\n if (Object.keys(payload).length === 0) return { ok: true, details: {} }\n return { ok: false, reason: 'unrecognised payload shape' }\n}\n\nfunction isStructuredEntry(value: unknown): value is ApiErrorEntry {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n const obj = value as { message?: unknown; code?: unknown }\n return typeof obj.message === 'string' && typeof obj.code === 'string'\n}\n\n/**\n * Accepts either a structured `{ message, code }` entry OR a bare\n * string. Bare strings synthesize a `code` at parse time\n * (`options.defaultCode`) and are useful for the Rails / Django REST\n * Framework / Laravel JSON shape that doesn't carry a per-field code.\n */\nfunction isAcceptedEntry(value: unknown): value is string | ApiErrorEntry {\n return typeof value === 'string' || isStructuredEntry(value)\n}\n\n/**\n * A record is a \"details\" record when every value is either an\n * accepted entry or an array of accepted entries (mixing structured +\n * bare-string in the same array is fine; the parser normalises per\n * entry). Half-structured objects (e.g. `{ message: 'x' }` missing\n * `code`) are still rejected so the bug surfaces — see the\n * `'rejects entries that are objects but missing required fields'`\n * test for the rationale.\n */\nfunction isDetailsRecord(value: unknown): value is ApiErrorDetails {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n // Reject prototype-polluted keys — we don't use them here, but downstream\n // spreads shouldn't have to worry about this input.\n const record = value as Record<string, unknown>\n for (const k of Object.keys(record)) {\n const v = record[k]\n if (isAcceptedEntry(v)) continue\n if (Array.isArray(v) && v.every((entry) => isAcceptedEntry(entry))) continue\n return false\n }\n return true\n}\n"],"names":["canonicalizePath","InvalidPathError"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/runtime/core/serialize-script.ts","../src/runtime/core/parse-api-errors.ts"],"sourcesContent":["/**\n * Escape a JSON string so it's safe to embed inside an inline\n * `<script>` tag during SSR. Plain `JSON.stringify` is not safe — a\n * form value containing the literal substring `</script>` would\n * break out of the script tag.\n *\n * ```ts\n * const payload = escapeForInlineScript(JSON.stringify(renderAttaformState(app)))\n * // `<script>window.__ATTAFORM_STATE__ = ${payload}</script>` is safe.\n * ```\n *\n * Output remains valid JSON — `JSON.parse` round-trips back to the\n * original value on the client.\n */\nexport function escapeForInlineScript(json: string): string {\n return json.replace(/[<>&\\u2028\\u2029]/g, (char) => {\n switch (char) {\n case '<':\n return '\\\\u003c'\n case '>':\n return '\\\\u003e'\n case '&':\n return '\\\\u0026'\n case '\\u2028':\n return '\\\\u2028'\n case '\\u2029':\n return '\\\\u2029'\n default:\n return char\n }\n })\n}\n","import type {\n ApiErrorDetails,\n ApiErrorEntry,\n ApiErrorEnvelope,\n FormKey,\n ValidationError,\n} from '../types/types-api'\nimport { InvalidPathError } from './errors'\nimport { canonicalizePath } from './paths'\n\n/**\n * Result of `parseApiErrors`. Branch on `ok` to handle the two cases:\n *\n * ```ts\n * const result = parseApiErrors(payload, { formKey: form.key })\n * if (result.ok) {\n * form.setFieldErrors(result.errors)\n * } else {\n * console.warn('Bad error payload:', result.rejected)\n * }\n * ```\n *\n * `ok: true` means the payload was recognised — `errors` may still be\n * empty if the payload was valid but had no actual errors.\n * `ok: false` means the payload didn't match a known shape; `rejected`\n * carries a one-line description of why.\n */\nexport type ParseApiErrorsResult = {\n /** `true` when the payload was recognised; `false` when the shape was unfamiliar. */\n readonly ok: boolean\n /** Errors extracted from the payload. May be empty even when `ok: true`. */\n readonly errors: ValidationError[]\n /** When `ok: false`, a one-line description of why the payload was rejected. */\n readonly rejected?: string\n}\n\n/**\n * Options for `parseApiErrors`. The size caps protect against\n * misbehaving or hostile servers — exceeding any cap causes the\n * parser to reject the payload wholesale rather than partially apply.\n */\nexport type ParseApiErrorsOptions = {\n /**\n * The form's identifier — pass `form.key`. Stamped on every\n * produced `ValidationError` so errors route to the right form.\n */\n readonly formKey: FormKey\n /**\n * Code stamped on `ValidationError`s synthesized from bare-string\n * entries (the Rails / DRF / Laravel `{ field: [\"msg\"] }` shape).\n * Default `'api:unknown'`. Pick something more specific\n * (`'api:server-validation'`, `'myapp:legacy'`, …) when you know\n * the source.\n *\n * Structured `{ message, code }` entries forward their `code`\n * verbatim and ignore this option.\n */\n readonly defaultCode?: string\n /**\n * Maximum number of distinct keys to accept. Default `1000`.\n * Raise for trusted backends that legitimately produce more.\n */\n readonly maxEntries?: number\n /**\n * Maximum number of path segments per key. Default `32`. Keys\n * deeper than this are dropped (the rest of the payload still\n * applies if it stays under the other caps).\n */\n readonly maxPathDepth?: number\n /**\n * Maximum total path segments summed across every accepted key.\n * Default `10000`. Bounds the worst-case traversal cost.\n */\n readonly maxTotalSegments?: number\n}\n\n/**\n * Default size caps + default fallback code used by `parseApiErrors`.\n * Conservative; pass larger values (or a more specific code) via the\n * options bag for trusted-backend integrations.\n */\nexport const PARSE_API_ERRORS_DEFAULTS = {\n maxEntries: 1000,\n maxPathDepth: 32,\n maxTotalSegments: 10000,\n defaultCode: 'api:unknown',\n} as const\n\n/**\n * Normalise a server-side validation error payload into\n * `ValidationError[]`. Pair with `form.setFieldErrors` /\n * `form.addFieldErrors` to surface server errors on the form:\n *\n * ```ts\n * const response = await fetch('/api/signup', { … })\n * if (!response.ok) {\n * const payload = await response.json()\n * const result = parseApiErrors(payload, { formKey: form.key })\n * if (result.ok) form.setFieldErrors(result.errors)\n * }\n * ```\n *\n * Recognised payload shapes:\n *\n * - Wrapped envelope:\n * `{ error: { details: { email: { message: 'taken', code: 'api:duplicate-email' } } } }`\n * - Unwrapped envelope:\n * `{ details: { email: { message: 'taken', code: 'api:duplicate-email' } } }`\n * - Raw details record:\n * `{ email: { message: 'taken', code: 'api:duplicate-email' } }`\n * - **Bare-string Rails / DRF / Laravel shape:**\n * `{ email: ['Email already taken.'], username: 'too short' }`\n * - `null` / `undefined` — returns `{ ok: true, errors: [] }`\n *\n * Two entry shapes are accepted:\n *\n * 1. **Structured** — `{ message: string, code: string }`. The `code`\n * is forwarded verbatim onto the produced `ValidationError`.\n * 2. **Bare-string** — a plain string. Synthesized into\n * `{ message: <string>, code: <defaultCode> }` where `defaultCode`\n * comes from `options.defaultCode` (default `'api:unknown'`).\n * Useful for the Rails / Django REST Framework / FastAPI / Laravel\n * JSON shape that doesn't carry a per-field code.\n *\n * Each detail key's value can be a single entry, an array, or a mix\n * of structured and bare-string entries; arrays expand into one\n * `ValidationError` per entry. Pick a prefix on the server (`api:`,\n * `auth:`, etc.) and stay consistent so error renderers can branch\n * on `code` — or rely on `defaultCode` when the wire shape is\n * message-only.\n *\n * Dotted keys (`\"address.line1\"`) are split into structured paths\n * automatically. Use a custom server response shape outside these\n * patterns? Build the `ValidationError[]` array yourself and pass\n * it to `setFieldErrors` directly — `parseApiErrors` is just a\n * convenience for the common shapes.\n */\nexport function parseApiErrors(\n payload: ApiErrorEnvelope | ApiErrorDetails | null | undefined | unknown,\n options: ParseApiErrorsOptions\n): ParseApiErrorsResult {\n const maxEntries = options.maxEntries ?? PARSE_API_ERRORS_DEFAULTS.maxEntries\n const maxPathDepth = options.maxPathDepth ?? PARSE_API_ERRORS_DEFAULTS.maxPathDepth\n const maxTotalSegments = options.maxTotalSegments ?? PARSE_API_ERRORS_DEFAULTS.maxTotalSegments\n const defaultCode = options.defaultCode ?? PARSE_API_ERRORS_DEFAULTS.defaultCode\n\n if (payload === null || payload === undefined) {\n return { ok: true, errors: [] }\n }\n if (typeof payload !== 'object') {\n return { ok: false, errors: [], rejected: `payload was ${typeof payload}, expected object` }\n }\n\n const extraction = extractDetails(payload as Record<string, unknown>)\n if (!extraction.ok) {\n return { ok: false, errors: [], rejected: extraction.reason }\n }\n\n const { details } = extraction\n const entryCount = Object.keys(details).length\n // Enforce the guardrails before we spend time walking the payload.\n // Rejecting wholesale (not partial-applying) keeps the failure visible\n // so consumers can tune the caps or investigate the server payload.\n if (entryCount > maxEntries) {\n return {\n ok: false,\n errors: [],\n rejected: `payload has ${entryCount} entries, exceeds maxEntries=${maxEntries}`,\n }\n }\n\n const errors: ValidationError[] = []\n let totalSegments = 0\n for (const [key, value] of Object.entries(details)) {\n const entryList: ReadonlyArray<string | ApiErrorEntry> = Array.isArray(value) ? value : [value]\n // `canonicalizePath` throws `InvalidPathError` for dotted strings with\n // empty segments (e.g. `'. '`, `'a..b'`). A misbehaving server can\n // genuinely emit such a key; the hydrator is a normaliser, not a\n // validator, so we drop offending keys rather than let the exception\n // escape. Well-formed keys continue as normal.\n let segments: readonly (string | number)[]\n try {\n segments = canonicalizePath(key).segments\n } catch (err) {\n if (err instanceof InvalidPathError) continue\n throw err\n }\n // Per-path depth cap. We drop the offending key (rather than\n // rejecting the whole payload) because a single stray deep path\n // in an otherwise legitimate error set is still worth surfacing\n // the rest. Consumers who want strict rejection can post-filter\n // on `result.errors.length < details entryCount`.\n if (segments.length > maxPathDepth) continue\n // Total-segment cap. Enforced wholesale (not per-key) so a payload\n // that passes the per-key gate but stacks into a pathological\n // total still fails visibly. Mirrors `maxEntries` strictness.\n totalSegments += segments.length\n if (totalSegments > maxTotalSegments) {\n return {\n ok: false,\n errors: [],\n rejected: `payload total path segments exceeds maxTotalSegments=${maxTotalSegments}`,\n }\n }\n for (const entry of entryList) {\n // Bare-string entries (Rails / DRF / Laravel shape) synthesize a\n // `code` from `options.defaultCode`; structured `{ message, code }`\n // entries forward `code` verbatim. Empty messages drop silently\n // (`{ message: '' }` or `''`) — same recoverable-malformed-server\n // policy as before.\n const message = typeof entry === 'string' ? entry : entry.message\n const code = typeof entry === 'string' ? defaultCode : entry.code\n if (message.length === 0) continue\n errors.push({\n message,\n path: Array.from(segments),\n formKey: options.formKey,\n code,\n })\n }\n }\n return { ok: true, errors }\n}\n\ntype ExtractResult = { ok: true; details: ApiErrorDetails } | { ok: false; reason: string }\n\nfunction extractDetails(payload: Record<string, unknown>): ExtractResult {\n const wrappedError = payload['error']\n if (wrappedError !== null && wrappedError !== undefined && typeof wrappedError === 'object') {\n const inner = (wrappedError as { details?: unknown }).details\n if (inner === undefined) {\n // A wrapped envelope without details is considered \"no errors\" — valid shape.\n return { ok: true, details: {} }\n }\n if (isDetailsRecord(inner)) return { ok: true, details: inner }\n return {\n ok: false,\n reason: 'error.details entries must be strings or { message, code } objects',\n }\n }\n\n // `{ error: 'oops' }` / `{ error: 42 }` is a malformed wrapped envelope —\n // the server meant an error object but sent a scalar. Without this guard\n // the payload would fall through to the raw-details branch below, where\n // `{ error: 'oops' }` satisfies `isDetailsRecord` and silently produces\n // a phantom `ValidationError` at path `['error']`.\n if (wrappedError !== null && wrappedError !== undefined && typeof wrappedError !== 'object') {\n return {\n ok: false,\n reason: `payload.error was ${typeof wrappedError}, expected an object with { details }`,\n }\n }\n\n if ('details' in payload) {\n const inner = payload['details']\n if (inner === undefined) return { ok: true, details: {} }\n if (isDetailsRecord(inner)) return { ok: true, details: inner }\n return { ok: false, reason: 'details entries must be strings or { message, code } objects' }\n }\n\n if (isDetailsRecord(payload)) return { ok: true, details: payload }\n\n // Heuristic: if the payload has keys but none of them look like details,\n // it's probably a completely different shape. Reject.\n if (Object.keys(payload).length === 0) return { ok: true, details: {} }\n return { ok: false, reason: 'unrecognised payload shape' }\n}\n\nfunction isStructuredEntry(value: unknown): value is ApiErrorEntry {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n const obj = value as { message?: unknown; code?: unknown }\n return typeof obj.message === 'string' && typeof obj.code === 'string'\n}\n\n/**\n * Accepts either a structured `{ message, code }` entry OR a bare\n * string. Bare strings synthesize a `code` at parse time\n * (`options.defaultCode`) and are useful for the Rails / Django REST\n * Framework / Laravel JSON shape that doesn't carry a per-field code.\n */\nfunction isAcceptedEntry(value: unknown): value is string | ApiErrorEntry {\n return typeof value === 'string' || isStructuredEntry(value)\n}\n\n/**\n * A record is a \"details\" record when every value is either an\n * accepted entry or an array of accepted entries (mixing structured +\n * bare-string in the same array is fine; the parser normalises per\n * entry). Half-structured objects (e.g. `{ message: 'x' }` missing\n * `code`) are still rejected so the bug surfaces — see the\n * `'rejects entries that are objects but missing required fields'`\n * test for the rationale.\n */\nfunction isDetailsRecord(value: unknown): value is ApiErrorDetails {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n // Reject prototype-polluted keys — we don't use them here, but downstream\n // spreads shouldn't have to worry about this input.\n const record = value as Record<string, unknown>\n for (const k of Object.keys(record)) {\n const v = record[k]\n if (isAcceptedEntry(v)) continue\n if (Array.isArray(v) && v.every((entry) => isAcceptedEntry(entry))) continue\n return false\n }\n return true\n}\n"],"names":["canonicalizePath","InvalidPathError"],"mappings":"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"}