attaform 0.20.1 → 0.21.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 (152) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/dev-key-collision-warnings.cjs +58 -0
  3. package/dist/chunks/dev-key-collision-warnings.cjs.map +1 -0
  4. package/dist/chunks/dev-key-collision-warnings.mjs +55 -0
  5. package/dist/chunks/dev-key-collision-warnings.mjs.map +1 -0
  6. package/dist/chunks/devtools.cjs +2 -2
  7. package/dist/chunks/devtools.cjs.map +1 -1
  8. package/dist/chunks/devtools.mjs +2 -2
  9. package/dist/chunks/devtools.mjs.map +1 -1
  10. package/dist/chunks/fingerprint.cjs +186 -0
  11. package/dist/chunks/fingerprint.cjs.map +1 -0
  12. package/dist/chunks/fingerprint.mjs +184 -0
  13. package/dist/chunks/fingerprint.mjs.map +1 -0
  14. package/dist/chunks/fingerprint2.cjs +162 -0
  15. package/dist/chunks/fingerprint2.cjs.map +1 -0
  16. package/dist/chunks/fingerprint2.mjs +160 -0
  17. package/dist/chunks/fingerprint2.mjs.map +1 -0
  18. package/dist/chunks/indexeddb.cjs +1 -1
  19. package/dist/chunks/indexeddb.mjs +1 -1
  20. package/dist/chunks/local-storage.cjs +1 -1
  21. package/dist/chunks/local-storage.mjs +1 -1
  22. package/dist/chunks/multi-tab-sync.cjs +367 -0
  23. package/dist/chunks/multi-tab-sync.cjs.map +1 -0
  24. package/dist/chunks/multi-tab-sync.mjs +364 -0
  25. package/dist/chunks/multi-tab-sync.mjs.map +1 -0
  26. package/dist/chunks/session-storage.cjs +1 -1
  27. package/dist/chunks/session-storage.mjs +1 -1
  28. package/dist/chunks/wire-persistence.cjs +396 -0
  29. package/dist/chunks/wire-persistence.cjs.map +1 -0
  30. package/dist/chunks/wire-persistence.mjs +394 -0
  31. package/dist/chunks/wire-persistence.mjs.map +1 -0
  32. package/dist/esbuild.cjs +28 -0
  33. package/dist/esbuild.cjs.map +1 -0
  34. package/dist/esbuild.d.cts +56 -0
  35. package/dist/esbuild.d.mts +56 -0
  36. package/dist/esbuild.d.ts +56 -0
  37. package/dist/esbuild.mjs +26 -0
  38. package/dist/esbuild.mjs.map +1 -0
  39. package/dist/index.cjs +5 -3
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +65 -70
  42. package/dist/index.d.mts +65 -70
  43. package/dist/index.d.ts +65 -70
  44. package/dist/index.mjs +5 -5
  45. package/dist/nuxt.d.cts +1 -1
  46. package/dist/nuxt.d.mts +1 -1
  47. package/dist/nuxt.d.ts +1 -1
  48. package/dist/rollup.cjs +24 -0
  49. package/dist/rollup.cjs.map +1 -0
  50. package/dist/rollup.d.cts +35 -0
  51. package/dist/rollup.d.mts +35 -0
  52. package/dist/rollup.d.ts +35 -0
  53. package/dist/rollup.mjs +22 -0
  54. package/dist/rollup.mjs.map +1 -0
  55. package/dist/rspack.cjs +10 -0
  56. package/dist/rspack.cjs.map +1 -0
  57. package/dist/rspack.d.cts +40 -0
  58. package/dist/rspack.d.mts +40 -0
  59. package/dist/rspack.d.ts +40 -0
  60. package/dist/rspack.mjs +8 -0
  61. package/dist/rspack.mjs.map +1 -0
  62. package/dist/runtime/plugins/attaform.cjs +2 -2
  63. package/dist/runtime/plugins/attaform.mjs +2 -2
  64. package/dist/shared/{attaform.D5-1XGQU.d.cts → attaform.7lzO9pdM.d.mts} +95 -1
  65. package/dist/shared/{attaform.SfhU0OEY.d.mts → attaform.B1nyO4ec.d.cts} +108 -39
  66. package/dist/shared/{attaform.SfhU0OEY.d.cts → attaform.B1nyO4ec.d.mts} +108 -39
  67. package/dist/shared/{attaform.SfhU0OEY.d.ts → attaform.B1nyO4ec.d.ts} +108 -39
  68. package/dist/shared/{attaform.BPy-4qRx.cjs → attaform.BA3vRDos.cjs} +53 -36
  69. package/dist/shared/attaform.BA3vRDos.cjs.map +1 -0
  70. package/dist/shared/{attaform.GbDo_lJi.d.cts → attaform.BDIEq9qP.d.cts} +1 -1
  71. package/dist/shared/attaform.BJGA_UOS.mjs +37 -0
  72. package/dist/shared/attaform.BJGA_UOS.mjs.map +1 -0
  73. package/dist/shared/{attaform.Dl5kDY-A.d.ts → attaform.BK1RE2ha.d.ts} +1 -1
  74. package/dist/shared/{attaform.DoKXru-a.d.mts → attaform.BQ6drorq.d.mts} +1 -1
  75. package/dist/shared/attaform.BRGIpZo4.cjs +26 -0
  76. package/dist/shared/attaform.BRGIpZo4.cjs.map +1 -0
  77. package/dist/shared/{attaform.DLnE5bZa.cjs → attaform.BUszFoKq.cjs} +388 -912
  78. package/dist/shared/attaform.BUszFoKq.cjs.map +1 -0
  79. package/dist/shared/{attaform.iWo9soNX.mjs → attaform.BnK_bfcb.mjs} +39 -392
  80. package/dist/shared/attaform.BnK_bfcb.mjs.map +1 -0
  81. package/dist/shared/{attaform.BCBxTyMC.cjs → attaform.BzvOdiSI.cjs} +101 -417
  82. package/dist/shared/attaform.BzvOdiSI.cjs.map +1 -0
  83. package/dist/shared/attaform.C3Doa9Pt.mjs +24 -0
  84. package/dist/shared/attaform.C3Doa9Pt.mjs.map +1 -0
  85. package/dist/shared/{attaform.BPxsYtTe.cjs → attaform.CEf6wYfD.cjs} +2 -2
  86. package/dist/shared/{attaform.BPxsYtTe.cjs.map → attaform.CEf6wYfD.cjs.map} +1 -1
  87. package/dist/shared/attaform.CQN9R62B.cjs +39 -0
  88. package/dist/shared/attaform.CQN9R62B.cjs.map +1 -0
  89. package/dist/shared/{attaform.EMzJcQci.d.mts → attaform.CRsXyy-Y.d.ts} +95 -1
  90. package/dist/shared/{attaform.D6CwqkPx.mjs → attaform.CkjTapyq.mjs} +89 -405
  91. package/dist/shared/attaform.CkjTapyq.mjs.map +1 -0
  92. package/dist/shared/{attaform.BqZuwLTK.mjs → attaform.DSqO6Db7.mjs} +377 -913
  93. package/dist/shared/attaform.DSqO6Db7.mjs.map +1 -0
  94. package/dist/shared/attaform.DuzQYscR.d.cts +41 -0
  95. package/dist/shared/attaform.DuzQYscR.d.mts +41 -0
  96. package/dist/shared/attaform.DuzQYscR.d.ts +41 -0
  97. package/dist/shared/{attaform.Bh3ACtts.d.ts → attaform.F8LMHHWV.d.cts} +95 -1
  98. package/dist/shared/attaform.LEWUFqUw.cjs +54 -0
  99. package/dist/shared/attaform.LEWUFqUw.cjs.map +1 -0
  100. package/dist/shared/{attaform.BrrXNmfK.cjs → attaform.PnqML3xW.cjs} +68 -402
  101. package/dist/shared/attaform.PnqML3xW.cjs.map +1 -0
  102. package/dist/shared/{attaform.BKozEdTr.mjs → attaform.Y_Mgg0Yp.mjs} +53 -37
  103. package/dist/shared/attaform.Y_Mgg0Yp.mjs.map +1 -0
  104. package/dist/shared/{attaform.DHRWn-cu.cjs → attaform._rsCZy2j.cjs} +172 -20
  105. package/dist/shared/attaform._rsCZy2j.cjs.map +1 -0
  106. package/dist/shared/{attaform.EZG6fOFb.mjs → attaform.ezb5Nh2t.mjs} +2 -2
  107. package/dist/shared/{attaform.EZG6fOFb.mjs.map → attaform.ezb5Nh2t.mjs.map} +1 -1
  108. package/dist/shared/{attaform.tVkmQh5w.mjs → attaform.r3PePkDR.mjs} +172 -21
  109. package/dist/shared/attaform.r3PePkDR.mjs.map +1 -0
  110. package/dist/shared/attaform.sHkHv_98.mjs +51 -0
  111. package/dist/shared/attaform.sHkHv_98.mjs.map +1 -0
  112. package/dist/vite.cjs +9 -45
  113. package/dist/vite.cjs.map +1 -1
  114. package/dist/vite.d.cts +36 -0
  115. package/dist/vite.d.mts +36 -0
  116. package/dist/vite.d.ts +36 -0
  117. package/dist/vite.mjs +8 -44
  118. package/dist/vite.mjs.map +1 -1
  119. package/dist/webpack.cjs +10 -0
  120. package/dist/webpack.cjs.map +1 -0
  121. package/dist/webpack.d.cts +37 -0
  122. package/dist/webpack.d.mts +37 -0
  123. package/dist/webpack.d.ts +37 -0
  124. package/dist/webpack.mjs +8 -0
  125. package/dist/webpack.mjs.map +1 -0
  126. package/dist/zod-v3.cjs +3 -3
  127. package/dist/zod-v3.d.cts +3 -3
  128. package/dist/zod-v3.d.mts +3 -3
  129. package/dist/zod-v3.d.ts +3 -3
  130. package/dist/zod-v3.mjs +3 -3
  131. package/dist/zod-v4.cjs +3 -3
  132. package/dist/zod-v4.d.cts +4 -4
  133. package/dist/zod-v4.d.mts +4 -4
  134. package/dist/zod-v4.d.ts +4 -4
  135. package/dist/zod-v4.mjs +3 -3
  136. package/dist/zod.cjs +8 -8
  137. package/dist/zod.cjs.map +1 -1
  138. package/dist/zod.d.cts +5 -5
  139. package/dist/zod.d.mts +5 -5
  140. package/dist/zod.d.ts +5 -5
  141. package/dist/zod.mjs +6 -6
  142. package/package.json +21 -7
  143. package/dist/shared/attaform.BCBxTyMC.cjs.map +0 -1
  144. package/dist/shared/attaform.BKozEdTr.mjs.map +0 -1
  145. package/dist/shared/attaform.BPy-4qRx.cjs.map +0 -1
  146. package/dist/shared/attaform.BqZuwLTK.mjs.map +0 -1
  147. package/dist/shared/attaform.BrrXNmfK.cjs.map +0 -1
  148. package/dist/shared/attaform.D6CwqkPx.mjs.map +0 -1
  149. package/dist/shared/attaform.DHRWn-cu.cjs.map +0 -1
  150. package/dist/shared/attaform.DLnE5bZa.cjs.map +0 -1
  151. package/dist/shared/attaform.iWo9soNX.mjs.map +0 -1
  152. package/dist/shared/attaform.tVkmQh5w.mjs.map +0 -1
