attaform 0.0.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -2
  3. package/dist/chunks/devtools.cjs +179 -0
  4. package/dist/chunks/devtools.cjs.map +1 -0
  5. package/dist/chunks/devtools.mjs +177 -0
  6. package/dist/chunks/devtools.mjs.map +1 -0
  7. package/dist/chunks/indexeddb.cjs +119 -0
  8. package/dist/chunks/indexeddb.cjs.map +1 -0
  9. package/dist/chunks/indexeddb.mjs +117 -0
  10. package/dist/chunks/indexeddb.mjs.map +1 -0
  11. package/dist/chunks/local-storage.cjs +58 -0
  12. package/dist/chunks/local-storage.cjs.map +1 -0
  13. package/dist/chunks/local-storage.mjs +56 -0
  14. package/dist/chunks/local-storage.mjs.map +1 -0
  15. package/dist/chunks/session-storage.cjs +58 -0
  16. package/dist/chunks/session-storage.cjs.map +1 -0
  17. package/dist/chunks/session-storage.mjs +56 -0
  18. package/dist/chunks/session-storage.mjs.map +1 -0
  19. package/dist/index.cjs +173 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +493 -0
  22. package/dist/index.d.mts +493 -0
  23. package/dist/index.d.ts +493 -0
  24. package/dist/index.mjs +141 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/nuxt.cjs +97 -0
  27. package/dist/nuxt.cjs.map +1 -0
  28. package/dist/nuxt.d.cts +38 -0
  29. package/dist/nuxt.d.mts +38 -0
  30. package/dist/nuxt.d.ts +38 -0
  31. package/dist/nuxt.mjs +94 -0
  32. package/dist/nuxt.mjs.map +1 -0
  33. package/dist/runtime/plugins/attaform.cjs +32 -0
  34. package/dist/runtime/plugins/attaform.cjs.map +1 -0
  35. package/dist/runtime/plugins/attaform.d.cts +5 -0
  36. package/dist/runtime/plugins/attaform.d.mts +5 -0
  37. package/dist/runtime/plugins/attaform.d.ts +5 -0
  38. package/dist/runtime/plugins/attaform.mjs +30 -0
  39. package/dist/runtime/plugins/attaform.mjs.map +1 -0
  40. package/dist/shared/attaform.B5GWYl76.cjs +386 -0
  41. package/dist/shared/attaform.B5GWYl76.cjs.map +1 -0
  42. package/dist/shared/attaform.BRTxpA3q.mjs +3283 -0
  43. package/dist/shared/attaform.BRTxpA3q.mjs.map +1 -0
  44. package/dist/shared/attaform.BYc9kugA.d.ts +124 -0
  45. package/dist/shared/attaform.Bubm_slq.cjs +622 -0
  46. package/dist/shared/attaform.Bubm_slq.cjs.map +1 -0
  47. package/dist/shared/attaform.BwaYWtMs.d.cts +126 -0
  48. package/dist/shared/attaform.BwaYWtMs.d.mts +126 -0
  49. package/dist/shared/attaform.BwaYWtMs.d.ts +126 -0
  50. package/dist/shared/attaform.CNJO3mME.cjs +3295 -0
  51. package/dist/shared/attaform.CNJO3mME.cjs.map +1 -0
  52. package/dist/shared/attaform.CRgix6_n.cjs +796 -0
  53. package/dist/shared/attaform.CRgix6_n.cjs.map +1 -0
  54. package/dist/shared/attaform.CXZgUECn.d.cts +124 -0
  55. package/dist/shared/attaform.CXpzmj38.mjs +617 -0
  56. package/dist/shared/attaform.CXpzmj38.mjs.map +1 -0
  57. package/dist/shared/attaform.Cc93zNzD.mjs +83 -0
  58. package/dist/shared/attaform.Cc93zNzD.mjs.map +1 -0
  59. package/dist/shared/attaform.DDXrY-1Q.d.cts +2568 -0
  60. package/dist/shared/attaform.DDXrY-1Q.d.mts +2568 -0
  61. package/dist/shared/attaform.DDXrY-1Q.d.ts +2568 -0
  62. package/dist/shared/attaform.DOKOyb3Y.d.mts +124 -0
  63. package/dist/shared/attaform.DlgKK10S.mjs +789 -0
  64. package/dist/shared/attaform.DlgKK10S.mjs.map +1 -0
  65. package/dist/shared/attaform.al_rpt7_.mjs +361 -0
  66. package/dist/shared/attaform.al_rpt7_.mjs.map +1 -0
  67. package/dist/shared/attaform.xKWYHMdq.cjs +89 -0
  68. package/dist/shared/attaform.xKWYHMdq.cjs.map +1 -0
  69. package/dist/transforms.cjs +11 -0
  70. package/dist/transforms.cjs.map +1 -0
  71. package/dist/transforms.d.cts +49 -0
  72. package/dist/transforms.d.mts +49 -0
  73. package/dist/transforms.d.ts +49 -0
  74. package/dist/transforms.mjs +2 -0
  75. package/dist/transforms.mjs.map +1 -0
  76. package/dist/vite.cjs +39 -0
  77. package/dist/vite.cjs.map +1 -0
  78. package/dist/vite.d.cts +53 -0
  79. package/dist/vite.d.mts +53 -0
  80. package/dist/vite.d.ts +53 -0
  81. package/dist/vite.mjs +37 -0
  82. package/dist/vite.mjs.map +1 -0
  83. package/dist/zod-v3.cjs +1511 -0
  84. package/dist/zod-v3.cjs.map +1 -0
  85. package/dist/zod-v3.d.cts +164 -0
  86. package/dist/zod-v3.d.mts +164 -0
  87. package/dist/zod-v3.d.ts +164 -0
  88. package/dist/zod-v3.mjs +1504 -0
  89. package/dist/zod-v3.mjs.map +1 -0
  90. package/dist/zod.cjs +1548 -0
  91. package/dist/zod.cjs.map +1 -0
  92. package/dist/zod.d.cts +67 -0
  93. package/dist/zod.d.mts +67 -0
  94. package/dist/zod.d.ts +67 -0
  95. package/dist/zod.mjs +1541 -0
  96. package/dist/zod.mjs.map +1 -0
  97. package/package.json +182 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attaform.al_rpt7_.mjs","sources":["../../src/runtime/core/dev.ts","../../src/runtime/core/errors.ts","../../src/runtime/core/ssr.ts","../../src/runtime/core/registry.ts","../../src/runtime/core/dev-stack-trace.ts","../../src/runtime/composables/use-register.ts","../../src/runtime/core/persistence/opt-in-registry.ts","../../src/runtime/core/persistence/sensitive-names.ts"],"sourcesContent":["/**\n * Portable dev-mode flag. True when the consumer's bundle / runtime\n * signals a non-production build; false in production.\n *\n * Resolves in this order:\n * 1. `process.env.NODE_ENV` — replaced at build time by Vite,\n * Webpack, Rollup + `@rollup/plugin-replace`, and read directly\n * in Node.\n * 2. Falls back to `false` when `process` is undeclared (some\n * sandboxed runtimes).\n *\n * Using this instead of `import.meta.dev` (Vite / Nuxt-specific)\n * keeps the library portable across bundlers and avoids esbuild's\n * `empty-import-meta` warning in non-ESM contexts.\n *\n * **Trade-off (browser CDN consumers).** When the library is\n * imported directly via a browser-native ESM CDN (esm.sh, Skypack,\n * unpkg) WITHOUT a bundler in front, `process` is undeclared and\n * `__DEV__` permanently resolves to `false` — every dev-only warning\n * is silenced even when the consumer is debugging. The library\n * works correctly; only the diagnostic surface degrades. The fix is\n * to put a bundler (Vite, Webpack, Rollup, esbuild) in the consumer\n * pipeline so `process.env.NODE_ENV` gets replaced. This is the\n * recommended path for any production app; CDN imports are useful\n * for prototyping but lose tree-shaking + dev diagnostics either way.\n *\n * Switching to `import.meta.env.DEV` would resolve correctly under\n * Vite but break Node consumers (no `import.meta.env`) and\n * pre-bundled distributions (esbuild emits an `empty-import-meta`\n * warning when `import.meta` resolves to `{}`). The current\n * `process.env.NODE_ENV` choice is the broadest-compatibility option.\n */\nexport const __DEV__: boolean =\n typeof process !== 'undefined' && process.env['NODE_ENV'] !== 'production'\n","/**\n * Typed error classes thrown by the form library. Each one signals a\n * distinct misuse so calling code can branch on `instanceof` instead\n * of pattern-matching error messages.\n *\n * Every class extends `AttaformError`, so consumers can write a single\n * polymorphic catch (`catch (e) { if (e instanceof AttaformError) ... }`)\n * instead of OR-chaining checks for each subclass. `AttaformError` itself\n * extends the standard `Error`, so existing `instanceof Error` usage\n * keeps working.\n */\n\n/**\n * Base for every error class thrown by `attaform`. Sets\n * `this.name` from the constructor's `new.target.name`, so subclasses\n * don't have to redeclare their own name override.\n */\nexport class AttaformError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = new.target.name\n }\n}\n\n/**\n * Thrown when a path string is malformed — typically a dotted path\n * with empty segments (e.g. `'a..b'`, leading or trailing dots).\n * Use array form (`['a', 'b']`) for keys that contain literal dots.\n */\nexport class InvalidPathError extends AttaformError {}\n\n/**\n * Thrown when a `handleSubmit`-supplied `onError` callback itself\n * throws or rejects. Wraps the inner failure so both the original\n * cause (via `error.cause`) and the propagation site are visible.\n */\nexport class SubmitErrorHandlerError extends AttaformError {}\n\n/**\n * Thrown by `useForm` / `injectForm` when the form library's\n * plugin hasn't been installed on the current Vue app.\n *\n * Fix: add `app.use(createAttaform())` to your app entry\n * (or `attaform/nuxt` for Nuxt projects).\n */\nexport class RegistryNotInstalledError extends AttaformError {\n constructor() {\n super('[attaform] Registry not found. Install the plugin via `app.use(createAttaform())`.')\n }\n}\n\n/**\n * Thrown when `useForm` / `injectForm` is called outside of a\n * Vue `setup()` function — typically from an event handler, watcher,\n * or async callback that runs after mount.\n *\n * Fix: move the call into `setup()`, or trigger it from a child\n * component whose `setup()` runs the composable.\n */\nexport class OutsideSetupError extends AttaformError {\n constructor() {\n super(\n '[attaform] useForm / injectForm called outside Vue setup(). ' +\n 'Move into setup or mount a child component to trigger from an event.'\n )\n }\n}\n\n/**\n * Thrown when a `useForm({ key })` call uses a key starting with\n * `__atta:`. That prefix is reserved for keys the library generates\n * internally (e.g. for anonymous `useForm()` calls without an\n * explicit key). Pick a different prefix for your form.\n */\nexport class ReservedFormKeyError extends AttaformError {\n constructor(key: string) {\n super(\n `[attaform] Form key \"${key}\" uses the reserved \"__atta:\" namespace. ` +\n `Use a different prefix — \"__atta:\" is for library-internal synthetic keys ` +\n `(anonymous useForm() calls without an explicit key).`\n )\n }\n}\n\n/**\n * Thrown (in dev) when `useForm({ persist: ... })` is configured on\n * an anonymous form (no `key:` provided). The synthetic `__atta:anon:`\n * identity isn't stable across remounts (Vue's `useId()` allocator\n * drifts under HMR, and any sibling `useId()` call shifts subsequent\n * IDs), so the persistence layer can't reliably find the previous\n * mount's draft. Result: stale entries pile up in storage and the\n * user's most recent edit doesn't always come back.\n *\n * Fix: pass an explicit `key` to `useForm()`.\n *\n * In production builds the runtime downgrades this to a one-shot\n * `console.warn` so a deployed third-party app shipping the\n * anti-pattern doesn't hard-crash.\n */\n// (AnonPersistError class declaration is below; this docblock is the\n// historical preamble — kept here so blame/PR review can trace the\n// original intent. The richer class supersedes the earlier basic version.)\n\n/**\n * Thrown when `register(path, { persist: true })` or `form.persist(path)`\n * targets a path whose name matches a sensitive-data heuristic\n * (password, cvv, ssn, token, etc.) without an explicit\n * `acknowledgeSensitive: true` override.\n *\n * Sensitive data in client-side storage (localStorage, sessionStorage,\n * IndexedDB) is a compliance risk — it survives logouts, is readable\n * by any same-origin script, and is unencrypted at rest.\n *\n * Fix: pass `acknowledgeSensitive: true` to confirm the persistence\n * is intentional, or persist the data server-side instead.\n */\nexport class SensitivePersistFieldError extends AttaformError {\n constructor(path: ReadonlyArray<string | number> | string) {\n const display = Array.isArray(path) ? path.join('.') : String(path)\n super(\n `[attaform] Refusing to persist \"${display}\" — this path matches a ` +\n `sensitive-name pattern (password / cvv / ssn / token / etc.). Storing sensitive ` +\n `data in client-side storage is a compliance risk (HIPAA / PII / PCI-DSS / SOC2). ` +\n `Fix: persist this server-side, OR pass \\`acknowledgeSensitive: true\\` to register() ` +\n `(or form.persist()) if the client-side persistence is intentional.`\n )\n }\n}\n\n/**\n * Thrown when persistence is misconfigured in a way that would either\n * (a) silently drop writes, or (b) namespace storage under a\n * non-deterministic synthetic key — both of which become security bugs\n * the moment encrypted persistence backends are added (the same key\n * may be derived for two unrelated forms).\n *\n * Two `cause` values, one error shape:\n *\n * - `'no-key'` — `useForm({ persist: ... })` called without `key:`.\n * Anonymous keys (`__atta:anon:*`) drift across mounts; persisting\n * to a non-deterministic location is refused outright.\n *\n * - `'register-without-config'` — `register(_, { persist: true })`\n * declared on a form whose `useForm()` options omit `persist:`.\n * The opt-in is recorded but nothing would ever land in storage.\n *\n * Fix: align the two layers — set `persist:` + `key:` on `useForm()`,\n * or remove `{ persist: true }` from the offending `register()` call.\n */\nexport class AnonPersistError extends AttaformError {\n readonly schemaFields: readonly string[] | undefined\n readonly callSite: string | undefined\n override readonly cause: 'no-key' | 'register-without-config'\n constructor(opts: {\n schemaFields?: readonly string[] | undefined\n callSite?: string | undefined\n cause: 'no-key' | 'register-without-config'\n }) {\n super(formatAnonPersistMessage(opts))\n this.schemaFields = opts.schemaFields\n this.callSite = opts.callSite\n this.cause = opts.cause\n }\n}\n\nfunction formatAnonPersistMessage(opts: {\n schemaFields?: readonly string[] | undefined\n callSite?: string | undefined\n cause: 'no-key' | 'register-without-config'\n}): string {\n const head =\n opts.cause === 'no-key'\n ? `useForm({ persist: ... }) requires an explicit \\`key:\\`. Anonymous synthetic keys (\\`__atta:anon:*\\`) drift across mounts and can collide between unrelated forms — refusing to persist to a non-deterministic location.`\n : `register(_, { persist: true }) declared on a form whose useForm() options have no \\`persist:\\` configured. The opt-in is recorded but nothing would ever land in storage.`\n const fields =\n opts.schemaFields !== undefined && opts.schemaFields.length > 0\n ? ` Form fields: { ${opts.schemaFields.join(', ')} }.`\n : ''\n const fix =\n opts.cause === 'no-key'\n ? ` Fix: add \\`key: '<stable-id>'\\` to useForm().`\n : ` Fix: add \\`persist: 'session'\\` (or 'local') and \\`key:\\` to useForm(), or remove \\`{ persist: true }\\` from this register() call.`\n const where = opts.callSite !== undefined ? ` ${opts.callSite}` : ''\n return `[attaform] ${head}${fields}${fix}${where}`\n}\n","/**\n * Portable SSR detection. The plugin captures this value at install time and\n * exposes it via the registry so every runtime branch reads a single source\n * of truth instead of sniffing `import.meta.*` (bundler-specific) at each\n * call site.\n *\n * Consumers can override explicitly via `createAttaform({ ssr: true })`;\n * the default heuristic handles the common Node-vs-browser split without\n * relying on any bundler-injected flag.\n */\n\nexport interface SSRDetectOptions {\n override?: boolean\n}\n\n/**\n * Returns true when running in a server-rendering context (no `window` / no\n * `document`). Explicit override always wins.\n *\n * Note: JSDOM-based test environments define `window`, so tests that need to\n * exercise SSR code paths must pass `{ override: true }` explicitly.\n */\nexport function detectSSR(options: SSRDetectOptions = {}): boolean {\n if (options.override !== undefined) return options.override\n return typeof window === 'undefined' && typeof document === 'undefined'\n}\n","import type { App, InjectionKey } from 'vue'\nimport { getCurrentInstance, inject, shallowReactive } from 'vue'\nimport type { AttaformDefaults, FormKey } from '../types/types-api'\nimport type { GenericForm } from '../types/types-core'\nimport type { FormStore } from './create-form-store'\nimport { OutsideSetupError, RegistryNotInstalledError } from './errors'\nimport { detectSSR, type SSRDetectOptions } from './ssr'\n\n/**\n * Per-Vue-app container for all form state instances. Each\n * `app.use(createAttaform())` call gets its own registry,\n * so the library runs under bare Vue 3 + SSR (via\n * `@vue/server-renderer`) and Nuxt with the same code path.\n *\n * Each form's state lives in `forms: Map<FormKey, FormStore<GenericForm>>`.\n * The type relaxation at storage time is necessary because different\n * forms in the same app have different `Form` generics; callers recover\n * the specific form type via `useForm`'s overloads.\n */\n\n/**\n * Serialised snapshot of one form's state, captured by\n * `renderAttaformState` for SSR and replayed by\n * `hydrateAttaformState` on the client. Round-trips through\n * JSON-safe tuples; field references are intentionally omitted\n * (DOM nodes don't survive serialisation).\n */\nexport type SerializedFormData = {\n /** The form's value at snapshot time. */\n readonly form: unknown\n /**\n * Errors produced by the schema at snapshot time. Replayed into\n * the client form's error state at hydration; cleared on\n * successful re-validation client-side.\n */\n readonly schemaErrors: ReadonlyArray<readonly [string, unknown]>\n /**\n * Errors set explicitly via `setFieldErrors` / `addFieldErrors`\n * (typically from a server response parsed via `parseApiErrors`)\n * at snapshot time. Replayed at hydration; persists across\n * client-side re-validation.\n */\n readonly userErrors: ReadonlyArray<readonly [string, unknown]>\n /** Per-field metadata (timestamps, raw values, connection flags) captured at snapshot time. */\n readonly fields: ReadonlyArray<readonly [string, unknown]>\n /**\n * Path keys that were in the form's `blankPaths` set at\n * snapshot time. Round-trips the \"displayed empty\" UI state across\n * the SSR boundary — without it, the client briefly renders\n * `String(slim-default)` (e.g. `'0'`) for fields the server\n * rendered as blank. Optional in the wire format so older payload\n * shapes deserialise cleanly.\n */\n readonly blankPaths?: ReadonlyArray<string>\n}\n\nexport type PendingHydration = Map<FormKey, SerializedFormData>\n\n/**\n * The library's per-Vue-app container. One `AttaformRegistry` is\n * created per `app.use(createAttaform())` call.\n *\n * Most consumers never touch this directly — `useForm` and\n * `injectForm` reach the registry on your behalf. Access it\n * explicitly only when wiring SSR or a custom plugin integration.\n */\nexport type AttaformRegistry = {\n /**\n * Live forms keyed by `FormKey`.\n * @internal\n */\n readonly forms: Map<FormKey, FormStore<GenericForm>>\n /**\n * Snapshots staged by `hydrateAttaformState` waiting to be consumed by the next `useForm` call.\n * @internal\n */\n readonly pendingHydration: PendingHydration\n /** `true` while running on the server during SSR; `false` on the client. */\n readonly isSSR: boolean\n /** App-level defaults applied to every `useForm` call. */\n readonly defaults: AttaformDefaults\n /**\n * Track a consumer of `key`. Returns a dispose function — call it\n * when the consumer unmounts. The form is evicted automatically\n * when the last consumer disposes, so long-running SPAs don't\n * leak detached state across navigations.\n * @internal\n */\n readonly trackConsumer: (key: FormKey) => () => void\n /**\n * Wait for all pending persistence writes across every live form\n * to settle. Useful for SSR shutdown and integration tests that\n * need a deterministic teardown.\n * @internal\n */\n readonly shutdown: () => Promise<void>\n}\n\n/**\n * The Vue `InjectionKey` under which the registry is provided on the\n * app. Most consumers never need this — `useForm` and\n * `injectForm` resolve the registry automatically.\n */\n// `Symbol.for(...)` so the key survives module duplication. If Vite's\n// dep optimizer ends up serving attaform as two separate copies (one\n// live-ESM, one pre-bundled — the standard hazard for linked-source\n// installs that opt into `optimizeDeps.include`), each copy still\n// resolves the same global symbol from the well-known string. Plugin\n// install's `app.provide(kAttaformRegistry, ...)` and the page's\n// `inject(kAttaformRegistry, null)` agree on the key, so `useForm`\n// finds its registry regardless of which copy did the provide. The\n// `attaform:` prefix namespaces the key safely. Same reasoning\n// for `kFormContext` and `kFormInstanceId` below.\nexport const kAttaformRegistry: InjectionKey<AttaformRegistry> = Symbol.for('attaform:registry')\n\n/**\n * Provides the current form's FormStore to descendants. Installed by\n * `useAbstractForm` after it resolves the state, so any nested component\n * can call `injectForm()` without prop-threading the form API.\n *\n * Typed as `FormStore<GenericForm>` — the descendant that re-emerges the\n * shape must supply its own `Form` generic, because Vue's InjectionKey\n * erases the generic at the provide/inject boundary.\n */\nexport const kFormContext: InjectionKey<FormStore<GenericForm>> =\n Symbol.for('attaform:form-context')\n\n/**\n * Provide / inject key for the per-`useForm()`-call instance ID. Provided\n * alongside `kFormContext` so descendants reaching via `injectForm()`\n * inherit the ancestor's `formInstanceId` and their locally-registered\n * elements tag against the SAME instance — keeps parent-submit-focus\n * working for inputs registered by deep children.\n *\n * Sibling `useForm({ key })` calls (e.g. sidebar + main rendering the\n * same form) sit at distinct tree positions, so each provides its own\n * ID; descendants of each branch inherit the branch's ID. Two ID spaces\n * stay isolated even when the underlying FormStore is shared.\n */\nexport const kFormInstanceId: InjectionKey<string> = Symbol.for('attaform:form-instance-id')\n\ndeclare module 'vue' {\n interface App {\n /** @internal */\n _attaform?: AttaformRegistry\n }\n}\n\n/** Options for `createRegistry`. */\nexport type CreateRegistryOptions = SSRDetectOptions & {\n /**\n * App-level defaults applied to every `useForm` call. Per-form\n * options always win. Omitted is equivalent to `{}`.\n */\n defaults?: AttaformDefaults\n}\n\n/**\n * Create a fresh `AttaformRegistry`. `createAttaform()` calls\n * this internally — most consumers never need to call it directly.\n * Use it when building a custom plugin that doesn't want the\n * `createAttaform` plugin's auto-install behaviour (e.g. test\n * harnesses, embedded apps).\n */\nexport function createRegistry(options: CreateRegistryOptions = {}): AttaformRegistry {\n const isSSR = detectSSR(options)\n // Frozen so accidental writes downstream throw in dev. Public surface\n // (`createAttaform({ defaults })`) treats this as data, not as\n // a mutation point — there's no public API to update defaults after\n // install, and adding one would invite race conditions with already-\n // mounted forms.\n const defaults: AttaformDefaults = Object.freeze({ ...(options.defaults ?? {}) })\n // The outer object is plain (it holds references we never rebind); inner\n // Maps are reactive via Vue's collection handlers so per-key reads track\n // per-key. `shallowReactive` avoids Vue's deep Ref-unwrapping, which would\n // mangle FormStore.form's Ref<F> type into F on lookup.\n const forms = shallowReactive(new Map<FormKey, FormStore<GenericForm>>())\n const pendingHydration = shallowReactive(new Map<FormKey, SerializedFormData>())\n // Consumer counts are bookkeeping — not reactive. No template should ever\n // depend on \"how many useForm calls are live\", and using a plain Map\n // avoids triggering watchers when we increment on every mount.\n const consumers = new Map<FormKey, number>()\n\n // Stores that have been evicted from `forms` but still have a\n // pending drain. `shutdown()` awaits these too so a process-exit\n // hook doesn't tear down before debounced writes from already-\n // unmounted forms have a chance to flush.\n const evicting = new Set<FormStore<GenericForm>>()\n\n function trackConsumer(key: FormKey): () => void {\n consumers.set(key, (consumers.get(key) ?? 0) + 1)\n let disposed = false\n return () => {\n if (disposed) return\n disposed = true\n const remaining = (consumers.get(key) ?? 1) - 1\n if (remaining <= 0) {\n // Tear down non-reactive resources the FormStore owns (field-\n // validation timers, abort controllers) BEFORE dropping the\n // registry reference — once the Map entry is gone we can't\n // reach the state anymore.\n const state = forms.get(key)\n consumers.delete(key)\n // Eviction from `forms` stays synchronous: any consumer that\n // reads `registry.forms` after unmount (tests, devtools) sees\n // the form gone immediately. Drain-then-dispose runs async in\n // the background so the persistence layer's debounced final\n // write can complete — the FormStore is reachable through the\n // closure here even after `forms.delete`.\n forms.delete(key)\n if (state !== undefined) {\n evicting.add(state)\n void state\n .awaitPendingWrites()\n .catch(() => undefined)\n .finally(() => {\n evicting.delete(state)\n state.dispose()\n })\n }\n } else {\n consumers.set(key, remaining)\n }\n }\n }\n\n async function shutdown(): Promise<void> {\n // Snapshot the keys — `awaitPendingWrites` may resolve mid-iteration\n // and trigger eviction that mutates `forms` while we're walking.\n // Include the evicting set so in-flight drains from already-\n // unmounted forms also flush before shutdown returns.\n const states = [...forms.values(), ...evicting]\n await Promise.allSettled(states.map((state) => state.awaitPendingWrites()))\n }\n\n return { forms, pendingHydration, isSSR, defaults, trackConsumer, shutdown }\n}\n\n/**\n * Look up the current app's registry from inside a component's\n * `setup()` (or any synchronous code on the setup call stack).\n *\n * Most consumers don't need this — `useForm` and `injectForm`\n * call it on your behalf. Reach for it directly when building\n * custom integrations that need the raw registry.\n *\n * Throws:\n * - `OutsideSetupError` when called outside a Vue setup context\n * (e.g. from an event handler or async callback). Move the call\n * into setup, or trigger it from a child component.\n * - `RegistryNotInstalledError` when called inside setup but the\n * plugin wasn't installed. Add\n * `app.use(createAttaform())` to your app entry.\n */\nexport function useRegistry(): AttaformRegistry {\n const instance = getCurrentInstance()\n if (instance === null) {\n throw new OutsideSetupError()\n }\n const registry = inject(kAttaformRegistry, null)\n if (registry === null) {\n throw new RegistryNotInstalledError()\n }\n return registry\n}\n\n/**\n * Look up a Vue app's registry by `App` reference. Used by\n * SSR helpers (`renderAttaformState`, `hydrateAttaformState`) that\n * run outside a component setup context.\n *\n * Throws `RegistryNotInstalledError` when the app hasn't been wired\n * with `createAttaform()`.\n */\nexport function getRegistryFromApp(app: App): AttaformRegistry {\n const registry = app._attaform\n if (registry === undefined) {\n throw new RegistryNotInstalledError()\n }\n return registry\n}\n\nexport function attachRegistryToApp(app: App, registry: AttaformRegistry): void {\n app.provide(kAttaformRegistry, registry)\n app._attaform = registry\n}\n","/**\n * Dev-only call-site capture for warnings that want to point the\n * reader at the offending line of their code (not at a attaform-internal\n * frame). Walks the stack past `attaform` frames, picks the\n * first frame that looks like user code, then strips the dev-server\n * scheme + host + Vite/Nuxt's `/_nuxt/` prefix so the warning doesn't\n * carry a wall of `https://localhost:3000/_nuxt/...` noise.\n *\n * Returns `undefined` on engines that don't expose `.stack` or when\n * parsing fails — callers should degrade to a generic message rather\n * than printing nothing.\n *\n * Click-through navigation isn't sacrificed: `console.warn` already\n * renders its own clickable stack trace below the message in\n * Chrome / Firefox DevTools (V8 frame format → Sources tab). The\n * captured frame is purely an inline pointer in the message text,\n * and short paths read better there than full URLs.\n *\n * The attaform-frame regex matches both the published path\n * (`attaform/...`) and the linked / source path\n * (`attaform/...`) so local dev via `make link-attaform` surfaces\n * the same trimmed frames.\n *\n * Dev-only; callers should gate on `__DEV__` before invoking.\n */\nexport function captureUserCallSite(): string | undefined {\n const raw = new Error().stack\n if (typeof raw !== 'string') return undefined\n const lines = raw.split('\\n')\n // Skip the \"Error\" message line and any frame inside attaform itself.\n for (let i = 1; i < lines.length; i++) {\n const frame = lines[i]\n if (frame === undefined) continue\n if (/attaform[/-]forms?/i.test(frame)) continue\n if (/\\bforms\\.[A-Za-z0-9_-]+\\.m?js\\b/.test(frame)) continue\n const trimmed = frame.trim()\n if (trimmed.length === 0) continue\n return shortenSourceFrame(trimmed)\n }\n return undefined\n}\n\n/**\n * Reduce a raw stack frame to `(<path>:<line>)`.\n *\n * Inputs we expect (V8, with or without `at fn (…)` wrapper):\n * - `at setup (https://example.test/_nuxt/pages/spike.vue:18:18)`\n * - `at https://example.com/foo.js:1:1`\n * - `at file:///Users/x/proj/spike.vue:18:18`\n * - `pages/foo.vue:18:18` (already path-like, no V8 wrapper)\n *\n * Outputs:\n * - `(pages/spike.vue:18)`\n * - `(foo.js:1)`\n * - `(Users/x/proj/spike.vue:18)`\n * - `(pages/foo.vue:18)`\n *\n * Why drop the column: Vite's sourcemaps round-trip line accurately\n * but column resolution is fuzzy in compiled contexts (Vue render\n * functions, JSX, anywhere the source-to-output mapping isn't\n * 1-to-1 per character). For a script-setup `useForm()` call the\n * column is meaningful; for a template-inlined `register(...)` it\n * lands somewhere mid-compiled-blob and is actively misleading. The\n * uniform `path:line` format avoids that asymmetry — line is enough\n * to navigate, the editor lands on the right region either way.\n *\n * If the frame doesn't match the trailing `…:line:col` shape at all,\n * the original trimmed frame is returned unchanged — better to\n * surface something than nothing.\n */\nexport function shortenSourceFrame(frame: string): string {\n const match = /(?:^|\\s|\\()([^\\s()]+):(\\d+):\\d+\\)?$/.exec(frame)\n if (match === null) return frame\n const [, urlOrPath, line] = match\n if (urlOrPath === undefined || line === undefined) return frame\n let path = urlOrPath\n // Strip `scheme://host/` (https://…, http://…). file:// gets the\n // same treatment, leaving the absolute filesystem path; we then\n // also strip its leading slash below so it reads as a relative path.\n path = path.replace(/^[a-z]+:\\/\\/[^/]+\\//i, '')\n // Strip Vite/Nuxt's dev-server prefix.\n path = path.replace(/^_nuxt\\//, '')\n // Strip leading slash (left over from file:// or absolute paths).\n path = path.replace(/^\\//, '')\n // Wrap in parens. Chrome's console auto-linker partial-matches\n // bare `pages/foo.vue:137` (it picks up `/foo.vue:137` and\n // drops the `pages` prefix). Parens are the V8 stack-frame\n // convention and Chrome reliably auto-links them end-to-end.\n return `(${path}:${line})`\n}\n","/**\n * Re-bind a parent's `v-register` onto an inner native element. Use\n * inside a component that wraps a single form field whose root is\n * NOT the input itself (e.g. a labelled-row that renders `<label>`\n * around the input).\n *\n * ```vue\n * <!-- Parent -->\n * <MyInput v-register=\"form.register('email')\" />\n *\n * <!-- MyInput.vue -->\n * <script setup lang=\"ts\">\n * import { useRegister } from 'attaform'\n * const register = useRegister()\n * </script>\n *\n * <template>\n * <label class=\"field\">\n * <span>Email</span>\n * <input v-register=\"register\" />\n * </label>\n * </template>\n * ```\n *\n * Returns a `ComputedRef<RegisterValue | undefined>`. The directive\n * handles `undefined` gracefully (silent no-op assigner, no listener\n * attachment), so always pass the result to `v-register` directly.\n *\n * Diagnostic: in dev mode, a single `console.warn` fires per instance\n * at `onMounted` if the captured value is still `undefined` — by then\n * the parent has had its full mount lifecycle to bind, so a missing\n * binding is conclusive misuse. The warn does NOT fire on every read\n * of the computed, and is intentionally silent under SSR\n * (`renderToString` skips `onMounted`); the CSR hydration pass\n * surfaces the same diagnostic without double-counting through Nuxt's\n * `dev:ssr-logs` channel.\n *\n * When the wrapper's root IS the input itself, Vue's attribute\n * fallthrough handles the binding and `useRegister` is unnecessary.\n * For wrappers that bind multiple fields (compound forms), use\n * `injectForm<Form>(key?)` and call `ctx.register(...)` directly.\n */\nimport {\n computed,\n getCurrentInstance,\n onBeforeMount,\n onBeforeUpdate,\n onMounted,\n shallowRef,\n type ComputedRef,\n} from 'vue'\nimport { __DEV__ } from '../core/dev'\nimport { captureUserCallSite } from '../core/dev-stack-trace'\nimport type { RegisterValue } from '../types/types-api'\n\n/**\n * Marker on the rendered root DOM element. Set by `useRegister`'s\n * `onMounted` hook; read by the directive's deferred warn check to\n * skip the \"is a no-op\" warn for components that handle binding via\n * an inner v-register.\n *\n * `Symbol.for(...)` so the marker round-trips across duplicate copies\n * of attaform — see `assignKey` in core/directive.ts for the same\n * reasoning. `useRegister` and the directive are typically loaded\n * from the same module copy, but a consumer importing from\n * `attaform/zod` (Vite-optimized bundle) and the Nuxt\n * plugin's relative-path import (live ESM) can land on different\n * copies; a global symbol means the marker check still works.\n */\nexport const REGISTER_OWNER_MARKER: unique symbol = Symbol.for('attaform:register-owner-marker')\n\nconst warnedNoParentRV: WeakSet<object> | null = __DEV__ ? new WeakSet<object>() : null\nlet warnedOutsideSetup = false\n\nexport function useRegister(): ComputedRef<RegisterValue | undefined> {\n const instance = getCurrentInstance()\n if (instance === null) {\n warnOutsideSetup()\n return computed(() => undefined)\n }\n\n // Capture the bridge `registerValue` from instance.attrs into a\n // local ref, then STRIP the bridge keys (`registerValue` + `value`)\n // from the attrs object. This prevents fallthrough to the rendered\n // root: without the strip, Vue would merge attrs onto the root's\n // vnode and the wrapper would render with stringified DOM attrs\n // (`<label registerValue=\"[object Object]\">`). Class/style/aria/data\n // fallthrough is unaffected — only the bridge keys are removed, so\n // the consumer doesn't have to set `defineOptions({ inheritAttrs:\n // false })` and lose those legitimate fallthroughs.\n //\n // Vue's `setFullProps` repopulates attrs on every parent re-render\n // (it iterates rawProps and re-assigns each key into attrs). So the\n // capture+strip has to run on every update, not just at setup. The\n // `onBeforeUpdate` hook fires after `updateComponentPreRender`\n // (which calls setFullProps) and before `renderComponentRoot`\n // (which reads attrs for fallthrough), giving us a clean window.\n //\n // We don't read from `useAttrs()` proxy in the computed because\n // the proxy reads off the same target we're mutating — after the\n // strip, the proxy returns undefined for the bridge keys. The\n // captured ref is the source of truth instead, refreshed in lockstep\n // with attrs.\n //\n // `shallowRef` (not `ref`) — `ref` calls `reactive()` on object\n // values, which would wrap the parent's RV in a reactive proxy and\n // break referential equality. The directive hooks downstream rely\n // on the rv being the same reference the parent holds, so we keep\n // it raw.\n const capturedRegisterValue = shallowRef<RegisterValue | undefined>(undefined)\n\n const refreshAndStripBridgeAttrs = (): void => {\n const rawAttrs = instance.attrs as Record<string, unknown>\n // Capture only when the bridge key is present. The strip below\n // removes `registerValue` from attrs, so a second invocation of\n // this function (e.g. `onBeforeMount` after the synchronous setup\n // call) would otherwise overwrite the captured rv with `undefined`.\n // Vue's `setFullProps` re-populates attrs on every parent render,\n // so the `onBeforeUpdate` invocation correctly sees the key again\n // and re-captures.\n if ('registerValue' in rawAttrs) {\n capturedRegisterValue.value = rawAttrs['registerValue'] as RegisterValue | undefined\n delete rawAttrs['registerValue']\n }\n if ('value' in rawAttrs) delete rawAttrs['value']\n }\n // Capture+strip three times: synchronously in setup, then on\n // beforeMount, then on every beforeUpdate. The synchronous call is\n // load-bearing for SSR — Vue skips lifecycle hooks during\n // `renderToString`, so an `onBeforeMount`-only capture leaves\n // `capturedRegisterValue` at `undefined` and the directive's first\n // server-side template read would otherwise misrender. Vue's\n // `setupComponent` runs `initProps` (which populates\n // `instance.attrs.registerValue` from the parent's `:registerValue`\n // binding injected by `selectNodeTransform`) before `setup()` runs,\n // so the sync read sees the correct value on both server and client.\n // The `onBeforeMount` hook stays as defence in depth against any\n // re-population that could happen after setup (e.g. from a parent's\n // directive re-running) — idempotent, safe to duplicate. The\n // `onBeforeUpdate` hook handles parent re-renders, where Vue's\n // `setFullProps` runs again and re-puts the bridge keys.\n refreshAndStripBridgeAttrs()\n onBeforeMount(refreshAndStripBridgeAttrs)\n onBeforeUpdate(refreshAndStripBridgeAttrs)\n\n // Single post-mount hook does two jobs: (1) marks the rendered root\n // DOM element with `REGISTER_OWNER_MARKER` so the parent directive's\n // deferred warn check skips the \"is a no-op\" warn for components that\n // handle binding via an inner v-register, and (2) emits the\n // no-parent-RV diagnostic exactly once per instance if the captured\n // value is still `undefined` by mount time — by then the parent has\n // had its full lifecycle to bind, so still-undefined is conclusive\n // misuse. The computed factory below stays pure: reads don't trigger\n // diagnostics, so a consumer that conditionally consumes the value\n // (or reads it many times) gets exactly the right behaviour. SSR is\n // intentionally silent — `onMounted` doesn't fire on the server, and\n // the CSR hydration pass surfaces the diagnostic on the only surface\n // a developer can act on without double-counting through the Nuxt\n // `dev:ssr-logs` channel.\n onMounted(() => {\n const el = instance.vnode.el\n if (el !== null && el !== undefined && typeof el === 'object') {\n ;(el as unknown as { [k: symbol]: unknown })[REGISTER_OWNER_MARKER] = true\n }\n if (capturedRegisterValue.value === undefined) {\n warnNoParentRV(instance as unknown as object)\n }\n })\n\n return computed(() => capturedRegisterValue.value)\n}\n\nfunction warnOutsideSetup(): void {\n if (!__DEV__) return\n if (warnedOutsideSetup) return\n warnedOutsideSetup = true\n const frame = captureUserCallSite()\n console.warn(\n `[attaform] useRegister() called outside a component setup; returning ComputedRef<undefined>. ` +\n `Fix: call it inside <script setup> or a setup() function — not from an event handler ` +\n `or async callback.` +\n (frame !== undefined ? ` ${frame}` : '')\n )\n}\n\nfunction warnNoParentRV(instance: object): void {\n if (!__DEV__ || warnedNoParentRV === null) return\n if (warnedNoParentRV.has(instance)) return\n warnedNoParentRV.add(instance)\n const frame = captureUserCallSite()\n console.warn(\n `[attaform] useRegister: no parent registerValue prop; returning ComputedRef<undefined>. ` +\n `Pass v-register on the parent: \\`<YourComponent v-register=\"form.register('field')\" />\\`.` +\n (frame !== undefined ? ` ${frame}` : '')\n )\n}\n","import type { PathKey } from '../paths'\n\n/**\n * Per-element identity for the persistence opt-in registry.\n *\n * Why WeakMap-keyed monotonic counter:\n * - **No DOM mutation.** A `data-atta-id` attribute would alter SSR\n * output and risk hydration discrepancies. WeakMap is invisible.\n * - **Auto-GC.** When the element is removed from the DOM and goes\n * out of all references, the WeakMap entry vanishes — no leak.\n * - **Counter over UUID.** Element IDs never cross runtime boundaries\n * (the directive that consumes them is client-only), so collision\n * resistance across processes is irrelevant. Smaller, easier to\n * debug (\"el-7\" vs a UUID).\n */\nconst idGenerator = (() => {\n let counter = 0\n return () => `el-${++counter}`\n})()\n\nconst elementIds = new WeakMap<HTMLElement, string>()\n\nexport function getOrAssignElementId(el: HTMLElement): string {\n let id = elementIds.get(el)\n if (id === undefined) {\n id = idGenerator()\n elementIds.set(el, id)\n }\n return id\n}\n\n/**\n * Per-FormStore registry tracking which DOM elements have opted into\n * persistence for which paths. Lives on the FormStore so that two SFCs\n * sharing a key share the registry — opt-ins are per-element, not\n * per-component.\n *\n * The directive's input handler computes `meta.persist` for each write\n * by calling `hasOptIn(elementId, path)` — only THIS element's writes\n * persist if THIS element opted in. Other call sites that aren't tied\n * to a single element (history undo/redo, field-array helpers, devtools\n * edits) use `hasAnyOptInForPath(path)` — persist if any element has\n * opted into that path.\n *\n * Internal data structure: `Map<PathKey, Set<elementId>>`. Small forms\n * have ~10-50 paths; iteration is cheap. All operations are O(1) given\n * (id, path).\n */\nexport type PersistOptInRegistry = {\n /** Add an opt-in entry; idempotent. */\n add(elementId: string, path: PathKey): void\n /** Remove a single (element, path) entry. */\n remove(elementId: string, path: PathKey): void\n /** Remove every opt-in for `elementId`. Called from directive's beforeUnmount. */\n removeAllFor(elementId: string): void\n /** Check whether THIS element has opted into THIS path. */\n hasOptIn(elementId: string, path: PathKey): boolean\n /** Check whether ANY element has opted into this path. */\n hasAnyOptInForPath(path: PathKey): boolean\n /** Iterate every path that currently has at least one opt-in. */\n optedInPaths(): IterableIterator<PathKey>\n /** True iff no element has opted into any path. */\n isEmpty(): boolean\n /** Drop every entry. Called from FormStore.dispose. */\n clear(): void\n}\n\nexport function createPersistOptInRegistry(): PersistOptInRegistry {\n const byPath = new Map<PathKey, Set<string>>()\n\n function add(elementId: string, path: PathKey): void {\n const existing = byPath.get(path)\n if (existing === undefined) {\n byPath.set(path, new Set([elementId]))\n return\n }\n existing.add(elementId)\n }\n\n function remove(elementId: string, path: PathKey): void {\n const existing = byPath.get(path)\n if (existing === undefined) return\n existing.delete(elementId)\n if (existing.size === 0) byPath.delete(path)\n }\n\n function removeAllFor(elementId: string): void {\n for (const [path, ids] of byPath) {\n if (!ids.delete(elementId)) continue\n if (ids.size === 0) byPath.delete(path)\n }\n }\n\n function hasOptIn(elementId: string, path: PathKey): boolean {\n return byPath.get(path)?.has(elementId) ?? false\n }\n\n function hasAnyOptInForPath(path: PathKey): boolean {\n const ids = byPath.get(path)\n return ids !== undefined && ids.size > 0\n }\n\n function optedInPaths(): IterableIterator<PathKey> {\n return byPath.keys()\n }\n\n function isEmpty(): boolean {\n return byPath.size === 0\n }\n\n function clear(): void {\n byPath.clear()\n }\n\n return {\n add,\n remove,\n removeAllFor,\n hasOptIn,\n hasAnyOptInForPath,\n optedInPaths,\n isEmpty,\n clear,\n }\n}\n","import { SensitivePersistFieldError } from '../errors'\nimport type { Path, PathKey, Segment } from '../paths'\n\n/**\n * Sensitive-name heuristic: a small, intentionally conservative set of\n * regexes that flag a path segment as \"this looks like data the consumer\n * almost certainly does not want serialised to client-side storage.\"\n *\n * The check fires when a binding opts into persistence\n * (`register(path, { persist: true })`) or when an imperative\n * `form.persist(path)` is called — the binding can override with\n * `{ acknowledgeSensitive: true }` if the persistence is genuinely\n * intentional.\n *\n * **Non-goals.** This is not a soundness guarantee. Adversarial paths\n * (`'pswd'`, `'cred'`, `'sensitive_data'`) can slip through; misnamed\n * fields (`'CCV'` instead of `'CVV'`, `'social-sec-num'`) may not match\n * depending on locale or naming convention. The intent is a code-review\n * trigger for the common-case footgun: a developer adds a `password`\n * field to a form that already has `persist: { storage: 'local' }` and\n * doesn't notice that the existing persistence config now reaches the\n * new field. The per-element opt-in model already requires explicit\n * intent for each field; the sensitive-name heuristic adds a second\n * speed bump for the names everyone agrees never belong in localStorage.\n *\n * Word-boundary anchors (`\\b`) on short tokens prevent false positives:\n * `'description'` does not match `pwd`; `'tokenizer'` does not match\n * `token`. Multi-word forms (`api[_\\s-]?key`) tolerate snake_case,\n * kebab-case, and space-separated variants for path segments emitted\n * by humans.\n */\nexport const SENSITIVE_NAME_PATTERNS: readonly RegExp[] = [\n // Passwords and PIN-like\n /password/i,\n /passwd/i,\n /passwords/i,\n /\\bpwd\\b/i,\n /\\bpin\\b/i,\n // Card / payment\n /\\bcvv\\b/i,\n /\\bcvc\\b/i,\n /card[_\\s-]?(?:number|num)/i,\n /\\bcard\\b/i,\n /\\biban\\b/i,\n /routing[_\\s-]?number/i,\n /account[_\\s-]?number/i,\n // Government / identity\n /\\bssn\\b/i,\n /social[_\\s-]?security/i,\n /\\bdob\\b/i,\n /date[_\\s-]?of[_\\s-]?birth/i,\n /passport/i,\n /driver[_\\s-]?license/i,\n // Tax IDs (US + international common variants)\n /\\btin\\b/i,\n /\\bein\\b/i,\n /\\bitin\\b/i,\n /tax[_\\s-]?id/i,\n // Tokens, secrets, API/auth credentials\n /\\btoken\\b/i,\n /\\btokens\\b/i,\n /secret/i,\n /secrets/i,\n /api[_\\s-]?key/i,\n /api[_\\s-]?secret/i,\n /api[_\\s-]?token/i,\n /private[_\\s-]?key/i,\n /\\bbearer\\b/i,\n /\\boauth\\b/i,\n /auth[_\\s-]?token/i,\n /access[_\\s-]?token/i,\n /refresh[_\\s-]?token/i,\n /session[_\\s-]?(?:id|key|token)/i,\n // MFA / OTP\n /\\botp\\b/i,\n /one[_\\s-]?time[_\\s-]?(?:password|code)/i,\n /mfa[_\\s-]?(?:secret|seed|code|token)/i,\n /two[_\\s-]?factor[_\\s-]?(?:code|token)/i,\n /\\b2fa[_\\s-]?(?:code|token)?\\b/i,\n /recovery[_\\s-]?code/i,\n /backup[_\\s-]?code/i,\n] as const\n\n/**\n * True iff `segment` itself matches a sensitive-name pattern. Numeric\n * segments are never sensitive (array indices carry no semantic\n * weight). Used by `isSensitivePath` AND by the devtools redact\n * walk, which short-circuits whole subtrees the moment any ancestor\n * segment matches — saving an O(leaves × ancestors) regex sweep\n * per timeline event.\n */\nexport function segmentMatchesSensitive(segment: Segment): boolean {\n if (typeof segment !== 'string') return false\n for (const pattern of SENSITIVE_NAME_PATTERNS) {\n if (pattern.test(segment)) return true\n }\n return false\n}\n\n/**\n * True iff any segment of the path matches a sensitive-name pattern.\n * Match is per-segment: `'profile.password'` triggers via the `password`\n * segment; `'description.text'` does NOT match `desc` because of the\n * word boundaries on the short tokens.\n *\n * Accepts either a structured `Path` (canonical segments) or a string\n * `PathKey` (canonicalised JSON form). For PathKey, the JSON-bracket\n * shape `[\"profile\",\"password\"]` parses cleanly into segments; falling\n * back to a dotted-string split keeps simple cases working without\n * a JSON.parse round-trip.\n */\nexport function isSensitivePath(path: Path | PathKey | string): boolean {\n if (typeof path !== 'string') {\n for (const segment of path) {\n if (segmentMatchesSensitive(segment)) return true\n }\n return false\n }\n // String input: try JSON-array first (PathKey), fall back to dotted.\n if (path.startsWith('[')) {\n try {\n const parsed = JSON.parse(path) as unknown[]\n if (Array.isArray(parsed)) {\n for (const segment of parsed) {\n if (segmentMatchesSensitive(segment as Segment)) return true\n }\n return false\n }\n } catch {\n // fall through\n }\n }\n for (const segment of path.split('.')) {\n if (segmentMatchesSensitive(segment)) return true\n }\n return false\n}\n\n/**\n * Throw `SensitivePersistFieldError` if `path` matches the heuristic\n * and `acknowledged` is not true. Idempotent / pure — the call site is\n * the directive's opt-in lifecycle (on every add) and `form.persist`\n * (on every imperative checkpoint).\n */\nexport function enforceSensitiveCheck(path: Path | PathKey | string, acknowledged: boolean): void {\n if (acknowledged) return\n if (!isSensitivePath(path)) return\n throw new SensitivePersistFieldError(path)\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ const sensitiveNames = require('./attaform.B5GWYl76.cjs');
