@vielzeug/forge 1.4.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +21 -53
  2. package/dist/_utils.cjs +1 -2
  3. package/dist/_utils.d.ts +1 -10
  4. package/dist/_utils.d.ts.map +1 -1
  5. package/dist/_utils.js +2 -33
  6. package/dist/adapters/form-data.cjs +1 -1
  7. package/dist/adapters/form-data.cjs.map +1 -1
  8. package/dist/adapters/form-data.d.ts.map +1 -1
  9. package/dist/adapters/form-data.js +5 -5
  10. package/dist/adapters/form-data.js.map +1 -1
  11. package/dist/core/path.cjs +2 -0
  12. package/dist/core/path.cjs.map +1 -0
  13. package/dist/core/path.d.ts +22 -0
  14. package/dist/core/path.d.ts.map +1 -0
  15. package/dist/core/path.js +125 -0
  16. package/dist/core/path.js.map +1 -0
  17. package/dist/devtools.cjs +1 -1
  18. package/dist/devtools.cjs.map +1 -1
  19. package/dist/devtools.d.ts +3 -36
  20. package/dist/devtools.d.ts.map +1 -1
  21. package/dist/devtools.js +6 -24
  22. package/dist/devtools.js.map +1 -1
  23. package/dist/dom.cjs +2 -0
  24. package/dist/dom.cjs.map +1 -0
  25. package/dist/dom.d.ts +9 -0
  26. package/dist/dom.d.ts.map +1 -0
  27. package/dist/dom.js +24 -0
  28. package/dist/dom.js.map +1 -0
  29. package/dist/errors.cjs +1 -1
  30. package/dist/errors.cjs.map +1 -1
  31. package/dist/errors.d.ts +1 -3
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +1 -6
  34. package/dist/errors.js.map +1 -1
  35. package/dist/forge.cjs +1 -1
  36. package/dist/forge.cjs.map +1 -1
  37. package/dist/forge.iife.js +1 -1
  38. package/dist/forge.iife.js.map +1 -1
  39. package/dist/forge.js +1 -1
  40. package/dist/forge.js.map +1 -1
  41. package/dist/form.cjs +1 -1
  42. package/dist/form.cjs.map +1 -1
  43. package/dist/form.d.ts +3 -5
  44. package/dist/form.d.ts.map +1 -1
  45. package/dist/form.js +223 -58
  46. package/dist/form.js.map +1 -1
  47. package/dist/index.cjs +1 -1
  48. package/dist/index.js +3 -4
  49. package/dist/spell.cjs +2 -0
  50. package/dist/spell.cjs.map +1 -0
  51. package/dist/spell.d.ts +5 -0
  52. package/dist/spell.d.ts.map +1 -0
  53. package/dist/spell.js +57 -0
  54. package/dist/spell.js.map +1 -0
  55. package/dist/types.d.ts +70 -392
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/vault.cjs +2 -0
  58. package/dist/vault.cjs.map +1 -0
  59. package/dist/vault.d.ts +10 -0
  60. package/dist/vault.d.ts.map +1 -0
  61. package/dist/vault.js +14 -0
  62. package/dist/vault.js.map +1 -0
  63. package/package.json +49 -13
  64. package/dist/_dev.cjs +0 -2
  65. package/dist/_dev.cjs.map +0 -1
  66. package/dist/_dev.js +0 -12
  67. package/dist/_dev.js.map +0 -1
  68. package/dist/_utils.cjs.map +0 -1
  69. package/dist/_utils.js.map +0 -1
  70. package/dist/adapters/validators.cjs +0 -2
  71. package/dist/adapters/validators.cjs.map +0 -1
  72. package/dist/adapters/validators.d.ts +0 -44
  73. package/dist/adapters/validators.d.ts.map +0 -1
  74. package/dist/adapters/validators.js +0 -20
  75. package/dist/adapters/validators.js.map +0 -1
  76. package/dist/core/array.cjs +0 -2
  77. package/dist/core/array.cjs.map +0 -1
  78. package/dist/core/array.d.ts +0 -10
  79. package/dist/core/array.d.ts.map +0 -1
  80. package/dist/core/array.js +0 -77
  81. package/dist/core/array.js.map +0 -1
  82. package/dist/core/asyncQueue.cjs +0 -2
  83. package/dist/core/asyncQueue.cjs.map +0 -1
  84. package/dist/core/asyncQueue.d.ts +0 -22
  85. package/dist/core/asyncQueue.d.ts.map +0 -1
  86. package/dist/core/asyncQueue.js +0 -67
  87. package/dist/core/asyncQueue.js.map +0 -1
  88. package/dist/core/context.cjs +0 -2
  89. package/dist/core/context.cjs.map +0 -1
  90. package/dist/core/context.d.ts +0 -7
  91. package/dist/core/context.d.ts.map +0 -1
  92. package/dist/core/context.js +0 -139
  93. package/dist/core/context.js.map +0 -1
  94. package/dist/core/fields.cjs +0 -2
  95. package/dist/core/fields.cjs.map +0 -1
  96. package/dist/core/fields.d.ts +0 -52
  97. package/dist/core/fields.d.ts.map +0 -1
  98. package/dist/core/fields.js +0 -310
  99. package/dist/core/fields.js.map +0 -1
  100. package/dist/core/lifecycle.cjs +0 -2
  101. package/dist/core/lifecycle.cjs.map +0 -1
  102. package/dist/core/lifecycle.d.ts +0 -15
  103. package/dist/core/lifecycle.d.ts.map +0 -1
  104. package/dist/core/lifecycle.js +0 -38
  105. package/dist/core/lifecycle.js.map +0 -1
  106. package/dist/core/notifier.cjs +0 -2
  107. package/dist/core/notifier.cjs.map +0 -1
  108. package/dist/core/notifier.d.ts +0 -33
  109. package/dist/core/notifier.d.ts.map +0 -1
  110. package/dist/core/notifier.js +0 -78
  111. package/dist/core/notifier.js.map +0 -1
  112. package/dist/core/observe.cjs +0 -2
  113. package/dist/core/observe.cjs.map +0 -1
  114. package/dist/core/observe.d.ts +0 -20
  115. package/dist/core/observe.d.ts.map +0 -1
  116. package/dist/core/observe.js +0 -79
  117. package/dist/core/observe.js.map +0 -1
  118. package/dist/core/scope.cjs +0 -2
  119. package/dist/core/scope.cjs.map +0 -1
  120. package/dist/core/scope.d.ts +0 -11
  121. package/dist/core/scope.d.ts.map +0 -1
  122. package/dist/core/scope.js +0 -235
  123. package/dist/core/scope.js.map +0 -1
  124. package/dist/types.cjs +0 -2
  125. package/dist/types.cjs.map +0 -1
  126. package/dist/types.js +0 -21
  127. package/dist/types.js.map +0 -1
package/README.md CHANGED
@@ -1,31 +1,6 @@
1
1
  # @vielzeug/forge
2
2
 