@@ -0,0 +1,56 @@
1
+ /** Options for the esbuild `attaform()` plugin. */
2
+ interface AttaformEsbuildPluginOptions {
3
+ /**
4
+ * Rewrite `attaform/zod` imports at build time to the matching adapter
5
+ * subpath. Default `true`. Set to `false` to keep the runtime-dispatch
6
+ * unified entry (ships both adapters).
7
+ */
8
+ resolveZodAlias?: boolean;
9
+ /**
10
+ * Project root to resolve the installed Zod from. Defaults to the
11
+ * build's `absWorkingDir`, falling back to `process.cwd()`. Set this
12
+ * when the build runs from a directory other than the project that owns
13
+ * the Zod dependency.
14
+ */
15
+ root?: string;
16
+ }
17
+ interface EsbuildOnResolveArgs {
18
+ path: string;
19
+ importer: string;
20
+ resolveDir: string;
21
+ kind: string;
22
+ }
23
+ interface EsbuildResolveResult {
24
+ path: string;
25
+ external: boolean;
26
+ namespace: string;
27
+ errors: unknown[];
28
+ }
29
+ interface EsbuildOnResolveResult {
30
+ path?: string;
31
+ external?: boolean;
32
+ namespace?: string;
33
+ errors?: unknown[];
34
+ }
35
+ interface EsbuildPluginBuild {
36
+ initialOptions: {
37
+ absWorkingDir?: string;
38
+ };
39
+ onResolve(options: {
40
+ filter: RegExp;
41
+ }, callback: (args: EsbuildOnResolveArgs) => Promise<EsbuildOnResolveResult>): void;
42
+ resolve(path: string, options: {
43
+ kind: string;
44
+ importer?: string;
45
+ resolveDir?: string;
46
+ }): Promise<EsbuildResolveResult>;
47
+ }
48
+ /** The structural shape esbuild requires of the plugin. */
49
+ interface AttaformEsbuildPlugin {
50
+ name: string;
51
+ setup(build: EsbuildPluginBuild): void;
52
+ }
53
+ declare function attaform(options?: AttaformEsbuildPluginOptions): AttaformEsbuildPlugin;
54
+
55
+ export { attaform };
56
+ export type { AttaformEsbuildPlugin, AttaformEsbuildPluginOptions };
@@ -0,0 +1,56 @@
1
+ /** Options for the esbuild `attaform()` plugin. */
2
+ interface AttaformEsbuildPluginOptions {
3
+ /**
4
+ * Rewrite `attaform/zod` imports at build time to the matching adapter
5
+ * subpath. Default `true`. Set to `false` to keep the runtime-dispatch
6
+ * unified entry (ships both adapters).
7
+ */
8
+ resolveZodAlias?: boolean;
9
+ /**
10
+ * Project root to resolve the installed Zod from. Defaults to the
11
+ * build's `absWorkingDir`, falling back to `process.cwd()`. Set this
12
+ * when the build runs from a directory other than the project that owns
13
+ * the Zod dependency.
14
+ */
15
+ root?: string;
16
+ }
17
+ interface EsbuildOnResolveArgs {
18
+ path: string;
19
+ importer: string;
20
+ resolveDir: string;
21
+ kind: string;
22
+ }
23
+ interface EsbuildResolveResult {
24
+ path: string;
25
+ external: boolean;
26
+ namespace: string;
27
+ errors: unknown[];
28
+ }
29
+ interface EsbuildOnResolveResult {
30
+ path?: string;
31
+ external?: boolean;
32
+ namespace?: string;
33
+ errors?: unknown[];
34
+ }
35
+ interface EsbuildPluginBuild {
36
+ initialOptions: {
37
+ absWorkingDir?: string;
38
+ };
39
+ onResolve(options: {
40
+ filter: RegExp;
41
+ }, callback: (args: EsbuildOnResolveArgs) => Promise<EsbuildOnResolveResult>): void;
42
+ resolve(path: string, options: {
43
+ kind: string;
44
+ importer?: string;
45
+ resolveDir?: string;
46
+ }): Promise<EsbuildResolveResult>;
47
+ }
48
+ /** The structural shape esbuild requires of the plugin. */
49
+ interface AttaformEsbuildPlugin {
50
+ name: string;
51
+ setup(build: EsbuildPluginBuild): void;
52
+ }
53
+ declare function attaform(options?: AttaformEsbuildPluginOptions): AttaformEsbuildPlugin;
54
+
55
+ export { attaform };
56
+ export type { AttaformEsbuildPlugin, AttaformEsbuildPluginOptions };
@@ -0,0 +1,26 @@
1
+ import { r as resolveZodAliasTarget } from './shared/attaform.sHkHv_98.mjs';
2
+
3
+ function attaform(options = {}) {
4
+ const resolveZodAlias = options.resolveZodAlias !== false;
5
+ const warnState = { warned: false };
6
+ return {
7
+ name: "attaform",
8
+ setup(build) {
9
+ const root = build.initialOptions.absWorkingDir ?? options.root ?? process.cwd();
10
+ const target = resolveZodAliasTarget(root, "attaform/esbuild", resolveZodAlias, warnState);
11
+ if (target === null) return;
12
+ build.onResolve({ filter: /^attaform\/zod$/ }, async (args) => {
13
+ const result = await build.resolve(target, {
14
+ kind: args.kind,
15
+ importer: args.importer,
16
+ resolveDir: args.resolveDir
17
+ });
18
+ if (result.errors.length > 0) return { errors: result.errors };
19
+ return { path: result.path, external: result.external, namespace: result.namespace };
20
+ });
21
+ }
22
+ };
23
+ }
24
+
25
+ export { attaform };
26
+ //# sourceMappingURL=esbuild.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild.mjs","sources":["../src/esbuild.ts"],"sourcesContent":["/**\n * `attaform/esbuild` — esbuild plugin that rewrites `attaform/zod`\n * imports to the single matching adapter subpath (`attaform/zod-v3` or\n * `attaform/zod-v4`) at build time, based on the consumer's installed Zod\n * major. Without it, esbuild ships both adapters because the unified\n * `attaform/zod` entry imports both for runtime dispatch.\n *\n * Usage:\n *\n * // build.mjs\n * import { build } from 'esbuild'\n * import { attaform } from 'attaform/esbuild'\n *\n * await build({\n * entryPoints: ['src/main.ts'],\n * bundle: true,\n * plugins: [attaform()],\n * })\n *\n * This plugin only does the adapter rewrite. The Vue SFC `v-register`\n * transforms that `attaform/vite` wires (load-bearing for SSR initial\n * render) are `@vitejs/plugin-vue`-specific and do not transfer; a\n * non-Vite consumer that needs them wires `attaform/transforms` into\n * their Vue compiler separately.\n *\n * Zero-dep: the plugin imports nothing from `esbuild` (the bundler injects\n * its plugin API at the consumer's build); the structural types below are\n * all it needs to compile.\n */\nimport { resolveZodAliasTarget } from './core/detect-zod-major'\n\n/** Options for the esbuild `attaform()` plugin. */\nexport interface AttaformEsbuildPluginOptions {\n /**\n * Rewrite `attaform/zod` imports at build time to the matching adapter\n * subpath. Default `true`. Set to `false` to keep the runtime-dispatch\n * unified entry (ships both adapters).\n */\n resolveZodAlias?: boolean\n /**\n * Project root to resolve the installed Zod from. Defaults to the\n * build's `absWorkingDir`, falling back to `process.cwd()`. Set this\n * when the build runs from a directory other than the project that owns\n * the Zod dependency.\n */\n root?: string\n}\n\ninterface EsbuildOnResolveArgs {\n path: string\n importer: string\n resolveDir: string\n kind: string\n}\ninterface EsbuildResolveResult {\n path: string\n external: boolean\n namespace: string\n errors: unknown[]\n}\ninterface EsbuildOnResolveResult {\n path?: string\n external?: boolean\n namespace?: string\n errors?: unknown[]\n}\ninterface EsbuildPluginBuild {\n initialOptions: { absWorkingDir?: string }\n onResolve(\n options: { filter: RegExp },\n callback: (args: EsbuildOnResolveArgs) => Promise<EsbuildOnResolveResult>\n ): void\n resolve(\n path: string,\n options: { kind: string; importer?: string; resolveDir?: string }\n ): Promise<EsbuildResolveResult>\n}\n\n/** The structural shape esbuild requires of the plugin. */\nexport interface AttaformEsbuildPlugin {\n name: string\n setup(build: EsbuildPluginBuild): void\n}\n\nexport function attaform(options: AttaformEsbuildPluginOptions = {}): AttaformEsbuildPlugin {\n const resolveZodAlias = options.resolveZodAlias !== false\n const warnState = { warned: false }\n return {\n name: 'attaform',\n setup(build) {\n const root = build.initialOptions.absWorkingDir ?? options.root ?? process.cwd()\n const target = resolveZodAliasTarget(root, 'attaform/esbuild', resolveZodAlias, warnState)\n // Nothing to rewrite (opt-out, or unclassifiable version): leave the\n // unified entry in place and register no hook.\n if (target === null) return\n build.onResolve({ filter: /^attaform\\/zod$/ }, async (args) => {\n // Re-resolve the rewritten specifier so esbuild returns a real\n // path. The filter is anchored to the bare unified specifier, so\n // resolving `attaform/zod-v4` does not re-enter this callback.\n const result = await build.resolve(target, {\n kind: args.kind,\n importer: args.importer,\n resolveDir: args.resolveDir,\n })\n if (result.errors.length > 0) return { errors: result.errors }\n return { path: result.path, external: result.external, namespace: result.namespace }\n })\n },\n }\n}\n"],"names":[],"mappings":";;AAoFO,SAAS,QAAA,CAAS,OAAA,GAAwC,EAAC,EAA0B;AAC1F,EAAA,MAAM,eAAA,GAAkB,QAAQ,eAAA,KAAoB,KAAA;AACpD,EAAA,MAAM,SAAA,GAAY,EAAE,MAAA,EAAQ,KAAA,EAAM;AAClC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,UAAA;AAAA,IACN,MAAM,KAAA,EAAO;AACX,MAAA,MAAM,OAAO,KAAA,CAAM,cAAA,CAAe,iBAAiB,OAAA,CAAQ,IAAA,IAAQ,QAAQ,GAAA,EAAI;AAC/E,MAAA,MAAM,MAAA,GAAS,qBAAA,CAAsB,IAAA,EAAM,kBAAA,EAAoB,iBAAiB,SAAS,CAAA;AAGzF,MAAA,IAAI,WAAW,IAAA,EAAM;AACrB,MAAA,KAAA,CAAM,UAAU,EAAE,MAAA,EAAQ,iBAAA,EAAkB,EAAG,OAAO,IAAA,KAAS;AAI7D,QAAA,MAAM,MAAA,GAAS,MAAM,KAAA,CAAM,OAAA,CAAQ,MAAA,EAAQ;AAAA,UACzC,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,UAAU,IAAA,CAAK,QAAA;AAAA,UACf,YAAY,IAAA,CAAK;AAAA,SAClB,CAAA;AACD,QAAA,IAAI,MAAA,CAAO,OAAO,MAAA,GAAS,CAAA,SAAU,EAAE,MAAA,EAAQ,OAAO,MAAA,EAAO;AAC7D,QAAA,OAAO,EAAE,MAAM,MAAA,CAAO,IAAA,EAAM,UAAU,MAAA,CAAO,QAAA,EAAU,SAAA,EAAW,MAAA,CAAO,SAAA,EAAU;AAAA,MACrF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF;;;;"}
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const paths = require('./shared/attaform.BPy-4qRx.cjs');
4
- const devtoolsShared = require('./shared/attaform.BPxsYtTe.cjs');
5
- const injectWizard = require('./shared/attaform.DLnE5bZa.cjs');
3
+ const paths = require('./shared/attaform.BA3vRDos.cjs');
4
+ const devtoolsShared = require('./shared/attaform.CEf6wYfD.cjs');
5
+ const injectWizard = require('./shared/attaform.BUszFoKq.cjs');
6
6
 