4
+
5
+ const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
6
+ function normalizeSegment(raw) {
7
+ if (typeof raw === "number") {
8
+ if (!Number.isInteger(raw) || raw < 0) {
9
+ throw new sensitiveNames.InvalidPathError(
10
+ `Path segments must be non-negative integers when numeric; got ${String(raw)}`
11
+ );
12
+ }
13
+ return raw;
14
+ }
15
+ if (INTEGER_SEGMENT.test(raw)) return Number(raw);
16
+ return raw;
17
+ }
18
+ function parseDottedPath(path) {
19
+ if (path.length === 0) return [];
20
+ const rawSegments = path.split(".");
21
+ const segments = [];
22
+ for (const raw of rawSegments) {
23
+ if (raw.length === 0) {
24
+ throw new sensitiveNames.InvalidPathError(
25
+ `Path '${path}' has an empty segment; use the array form for empty keys.`
26
+ );
27
+ }
28
+ segments.push(normalizeSegment(raw));
29
+ }
30
+ return segments;
31
+ }
32
+ const CANONICAL_STRING_CACHE_MAX = 128;
33
+ const canonicalStringCache = /* @__PURE__ */ new Map();
34
+ const PATHKEY_TO_SEGMENTS_MAX = 4096;
35
+ const pathKeyToSegments = /* @__PURE__ */ new Map();
36
+ function rememberSegmentsForPathKey(key, segments) {
37
+ if (!pathKeyToSegments.has(key) && pathKeyToSegments.size >= PATHKEY_TO_SEGMENTS_MAX) {
38
+ const oldest = pathKeyToSegments.keys().next().value;
39
+ if (oldest !== void 0) pathKeyToSegments.delete(oldest);
40
+ }
41
+ pathKeyToSegments.set(key, segments);
42
+ }
43
+ function segmentsForPathKey(key) {
44
+ const cached = pathKeyToSegments.get(key);
45
+ if (cached !== void 0) return cached;
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(key);
49
+ } catch {
50
+ return null;
51
+ }
52
+ if (!Array.isArray(parsed)) return null;
53
+ const segments = [];
54
+ for (const raw of parsed) {
55
+ if (typeof raw !== "string" && typeof raw !== "number") return null;
56
+ segments.push(normalizeSegment(raw));
57
+ }
58
+ rememberSegmentsForPathKey(key, segments);
59
+ return segments;
60
+ }
61
+ function canonicalizePath(input) {
62
+ if (typeof input === "string") {
63
+ const cached = canonicalStringCache.get(input);
64
+ if (cached !== void 0) return cached;
65
+ const segments2 = parseDottedPath(input);
66
+ const key2 = JSON.stringify(segments2);
67
+ const entry = { segments: segments2, key: key2 };
68
+ if (canonicalStringCache.size >= CANONICAL_STRING_CACHE_MAX) {
69
+ const oldest = canonicalStringCache.keys().next().value;
70
+ if (oldest !== void 0) canonicalStringCache.delete(oldest);
71
+ }
72
+ canonicalStringCache.set(input, entry);
73
+ rememberSegmentsForPathKey(key2, segments2);
74
+ return entry;
75
+ }
76
+ const segments = Array.from(input).map(normalizeSegment);
77
+ const key = JSON.stringify(segments);
78
+ rememberSegmentsForPathKey(key, segments);
79
+ return { segments, key };
80
+ }
81
+ const ROOT_PATH = Object.freeze([]);
82
+ const ROOT_PATH_KEY = "[]";
83
+
84
+ exports.ROOT_PATH = ROOT_PATH;
85
+ exports.ROOT_PATH_KEY = ROOT_PATH_KEY;
86
+ exports.canonicalizePath = canonicalizePath;
87
+ exports.parseDottedPath = parseDottedPath;
88
+ exports.segmentsForPathKey = segmentsForPathKey;
89
+ //# sourceMappingURL=attaform.xKWYHMdq.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attaform.xKWYHMdq.cjs","sources":["../../src/runtime/core/paths.ts"],"sourcesContent":["import { InvalidPathError } from './errors'\n\n/**\n * Path primitives for advanced integrations. The form library accepts\n * paths in dotted-string form (`'user.email'`) at every public API.\n * These primitives are exposed for adapter authors who need to\n * canonicalise user-provided paths.\n */\n\ndeclare const pathKeyBrand: unique symbol\n\n/**\n * Branded string identifier for a canonicalised path. Useful as a\n * `Map` key — two paths that resolve to the same canonical form\n * produce the same `PathKey`. Treat as opaque; don't try to parse.\n */\nexport type PathKey = string & { readonly [pathKeyBrand]: 'PathKey' }\n\n/** A single path segment — a property name or array index. */\nexport type Segment = string | number\n/** A structured path as a read-only sequence of segments. */\nexport type Path = readonly Segment[]\n\n/** Tests an integer-like string without leading zeros. `'0'` | `'1'` | `'42'` pass; `'01'`, `'-1'`, `'1.5'` do not. */\nconst INTEGER_SEGMENT = /^(?:0|[1-9]\\d*)$/\n\nfunction normalizeSegment(raw: Segment): Segment {\n if (typeof raw === 'number') {\n if (!Number.isInteger(raw) || raw < 0) {\n throw new InvalidPathError(\n `Path segments must be non-negative integers when numeric; got ${String(raw)}`\n )\n }\n return raw\n }\n // Integer-looking strings normalise to numbers so that dotted-form\n // `'items.0.name'` and array-form `['items', 0, 'name']` yield the same\n // canonical path (and PathKey).\n if (INTEGER_SEGMENT.test(raw)) return Number(raw)\n return raw\n}\n\n/**\n * Parse a dotted-string path into structured segments.\n *\n * ```ts\n * parseDottedPath('user.address.line1') // ['user', 'address', 'line1']\n * parseDottedPath('items.0.name') // ['items', 0, 'name']\n * parseDottedPath('') // [] (root)\n * ```\n *\n * Throws `InvalidPathError` for paths with empty segments\n * (`'a..b'`, leading or trailing dots). For keys containing literal\n * dots, pass an array form (`['user.name']`) instead.\n */\nexport function parseDottedPath(path: string): Segment[] {\n if (path.length === 0) return []\n const rawSegments = path.split('.')\n const segments: Segment[] = []\n for (const raw of rawSegments) {\n if (raw.length === 0) {\n throw new InvalidPathError(\n `Path '${path}' has an empty segment; use the array form for empty keys.`\n )\n }\n segments.push(normalizeSegment(raw))\n }\n return segments\n}\n\n/**\n * Bounded FIFO cache for canonicalizePath on dotted-string inputs.\n * Real forms re-canonicalise a small working-set of paths thousands\n * of times per session (every keystroke on a registered field, every\n * validate, every getValue), so a small cache amortises the parse +\n * stringify cost across repeat calls without pinning memory as apps\n * accumulate fields.\n *\n * Eviction is FIFO (oldest insertion wins), not LRU. The 128-entry\n * cap is generous relative to a typical form's working set\n * (playground: ~15 paths; the entire test suite: 45 unique register\n * patterns) — overflow doesn't fire in practice. On the rare overflow\n * a re-canonicalisation hit is still O(segments) and lands back in\n * the cache. Bumping recency on every hit (`delete` + `set`) costs\n * two Map operations per cache hit, in the hottest read-side loop in\n * the library, with no observable benefit at this cap — so we don't.\n *\n * Array inputs are not cached: callers in the runtime (unset-walker's\n * recursive `[...segments, i]`, devtools' inspector `payload.path.slice(...)`)\n * overwhelmingly pass freshly-allocated arrays per call, so a\n * WeakMap-keyed cache would miss on every call and pay the\n * lookup-then-set cost without benefit.\n */\nconst CANONICAL_STRING_CACHE_MAX = 128\nconst canonicalStringCache = new Map<string, { segments: readonly Segment[]; key: PathKey }>()\n\n/**\n * Inverse cache: PathKey → segments. Populated by `canonicalizePath`\n * (string and array branches) so any consumer holding a PathKey\n * produced through the canonical pipeline can recover its structured\n * segments without `JSON.parse`. Callers reach this through\n * `segmentsForPathKey` below.\n *\n * The store-side data structures keyed by PathKey (form-store error\n * maps, blank-paths set, variant-memory map, persistence opt-in\n * registry) all source their keys from `canonicalizePath`, so reads\n * are dominantly cache hits. Cold paths (PathKeys round-tripped from\n * a persisted payload that came from disk) still hit a single\n * `JSON.parse` on first lookup, then warm the cache.\n *\n * Bounded FIFO at 4096 entries — generous relative to a typical form's\n * working set (~tens to ~hundreds of paths per form) but small enough\n * that long-running multi-form apps don't accumulate unbounded\n * references. Eviction only fires on net-new entries; idempotent\n * overwrites (same key, same segments) don't count toward the cap.\n */\nconst PATHKEY_TO_SEGMENTS_MAX = 4096\nconst pathKeyToSegments = new Map<PathKey, readonly Segment[]>()\n\nfunction rememberSegmentsForPathKey(key: PathKey, segments: readonly Segment[]): void {\n if (!pathKeyToSegments.has(key) && pathKeyToSegments.size >= PATHKEY_TO_SEGMENTS_MAX) {\n const oldest = pathKeyToSegments.keys().next().value\n if (oldest !== undefined) pathKeyToSegments.delete(oldest)\n }\n pathKeyToSegments.set(key, segments)\n}\n\n/**\n * Recover the structured `Segment[]` for a `PathKey` produced by\n * `canonicalizePath`. O(1) on the hot path (cache hit); cold keys\n * fall back to `JSON.parse(key)` plus segment normalization, then\n * warm the cache so subsequent lookups hit.\n *\n * Returns `null` for malformed PathKeys (non-JSON, non-array, or\n * containing values that aren't strings/numbers). Keys produced by\n * `canonicalizePath` never trip this — corrupt persistence payloads\n * (or test fixtures crafting raw strings) are the only realistic\n * sources.\n */\nexport function segmentsForPathKey(key: PathKey): readonly Segment[] | null {\n const cached = pathKeyToSegments.get(key)\n if (cached !== undefined) return cached\n let parsed: unknown\n try {\n parsed = JSON.parse(key)\n } catch {\n return null\n }\n if (!Array.isArray(parsed)) return null\n const segments: Segment[] = []\n for (const raw of parsed) {\n if (typeof raw !== 'string' && typeof raw !== 'number') return null\n segments.push(normalizeSegment(raw))\n }\n rememberSegmentsForPathKey(key, segments)\n return segments\n}\n\n/**\n * Canonicalise a path into structured segments plus a stable string\n * key. Accepts either dotted-string or array form; integer-looking\n * segments normalise to numbers.\n *\n * ```ts\n * canonicalizePath('items.0.name')\n * // { segments: ['items', 0, 'name'], key: '[\"items\",0,\"name\"]' as PathKey }\n *\n * canonicalizePath(['items', 0, 'name'])\n * // → same result\n * ```\n *\n * The returned `key` is suitable as a `Map`/`Set` key — equal paths\n * produce equal keys regardless of input form.\n */\nexport function canonicalizePath(input: string | Path): {\n segments: readonly Segment[]\n key: PathKey\n} {\n if (typeof input === 'string') {\n const cached = canonicalStringCache.get(input)\n if (cached !== undefined) return cached\n // `parseDottedPath` already normalises each segment; the previous\n // `.map(normalizeSegment)` second pass was a no-op. We drop it here.\n const segments: readonly Segment[] = parseDottedPath(input)\n const key = JSON.stringify(segments) as PathKey\n const entry = { segments, key }\n if (canonicalStringCache.size >= CANONICAL_STRING_CACHE_MAX) {\n const oldest = canonicalStringCache.keys().next().value\n if (oldest !== undefined) canonicalStringCache.delete(oldest)\n }\n canonicalStringCache.set(input, entry)\n rememberSegmentsForPathKey(key, segments)\n return entry\n }\n const segments = Array.from(input).map(normalizeSegment)\n const key = JSON.stringify(segments) as PathKey\n rememberSegmentsForPathKey(key, segments)\n return { segments, key }\n}\n\n/**\n * The root path — an empty segment tuple. Pass to APIs that accept\n * a `Path` to address the form value as a whole.\n */\nexport const ROOT_PATH: Path = Object.freeze([])\n/** Stable string key for the root path. */\nexport const ROOT_PATH_KEY = '[]' as PathKey\n"],"names":["InvalidPathError","segments","key"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const vRegisterPreambleTransform = require('./shared/attaform.Bubm_slq.cjs');
4
+
5
+
6
+
7
+ exports.inputTextAreaNodeTransform = vRegisterPreambleTransform.inputTextAreaNodeTransform;
8
+ exports.selectNodeTransform = vRegisterPreambleTransform.selectNodeTransform;
9
+ exports.vRegisterHintTransform = vRegisterPreambleTransform.vRegisterHintTransform;
10
+ exports.vRegisterPreambleTransform = vRegisterPreambleTransform.vRegisterPreambleTransform;
11
+ //# sourceMappingURL=transforms.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
@@ -0,0 +1,49 @@
1
+ import { NodeTransform } from '@vue/compiler-core';
2
+
3
+ /**
4
+ * Vue compiler node transform for `<input v-register>` and
5
+ * `<textarea v-register>`. Injects the `:value` / `:checked`
6
+ * bindings required for SSR-correct initial render.
7
+ *
8
+ * Wired automatically by `attaform/vite` and
9
+ * `attaform/nuxt`. Use directly only when integrating with
10
+ * a custom bundler.
11
+ */
12
+ declare const inputTextAreaNodeTransform: NodeTransform;
13
+
14
+ /**
15
+ * Vue compiler node transform for `<select v-register>` and any
16
+ * component that wraps a select. Injects the `:value` /
17
+ * `:registerValue` bridge bindings the runtime directive needs to
18
+ * pre-mark selected options at SSR time.
19
+ *
20
+ * Wired automatically by `attaform/vite` and
21
+ * `attaform/nuxt`. Use directly only when integrating with
22
+ * a custom bundler.
23
+ */
24
+ declare const selectNodeTransform: NodeTransform;
25
+
26
+ /**
27
+ * Vue compiler node transform that wraps every `v-register`
28
+ * expression in a small IIFE so the directive can flag a field as
29
+ * connected during SSR. Eliminates the `false → true` flicker on
30
+ * `getFieldState(path).isConnected` after hydration.
31
+ *
32
+ * Must run after `vRegisterPreambleTransform`. Wired automatically
33
+ * by `attaform/vite` and `attaform/nuxt`.
34
+ */
35
+ declare const vRegisterHintTransform: NodeTransform;
36
+
37
+ /**
38
+ * Vue compiler node transform that hoists `v-register`'s SSR
39
+ * connection marks to the root of the template. Together with
40
+ * `vRegisterHintTransform`, ensures expressions earlier in the
41
+ * template that read `getFieldState(path).isConnected` see the
42
+ * correct value during the server's single-pass render.
43
+ *
44
+ * Must run before `vRegisterHintTransform`. Wired automatically
45
+ * by `attaform/vite` and `attaform/nuxt`.
46
+ */
47
+ declare const vRegisterPreambleTransform: NodeTransform;
48
+
49
+ export { inputTextAreaNodeTransform, selectNodeTransform, vRegisterHintTransform, vRegisterPreambleTransform };
@@ -0,0 +1,49 @@
1
+ import { NodeTransform } from '@vue/compiler-core';
2
+
3
+ /**
4
+ * Vue compiler node transform for `<input v-register>` and
5
+ * `<textarea v-register>`. Injects the `:value` / `:checked`
6
+ * bindings required for SSR-correct initial render.
7
+ *
8
+ * Wired automatically by `attaform/vite` and
9
+ * `attaform/nuxt`. Use directly only when integrating with
10
+ * a custom bundler.
11
+ */
12
+ declare const inputTextAreaNodeTransform: NodeTransform;
13
+
14
+ /**
15
+ * Vue compiler node transform for `<select v-register>` and any
16
+ * component that wraps a select. Injects the `:value` /
17
+ * `:registerValue` bridge bindings the runtime directive needs to
18
+ * pre-mark selected options at SSR time.
19
+ *
20
+ * Wired automatically by `attaform/vite` and
21
+ * `attaform/nuxt`. Use directly only when integrating with
22
+ * a custom bundler.
23
+ */
24
+ declare const selectNodeTransform: NodeTransform;
25
+
26
+ /**
27
+ * Vue compiler node transform that wraps every `v-register`
28
+ * expression in a small IIFE so the directive can flag a field as
29
+ * connected during SSR. Eliminates the `false → true` flicker on
30
+ * `getFieldState(path).isConnected` after hydration.
31
+ *
32
+ * Must run after `vRegisterPreambleTransform`. Wired automatically
33
+ * by `attaform/vite` and `attaform/nuxt`.
34
+ */
35
+ declare const vRegisterHintTransform: NodeTransform;
36
+
37
+ /**
38
+ * Vue compiler node transform that hoists `v-register`'s SSR
39
+ * connection marks to the root of the template. Together with
40
+ * `vRegisterHintTransform`, ensures expressions earlier in the
41
+ * template that read `getFieldState(path).isConnected` see the
42
+ * correct value during the server's single-pass render.
43
+ *
44
+ * Must run before `vRegisterHintTransform`. Wired automatically
45
+ * by `attaform/vite` and `attaform/nuxt`.
46
+ */
47
+ declare const vRegisterPreambleTransform: NodeTransform;
48
+
49
+ export { inputTextAreaNodeTransform, selectNodeTransform, vRegisterHintTransform, vRegisterPreambleTransform };
@@ -0,0 +1,49 @@
1
+ import { NodeTransform } from '@vue/compiler-core';
2
+
3
+ /**
4
+ * Vue compiler node transform for `<input v-register>` and
5
+ * `<textarea v-register>`. Injects the `:value` / `:checked`
6
+ * bindings required for SSR-correct initial render.
7
+ *
8
+ * Wired automatically by `attaform/vite` and
9
+ * `attaform/nuxt`. Use directly only when integrating with
10
+ * a custom bundler.
11
+ */
12
+ declare const inputTextAreaNodeTransform: NodeTransform;
13
+
14
+ /**
15
+ * Vue compiler node transform for `<select v-register>` and any
16
+ * component that wraps a select. Injects the `:value` /
17
+ * `:registerValue` bridge bindings the runtime directive needs to
18
+ * pre-mark selected options at SSR time.
19
+ *
20
+ * Wired automatically by `attaform/vite` and
21
+ * `attaform/nuxt`. Use directly only when integrating with
22
+ * a custom bundler.
23
+ */
24
+ declare const selectNodeTransform: NodeTransform;
25
+
26
+ /**
27
+ * Vue compiler node transform that wraps every `v-register`
28
+ * expression in a small IIFE so the directive can flag a field as
29
+ * connected during SSR. Eliminates the `false → true` flicker on
30
+ * `getFieldState(path).isConnected` after hydration.
31
+ *
32
+ * Must run after `vRegisterPreambleTransform`. Wired automatically
33
+ * by `attaform/vite` and `attaform/nuxt`.
34
+ */
35
+ declare const vRegisterHintTransform: NodeTransform;
36
+
37
+ /**
38
+ * Vue compiler node transform that hoists `v-register`'s SSR
39
+ * connection marks to the root of the template. Together with
40
+ * `vRegisterHintTransform`, ensures expressions earlier in the
41
+ * template that read `getFieldState(path).isConnected` see the
42
+ * correct value during the server's single-pass render.
43
+ *
44
+ * Must run before `vRegisterHintTransform`. Wired automatically
45
+ * by `attaform/vite` and `attaform/nuxt`.
46
+ */
47
+ declare const vRegisterPreambleTransform: NodeTransform;
48
+
49
+ export { inputTextAreaNodeTransform, selectNodeTransform, vRegisterHintTransform, vRegisterPreambleTransform };
@@ -0,0 +1,2 @@
1
+ export { i as inputTextAreaNodeTransform, s as selectNodeTransform, v as vRegisterHintTransform, a as vRegisterPreambleTransform } from './shared/attaform.CXpzmj38.mjs';
2
+ //# sourceMappingURL=transforms.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transforms.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/dist/vite.cjs ADDED
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const vRegisterPreambleTransform = require('./shared/attaform.Bubm_slq.cjs');
4
+
5
+ function attaform(_options = {}) {
6
+ return {
7
+ name: "attaform",
8
+ enforce: "pre",
9
+ configResolved(resolved) {
10
+ var _a, _b;
11
+ const vuePlugin = resolved.plugins.find((p) => p.name === "vite:vue");
12
+ if (vuePlugin === void 0) {
13
+ throw new Error(
14
+ "[attaform/vite] @vitejs/plugin-vue is not installed (or not registered before attaform()). Install @vitejs/plugin-vue and place `attaform()` after `vue()` in your plugins array."
15
+ );
16
+ }
17
+ const api = vuePlugin.api;
18
+ if (api?.options === void 0) {
19
+ throw new Error(
20
+ "[attaform/vite] Found @vitejs/plugin-vue but it does not expose `api.options`. This usually means a version-incompatible @vitejs/plugin-vue (or a wrapper plugin re-exporting it). Pin @vitejs/plugin-vue to a version compatible with the documented `api.options.template.compilerOptions.nodeTransforms` surface."
21
+ );
22
+ }
23
+ (_a = api.options).template ?? (_a.template = {});
24
+ (_b = api.options.template).compilerOptions ?? (_b.compilerOptions = {});
25
+ const existing = api.options.template.compilerOptions.nodeTransforms ?? [];
26
+ if (existing.includes(vRegisterPreambleTransform.vRegisterPreambleTransform)) return;
27
+ api.options.template.compilerOptions.nodeTransforms = [
28
+ ...existing,
29
+ vRegisterPreambleTransform.selectNodeTransform,
30
+ vRegisterPreambleTransform.inputTextAreaNodeTransform,
31
+ vRegisterPreambleTransform.vRegisterPreambleTransform,
32
+ vRegisterPreambleTransform.vRegisterHintTransform
33
+ ];
34
+ }
35
+ };
36
+ }
37
+
38
+ exports.attaform = attaform;
39
+ //# sourceMappingURL=vite.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.cjs","sources":["../src/vite.ts"],"sourcesContent":["/**\n * `attaform/vite` — Vite plugin that registers the compile-time\n * node transforms with @vitejs/plugin-vue.\n *\n * Usage (bare Vue 3 consumers):\n *\n * // vite.config.ts\n * import vue from '@vitejs/plugin-vue'\n * import { attaform } from 'attaform/vite'\n *\n * export default defineConfig({\n * plugins: [vue(), attaform()],\n * })\n *\n * The transforms inject `:value`, `:checked`, and `:selected` bindings into\n * elements that use the `v-register` directive — load-bearing for SSR\n * initial-render correctness. Omitting this plugin under CSR is tolerable\n * (one-frame flash on mount); omitting it under SSR produces visibly wrong\n * initial HTML.\n *\n * Implementation note: this plugin mutates @vitejs/plugin-vue's options via\n * the documented but somewhat informal `api.options` surface used by\n * VueUse, Vite PWA, and other Vue ecosystem plugins. If you're using a\n * custom Vue plugin wrapper, fall back to `attaform/transforms`\n * and wire them yourself.\n */\nimport type { Plugin } from 'vite'\nimport { inputTextAreaNodeTransform } from './runtime/lib/core/transforms/input-text-area-transform'\nimport { selectNodeTransform } from './runtime/lib/core/transforms/select-transform'\nimport { vRegisterHintTransform } from './runtime/lib/core/transforms/v-register-hint-transform'\nimport { vRegisterPreambleTransform } from './runtime/lib/core/transforms/v-register-preamble-transform'\n\n/** Options for `attaform()`. Reserved for future use; pass `{}` or omit. */\nexport type AttaformVitePluginOptions = Record<string, never>\n\ninterface VitePluginVueApi {\n options?: {\n template?: {\n compilerOptions?: {\n nodeTransforms?: unknown[]\n }\n }\n }\n}\n\n/**\n * Vite plugin that wires the form library's compile-time template\n * transforms into `@vitejs/plugin-vue`. Required for SSR and for\n * hydration accuracy under bare Vue 3.\n *\n * ```ts\n * // vite.config.ts\n * import vue from '@vitejs/plugin-vue'\n * import { attaform } from 'attaform/vite'\n *\n * export default defineConfig({\n * plugins: [vue(), attaform()],\n * })\n * ```\n *\n * Place the call after `vue()` in the plugins array. Nuxt projects\n * don't need this — `attaform/nuxt` handles it.\n */\nexport function attaform(_options: AttaformVitePluginOptions = {}): Plugin {\n // Unused-var suppression until options exist.\n void _options\n return {\n name: 'attaform',\n enforce: 'pre',\n configResolved(resolved) {\n const vuePlugin = resolved.plugins.find((p) => p.name === 'vite:vue')\n // Two distinct failure modes — separate error messages so the\n // consumer's fix is unambiguous:\n // 1. plugin not in the plugins array → install + register vue()\n // 2. plugin found but version-incompatible (no `api.options`) →\n // version mismatch with @vitejs/plugin-vue\n if (vuePlugin === undefined) {\n throw new Error(\n '[attaform/vite] @vitejs/plugin-vue is not installed (or not registered before attaform()). ' +\n 'Install @vitejs/plugin-vue and place `attaform()` after `vue()` in your plugins array.'\n )\n }\n const api = (vuePlugin as unknown as { api?: VitePluginVueApi }).api\n if (api?.options === undefined) {\n throw new Error(\n '[attaform/vite] Found @vitejs/plugin-vue but it does not expose `api.options`. ' +\n 'This usually means a version-incompatible @vitejs/plugin-vue (or a wrapper plugin re-exporting it). ' +\n 'Pin @vitejs/plugin-vue to a version compatible with the documented `api.options.template.compilerOptions.nodeTransforms` surface.'\n )\n }\n api.options.template ??= {}\n api.options.template.compilerOptions ??= {}\n const existing = api.options.template.compilerOptions.nodeTransforms ?? []\n // Idempotent install: if a previous attaform() invocation\n // (vite + nuxt module + manual `plugins: [attaform()]`) has\n // already pushed our transforms, skip — re-pushing would double\n // every binding the AST emits, breaking the IIFE-wrapping\n // invariants downstream transforms depend on. We detect the\n // sentinel via reference equality; user-supplied transforms with\n // the same name don't collide.\n if (existing.includes(vRegisterPreambleTransform as unknown)) return\n // vRegisterPreambleTransform MUST come before vRegisterHintTransform\n // — the preamble's pre-order captures each `v-register` expression\n // in its raw (un-wrapped) form, and the hint then mutates the same\n // directive's `exp` to wrap it. Reversing the order would have the\n // preamble pick up an already-wrapped IIFE, double-wrapping it\n // when injected at the root.\n api.options.template.compilerOptions.nodeTransforms = [\n ...existing,\n selectNodeTransform,\n inputTextAreaNodeTransform,\n vRegisterPreambleTransform,\n vRegisterHintTransform,\n ]\n },\n }\n}\n"],"names":["vRegisterPreambleTransform","selectNodeTransform","inputTextAreaNodeTransform","vRegisterHintTransform"],"mappings":";;;;AA+DO,SAAS,QAAA,CAAS,QAAA,GAAsC,EAAC,EAAW;AAGzE,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,UAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,eAAe,QAAA,EAAU;AArE7B,MAAA,IAAA,EAAA,EAAA,EAAA;AAsEM,MAAA,MAAM,SAAA,GAAY,SAAS,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,UAAU,CAAA;AAMpE,MAAA,IAAI,cAAc,MAAA,EAAW;AAC3B,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SAEF;AAAA,MACF;AACA,MAAA,MAAM,MAAO,SAAA,CAAoD,GAAA;AACjE,MAAA,IAAI,GAAA,EAAK,YAAY,MAAA,EAAW;AAC9B,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SAGF;AAAA,MACF;AACA,MAAA,CAAA,EAAA,GAAA,GAAA,CAAI,OAAA,EAAQ,QAAA,KAAZ,EAAA,CAAY,QAAA,GAAa,EAAC,CAAA;AAC1B,MAAA,CAAA,EAAA,GAAA,GAAA,CAAI,OAAA,CAAQ,QAAA,EAAS,eAAA,KAArB,EAAA,CAAqB,kBAAoB,EAAC,CAAA;AAC1C,MAAA,MAAM,WAAW,GAAA,CAAI,OAAA,CAAQ,QAAA,CAAS,eAAA,CAAgB,kBAAkB,EAAC;AAQzE,MAAA,IAAI,QAAA,CAAS,QAAA,CAASA,qDAAqC,CAAA,EAAG;AAO9D,MAAA,GAAA,CAAI,OAAA,CAAQ,QAAA,CAAS,eAAA,CAAgB,cAAA,GAAiB;AAAA,QACpD,GAAG,QAAA;AAAA,QACHC,8CAAA;AAAA,QACAC,qDAAA;AAAA,QACAF,qDAAA;AAAA,QACAG;AAAA,OACF;AAAA,IACF;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,53 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * `attaform/vite` — Vite plugin that registers the compile-time
5
+ * node transforms with @vitejs/plugin-vue.
6
+ *
7
+ * Usage (bare Vue 3 consumers):
8
+ *
9
+ * // vite.config.ts
10
+ * import vue from '@vitejs/plugin-vue'
11
+ * import { attaform } from 'attaform/vite'
12
+ *
13
+ * export default defineConfig({
14
+ * plugins: [vue(), attaform()],
15
+ * })
16
+ *
17
+ * The transforms inject `:value`, `:checked`, and `:selected` bindings into
18
+ * elements that use the `v-register` directive — load-bearing for SSR
19
+ * initial-render correctness. Omitting this plugin under CSR is tolerable
20
+ * (one-frame flash on mount); omitting it under SSR produces visibly wrong
21
+ * initial HTML.
22
+ *
23
+ * Implementation note: this plugin mutates @vitejs/plugin-vue's options via
24
+ * the documented but somewhat informal `api.options` surface used by
25
+ * VueUse, Vite PWA, and other Vue ecosystem plugins. If you're using a
26
+ * custom Vue plugin wrapper, fall back to `attaform/transforms`
27
+ * and wire them yourself.
28
+ */
29
+
30
+ /** Options for `attaform()`. Reserved for future use; pass `{}` or omit. */
31
+ type AttaformVitePluginOptions = Record<string, never>;
32
+ /**
33
+ * Vite plugin that wires the form library's compile-time template
34
+ * transforms into `@vitejs/plugin-vue`. Required for SSR and for
35
+ * hydration accuracy under bare Vue 3.
36
+ *
37
+ * ```ts
38
+ * // vite.config.ts
39
+ * import vue from '@vitejs/plugin-vue'
40
+ * import { attaform } from 'attaform/vite'
41
+ *
42
+ * export default defineConfig({
43
+ * plugins: [vue(), attaform()],
44
+ * })
45
+ * ```
46
+ *
47
+ * Place the call after `vue()` in the plugins array. Nuxt projects
48
+ * don't need this — `attaform/nuxt` handles it.
49
+ */
50
+ declare function attaform(_options?: AttaformVitePluginOptions): Plugin;
51
+
52
+ export { attaform };
53
+ export type { AttaformVitePluginOptions };
@@ -0,0 +1,53 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * `attaform/vite` — Vite plugin that registers the compile-time
5
+ * node transforms with @vitejs/plugin-vue.
6
+ *
7
+ * Usage (bare Vue 3 consumers):
8
+ *
9
+ * // vite.config.ts
10
+ * import vue from '@vitejs/plugin-vue'
11
+ * import { attaform } from 'attaform/vite'
12
+ *
13
+ * export default defineConfig({
14
+ * plugins: [vue(), attaform()],
15
+ * })
16
+ *
17
+ * The transforms inject `:value`, `:checked`, and `:selected` bindings into
18
+ * elements that use the `v-register` directive — load-bearing for SSR
19
+ * initial-render correctness. Omitting this plugin under CSR is tolerable
20
+ * (one-frame flash on mount); omitting it under SSR produces visibly wrong
21
+ * initial HTML.
22
+ *
23
+ * Implementation note: this plugin mutates @vitejs/plugin-vue's options via
24
+ * the documented but somewhat informal `api.options` surface used by
25
+ * VueUse, Vite PWA, and other Vue ecosystem plugins. If you're using a
26
+ * custom Vue plugin wrapper, fall back to `attaform/transforms`
27
+ * and wire them yourself.
28
+ */
29
+
30
+ /** Options for `attaform()`. Reserved for future use; pass `{}` or omit. */
31
+ type AttaformVitePluginOptions = Record<string, never>;
32
+ /**
33
+ * Vite plugin that wires the form library's compile-time template
34
+ * transforms into `@vitejs/plugin-vue`. Required for SSR and for
35
+ * hydration accuracy under bare Vue 3.
36
+ *
37
+ * ```ts
38
+ * // vite.config.ts
39
+ * import vue from '@vitejs/plugin-vue'
40
+ * import { attaform } from 'attaform/vite'
41
+ *
42
+ * export default defineConfig({
43
+ * plugins: [vue(), attaform()],
44
+ * })
45
+ * ```
46
+ *
47
+ * Place the call after `vue()` in the plugins array. Nuxt projects
48
+ * don't need this — `attaform/nuxt` handles it.
49
+ */
50
+ declare function attaform(_options?: AttaformVitePluginOptions): Plugin;
51
+
52
+ export { attaform };
53
+ export type { AttaformVitePluginOptions };