3
- > Framework-agnostic typed form state with path-safe fields, unified validation API, deterministic submit flow, and browser-friendly helpers.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@vielzeug/forge)](https://www.npmjs.com/package/@vielzeug/forge) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
-
7
- <details>
8
- <summary>Quick Reference</summary>
9
-
10
- **Package:** `@vielzeug/forge` &nbsp;·&nbsp; **Category:** Forms
11
-
12
- **Key exports:** `createForm`, `toFormData`, `ValidationModes`, `FORM_ERROR`
13
-
14
- **When to use:** Typed form state with field validation, dirty tracking, submission handling, and browser helpers. Works with any UI framework or vanilla JS.
15
-
16
- **Related:** [@vielzeug/spell](https://vielzeug.dev/spell/) · [@vielzeug/ripple](https://vielzeug.dev/ripple/) · [@vielzeug/courier](https://vielzeug.dev/courier/)
17
-
18
- </details>
19
-
20
- `@vielzeug/forge` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output.
21
-
22
- ## Installation
23
-
24
- ```sh
25
- pnpm add @vielzeug/forge
26
- npm install @vielzeug/forge
27
- yarn add @vielzeug/forge
28
- ```
3
+ > Immutable typed form state with focused fields and explicit full-form validation.
29
4
 
30
5
  ## Quick Start
31
6
 
@@ -33,46 +8,39 @@ yarn add @vielzeug/forge
33
8
  import { createForm } from '@vielzeug/forge';
34
9
 
35
10
  const form = createForm({
36
- defaultValues: { email: '', password: '' },
37
- validators: {
38
- email: (v) => (!String(v).includes('@') ? 'Invalid email' : undefined),
39
- password: (v) => (String(v).length < 8 ? 'Min 8 chars' : undefined),
40
- },
11
+ initialValues: { profile: { email: '', name: '' } },
12
+ validate: (value) => ({
13
+ fields: { profile: { email: value.profile.email.includes('@') ? undefined : 'Enter a valid email' } },
14
+ }),
41
15
  });
42
16
 
43
- form.set('email', 'alice@example.com');
17
+ const email = form.field('profile').field('email');
18
+ email.set('ada@example.com');
44
19
 
45
- const result = await form.validate();
46
- console.log(result.valid, result.errors);
20
+ const result = await form.submit((value) => saveProfile(value));
21
+ ```
47
22
 
48
- const submission = await form.submit(async (values) => {
49
- await fetch('/api/login', {
50
- body: JSON.stringify(values),
51
- headers: { 'Content-Type': 'application/json' },
52
- method: 'POST',
53
- });
54
- });
23
+ `Form` owns one immutable value tree of primitives, plain objects, arrays, `File`, and `Blob`. Object fields select child branches. Arrays are replaced through immutable updater functions. One validator evaluates the whole form and returns `{ fields, formError }`.
55
24
 
56
- if (!submission.ok && submission.type === 'validation') {
57
- console.log(submission.errors);
58
- }
59
- ```
25
+ ## Optional adapters
60
26
 
61
- ## Scoped Forms Semantics
27
+ - `@vielzeug/forge/dom` explicit element binding; application owns validation timing.
28
+ - `@vielzeug/forge/spell` — typed `customValidator(schema)` adapter with per-union diagnostics.
29
+ - `@vielzeug/forge/vault` — explicit `saveForm()` and `loadForm()` draft helpers.
62
30
 
63
- `form.scope(prefix)` is memoized and returns a sub-form with **relative** field paths for its APIs and state projection.
31
+ ## Breaking 2.0 changes
64
32
 
65
- - Root form state paths are absolute (for example, `address.city`).
66
- - Scoped form state paths are relative (for example, `city` within `form.scope('address')`).
67
- - Prefer `scoped.validate()` / `scoped.submit()` instead of manually passing touched field arrays across root/scoped boundaries.
33
+ - Removed field validation, numeric array field handles, stable field identity, direct metadata getters, and Ripple runtime coupling.
34
+ - `validate()` returns `{ status: 'valid' | 'invalid' | 'aborted' }`.
35
+ - Read metadata from `form.state`; read values from `form.value`.
68
36
 
69
37
  ## Documentation
70
38
 
71
39
  - [Overview](https://vielzeug.dev/forge/)
72
- - [Usage Guide](https://vielzeug.dev/forge/usage)
73
- - [API Reference](https://vielzeug.dev/forge/api)
40
+ - [Usage guide](https://vielzeug.dev/forge/usage)
41
+ - [API reference](https://vielzeug.dev/forge/api)
74
42
  - [Examples](https://vielzeug.dev/forge/examples)
75
43
 
76
44
  ## License
77
45
 
78
- MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
46
+ MIT © Helmuth Saatkamp — part of the Vielzeug monorepo.
package/dist/_utils.cjs CHANGED
@@ -1,2 +1 @@
1
- const e=require("./_dev.cjs"),t=require("./errors.cjs");require("./types.cjs"),require("@vielzeug/arsenal");var n=new Set([`__proto__`,`constructor`,`prototype`]);function r(e){return e.split(`.`).every(e=>!n.has(e))}function i(e){if(o(e),!r(e))throw new t.ForgeConfigError(`Unsafe key '${e}': segments __proto__, constructor, and prototype are reserved.`)}var a=new Set;function o(t){t.includes(`.`)&&(t.split(`.`).length<=5||a.has(t)||(a.add(t),e.devOnly(()=>{e.warn(`Path '${s(t,120)}' exceeds typed depth limits and may be treated as an untyped string path.`)})))}function s(e,t=200){return e.replace(/\p{C}/gu,`?`).slice(0,t)}function c(...e){let t=e.filter(e=>e!=null);if(t.length!==0)return t.length===1?t[0]:AbortSignal.any(t)}exports.anySignal=c,exports.assertSafeKey=i,exports.isSafeKey=r,exports.sanitizeForLog=s;
2
- //# sourceMappingURL=_utils.cjs.map
1
+ require("@vielzeug/arsenal/object");
package/dist/_utils.d.ts CHANGED
@@ -1,12 +1,3 @@
1
- export { flattenPaths as flattenValues, isPlainObject, unflattenPaths as unflattenValues } from '@vielzeug/arsenal';
2
- /** Returns `true` when a dot-notation path contains no prototype-polluting segments. */
3
- export declare function isSafeKey(path: string): boolean;
4
- /** R2: Single assertion helper — replaces 5 copy-pasted guard blocks. */
5
- export declare function assertSafeKey(key: string): void;
6
- /**
7
- * Strips control characters (which could otherwise inject terminal escape sequences into
8
- * `console.warn` output) and caps length before interpolating caller-controlled text into a
9
- * dev warning. Mirrors the sanitization already applied to field keys in `warnIfArrayItemKey`.
10
- */
1
+ export { flattenPaths as flattenValues } from '@vielzeug/arsenal/object';
11
2
  export declare function sanitizeForLog(text: string, maxLength?: number): string;
12
3
  //# sourceMappingURL=_utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_utils.d.ts","sourceRoot":"","sources":["../src/_utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,IAAI,aAAa,EAAE,aAAa,EAAE,cAAc,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpH,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK/C;AAuBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CAEpE"}
1
+ {"version":3,"file":"_utils.d.ts","sourceRoot":"","sources":["../src/_utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CAEpE"}
package/dist/_utils.js CHANGED
@@ -1,33 +1,2 @@
1
- import { devOnly as e, warn as t } from "./_dev.js";
2
- import { ForgeConfigError as n } from "./errors.js";
3
- import "./types.js";
4
- import { flattenPaths as r, unflattenPaths as i } from "@vielzeug/arsenal";
5
- //#region src/_utils.ts
6
- var a = /* @__PURE__ */ new Set([
7
- "__proto__",
8
- "constructor",
9
- "prototype"
10
- ]);
11
- function o(e) {
12
- return e.split(".").every((e) => !a.has(e));
13
- }
14
- function s(e) {
15
- if (l(e), !o(e)) throw new n(`Unsafe key '${e}': segments __proto__, constructor, and prototype are reserved.`);
16
- }
17
- var c = /* @__PURE__ */ new Set();
18
- function l(n) {
19
- n.includes(".") && (n.split(".").length <= 5 || c.has(n) || (c.add(n), e(() => {
20
- t(`Path '${u(n, 120)}' exceeds typed depth limits and may be treated as an untyped string path.`);
21
- })));
22
- }
23
- function u(e, t = 200) {
24
- return e.replace(/\p{C}/gu, "?").slice(0, t);
25
- }
26
- function d(...e) {
27
- let t = e.filter((e) => e != null);
28
- if (t.length !== 0) return t.length === 1 ? t[0] : AbortSignal.any(t);
29
- }
30
- //#endregion
31
- export { d as anySignal, s as assertSafeKey, r as flattenValues, o as isSafeKey, u as sanitizeForLog, i as unflattenValues };
32
-
33
- //# sourceMappingURL=_utils.js.map
1
+ import { flattenPaths as e } from "@vielzeug/arsenal/object";
2
+ export { e as flattenValues };
@@ -1,2 +1,2 @@
1
- require("../_utils.cjs");let e=require("@vielzeug/arsenal");function t(t){let n=new FormData;for(let[r,i]of Object.entries((0,e.flattenPaths)(t)))if(i!=null)if(i instanceof File||i instanceof Blob)n.append(r,i);else if(i instanceof FileList)for(let e=0;e<i.length;e++)n.append(r,i[e]);else if(Array.isArray(i))for(let e of i)n.append(r,e instanceof File||e instanceof Blob?e:String(e));else n.append(r,String(i));return n}exports.toFormData=t;
1
+ require("../_utils.cjs");let e=require("@vielzeug/arsenal/object");function t(t){let n=new FormData,r=e=>typeof Blob<`u`&&e instanceof Blob,i=e=>typeof File<`u`&&e instanceof File,a=e=>typeof FileList<`u`&&e instanceof FileList;for(let[o,s]of Object.entries((0,e.flattenPaths)(t)))if(s!=null)if(i(s)||r(s))n.append(o,s);else if(a(s))for(let e=0;e<s.length;e++)n.append(o,s[e]);else if(Array.isArray(s))for(let e of s)n.append(o,i(e)||r(e)?e:String(e));else n.append(o,String(s));return n}exports.toFormData=t;
2
2
  //# sourceMappingURL=form-data.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-data.cjs","names":[],"sources":["../../src/adapters/form-data.ts"],"sourcesContent":["import { flattenValues } from '../_utils';\n\n/**\n * Converts a form values object into a `FormData` instance.\n * Nested objects are flattened to dot-notation keys (e.g. `user.name`).\n * `File`, `Blob`, and `FileList` values are appended as-is; all others are coerced to strings.\n * `null` and `undefined` values are omitted.\n */\nexport function toFormData(values: Record<string, unknown>): FormData {\n const fd = new FormData();\n\n for (const [name, value] of Object.entries(flattenValues(values))) {\n if (value === null || value === undefined) continue;\n\n if (value instanceof File || value instanceof Blob) {\n fd.append(name, value);\n } else if (value instanceof FileList) {\n for (let i = 0; i < value.length; i++) fd.append(name, value[i]);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n fd.append(name, item instanceof File || item instanceof Blob ? item : String(item));\n }\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":"4DAQA,SAAgB,EAAW,EAA2C,CACpE,IAAM,EAAK,IAAI,SAEf,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,SAAA,EAAA,EAAA,aAAA,CAAsB,CAAM,CAAC,EAC1D,MAAU,KAEd,GAAI,aAAiB,MAAQ,aAAiB,KAC5C,EAAG,OAAO,EAAM,CAAK,OAChB,GAAI,aAAiB,SAC1B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,EAAG,OAAO,EAAM,EAAM,EAAE,OAC1D,GAAI,MAAM,QAAQ,CAAK,EAC5B,IAAK,IAAM,KAAQ,EACjB,EAAG,OAAO,EAAM,aAAgB,MAAQ,aAAgB,KAAO,EAAO,OAAO,CAAI,CAAC,OAGpF,EAAG,OAAO,EAAM,OAAO,CAAK,CAAC,EAIjC,OAAO,CACT"}
1
+ {"version":3,"file":"form-data.cjs","names":[],"sources":["../../src/adapters/form-data.ts"],"sourcesContent":["import { flattenValues } from '../_utils';\n\n/**\n * Converts a form values object into a `FormData` instance.\n * Nested objects are flattened to dot-notation keys (e.g. `user.name`).\n * `File`, `Blob`, and `FileList` values are appended as-is; all others are coerced to strings.\n * `null` and `undefined` values are omitted.\n */\nexport function toFormData(values: Record<string, unknown>): FormData {\n const fd = new FormData();\n\n const isBlob = (value: unknown): value is Blob => typeof Blob !== 'undefined' && value instanceof Blob;\n const isFile = (value: unknown): value is File => typeof File !== 'undefined' && value instanceof File;\n const isFileList = (value: unknown): value is FileList =>\n typeof FileList !== 'undefined' && value instanceof FileList;\n\n for (const [name, value] of Object.entries(flattenValues(values))) {\n if (value === null || value === undefined) continue;\n\n if (isFile(value) || isBlob(value)) {\n fd.append(name, value);\n } else if (isFileList(value)) {\n for (let i = 0; i < value.length; i++) fd.append(name, value[i]);\n } else if (Array.isArray(value)) {\n for (const item of value) fd.append(name, isFile(item) || isBlob(item) ? item : String(item));\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":"mEAQA,SAAgB,EAAW,EAA2C,CACpE,IAAM,EAAK,IAAI,SAET,EAAU,GAAkC,OAAO,KAAS,KAAe,aAAiB,KAC5F,EAAU,GAAkC,OAAO,KAAS,KAAe,aAAiB,KAC5F,EAAc,GAClB,OAAO,SAAa,KAAe,aAAiB,SAEtD,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,SAAA,EAAQ,EAAA,aAAA,CAAc,CAAM,CAAC,EAC1D,MAAU,KAEd,GAAI,EAAO,CAAK,GAAK,EAAO,CAAK,EAC/B,EAAG,OAAO,EAAM,CAAK,OAChB,GAAI,EAAW,CAAK,EACzB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,EAAG,OAAO,EAAM,EAAM,EAAE,OAC1D,GAAI,MAAM,QAAQ,CAAK,EAC5B,IAAK,IAAM,KAAQ,EAAO,EAAG,OAAO,EAAM,EAAO,CAAI,GAAK,EAAO,CAAI,EAAI,EAAO,OAAO,CAAI,CAAC,OAE5F,EAAG,OAAO,EAAM,OAAO,CAAK,CAAC,EAIjC,OAAO,CACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-data.d.ts","sourceRoot":"","sources":["../../src/adapters/form-data.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAoBpE"}
1
+ {"version":3,"file":"form-data.d.ts","sourceRoot":"","sources":["../../src/adapters/form-data.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAuBpE"}
@@ -1,11 +1,11 @@
1
1
  import { flattenValues as e } from "../_utils.js";
2
2
  //#region src/adapters/form-data.ts
3
3
  function t(t) {
4
- let n = new FormData();
5
- for (let [r, i] of Object.entries(e(t))) if (i != null) if (i instanceof File || i instanceof Blob) n.append(r, i);
6
- else if (i instanceof FileList) for (let e = 0; e < i.length; e++) n.append(r, i[e]);
7
- else if (Array.isArray(i)) for (let e of i) n.append(r, e instanceof File || e instanceof Blob ? e : String(e));
8
- else n.append(r, String(i));
4
+ let n = new FormData(), r = (e) => typeof Blob < "u" && e instanceof Blob, i = (e) => typeof File < "u" && e instanceof File, a = (e) => typeof FileList < "u" && e instanceof FileList;
5
+ for (let [o, s] of Object.entries(e(t))) if (s != null) if (i(s) || r(s)) n.append(o, s);
6
+ else if (a(s)) for (let e = 0; e < s.length; e++) n.append(o, s[e]);
7
+ else if (Array.isArray(s)) for (let e of s) n.append(o, i(e) || r(e) ? e : String(e));
8
+ else n.append(o, String(s));
9
9
  return n;
10
10
  }
11
11
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"form-data.js","names":[],"sources":["../../src/adapters/form-data.ts"],"sourcesContent":["import { flattenValues } from '../_utils';\n\n/**\n * Converts a form values object into a `FormData` instance.\n * Nested objects are flattened to dot-notation keys (e.g. `user.name`).\n * `File`, `Blob`, and `FileList` values are appended as-is; all others are coerced to strings.\n * `null` and `undefined` values are omitted.\n */\nexport function toFormData(values: Record<string, unknown>): FormData {\n const fd = new FormData();\n\n for (const [name, value] of Object.entries(flattenValues(values))) {\n if (value === null || value === undefined) continue;\n\n if (value instanceof File || value instanceof Blob) {\n fd.append(name, value);\n } else if (value instanceof FileList) {\n for (let i = 0; i < value.length; i++) fd.append(name, value[i]);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n fd.append(name, item instanceof File || item instanceof Blob ? item : String(item));\n }\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":";;AAQA,SAAgB,EAAW,GAA2C;CACpE,IAAM,IAAK,IAAI,SAAS;CAExB,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,EAAc,CAAM,CAAC,GAC1D,SAAU,MAEd,IAAI,aAAiB,QAAQ,aAAiB,MAC5C,EAAG,OAAO,GAAM,CAAK;MAChB,IAAI,aAAiB,UAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK,EAAG,OAAO,GAAM,EAAM,EAAE;MAC1D,IAAI,MAAM,QAAQ,CAAK,GAC5B,KAAK,IAAM,KAAQ,GACjB,EAAG,OAAO,GAAM,aAAgB,QAAQ,aAAgB,OAAO,IAAO,OAAO,CAAI,CAAC;MAGpF,EAAG,OAAO,GAAM,OAAO,CAAK,CAAC;CAIjC,OAAO;AACT"}
1
+ {"version":3,"file":"form-data.js","names":[],"sources":["../../src/adapters/form-data.ts"],"sourcesContent":["import { flattenValues } from '../_utils';\n\n/**\n * Converts a form values object into a `FormData` instance.\n * Nested objects are flattened to dot-notation keys (e.g. `user.name`).\n * `File`, `Blob`, and `FileList` values are appended as-is; all others are coerced to strings.\n * `null` and `undefined` values are omitted.\n */\nexport function toFormData(values: Record<string, unknown>): FormData {\n const fd = new FormData();\n\n const isBlob = (value: unknown): value is Blob => typeof Blob !== 'undefined' && value instanceof Blob;\n const isFile = (value: unknown): value is File => typeof File !== 'undefined' && value instanceof File;\n const isFileList = (value: unknown): value is FileList =>\n typeof FileList !== 'undefined' && value instanceof FileList;\n\n for (const [name, value] of Object.entries(flattenValues(values))) {\n if (value === null || value === undefined) continue;\n\n if (isFile(value) || isBlob(value)) {\n fd.append(name, value);\n } else if (isFileList(value)) {\n for (let i = 0; i < value.length; i++) fd.append(name, value[i]);\n } else if (Array.isArray(value)) {\n for (const item of value) fd.append(name, isFile(item) || isBlob(item) ? item : String(item));\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":";;AAQA,SAAgB,EAAW,GAA2C;CACpE,IAAM,IAAK,IAAI,SAAS,GAElB,KAAU,MAAkC,OAAO,OAAS,OAAe,aAAiB,MAC5F,KAAU,MAAkC,OAAO,OAAS,OAAe,aAAiB,MAC5F,KAAc,MAClB,OAAO,WAAa,OAAe,aAAiB;CAEtD,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,EAAc,CAAM,CAAC,GAC1D,SAAU,MAEd,IAAI,EAAO,CAAK,KAAK,EAAO,CAAK,GAC/B,EAAG,OAAO,GAAM,CAAK;MAChB,IAAI,EAAW,CAAK,GACzB,KAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK,EAAG,OAAO,GAAM,EAAM,EAAE;MAC1D,IAAI,MAAM,QAAQ,CAAK,GAC5B,KAAK,IAAM,KAAQ,GAAO,EAAG,OAAO,GAAM,EAAO,CAAI,KAAK,EAAO,CAAI,IAAI,IAAO,OAAO,CAAI,CAAC;MAE5F,EAAG,OAAO,GAAM,OAAO,CAAK,CAAC;CAIjC,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ const e=require("../errors.cjs");var t=new Set([`__proto__`,`constructor`,`prototype`]);function n(e){if(typeof e!=`object`||!e||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function r(n){if(!n||t.has(n))throw new e.ForgeConfigError(`Invalid field key '${n}'.`)}function i(e,r,a){let[o,...s]=r;if(!o||t.has(o))return e;if(s.length===0)return e[o]===void 0?{...e,[o]:a}:e;let c=e[o];return c!==void 0&&!n(c)?e:{...e,[o]:i(c??{},s,a)}}function a(e){return typeof Blob<`u`&&e instanceof Blob||typeof File<`u`&&e instanceof File}function o(t){if(Array.isArray(t))return Object.freeze(t.map(o));if(typeof t!=`object`||!t||a(t))return t;if(!n(t))throw new e.ForgeConfigError(`Form values must contain only plain objects, arrays, File, Blob, or primitive values.`);return Object.freeze(Object.fromEntries(Object.entries(t).map(([e,t])=>[e,o(t)])))}function s(e,t){let r=e;for(let e of t){if(!n(r)||!Object.hasOwn(r,e))return;r=r[e]}return r}function c(e,t){let r=e;for(let e of t){if(!n(r)||!Object.hasOwn(r,e))return!1;r=r[e]}return!0}function l(t,i,a){let[s,...c]=i;if(r(s),c.length===0)return Object.freeze({...t,[s]:o(a)});let u=t[s];if(u!==void 0&&!n(u))throw new e.ForgeConfigError(`Cannot select '${s}' because it is not an object.`);return Object.freeze({...t,[s]:l(u??{},c,a)})}function u(e,t,r){let[i,...a]=r;if(a.length===0){if(Object.hasOwn(t,i))return Object.freeze({...e,[i]:t[i]});let{[i]:n,...r}=e;return Object.freeze(r)}let o=t[i];if(!n(o)){if(Object.hasOwn(t,i))return Object.freeze({...e,[i]:o});let{[i]:n,...r}=e;return Object.freeze(r)}let s=n(e[i])?e[i]:{};return Object.freeze({...e,[i]:u(s,o,a)})}function d(e,t,r){let[i,...a]=t,o={...e};if(a.length===0)r?o[i]=!0:delete o[i];else{let t=d(n(e[i])?e[i]:{},a,r);Object.keys(t).length===0?delete o[i]:o[i]=t}return Object.freeze(o)}function f(e,t){let r=e;for(let e of t){if(!n(r)||!Object.hasOwn(r,e))return!1;r=r[e]}return r===!0}function p(e){return n(e)?Object.freeze(Object.fromEntries(Object.entries(e).map(([e,t])=>[e,!n(t)||p(t)]))):{}}function m(e,t){let n=s(e,t);return typeof n==`string`?n:void 0}function h(e){if(typeof e==`string`)return e;if(!n(e))return;let t=Object.entries(e).map(([e,t])=>[e,h(t)]).filter(([,e])=>e!==void 0);return t.length===0?void 0:Object.freeze(Object.fromEntries(t))}exports.assertSafeKey=r,exports.hasAtPath=c,exports.immutable=o,exports.isRecord=n,exports.normalizeErrors=h,exports.readAtPath=s,exports.readError=m,exports.readMeta=f,exports.resetAtPath=u,exports.touchAll=p,exports.writeAtPath=l,exports.writeError=i,exports.writeMeta=d;
2
+ //# sourceMappingURL=path.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.cjs","names":[],"sources":["../../src/core/path.ts"],"sourcesContent":["import { ForgeConfigError } from '../errors';\n\nconst unsafeKeys = new Set(['__proto__', 'constructor', 'prototype']);\n\ntype ErrorTree = Record<string, unknown>;\ntype RecordValue = Record<string, unknown>;\ntype MetaTree = true | MetaNode;\ninterface MetaNode {\n readonly [key: string]: MetaTree;\n}\nexport type MetaRoot = MetaNode;\n\nexport function isRecord(value: unknown): value is RecordValue {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;\n\n const prototype = Object.getPrototypeOf(value);\n\n return prototype === Object.prototype || prototype === null;\n}\n\nexport function assertSafeKey(key: string): void {\n if (!key || unsafeKeys.has(key)) throw new ForgeConfigError(`Invalid field key '${key}'.`);\n}\n\nexport function writeError(tree: ErrorTree, path: readonly string[], message: string): ErrorTree {\n const [key, ...rest] = path;\n\n if (!key || unsafeKeys.has(key)) return tree;\n\n if (rest.length === 0) return tree[key] === undefined ? { ...tree, [key]: message } : tree;\n\n const child = tree[key];\n\n if (child !== undefined && !isRecord(child)) return tree;\n\n return { ...tree, [key]: writeError((child ?? {}) as ErrorTree, rest, message) };\n}\n\nfunction isAtomicLeaf(value: unknown): boolean {\n return (\n (typeof Blob !== 'undefined' && value instanceof Blob) || (typeof File !== 'undefined' && value instanceof File)\n );\n}\n\nexport function immutable<T>(value: T): T {\n if (Array.isArray(value)) return Object.freeze(value.map(immutable)) as T;\n\n if (value === null || typeof value !== 'object' || isAtomicLeaf(value)) return value;\n\n if (!isRecord(value)) {\n throw new ForgeConfigError('Form values must contain only plain objects, arrays, File, Blob, or primitive values.');\n }\n\n return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child]) => [key, immutable(child)]))) as T;\n}\n\nexport function readAtPath<T>(value: unknown, path: readonly string[]): T | undefined {\n let current = value;\n\n for (const key of path) {\n if (!isRecord(current) || !Object.hasOwn(current, key)) return undefined;\n\n current = current[key];\n }\n\n return current as T | undefined;\n}\n\nexport function hasAtPath(value: unknown, path: readonly string[]): boolean {\n let current = value;\n\n for (const key of path) {\n if (!isRecord(current) || !Object.hasOwn(current, key)) return false;\n\n current = current[key];\n }\n\n return true;\n}\n\nexport function writeAtPath<T extends RecordValue>(value: T, path: readonly string[], next: unknown): T {\n const [key, ...rest] = path;\n\n assertSafeKey(key);\n\n if (rest.length === 0) return Object.freeze({ ...value, [key]: immutable(next) }) as T;\n\n const child = value[key];\n\n if (child !== undefined && !isRecord(child)) {\n throw new ForgeConfigError(`Cannot select '${key}' because it is not an object.`);\n }\n\n return Object.freeze({ ...value, [key]: writeAtPath((child ?? {}) as RecordValue, rest, next) }) as T;\n}\n\nexport function resetAtPath<T extends RecordValue>(value: T, baseline: T, path: readonly string[]): T {\n const [key, ...rest] = path;\n\n if (rest.length === 0) {\n if (Object.hasOwn(baseline, key)) return Object.freeze({ ...value, [key]: baseline[key] }) as T;\n\n const { [key]: _removed, ...next } = value;\n\n return Object.freeze(next) as T;\n }\n\n const baselineChild = baseline[key];\n\n if (!isRecord(baselineChild)) {\n if (Object.hasOwn(baseline, key)) return Object.freeze({ ...value, [key]: baselineChild }) as T;\n\n const { [key]: _removed, ...next } = value;\n\n return Object.freeze(next) as T;\n }\n\n const currentChild = isRecord(value[key]) ? value[key] : {};\n\n return Object.freeze({ ...value, [key]: resetAtPath(currentChild, baselineChild, rest) }) as T;\n}\n\nexport function writeMeta(meta: MetaRoot, path: readonly string[], next: boolean): MetaRoot {\n const [key, ...rest] = path;\n const copy: Record<string, MetaTree> = { ...meta };\n\n if (rest.length === 0) {\n if (next) copy[key] = true;\n else delete copy[key];\n } else {\n const child = isRecord(meta[key]) ? (meta[key] as MetaRoot) : {};\n const updated = writeMeta(child, rest, next);\n\n if (Object.keys(updated).length === 0) delete copy[key];\n else copy[key] = updated;\n }\n\n return Object.freeze(copy);\n}\n\nexport function readMeta(meta: MetaRoot, path: readonly string[]): boolean {\n let current: MetaTree | undefined = meta;\n\n for (const key of path) {\n if (!isRecord(current) || !Object.hasOwn(current, key)) return false;\n\n current = current[key] as MetaTree;\n }\n\n return current === true;\n}\n\nexport function touchAll(value: unknown): MetaRoot {\n if (!isRecord(value)) return {};\n\n return Object.freeze(\n Object.fromEntries(\n Object.entries(value).map(([key, child]) => [key, isRecord(child) ? touchAll(child) : (true as const)]),\n ),\n );\n}\n\nexport function readError(errors: unknown, path: readonly string[]): string | undefined {\n const error = readAtPath(errors, path);\n\n return typeof error === 'string' ? error : undefined;\n}\n\nexport function normalizeErrors<T>(errors: T): T | undefined {\n if (typeof errors === 'string') return errors;\n\n if (!isRecord(errors)) return undefined;\n\n const entries = Object.entries(errors)\n .map(([key, value]) => [key, normalizeErrors(value)] as const)\n .filter(([, value]) => value !== undefined);\n\n return entries.length === 0 ? undefined : (Object.freeze(Object.fromEntries(entries)) as T);\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ type ErrorTree = Record<string, unknown>;
2
+ type RecordValue = Record<string, unknown>;
3
+ type MetaTree = true | MetaNode;
4
+ interface MetaNode {
5
+ readonly [key: string]: MetaTree;
6
+ }
7
+ export type MetaRoot = MetaNode;
8
+ export declare function isRecord(value: unknown): value is RecordValue;
9
+ export declare function assertSafeKey(key: string): void;
10
+ export declare function writeError(tree: ErrorTree, path: readonly string[], message: string): ErrorTree;
11
+ export declare function immutable<T>(value: T): T;
12
+ export declare function readAtPath<T>(value: unknown, path: readonly string[]): T | undefined;
13
+ export declare function hasAtPath(value: unknown, path: readonly string[]): boolean;
14
+ export declare function writeAtPath<T extends RecordValue>(value: T, path: readonly string[], next: unknown): T;
15
+ export declare function resetAtPath<T extends RecordValue>(value: T, baseline: T, path: readonly string[]): T;
16
+ export declare function writeMeta(meta: MetaRoot, path: readonly string[], next: boolean): MetaRoot;
17
+ export declare function readMeta(meta: MetaRoot, path: readonly string[]): boolean;
18
+ export declare function touchAll(value: unknown): MetaRoot;
19
+ export declare function readError(errors: unknown, path: readonly string[]): string | undefined;
20
+ export declare function normalizeErrors<T>(errors: T): T | undefined;
21
+ export {};
22
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"AAIA,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACzC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC3C,KAAK,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAAC;AAChC,UAAU,QAAQ;IAChB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC;CAClC;AACD,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAEhC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAM7D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAY/F;AAQD,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAUxC;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAUpF;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAU1E;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,CActG;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,CAwBpG;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,QAAQ,CAgB1F;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAUzE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAQjD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAItF;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAU3D"}
@@ -0,0 +1,125 @@
1
+ import { ForgeConfigError as e } from "../errors.js";
2
+ //#region src/core/path.ts
3
+ var t = /* @__PURE__ */ new Set([
4
+ "__proto__",
5
+ "constructor",
6
+ "prototype"
7
+ ]);
8
+ function n(e) {
9
+ if (typeof e != "object" || !e || Array.isArray(e)) return !1;
10
+ let t = Object.getPrototypeOf(e);
11
+ return t === Object.prototype || t === null;
12
+ }
13
+ function r(n) {
14
+ if (!n || t.has(n)) throw new e(`Invalid field key '${n}'.`);
15
+ }
16
+ function i(e, r, a) {
17
+ let [o, ...s] = r;
18
+ if (!o || t.has(o)) return e;
19
+ if (s.length === 0) return e[o] === void 0 ? {
20
+ ...e,
21
+ [o]: a
22
+ } : e;
23
+ let c = e[o];
24
+ return c !== void 0 && !n(c) ? e : {
25
+ ...e,
26
+ [o]: i(c ?? {}, s, a)
27
+ };
28
+ }
29
+ function a(e) {
30
+ return typeof Blob < "u" && e instanceof Blob || typeof File < "u" && e instanceof File;
31
+ }
32
+ function o(t) {
33
+ if (Array.isArray(t)) return Object.freeze(t.map(o));
34
+ if (typeof t != "object" || !t || a(t)) return t;
35
+ if (!n(t)) throw new e("Form values must contain only plain objects, arrays, File, Blob, or primitive values.");
36
+ return Object.freeze(Object.fromEntries(Object.entries(t).map(([e, t]) => [e, o(t)])));
37
+ }
38
+ function s(e, t) {
39
+ let r = e;
40
+ for (let e of t) {
41
+ if (!n(r) || !Object.hasOwn(r, e)) return;
42
+ r = r[e];
43
+ }
44
+ return r;
45
+ }
46
+ function c(e, t) {
47
+ let r = e;
48
+ for (let e of t) {
49
+ if (!n(r) || !Object.hasOwn(r, e)) return !1;
50
+ r = r[e];
51
+ }
52
+ return !0;
53
+ }
54
+ function l(t, i, a) {
55
+ let [s, ...c] = i;
56
+ if (r(s), c.length === 0) return Object.freeze({
57
+ ...t,
58
+ [s]: o(a)
59
+ });
60
+ let u = t[s];
61
+ if (u !== void 0 && !n(u)) throw new e(`Cannot select '${s}' because it is not an object.`);
62
+ return Object.freeze({
63
+ ...t,
64
+ [s]: l(u ?? {}, c, a)
65
+ });
66
+ }
67
+ function u(e, t, r) {
68
+ let [i, ...a] = r;
69
+ if (a.length === 0) {
70
+ if (Object.hasOwn(t, i)) return Object.freeze({
71
+ ...e,
72
+ [i]: t[i]
73
+ });
74
+ let { [i]: n, ...r } = e;
75
+ return Object.freeze(r);
76
+ }
77
+ let o = t[i];
78
+ if (!n(o)) {
79
+ if (Object.hasOwn(t, i)) return Object.freeze({
80
+ ...e,
81
+ [i]: o
82
+ });
83
+ let { [i]: n, ...r } = e;
84
+ return Object.freeze(r);
85
+ }
86
+ let s = n(e[i]) ? e[i] : {};
87
+ return Object.freeze({
88
+ ...e,
89
+ [i]: u(s, o, a)
90
+ });
91
+ }
92
+ function d(e, t, r) {
93
+ let [i, ...a] = t, o = { ...e };
94
+ if (a.length === 0) r ? o[i] = !0 : delete o[i];
95
+ else {
96
+ let t = d(n(e[i]) ? e[i] : {}, a, r);
97
+ Object.keys(t).length === 0 ? delete o[i] : o[i] = t;
98
+ }
99
+ return Object.freeze(o);
100
+ }
101
+ function f(e, t) {
102
+ let r = e;
103
+ for (let e of t) {
104
+ if (!n(r) || !Object.hasOwn(r, e)) return !1;
105
+ r = r[e];
106
+ }
107
+ return r === !0;
108
+ }
109
+ function p(e) {
110
+ return n(e) ? Object.freeze(Object.fromEntries(Object.entries(e).map(([e, t]) => [e, !n(t) || p(t)]))) : {};
111
+ }
112
+ function m(e, t) {
113
+ let n = s(e, t);
114
+ return typeof n == "string" ? n : void 0;
115
+ }
116
+ function h(e) {
117
+ if (typeof e == "string") return e;
118
+ if (!n(e)) return;
119
+ let t = Object.entries(e).map(([e, t]) => [e, h(t)]).filter(([, e]) => e !== void 0);
120
+ return t.length === 0 ? void 0 : Object.freeze(Object.fromEntries(t));
121
+ }
122
+ //#endregion
123
+ export { r as assertSafeKey, c as hasAtPath, o as immutable, n as isRecord, h as normalizeErrors, s as readAtPath, m as readError, f as readMeta, u as resetAtPath, p as touchAll, l as writeAtPath, i as writeError, d as writeMeta };
124
+
125
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","names":[],"sources":["../../src/core/path.ts"],"sourcesContent":["import { ForgeConfigError } from '../errors';\n\nconst unsafeKeys = new Set(['__proto__', 'constructor', 'prototype']);\n\ntype ErrorTree = Record<string, unknown>;\ntype RecordValue = Record<string, unknown>;\ntype MetaTree = true | MetaNode;\ninterface MetaNode {\n readonly [key: string]: MetaTree;\n}\nexport type MetaRoot = MetaNode;\n\nexport function isRecord(value: unknown): value is RecordValue {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;\n\n const prototype = Object.getPrototypeOf(value);\n\n return prototype === Object.prototype || prototype === null;\n}\n\nexport function assertSafeKey(key: string): void {\n if (!key || unsafeKeys.has(key)) throw new ForgeConfigError(`Invalid field key '${key}'.`);\n}\n\nexport function writeError(tree: ErrorTree, path: readonly string[], message: string): ErrorTree {\n const [key, ...rest] = path;\n\n if (!key || unsafeKeys.has(key)) return tree;\n\n if (rest.length === 0) return tree[key] === undefined ? { ...tree, [key]: message } : tree;\n\n const child = tree[key];\n\n if (child !== undefined && !isRecord(child)) return tree;\n\n return { ...tree, [key]: writeError((child ?? {}) as ErrorTree, rest, message) };\n}\n\nfunction isAtomicLeaf(value: unknown): boolean {\n return (\n (typeof Blob !== 'undefined' && value instanceof Blob) || (typeof File !== 'undefined' && value instanceof File)\n );\n}\n\nexport function immutable<T>(value: T): T {\n if (Array.isArray(value)) return Object.freeze(value.map(immutable)) as T;\n\n if (value === null || typeof value !== 'object' || isAtomicLeaf(value)) return value;\n\n if (!isRecord(value)) {\n throw new ForgeConfigError('Form values must contain only plain objects, arrays, File, Blob, or primitive values.');\n }\n\n return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child]) => [key, immutable(child)]))) as T;\n}\n\nexport function readAtPath<T>(value: unknown, path: readonly string[]): T | undefined {\n let current = value;\n\n for (const key of path) {\n if (!isRecord(current) || !Object.hasOwn(current, key)) return undefined;\n\n current = current[key];\n }\n\n return current as T | undefined;\n}\n\nexport function hasAtPath(value: unknown, path: readonly string[]): boolean {\n let current = value;\n\n for (const key of path) {\n if (!isRecord(current) || !Object.hasOwn(current, key)) return false;\n\n current = current[key];\n }\n\n return true;\n}\n\nexport function writeAtPath<T extends RecordValue>(value: T, path: readonly string[], next: unknown): T {\n const [key, ...rest] = path;\n\n assertSafeKey(key);\n\n if (rest.length === 0) return Object.freeze({ ...value, [key]: immutable(next) }) as T;\n\n const child = value[key];\n\n if (child !== undefined && !isRecord(child)) {\n throw new ForgeConfigError(`Cannot select '${key}' because it is not an object.`);\n }\n\n return Object.freeze({ ...value, [key]: writeAtPath((child ?? {}) as RecordValue, rest, next) }) as T;\n}\n\nexport function resetAtPath<T extends RecordValue>(value: T, baseline: T, path: readonly string[]): T {\n const [key, ...rest] = path;\n\n if (rest.length === 0) {\n if (Object.hasOwn(baseline, key)) return Object.freeze({ ...value, [key]: baseline[key] }) as T;\n\n const { [key]: _removed, ...next } = value;\n\n return Object.freeze(next) as T;\n }\n\n const baselineChild = baseline[key];\n\n if (!isRecord(baselineChild)) {\n if (Object.hasOwn(baseline, key)) return Object.freeze({ ...value, [key]: baselineChild }) as T;\n\n const { [key]: _removed, ...next } = value;\n\n return Object.freeze(next) as T;\n }\n\n const currentChild = isRecord(value[key]) ? value[key] : {};\n\n return Object.freeze({ ...value, [key]: resetAtPath(currentChild, baselineChild, rest) }) as T;\n}\n\nexport function writeMeta(meta: MetaRoot, path: readonly string[], next: boolean): MetaRoot {\n const [key, ...rest] = path;\n const copy: Record<string, MetaTree> = { ...meta };\n\n if (rest.length === 0) {\n if (next) copy[key] = true;\n else delete copy[key];\n } else {\n const child = isRecord(meta[key]) ? (meta[key] as MetaRoot) : {};\n const updated = writeMeta(child, rest, next);\n\n if (Object.keys(updated).length === 0) delete copy[key];\n else copy[key] = updated;\n }\n\n return Object.freeze(copy);\n}\n\nexport function readMeta(meta: MetaRoot, path: readonly string[]): boolean {\n let current: MetaTree | undefined = meta;\n\n for (const key of path) {\n if (!isRecord(current) || !Object.hasOwn(current, key)) return false;\n\n current = current[key] as MetaTree;\n }\n\n return current === true;\n}\n\nexport function touchAll(value: unknown): MetaRoot {\n if (!isRecord(value)) return {};\n\n return Object.freeze(\n Object.fromEntries(\n Object.entries(value).map(([key, child]) => [key, isRecord(child) ? touchAll(child) : (true as const)]),\n ),\n );\n}\n\nexport function readError(errors: unknown, path: readonly string[]): string | undefined {\n const error = readAtPath(errors, path);\n\n return typeof error === 'string' ? error : undefined;\n}\n\nexport function normalizeErrors<T>(errors: T): T | undefined {\n if (typeof errors === 'string') return errors;\n\n if (!isRecord(errors)) return undefined;\n\n const entries = Object.entries(errors)\n .map(([key, value]) => [key, normalizeErrors(value)] as const)\n .filter(([, value]) => value !== undefined);\n\n return entries.length === 0 ? undefined : (Object.freeze(Object.fromEntries(entries)) as T);\n}\n"],"mappings":"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"}
package/dist/devtools.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_utils.cjs");let t=require("@vielzeug/arsenal");var n=!globalThis.__FORGE_PROD__;function r(r,i={}){if(!n)return()=>{};let a=`[forge:devtools:${i.label??`form`}]`,o=new Map,s=r.state.isSubmitting,c=r.state.isLoading;function l(e){let n=new Set(o.keys());for(let e of Object.keys((0,t.flattenPaths)(r.values())))n.add(e);for(let t of Object.keys(e.errors))n.add(t);for(let t of e.touchedFields)n.add(t);return n}function u(t){for(let n of l(t)){let t=r.field(n),i=o.get(n);if(o.set(n,t),!i)continue;let s=e.sanitizeForLog(n,80);i.value!==t.value&&console.debug(`${a} field "${s}" value:`,i.value,`→`,t.value),i.error!==t.error&&console.debug(`${a} field "${s}" error:`,i.error,`→`,t.error),i.touched!==t.touched&&console.debug(`${a} field "${s}" touched:`,t.touched),i.dirty!==t.dirty&&console.debug(`${a} field "${s}" dirty:`,t.dirty)}}return r.subscribe(e=>{u(e),e.isSubmitting!==s&&(s=e.isSubmitting,console.debug(`${a} submit ${e.isSubmitting?`started`:`ended`} (submitCount=${e.submitCount})`)),e.isLoading!==c&&(c=e.isLoading,console.debug(`${a} isLoading:`,e.isLoading))},{sync:!0})}exports.debugForm=r;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e,t={}){let n=`[forge:devtools:${t.label??`form`}]`,r;return e.subscribe(e=>{r&&(r.submitting!==e.submitting&&console.debug(`${n} submitting:`,e.submitting),r.validating!==e.validating&&console.debug(`${n} validating:`,e.validating),r.valid!==e.valid&&console.debug(`${n} valid:`,e.valid)),r=e},{immediate:!0})}exports.debugForm=e;
2
2
  //# sourceMappingURL=devtools.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.cjs","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["/**\n * @vielzeug/forge — debug utilities for form state visualisation.\n *\n * Import from the dedicated sub-path so it is tree-shaken from production bundles:\n * ```ts\n * import { debugForm } from '@vielzeug/forge/devtools';\n * ```\n */\n\nimport type { FieldState, FlatKeyOf, Form, FormState, Unsubscribe } from './types';\n\nimport { flattenValues, sanitizeForLog } from './_utils';\n\nconst isDev = !(globalThis as { __FORGE_PROD__?: boolean }).__FORGE_PROD__;\n\n/** Options for {@link debugForm}. */\nexport type ForgeDevtoolsOptions = {\n /** Label included in every log line, useful when debugging multiple forms at once. Default: `\"form\"`. */\n label?: string;\n};\n\n/**\n * Attaches a `console.debug`-based observer to `form` that logs a distinct line per observable\n * state transition: per-field value/error/touched/dirty changes, submit start/end (via the\n * `isSubmitting` edge), and `isLoading` edge (async `defaultValues` resolving).\n *\n * **Development only** by default — logging is a no-op when `__FORGE_PROD__` is set (the same\n * convention `_dev.ts` uses internally). Import from the dedicated `/devtools` sub-path so the\n * logging code is tree-shaken from production bundles entirely.\n *\n * Works identically on scoped sub-forms (`form.scope('address')`) — field paths logged are\n * relative to whatever `form` object is passed in, matching that form's own `state` convention.\n *\n * @example\n * ```ts\n * import { createForm } from '@vielzeug/forge';\n * import { debugForm } from '@vielzeug/forge/devtools';\n *\n * const form = createForm({ defaultValues: { email: '' } });\n * const detach = debugForm(form, { label: 'signup' });\n * // [forge:devtools:signup] field \"email\" value: \"\" → \"a@b.com\"\n *\n * detach(); // stop logging\n * ```\n */\nexport function debugForm<TValues extends Record<string, unknown>>(\n form: Form<TValues>,\n options: ForgeDevtoolsOptions = {},\n): Unsubscribe {\n if (!isDev) return () => {};\n\n const prefix = `[forge:devtools:${options.label ?? 'form'}]`;\n const fieldSnapshots = new Map<string, FieldState<unknown>>();\n\n let prevIsSubmitting = form.state.isSubmitting;\n let prevIsLoading = form.state.isLoading;\n\n function knownFieldKeys(state: FormState): Set<string> {\n const keys = new Set<string>(fieldSnapshots.keys());\n\n for (const key of Object.keys(flattenValues(form.values() as Record<string, unknown>))) keys.add(key);\n for (const key of Object.keys(state.errors)) keys.add(key);\n for (const key of state.touchedFields) keys.add(key);\n\n return keys;\n }\n\n function logFieldDiffs(state: FormState): void {\n for (const key of knownFieldKeys(state)) {\n const next = form.field(key as FlatKeyOf<TValues>);\n const prev = fieldSnapshots.get(key);\n\n fieldSnapshots.set(key, next);\n\n // First observation of this field establishes a baseline — logging it would just be\n // noise (every field would \"change\" from undefined on attach).\n if (!prev) continue;\n\n // Security: `key` is a caller-chosen field path (assertSafeKey only rejects reserved\n // segments, not control characters) — sanitize before interpolating into console output\n // to prevent terminal-escape-sequence injection, mirroring the sanitization `warn()`\n // call sites already apply (see `_utils.ts` sanitizeForLog).\n const safeKey = sanitizeForLog(key, 80);\n\n if (prev.value !== next.value) console.debug(`${prefix} field \"${safeKey}\" value:`, prev.value, '→', next.value);\n\n if (prev.error !== next.error) console.debug(`${prefix} field \"${safeKey}\" error:`, prev.error, '→', next.error);\n\n if (prev.touched !== next.touched) console.debug(`${prefix} field \"${safeKey}\" touched:`, next.touched);\n\n if (prev.dirty !== next.dirty) console.debug(`${prefix} field \"${safeKey}\" dirty:`, next.dirty);\n }\n }\n\n return form.subscribe(\n (state) => {\n logFieldDiffs(state);\n\n if (state.isSubmitting !== prevIsSubmitting) {\n prevIsSubmitting = state.isSubmitting;\n console.debug(\n `${prefix} submit ${state.isSubmitting ? 'started' : 'ended'} (submitCount=${state.submitCount})`,\n );\n }\n\n if (state.isLoading !== prevIsLoading) {\n prevIsLoading = state.isLoading;\n console.debug(`${prefix} isLoading:`, state.isLoading);\n }\n },\n { sync: true },\n );\n}\n"],"mappings":"sIAaA,IAAM,EAAQ,CAAE,WAA4C,eAgC5D,SAAgB,EACd,EACA,EAAgC,CAAC,EACpB,CACb,GAAI,CAAC,EAAO,UAAa,CAAC,EAE1B,IAAM,EAAS,mBAAmB,EAAQ,OAAS,OAAO,GACpD,EAAiB,IAAI,IAEvB,EAAmB,EAAK,MAAM,aAC9B,EAAgB,EAAK,MAAM,UAE/B,SAAS,EAAe,EAA+B,CACrD,IAAM,EAAO,IAAI,IAAY,EAAe,KAAK,CAAC,EAElD,IAAK,IAAM,KAAO,OAAO,MAAA,EAAA,EAAA,aAAA,CAAmB,EAAK,OAAO,CAA4B,CAAC,EAAG,EAAK,IAAI,CAAG,EACpG,IAAK,IAAM,KAAO,OAAO,KAAK,EAAM,MAAM,EAAG,EAAK,IAAI,CAAG,EACzD,IAAK,IAAM,KAAO,EAAM,cAAe,EAAK,IAAI,CAAG,EAEnD,OAAO,CACT,CAEA,SAAS,EAAc,EAAwB,CAC7C,IAAK,IAAM,KAAO,EAAe,CAAK,EAAG,CACvC,IAAM,EAAO,EAAK,MAAM,CAAyB,EAC3C,EAAO,EAAe,IAAI,CAAG,EAMnC,GAJA,EAAe,IAAI,EAAK,CAAI,EAIxB,CAAC,EAAM,SAMX,IAAM,EAAU,EAAA,eAAe,EAAK,EAAE,EAElC,EAAK,QAAU,EAAK,OAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,UAAW,EAAK,MAAO,IAAK,EAAK,KAAK,EAE3G,EAAK,QAAU,EAAK,OAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,UAAW,EAAK,MAAO,IAAK,EAAK,KAAK,EAE3G,EAAK,UAAY,EAAK,SAAS,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,YAAa,EAAK,OAAO,EAElG,EAAK,QAAU,EAAK,OAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,UAAW,EAAK,KAAK,CAChG,CACF,CAEA,OAAO,EAAK,UACT,GAAU,CACT,EAAc,CAAK,EAEf,EAAM,eAAiB,IACzB,EAAmB,EAAM,aACzB,QAAQ,MACN,GAAG,EAAO,UAAU,EAAM,aAAe,UAAY,QAAQ,gBAAgB,EAAM,YAAY,EACjG,GAGE,EAAM,YAAc,IACtB,EAAgB,EAAM,UACtB,QAAQ,MAAM,GAAG,EAAO,aAAc,EAAM,SAAS,EAEzD,EACA,CAAE,KAAM,EAAK,CACf,CACF"}
1
+ {"version":3,"file":"devtools.cjs","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["import type { Form, FormState, Unsubscribe } from './types';\n\nexport type ForgeDevtoolsOptions = Readonly<{\n label?: string;\n}>;\n\n/** Logs public form-state transitions without coupling diagnostics to form internals. */\nexport function debugForm<TValues extends Record<string, unknown>>(\n form: Form<TValues>,\n options: ForgeDevtoolsOptions = {},\n): Unsubscribe {\n const prefix = `[forge:devtools:${options.label ?? 'form'}]`;\n let previous: FormState<TValues> | undefined;\n\n return form.subscribe(\n (next) => {\n if (previous) {\n if (previous.submitting !== next.submitting) console.debug(`${prefix} submitting:`, next.submitting);\n\n if (previous.validating !== next.validating) console.debug(`${prefix} validating:`, next.validating);\n\n if (previous.valid !== next.valid) console.debug(`${prefix} valid:`, next.valid);\n }\n\n previous = next;\n },\n { immediate: true },\n );\n}\n"],"mappings":"mEAOA,SAAgB,EACd,EACA,EAAgC,CAAC,EACpB,CACb,IAAM,EAAS,mBAAmB,EAAQ,OAAS,OAAO,GACtD,EAEJ,OAAO,EAAK,UACT,GAAS,CACJ,IACE,EAAS,aAAe,EAAK,YAAY,QAAQ,MAAM,GAAG,EAAO,cAAe,EAAK,UAAU,EAE/F,EAAS,aAAe,EAAK,YAAY,QAAQ,MAAM,GAAG,EAAO,cAAe,EAAK,UAAU,EAE/F,EAAS,QAAU,EAAK,OAAO,QAAQ,MAAM,GAAG,EAAO,SAAU,EAAK,KAAK,GAGjF,EAAW,CACb,EACA,CAAE,UAAW,EAAK,CACpB,CACF"}
@@ -1,40 +1,7 @@
1
- /**
2
- * @vielzeug/forge — debug utilities for form state visualisation.
3
- *
4
- * Import from the dedicated sub-path so it is tree-shaken from production bundles:
5
- * ```ts
6
- * import { debugForm } from '@vielzeug/forge/devtools';
7
- * ```
8
- */
9
1
  import type { Form, Unsubscribe } from './types';
10
- /** Options for {@link debugForm}. */
11
- export type ForgeDevtoolsOptions = {
12
- /** Label included in every log line, useful when debugging multiple forms at once. Default: `"form"`. */
2
+ export type ForgeDevtoolsOptions = Readonly<{
13
3
  label?: string;
14
- };
15
- /**
16
- * Attaches a `console.debug`-based observer to `form` that logs a distinct line per observable
17
- * state transition: per-field value/error/touched/dirty changes, submit start/end (via the
18
- * `isSubmitting` edge), and `isLoading` edge (async `defaultValues` resolving).
19
- *
20
- * **Development only** by default — logging is a no-op when `__FORGE_PROD__` is set (the same
21
- * convention `_dev.ts` uses internally). Import from the dedicated `/devtools` sub-path so the
22
- * logging code is tree-shaken from production bundles entirely.
23
- *
24
- * Works identically on scoped sub-forms (`form.scope('address')`) — field paths logged are
25
- * relative to whatever `form` object is passed in, matching that form's own `state` convention.
26
- *
27
- * @example
28
- * ```ts
29
- * import { createForm } from '@vielzeug/forge';
30
- * import { debugForm } from '@vielzeug/forge/devtools';
31
- *
32
- * const form = createForm({ defaultValues: { email: '' } });
33
- * const detach = debugForm(form, { label: 'signup' });
34
- * // [forge:devtools:signup] field "email" value: "" → "a@b.com"
35
- *
36
- * detach(); // stop logging
37
- * ```
38
- */
4
+ }>;
5
+ /** Logs public form-state transitions without coupling diagnostics to form internals. */
39
6
  export declare function debugForm<TValues extends Record<string, unknown>>(form: Form<TValues>, options?: ForgeDevtoolsOptions): Unsubscribe;
40
7
  //# sourceMappingURL=devtools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAyB,IAAI,EAAa,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnF,qCAAqC;AACrC,MAAM,MAAM,oBAAoB,GAAG;IACjC,yGAAyG;IACzG,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,EACnB,OAAO,GAAE,oBAAyB,GACjC,WAAW,CAgEb"}
1
+ {"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAa,WAAW,EAAE,MAAM,SAAS,CAAC;AAE5D,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,EACnB,OAAO,GAAE,oBAAyB,GACjC,WAAW,CAkBb"}
package/dist/devtools.js CHANGED
@@ -1,29 +1,11 @@
1
- import { flattenValues as e, sanitizeForLog as t } from "./_utils.js";
2
1
  //#region src/devtools.ts
3
- var n = !globalThis.__FORGE_PROD__;
4
- function r(r, i = {}) {
5
- if (!n) return () => {};
6
- let a = `[forge:devtools:${i.label ?? "form"}]`, o = /* @__PURE__ */ new Map(), s = r.state.isSubmitting, c = r.state.isLoading;
7
- function l(t) {
8
- let n = new Set(o.keys());
9
- for (let t of Object.keys(e(r.values()))) n.add(t);
10
- for (let e of Object.keys(t.errors)) n.add(e);
11
- for (let e of t.touchedFields) n.add(e);
12
- return n;
13
- }
14
- function u(e) {
15
- for (let n of l(e)) {
16
- let e = r.field(n), i = o.get(n);
17
- if (o.set(n, e), !i) continue;
18
- let s = t(n, 80);
19
- i.value !== e.value && console.debug(`${a} field "${s}" value:`, i.value, "→", e.value), i.error !== e.error && console.debug(`${a} field "${s}" error:`, i.error, "→", e.error), i.touched !== e.touched && console.debug(`${a} field "${s}" touched:`, e.touched), i.dirty !== e.dirty && console.debug(`${a} field "${s}" dirty:`, e.dirty);
20
- }
21
- }
22
- return r.subscribe((e) => {
23
- u(e), e.isSubmitting !== s && (s = e.isSubmitting, console.debug(`${a} submit ${e.isSubmitting ? "started" : "ended"} (submitCount=${e.submitCount})`)), e.isLoading !== c && (c = e.isLoading, console.debug(`${a} isLoading:`, e.isLoading));
24
- }, { sync: !0 });
2
+ function e(e, t = {}) {
3
+ let n = `[forge:devtools:${t.label ?? "form"}]`, r;
4
+ return e.subscribe((e) => {
5
+ r && (r.submitting !== e.submitting && console.debug(`${n} submitting:`, e.submitting), r.validating !== e.validating && console.debug(`${n} validating:`, e.validating), r.valid !== e.valid && console.debug(`${n} valid:`, e.valid)), r = e;
6
+ }, { immediate: !0 });
25
7
  }
26
8
  //#endregion
27
- export { r as debugForm };
9
+ export { e as debugForm };
28
10
 
29
11
  //# sourceMappingURL=devtools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.js","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["/**\n * @vielzeug/forge — debug utilities for form state visualisation.\n *\n * Import from the dedicated sub-path so it is tree-shaken from production bundles:\n * ```ts\n * import { debugForm } from '@vielzeug/forge/devtools';\n * ```\n */\n\nimport type { FieldState, FlatKeyOf, Form, FormState, Unsubscribe } from './types';\n\nimport { flattenValues, sanitizeForLog } from './_utils';\n\nconst isDev = !(globalThis as { __FORGE_PROD__?: boolean }).__FORGE_PROD__;\n\n/** Options for {@link debugForm}. */\nexport type ForgeDevtoolsOptions = {\n /** Label included in every log line, useful when debugging multiple forms at once. Default: `\"form\"`. */\n label?: string;\n};\n\n/**\n * Attaches a `console.debug`-based observer to `form` that logs a distinct line per observable\n * state transition: per-field value/error/touched/dirty changes, submit start/end (via the\n * `isSubmitting` edge), and `isLoading` edge (async `defaultValues` resolving).\n *\n * **Development only** by default — logging is a no-op when `__FORGE_PROD__` is set (the same\n * convention `_dev.ts` uses internally). Import from the dedicated `/devtools` sub-path so the\n * logging code is tree-shaken from production bundles entirely.\n *\n * Works identically on scoped sub-forms (`form.scope('address')`) — field paths logged are\n * relative to whatever `form` object is passed in, matching that form's own `state` convention.\n *\n * @example\n * ```ts\n * import { createForm } from '@vielzeug/forge';\n * import { debugForm } from '@vielzeug/forge/devtools';\n *\n * const form = createForm({ defaultValues: { email: '' } });\n * const detach = debugForm(form, { label: 'signup' });\n * // [forge:devtools:signup] field \"email\" value: \"\" → \"a@b.com\"\n *\n * detach(); // stop logging\n * ```\n */\nexport function debugForm<TValues extends Record<string, unknown>>(\n form: Form<TValues>,\n options: ForgeDevtoolsOptions = {},\n): Unsubscribe {\n if (!isDev) return () => {};\n\n const prefix = `[forge:devtools:${options.label ?? 'form'}]`;\n const fieldSnapshots = new Map<string, FieldState<unknown>>();\n\n let prevIsSubmitting = form.state.isSubmitting;\n let prevIsLoading = form.state.isLoading;\n\n function knownFieldKeys(state: FormState): Set<string> {\n const keys = new Set<string>(fieldSnapshots.keys());\n\n for (const key of Object.keys(flattenValues(form.values() as Record<string, unknown>))) keys.add(key);\n for (const key of Object.keys(state.errors)) keys.add(key);\n for (const key of state.touchedFields) keys.add(key);\n\n return keys;\n }\n\n function logFieldDiffs(state: FormState): void {\n for (const key of knownFieldKeys(state)) {\n const next = form.field(key as FlatKeyOf<TValues>);\n const prev = fieldSnapshots.get(key);\n\n fieldSnapshots.set(key, next);\n\n // First observation of this field establishes a baseline — logging it would just be\n // noise (every field would \"change\" from undefined on attach).\n if (!prev) continue;\n\n // Security: `key` is a caller-chosen field path (assertSafeKey only rejects reserved\n // segments, not control characters) — sanitize before interpolating into console output\n // to prevent terminal-escape-sequence injection, mirroring the sanitization `warn()`\n // call sites already apply (see `_utils.ts` sanitizeForLog).\n const safeKey = sanitizeForLog(key, 80);\n\n if (prev.value !== next.value) console.debug(`${prefix} field \"${safeKey}\" value:`, prev.value, '→', next.value);\n\n if (prev.error !== next.error) console.debug(`${prefix} field \"${safeKey}\" error:`, prev.error, '→', next.error);\n\n if (prev.touched !== next.touched) console.debug(`${prefix} field \"${safeKey}\" touched:`, next.touched);\n\n if (prev.dirty !== next.dirty) console.debug(`${prefix} field \"${safeKey}\" dirty:`, next.dirty);\n }\n }\n\n return form.subscribe(\n (state) => {\n logFieldDiffs(state);\n\n if (state.isSubmitting !== prevIsSubmitting) {\n prevIsSubmitting = state.isSubmitting;\n console.debug(\n `${prefix} submit ${state.isSubmitting ? 'started' : 'ended'} (submitCount=${state.submitCount})`,\n );\n }\n\n if (state.isLoading !== prevIsLoading) {\n prevIsLoading = state.isLoading;\n console.debug(`${prefix} isLoading:`, state.isLoading);\n }\n },\n { sync: true },\n );\n}\n"],"mappings":";;AAaA,IAAM,IAAQ,CAAE,WAA4C;AAgC5D,SAAgB,EACd,GACA,IAAgC,CAAC,GACpB;CACb,IAAI,CAAC,GAAO,aAAa,CAAC;CAE1B,IAAM,IAAS,mBAAmB,EAAQ,SAAS,OAAO,IACpD,oBAAiB,IAAI,IAAiC,GAExD,IAAmB,EAAK,MAAM,cAC9B,IAAgB,EAAK,MAAM;CAE/B,SAAS,EAAe,GAA+B;EACrD,IAAM,IAAO,IAAI,IAAY,EAAe,KAAK,CAAC;EAElD,KAAK,IAAM,KAAO,OAAO,KAAK,EAAc,EAAK,OAAO,CAA4B,CAAC,GAAG,EAAK,IAAI,CAAG;EACpG,KAAK,IAAM,KAAO,OAAO,KAAK,EAAM,MAAM,GAAG,EAAK,IAAI,CAAG;EACzD,KAAK,IAAM,KAAO,EAAM,eAAe,EAAK,IAAI,CAAG;EAEnD,OAAO;CACT;CAEA,SAAS,EAAc,GAAwB;EAC7C,KAAK,IAAM,KAAO,EAAe,CAAK,GAAG;GACvC,IAAM,IAAO,EAAK,MAAM,CAAyB,GAC3C,IAAO,EAAe,IAAI,CAAG;GAMnC,IAJA,EAAe,IAAI,GAAK,CAAI,GAIxB,CAAC,GAAM;GAMX,IAAM,IAAU,EAAe,GAAK,EAAE;GAQtC,AANI,EAAK,UAAU,EAAK,SAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,WAAW,EAAK,OAAO,KAAK,EAAK,KAAK,GAE3G,EAAK,UAAU,EAAK,SAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,WAAW,EAAK,OAAO,KAAK,EAAK,KAAK,GAE3G,EAAK,YAAY,EAAK,WAAS,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,aAAa,EAAK,OAAO,GAElG,EAAK,UAAU,EAAK,SAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAQ,WAAW,EAAK,KAAK;EAChG;CACF;CAEA,OAAO,EAAK,WACT,MAAU;EAUT,AATA,EAAc,CAAK,GAEf,EAAM,iBAAiB,MACzB,IAAmB,EAAM,cACzB,QAAQ,MACN,GAAG,EAAO,UAAU,EAAM,eAAe,YAAY,QAAQ,gBAAgB,EAAM,YAAY,EACjG,IAGE,EAAM,cAAc,MACtB,IAAgB,EAAM,WACtB,QAAQ,MAAM,GAAG,EAAO,cAAc,EAAM,SAAS;CAEzD,GACA,EAAE,MAAM,GAAK,CACf;AACF"}
1
+ {"version":3,"file":"devtools.js","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["import type { Form, FormState, Unsubscribe } from './types';\n\nexport type ForgeDevtoolsOptions = Readonly<{\n label?: string;\n}>;\n\n/** Logs public form-state transitions without coupling diagnostics to form internals. */\nexport function debugForm<TValues extends Record<string, unknown>>(\n form: Form<TValues>,\n options: ForgeDevtoolsOptions = {},\n): Unsubscribe {\n const prefix = `[forge:devtools:${options.label ?? 'form'}]`;\n let previous: FormState<TValues> | undefined;\n\n return form.subscribe(\n (next) => {\n if (previous) {\n if (previous.submitting !== next.submitting) console.debug(`${prefix} submitting:`, next.submitting);\n\n if (previous.validating !== next.validating) console.debug(`${prefix} validating:`, next.validating);\n\n if (previous.valid !== next.valid) console.debug(`${prefix} valid:`, next.valid);\n }\n\n previous = next;\n },\n { immediate: true },\n );\n}\n"],"mappings":";AAOA,SAAgB,EACd,GACA,IAAgC,CAAC,GACpB;CACb,IAAM,IAAS,mBAAmB,EAAQ,SAAS,OAAO,IACtD;CAEJ,OAAO,EAAK,WACT,MAAS;EASR,AARI,MACE,EAAS,eAAe,EAAK,cAAY,QAAQ,MAAM,GAAG,EAAO,eAAe,EAAK,UAAU,GAE/F,EAAS,eAAe,EAAK,cAAY,QAAQ,MAAM,GAAG,EAAO,eAAe,EAAK,UAAU,GAE/F,EAAS,UAAU,EAAK,SAAO,QAAQ,MAAM,GAAG,EAAO,UAAU,EAAK,KAAK,IAGjF,IAAW;CACb,GACA,EAAE,WAAW,GAAK,CACpB;AACF"}
package/dist/dom.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e,t,n){let r=n.event??`input`,i=!1,a=()=>{i=!0;try{t.set(n.read(e))}finally{i=!1}},o=()=>t.touch(),s=!1,c=t.value,l=n.write?t.subscribe(({value:t})=>{if(i){s=!0,c=t;return}s&&t===c||(s=!0,c=t,n.write?.(e,t))},{immediate:!0}):()=>{};return e.addEventListener(r,a),e.addEventListener(`blur`,o),()=>{e.removeEventListener(r,a),e.removeEventListener(`blur`,o),l()}}exports.bindField=e;
2
+ //# sourceMappingURL=dom.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.cjs","names":[],"sources":["../src/dom.ts"],"sourcesContent":["import type { Field, ReadonlyDeep } from './types';\n\nexport type FieldBindingOptions<Element extends HTMLElement, V> = Readonly<{\n event?: keyof HTMLElementEventMap;\n read(element: Element): V;\n write?: (element: Element, value: ReadonlyDeep<V>) => void;\n}>;\n\n/** Binds one field to one element without imposing parsing, debounce, or validation policy. */\nexport function bindField<Element extends HTMLElement, V>(\n element: Element,\n field: Field<V>,\n options: FieldBindingOptions<Element, V>,\n): () => void {\n const event = options.event ?? 'input';\n let updatingFromElement = false;\n const onInput = () => {\n updatingFromElement = true;\n\n try {\n field.set(options.read(element));\n } finally {\n updatingFromElement = false;\n }\n };\n const onBlur = () => field.touch();\n let hasRendered = false;\n let rendered = field.value;\n const unsubscribe = options.write\n ? field.subscribe(\n ({ value }) => {\n if (updatingFromElement) {\n hasRendered = true;\n rendered = value;\n\n return;\n }\n\n if (hasRendered && value === rendered) return;\n\n hasRendered = true;\n rendered = value;\n options.write?.(element, value);\n },\n { immediate: true },\n )\n : () => {};\n\n element.addEventListener(event, onInput);\n element.addEventListener('blur', onBlur);\n\n return () => {\n element.removeEventListener(event, onInput);\n element.removeEventListener('blur', onBlur);\n unsubscribe();\n };\n}\n"],"mappings":"mEASA,SAAgB,EACd,EACA,EACA,EACY,CACZ,IAAM,EAAQ,EAAQ,OAAS,QAC3B,EAAsB,GACpB,MAAgB,CACpB,EAAsB,GAEtB,GAAI,CACF,EAAM,IAAI,EAAQ,KAAK,CAAO,CAAC,CACjC,QAAU,CACR,EAAsB,EACxB,CACF,EACM,MAAe,EAAM,MAAM,EAC7B,EAAc,GACd,EAAW,EAAM,MACf,EAAc,EAAQ,MACxB,EAAM,WACH,CAAE,WAAY,CACb,GAAI,EAAqB,CACvB,EAAc,GACd,EAAW,EAEX,MACF,CAEI,GAAe,IAAU,IAE7B,EAAc,GACd,EAAW,EACX,EAAQ,QAAQ,EAAS,CAAK,EAChC,EACA,CAAE,UAAW,EAAK,CACpB,MACM,CAAC,EAKX,OAHA,EAAQ,iBAAiB,EAAO,CAAO,EACvC,EAAQ,iBAAiB,OAAQ,CAAM,MAE1B,CACX,EAAQ,oBAAoB,EAAO,CAAO,EAC1C,EAAQ,oBAAoB,OAAQ,CAAM,EAC1C,EAAY,CACd,CACF"}
package/dist/dom.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { Field, ReadonlyDeep } from './types';
2
+ export type FieldBindingOptions<Element extends HTMLElement, V> = Readonly<{
3
+ event?: keyof HTMLElementEventMap;
4
+ read(element: Element): V;
5
+ write?: (element: Element, value: ReadonlyDeep<V>) => void;
6
+ }>;
7
+ /** Binds one field to one element without imposing parsing, debounce, or validation policy. */
8
+ export declare function bindField<Element extends HTMLElement, V>(element: Element, field: Field<V>, options: FieldBindingOptions<Element, V>): () => void;
9
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../src/dom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,MAAM,MAAM,mBAAmB,CAAC,OAAO,SAAS,WAAW,EAAE,CAAC,IAAI,QAAQ,CAAC;IACzE,KAAK,CAAC,EAAE,MAAM,mBAAmB,CAAC;IAClC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CAC5D,CAAC,CAAC;AAEH,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,OAAO,SAAS,WAAW,EAAE,CAAC,EACtD,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EACf,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,GACvC,MAAM,IAAI,CA2CZ"}