7
7
  function escapeForInlineScript(json) {
8
8
  return json.replace(/[<>&\u2028\u2029]/g, (char) => {
@@ -181,6 +181,7 @@ exports.DEVTOOLS_WINDOW_KEY = devtoolsShared.DEVTOOLS_WINDOW_KEY;
181
181
  exports.hydrateAttaformState = devtoolsShared.hydrateAttaformState;
182
182
  exports.renderAttaformState = devtoolsShared.renderAttaformState;
183
183
  exports.AttaformErrorCode = injectWizard.AttaformErrorCode;
184
+ exports.DEFAULT_TIMINGS = injectWizard.DEFAULT_TIMINGS;
184
185
  exports.defaultCoercionRules = injectWizard.defaultCoercionRules;
185
186
  exports.defaultDisplayState = injectWizard.defaultDisplayState;
186
187
  exports.defineCoercion = injectWizard.defineCoercion;
@@ -188,6 +189,7 @@ exports.injectForm = injectWizard.injectForm;
188
189
  exports.injectWizard = injectWizard.injectWizard;
189
190
  exports.isUnset = injectWizard.isUnset;
190
191
  exports.lazy = injectWizard.lazy;
192
+ exports.makeDefaultDisplayState = injectWizard.makeDefaultDisplayState;
191
193
  exports.unset = injectWizard.unset;
192
194
  exports.useForm = injectWizard.useAbstractForm;
193
195
  exports.useWizard = injectWizard.useWizard;
@@ -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 { normalizeNumericOption } from './defaults'\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 // Sanitise the caps. Comparison gates (`>` against the count /\n // depth) yield `false` for `NaN`, so without sanitisation a\n // hostile or malformed `NaN` cap would let pathological payloads\n // run unbounded. `Infinity` would do the same. Negatives and\n // non-integers would discard legitimate entries. Falls back to\n // the library default on garbage.\n const maxEntries = normalizeNumericOption({\n value: options.maxEntries ?? PARSE_API_ERRORS_DEFAULTS.maxEntries,\n source: 'parseApiErrors.maxEntries',\n allowInfinity: false,\n min: 0,\n defaultValue: PARSE_API_ERRORS_DEFAULTS.maxEntries,\n })\n const maxPathDepth = normalizeNumericOption({\n value: options.maxPathDepth ?? PARSE_API_ERRORS_DEFAULTS.maxPathDepth,\n source: 'parseApiErrors.maxPathDepth',\n allowInfinity: false,\n min: 0,\n defaultValue: PARSE_API_ERRORS_DEFAULTS.maxPathDepth,\n })\n const maxTotalSegments = normalizeNumericOption({\n value: options.maxTotalSegments ?? PARSE_API_ERRORS_DEFAULTS.maxTotalSegments,\n source: 'parseApiErrors.maxTotalSegments',\n allowInfinity: false,\n min: 0,\n defaultValue: PARSE_API_ERRORS_DEFAULTS.maxTotalSegments,\n })\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":["normalizeNumericOption","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 { normalizeNumericOption } from './defaults'\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 // Sanitise the caps. Comparison gates (`>` against the count /\n // depth) yield `false` for `NaN`, so without sanitisation a\n // hostile or malformed `NaN` cap would let pathological payloads\n // run unbounded. `Infinity` would do the same. Negatives and\n // non-integers would discard legitimate entries. Falls back to\n // the library default on garbage.\n const maxEntries = normalizeNumericOption({\n value: options.maxEntries ?? PARSE_API_ERRORS_DEFAULTS.maxEntries,\n source: 'parseApiErrors.maxEntries',\n allowInfinity: false,\n min: 0,\n defaultValue: PARSE_API_ERRORS_DEFAULTS.maxEntries,\n })\n const maxPathDepth = normalizeNumericOption({\n value: options.maxPathDepth ?? PARSE_API_ERRORS_DEFAULTS.maxPathDepth,\n source: 'parseApiErrors.maxPathDepth',\n allowInfinity: false,\n min: 0,\n defaultValue: PARSE_API_ERRORS_DEFAULTS.maxPathDepth,\n })\n const maxTotalSegments = normalizeNumericOption({\n value: options.maxTotalSegments ?? PARSE_API_ERRORS_DEFAULTS.maxTotalSegments,\n source: 'parseApiErrors.maxTotalSegments',\n allowInfinity: false,\n min: 0,\n defaultValue: PARSE_API_ERRORS_DEFAULTS.maxTotalSegments,\n })\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":["normalizeNumericOption","canonicalizePath","InvalidPathError"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Plugin, App } from 'vue';
2
- import { S as SSRDetectOptions, c as SerializedFormData, b as AttaformRegistry } from './shared/attaform.D5-1XGQU.cjs';
3
- export { A as AnyForm, a as AttaformErrorCode, C as CompiledStep, F as FormStatus, I as InjectWizardInput, L as LazyMarker, d as StepSlot, U as UseRegisterReturn, e as UseWizardReturnType, W as WizardAggregateError, f as WizardCtx, g as WizardCtxForm, h as WizardOnError, i as WizardOnSubmit, j as WizardOptions, k as WizardPersistFn, l as WizardRestoreFn, m as WizardRestoreState, n as WizardStatusesProxy, o as WizardSubmitContext, p as createRegistry, q as defaultCoercionRules, r as defineCoercion, s as getRegistryFromApp, t as injectForm, u as injectWizard, v as kAttaformRegistry, w as lazy, x as useRegister, y as useRegistry, z as useWizard } from './shared/attaform.D5-1XGQU.cjs';
4
- import { f as AttaformDefaults, t as FormKey, G as GenericForm, ai as UseFormConfiguration, A as AbstractSchema, j as DefaultValuesInput, aj as UseFormReturnType, a8 as RegisterValue, a3 as RegisterModelDynamicCustomDirective, x as GetDisplayState, am as ValidationError, c as ApiErrorEnvelope, a as ApiErrorDetails } from './shared/attaform.SfhU0OEY.cjs';
5
- export { b as ApiErrorEntry, d as ArrayItem, e as ArrayPath, C as CoercionEntry, g as CoercionRegistry, h as CoercionResult, i as CustomDirectiveRegisterAssignerFn, D as DeepPartial, k as DefaultValuesResponse, l as DefaultValuesShape, m as DisplayState, E as ErrorsProxyShape, F as FieldMetaPayload, n as FieldState, o as FieldStateMap, p as FieldStateMapEntry, q as FlatPath, r as FormErrorRecord, s as FormErrorsSurface, u as FormMeta, v as FormStorage, w as FormStorageKind, H as HandleSubmit, y as HistoryConfig, I as IsTuple, z as IsUnion, J as JoinSegments, K as KeyofUnion, L as LiftedValueShape, M as MetaTrackerValue, N as NestedReadType, B as NestedType, O as OnError, P as OnInvalidSubmitPolicy, Q as OnSubmit, R as PartialFlatPath, S as Path, T as PathKey, U as PendingValidationStatus, V as PersistConfig, W as PersistConfigOptions, X as PersistIncludeMode, Z as Primitive, _ as ROOT_PATH, $ as ROOT_PATH_KEY, a0 as ReactiveValidationStatus, a1 as RegisterDirective, a2 as RegisterFlatPath, a4 as RegisterOptions, a5 as RegisterSelectModifier, a6 as RegisterTextModifier, a7 as RegisterTransform, aa as Segment, ab as SetValueCallback, ac as SetValuePayload, ad as SettledValidationStatus, ae as SlimPrimitiveKind, af as SlimRuntimeOf, ag as SubmitHandler, ah as Unset, ak as ValidateOn, al as ValidateOnConfig, an as ValidationResponse, ao as ValidationResponseWithoutValue, ap as ValueOfUnion, aq as WriteMeta, ar as WriteShape, as as canonicalizePath, at as isPathPrefix, au as isUnset, av as parseDottedPath, aw as unset } from './shared/attaform.SfhU0OEY.cjs';
2
+ import { S as SSRDetectOptions, c as SerializedFormData, b as AttaformRegistry } from './shared/attaform.F8LMHHWV.cjs';
3
+ export { A as AnyForm, a as AttaformErrorCode, C as CompiledStep, F as FormStatus, I as InjectWizardInput, L as LazyMarker, d as StepSlot, U as UseRegisterReturn, e as UseWizardReturnType, W as WizardAggregateError, f as WizardCtx, g as WizardCtxForm, h as WizardOnError, i as WizardOnSubmit, j as WizardOptions, k as WizardPersistFn, l as WizardRestoreFn, m as WizardRestoreState, n as WizardStatusesProxy, o as WizardSubmitContext, p as createRegistry, q as defaultCoercionRules, r as defineCoercion, s as getRegistryFromApp, t as injectForm, u as injectWizard, v as kAttaformRegistry, w as lazy, x as useRegister, y as useRegistry, z as useWizard } from './shared/attaform.F8LMHHWV.cjs';
4
+ import { f as AttaformDefaults, v as FormKey, G as GenericForm, ak as UseFormConfiguration, A as AbstractSchema, j as DefaultValuesInput, al as UseFormReturnType, aa as RegisterValue, a5 as RegisterModelDynamicCustomDirective, z as GetDisplayState, ao as ValidationError, c as ApiErrorEnvelope, a as ApiErrorDetails } from './shared/attaform.B1nyO4ec.cjs';
5
+ export { b as ApiErrorEntry, d as ArrayItem, e as ArrayPath, C as CoercionEntry, g as CoercionRegistry, h as CoercionResult, i as CustomDirectiveRegisterAssignerFn, D as DeepPartial, k as DefaultValuesResponse, l as DefaultValuesShape, m as DisplayCtx, n as DisplayMachine, o as DisplayState, E as ErrorsProxyShape, F as FieldMetaPayload, p as FieldState, q as FieldStateMap, r as FieldStateMapEntry, s as FlatPath, t as FormErrorRecord, u as FormErrorsSurface, w as FormMeta, x as FormStorage, y as FormStorageKind, H as HandleSubmit, B as HistoryConfig, I as IsTuple, J as IsUnion, K as JoinSegments, L as KeyofUnion, M as LiftedValueShape, N as MetaTrackerValue, O as NestedReadType, P as NestedType, Q as OnError, R as OnInvalidSubmitPolicy, S as OnSubmit, T as PartialFlatPath, U as Path, V as PathKey, W as PendingValidationStatus, X as PersistConfig, Y as PersistConfigOptions, Z as PersistIncludeMode, $ as Primitive, a0 as ROOT_PATH, a1 as ROOT_PATH_KEY, a2 as ReactiveValidationStatus, a3 as RegisterDirective, a4 as RegisterFlatPath, a6 as RegisterOptions, a7 as RegisterSelectModifier, a8 as RegisterTextModifier, a9 as RegisterTransform, ac as Segment, ad as SetValueCallback, ae as SetValuePayload, af as SettledValidationStatus, ag as SlimPrimitiveKind, ah as SlimRuntimeOf, ai as SubmitHandler, aj as Unset, am as ValidateOn, an as ValidateOnConfig, ap as ValidationResponse, aq as ValidationResponseWithoutValue, ar as ValueOfUnion, as as WriteMeta, at as WriteShape, au as canonicalizePath, av as isPathPrefix, aw as isUnset, ax as parseDottedPath, ay as unset } from './shared/attaform.B1nyO4ec.cjs';
6
6
  export { A as AnonPersistError, a as AttaformError, I as InvalidPathError, b as InvalidUseFormConfigError, O as OutsideSetupError, R as RegistryNotInstalledError, c as ReservedFormKeyError, S as SubmitErrorHandlerError } from './shared/attaform.DkA5J8NW.cjs';
7
7
 
8
8
  /**
@@ -11,10 +11,11 @@ export { A as AnonPersistError, a as AttaformError, I as InvalidPathError, b as
11
11
  type AttaformPluginOptions = SSRDetectOptions & {
12
12
  /**
13
13
  * Whether to install the Vue DevTools integration. Default `true`.
14
- * The DevTools peer dependency is loaded lazily in production
15
- * builds where it's absent, the import fails silently and no
16
- * extra bundle is shipped. Pass `false` to skip even attempting
17
- * the import.
14
+ * The integration is dev-only: the `import('./devtools')` sits behind
15
+ * the `__DEV__` flag, so a consumer's production build folds it out
16
+ * entirely (no devtools chunk shipped, no fetch attempted). In
17
+ * development the DevTools peer is loaded lazily and a missing peer
18
+ * fails silently. Pass `false` to skip the integration even in dev.
18
19
  */
19
20
  devtools?: boolean;
20
21
  /**
@@ -261,74 +262,68 @@ declare global {
261
262
  }
262
263
 
263
264
  /**
264
- * Library-default `getDisplayState` heuristic. Resolves every path's
265
+ * Anti-flash timing for the library-default display reducer.
266
+ *
267
+ * - `showDelay` — how long a validation may run before its spinner is
268
+ * allowed to show. A validation that settles inside this window never
269
+ * reveals `'pending'` at all, so a fast (often synchronous) check does
270
+ * not flash a spinner on every keystroke.
271
+ * - `minVisible` — once shown, the minimum time the spinner stays up. A
272
+ * validation that lands just past `showDelay` is held here so the
273
+ * spinner does not itself flash on and immediately off.
274
+ *
275
+ * Both are milliseconds.
276
+ */
277
+ type DisplayTimings = {
278
+ readonly showDelay: number;
279
+ readonly minVisible: number;
280
+ };
281
+ /**
282
+ * Library-default anti-flash timings. `showDelay: 100` cleanly swallows
283
+ * synchronous, microtask-resolved, and tiny-async validators (no spinner
284
+ * for any of them); `minVisible: 120` keeps a shown spinner snappy.
285
+ * Retune via {@link makeDefaultDisplayState} without touching the engine.
286
+ */
287
+ declare const DEFAULT_TIMINGS: DisplayTimings;
288
+ /**
289
+ * Build a default `getDisplayState` reducer with custom anti-flash timing.
290
+ * Power users who want a tighter or looser spinner than {@link DEFAULT_TIMINGS}
291
+ * pass their own `{ showDelay, minVisible }`:
292
+ *
293
+ * ```ts
294
+ * import { makeDefaultDisplayState } from 'attaform'
295
+ *
296
+ * useForm({
297
+ * schema,
298
+ * getDisplayState: makeDefaultDisplayState({ showDelay: 50, minVisible: 200 }),
299
+ * })
300
+ * ```
301
+ *
302
+ * The returned reducer is pure: the engine injects `now` and threads the
303
+ * previous machine, so the same `(prev, ctx)` always yields the same next
304
+ * machine. It shapes only the display projection — `errors`, `valid`,
305
+ * `validating`, and the underlying validation run exactly as before.
306
+ */
307
+ declare function makeDefaultDisplayState({ showDelay, minVisible, }: DisplayTimings): GetDisplayState;
308
+ /**
309
+ * Library-default `getDisplayState` reducer. Resolves every path's
265
310
  * `field.displayState` — and thus `field.show*` and the `form.meta`
266
311
  * rollups — whenever the consumer has not configured an override at the
267
- * per-form or plugin level.
268
- *
269
- * One timing gate, then precedence:
270
- *
271
- * 1. **Timing gate.** `gateOpen` once the form has been submitted
272
- * (`submissionAttempts > 0`) OR the field has been edited and then
273
- * left (`blurredAfterInteraction === true`). Before the gate opens
274
- * the verdict is `'idle'` regardless of errors. This is the "reward
275
- * early, punish late" rule:
276
- * - A clean tab-through never engages. `blurredAfterInteraction`
277
- * only flips on a blur that follows an edit, so visiting a field
278
- * and moving on without editing it stays quiet until a submit
279
- * forces the issue, even if the field was tabbed through before.
280
- * - The first pass stays quiet. Editing alone (`interacted`) does
281
- * not open the gate; the error reveals only once the user
282
- * finishes that pass and leaves the field, never mid-entry.
283
- * - Recovery is live. The bit is sticky and carries no not-focused
284
- * term, so once a field has been revealed it stays open through a
285
- * re-focus: a shown error clears (or greens) the instant the
286
- * value becomes valid, without forcing another blur.
287
- *
288
- * The submit arm covers `form.handleSubmit` directly and
289
- * `wizard.handleSubmit` (which bumps `submissionAttempts` on the
290
- * active form at intermediate steps and on every form at the final
291
- * step, lighting up the whole flow at once).
292
- *
293
- * 2. **Pending.** With the gate open, a per-field run in flight
294
- * (`validating === true`) wins: the verdict in `errors` is stale by
295
- * definition, so surface `'pending'` (a spinner) rather than a
296
- * possibly-wrong error or success. Containers roll `validating` up as
297
- * a disjunction, so any descendant under revalidation reads
298
- * `'pending'` at the container too.
299
- *
300
- * 3. **Error.** An own-path error (one whose path equals the field's own
301
- * path) resolves to `'error'`. The own-path filter means a container
302
- * never duplicates an error a more-specific descendant already
303
- * renders; aggregate banners bind to `form.meta.errorCount` instead.
304
- *
305
- * 4. **Success.** No error, `valid === true`, and the green check is
306
- * earned: the field is non-blank and `dirty` (its value diverges from
307
- * the hydration original). Gating success on `dirty && !blank` keeps
308
- * the check meaningful — an empty field that happens to pass, a
309
- * pre-filled field merely tabbed through, and the post-submit flood of
310
- * every valid field all stay `'idle'` rather than greening for free.
311
- * `valid` already gates async schemas on the form-wide first
312
- * validation pass, so success never fires before the first verdict
313
- * lands.
314
- *
315
- * 5. **Idle.** Anything else — gate open but not validating, no own-path
316
- * error, and either not yet `valid` or valid-but-unearned (blank or
317
- * unchanged) — stays `'idle'`.
318
- *
319
- * Public re-export so adopters can compose with this without
320
- * copy-pasting the rule body — a layered predicate that special-cases a
321
- * subtree but otherwise defers picks up future refinements
322
- * automatically:
312
+ * per-form or plugin level. Built from {@link DEFAULT_TIMINGS}; publicly
313
+ * re-exported so an override can compose with it (a layered reducer that
314
+ * special-cases a subtree but otherwise defers picks up future
315
+ * refinements for free):
323
316
  *
324
317
  * ```ts
325
318
  * import { defaultDisplayState } from 'attaform'
326
319
  *
327
320
  * useForm({
328
321
  * schema,
329
- * getDisplayState: (field, formMeta) => {
330
- * const state = defaultDisplayState(field, formMeta)
331
- * return field.path[0] === 'username' && state === 'success' ? 'idle' : state
322
+ * getDisplayState: (prev, ctx) => {
323
+ * const next = defaultDisplayState(prev, ctx)
324
+ * return next.display === 'success' && ctx.field.path[0] === 'username'
325
+ * ? { display: 'idle' }
326
+ * : next
332
327
  * },
333
328
  * })
334
329
  * ```
@@ -496,5 +491,5 @@ declare const PARSE_API_ERRORS_DEFAULTS: {
496
491
  */
497
492
  declare function parseApiErrors(payload: ApiErrorEnvelope | ApiErrorDetails | null | undefined | unknown, options: ParseApiErrorsOptions): ParseApiErrorsResult;
498
493
 
499
- export { AbstractSchema, ApiErrorDetails, ApiErrorEnvelope, AttaformDefaults, AttaformRegistry, DEFAULT_SENSITIVE_NAMES, DEVTOOLS_WINDOW_KEY, DefaultValuesInput, FormKey, GenericForm, GetDisplayState, PARSE_API_ERRORS_DEFAULTS, RegisterValue, SerializedFormData, UseFormConfiguration, UseFormReturnType, ValidationError, assignKey, createAttaform, defaultDisplayState, escapeForInlineScript, hydrateAttaformState, isRegisterValue, parseApiErrors, renderAttaformState, useAbstractForm as useForm, vRegister };
500
- export type { AttaformDevtoolsBridge, AttaformPluginOptions, ParseApiErrorsOptions, ParseApiErrorsResult, SerializedAttaformState };
494
+ export { AbstractSchema, ApiErrorDetails, ApiErrorEnvelope, AttaformDefaults, AttaformRegistry, DEFAULT_SENSITIVE_NAMES, DEFAULT_TIMINGS, DEVTOOLS_WINDOW_KEY, DefaultValuesInput, FormKey, GenericForm, GetDisplayState, PARSE_API_ERRORS_DEFAULTS, RegisterValue, SerializedFormData, UseFormConfiguration, UseFormReturnType, ValidationError, assignKey, createAttaform, defaultDisplayState, escapeForInlineScript, hydrateAttaformState, isRegisterValue, makeDefaultDisplayState, parseApiErrors, renderAttaformState, useAbstractForm as useForm, vRegister };
495
+ export type { AttaformDevtoolsBridge, AttaformPluginOptions, DisplayTimings, ParseApiErrorsOptions, ParseApiErrorsResult, SerializedAttaformState };