@vielzeug/forge 1.3.1 → 2.0.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.
- package/README.md +21 -53
- package/dist/_utils.cjs +1 -2
- package/dist/_utils.d.ts +1 -10
- package/dist/_utils.d.ts.map +1 -1
- package/dist/_utils.js +2 -32
- package/dist/adapters/form-data.cjs +1 -1
- package/dist/adapters/form-data.cjs.map +1 -1
- package/dist/adapters/form-data.d.ts.map +1 -1
- package/dist/adapters/form-data.js +5 -5
- package/dist/adapters/form-data.js.map +1 -1
- package/dist/core/path.cjs +2 -0
- package/dist/core/path.cjs.map +1 -0
- package/dist/core/path.d.ts +22 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +125 -0
- package/dist/core/path.js.map +1 -0
- package/dist/devtools.cjs +1 -1
- package/dist/devtools.cjs.map +1 -1
- package/dist/devtools.d.ts +3 -36
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +6 -24
- package/dist/devtools.js.map +1 -1
- package/dist/dom.cjs +2 -0
- package/dist/dom.cjs.map +1 -0
- package/dist/dom.d.ts +9 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +24 -0
- package/dist/dom.js.map +1 -0
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +4 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -7
- package/dist/errors.js.map +1 -1
- package/dist/forge.cjs +1 -1
- package/dist/forge.cjs.map +1 -1
- package/dist/forge.iife.js +1 -1
- package/dist/forge.iife.js.map +1 -1
- package/dist/forge.js +1 -1
- package/dist/forge.js.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.cjs.map +1 -1
- package/dist/form.d.ts +3 -5
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +225 -71
- package/dist/form.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +3 -4
- package/dist/spell.cjs +2 -0
- package/dist/spell.cjs.map +1 -0
- package/dist/spell.d.ts +5 -0
- package/dist/spell.d.ts.map +1 -0
- package/dist/spell.js +57 -0
- package/dist/spell.js.map +1 -0
- package/dist/types.d.ts +70 -375
- package/dist/types.d.ts.map +1 -1
- package/dist/vault.cjs +2 -0
- package/dist/vault.cjs.map +1 -0
- package/dist/vault.d.ts +10 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +14 -0
- package/dist/vault.js.map +1 -0
- package/package.json +36 -11
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.js +0 -12
- package/dist/_dev.js.map +0 -1
- package/dist/_utils.cjs.map +0 -1
- package/dist/_utils.js.map +0 -1
- package/dist/adapters/validators.cjs +0 -2
- package/dist/adapters/validators.cjs.map +0 -1
- package/dist/adapters/validators.d.ts +0 -44
- package/dist/adapters/validators.d.ts.map +0 -1
- package/dist/adapters/validators.js +0 -20
- package/dist/adapters/validators.js.map +0 -1
- package/dist/core/array.cjs +0 -2
- package/dist/core/array.cjs.map +0 -1
- package/dist/core/array.d.ts +0 -10
- package/dist/core/array.d.ts.map +0 -1
- package/dist/core/array.js +0 -46
- package/dist/core/array.js.map +0 -1
- package/dist/core/asyncQueue.cjs +0 -2
- package/dist/core/asyncQueue.cjs.map +0 -1
- package/dist/core/asyncQueue.d.ts +0 -22
- package/dist/core/asyncQueue.d.ts.map +0 -1
- package/dist/core/asyncQueue.js +0 -67
- package/dist/core/asyncQueue.js.map +0 -1
- package/dist/core/context.cjs +0 -2
- package/dist/core/context.cjs.map +0 -1
- package/dist/core/context.d.ts +0 -10
- package/dist/core/context.d.ts.map +0 -1
- package/dist/core/context.js +0 -176
- package/dist/core/context.js.map +0 -1
- package/dist/core/lifecycle.cjs +0 -2
- package/dist/core/lifecycle.cjs.map +0 -1
- package/dist/core/lifecycle.d.ts +0 -13
- package/dist/core/lifecycle.d.ts.map +0 -1
- package/dist/core/lifecycle.js +0 -42
- package/dist/core/lifecycle.js.map +0 -1
- package/dist/core/observe.cjs +0 -2
- package/dist/core/observe.cjs.map +0 -1
- package/dist/core/observe.d.ts +0 -18
- package/dist/core/observe.d.ts.map +0 -1
- package/dist/core/observe.js +0 -90
- package/dist/core/observe.js.map +0 -1
- package/dist/core/scope.cjs +0 -2
- package/dist/core/scope.cjs.map +0 -1
- package/dist/core/scope.d.ts +0 -8
- package/dist/core/scope.d.ts.map +0 -1
- package/dist/core/scope.js +0 -286
- package/dist/core/scope.js.map +0 -1
- package/dist/core/validation.cjs +0 -2
- package/dist/core/validation.cjs.map +0 -1
- package/dist/core/validation.d.ts +0 -23
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/core/validation.js +0 -173
- package/dist/core/validation.js.map +0 -1
- package/dist/core/values.cjs +0 -2
- package/dist/core/values.cjs.map +0 -1
- package/dist/core/values.d.ts +0 -31
- package/dist/core/values.d.ts.map +0 -1
- package/dist/core/values.js +0 -149
- package/dist/core/values.js.map +0 -1
- package/dist/types.cjs +0 -2
- package/dist/types.cjs.map +0 -1
- package/dist/types.js +0 -21
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
# @vielzeug/forge
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@vielzeug/forge) [](https://opensource.org/licenses/MIT)
|
|
6
|
-
|
|
7
|
-
<details>
|
|
8
|
-
<summary>Quick Reference</summary>
|
|
9
|
-
|
|
10
|
-
**Package:** `@vielzeug/forge` · **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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
17
|
+
const email = form.field('profile').field('email');
|
|
18
|
+
email.set('ada@example.com');
|
|
44
19
|
|
|
45
|
-
const result = await form.
|
|
46
|
-
|
|
20
|
+
const result = await form.submit((value) => saveProfile(value));
|
|
21
|
+
```
|
|
47
22
|
|
|
48
|
-
|
|
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
|
-
|
|
57
|
-
console.log(submission.errors);
|
|
58
|
-
}
|
|
59
|
-
```
|
|
25
|
+
## Optional adapters
|
|
60
26
|
|
|
61
|
-
|
|
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
|
-
|
|
31
|
+
## Breaking 2.0 changes
|
|
64
32
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
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
|
|
73
|
-
- [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 ©
|
|
46
|
+
MIT © Helmuth Saatkamp — part of the Vielzeug monorepo.
|
package/dist/_utils.cjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
package/dist/_utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_utils.d.ts","sourceRoot":"","sources":["../src/_utils.ts"],"names":[],"mappings":"
|
|
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,32 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { flattenPaths as r, unflattenPaths as i } from "@vielzeug/arsenal";
|
|
4
|
-
//#region src/_utils.ts
|
|
5
|
-
var a = /* @__PURE__ */ new Set([
|
|
6
|
-
"__proto__",
|
|
7
|
-
"constructor",
|
|
8
|
-
"prototype"
|
|
9
|
-
]);
|
|
10
|
-
function o(e) {
|
|
11
|
-
return e.split(".").every((e) => !a.has(e));
|
|
12
|
-
}
|
|
13
|
-
function s(e) {
|
|
14
|
-
if (l(e), !o(e)) throw new n(`Unsafe key '${e}': segments __proto__, constructor, and prototype are reserved.`);
|
|
15
|
-
}
|
|
16
|
-
var c = /* @__PURE__ */ new Set();
|
|
17
|
-
function l(n) {
|
|
18
|
-
n.includes(".") && (n.split(".").length <= 5 || c.has(n) || (c.add(n), e(() => {
|
|
19
|
-
t(`Path '${u(n, 120)}' exceeds typed depth limits and may be treated as an untyped string path.`);
|
|
20
|
-
})));
|
|
21
|
-
}
|
|
22
|
-
function u(e, t = 200) {
|
|
23
|
-
return e.replace(/\p{C}/gu, "?").slice(0, t);
|
|
24
|
-
}
|
|
25
|
-
function d(...e) {
|
|
26
|
-
let t = e.filter((e) => e != null);
|
|
27
|
-
if (t.length !== 0) return t.length === 1 ? t[0] : AbortSignal.any(t);
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { d as anySignal, s as assertSafeKey, r as flattenValues, o as isSafeKey, u as sanitizeForLog, i as unflattenValues };
|
|
31
|
-
|
|
32
|
-
//# 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[
|
|
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
|
|
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,
|
|
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 [
|
|
6
|
-
else if (
|
|
7
|
-
else if (Array.isArray(
|
|
8
|
-
else n.append(
|
|
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
|
|
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`});
|
|
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
|
package/dist/devtools.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.cjs","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["
|
|
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"}
|
package/dist/devtools.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/devtools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAAA
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
9
|
+
export { e as debugForm };
|
|
28
10
|
|
|
29
11
|
//# sourceMappingURL=devtools.js.map
|
package/dist/devtools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.js","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["
|
|
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
|
package/dist/dom.cjs.map
ADDED
|
@@ -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"}
|