@vielzeug/forge 3.1.0 → 3.1.2

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 (50) hide show
  1. package/dist/_field.cjs +1 -1
  2. package/dist/_field.cjs.map +1 -1
  3. package/dist/_field.js +1 -63
  4. package/dist/_field.js.map +1 -1
  5. package/dist/_notify.cjs +1 -1
  6. package/dist/_notify.cjs.map +1 -1
  7. package/dist/_notify.js +1 -52
  8. package/dist/_notify.js.map +1 -1
  9. package/dist/core/path.cjs +1 -1
  10. package/dist/core/path.cjs.map +1 -1
  11. package/dist/core/path.js +1 -219
  12. package/dist/core/path.js.map +1 -1
  13. package/dist/dom.cjs +1 -1
  14. package/dist/dom.cjs.map +1 -1
  15. package/dist/dom.js +1 -23
  16. package/dist/dom.js.map +1 -1
  17. package/dist/errors.cjs +1 -1
  18. package/dist/errors.cjs.map +1 -1
  19. package/dist/errors.d.ts +0 -5
  20. package/dist/errors.d.ts.map +1 -1
  21. package/dist/errors.js +1 -21
  22. package/dist/errors.js.map +1 -1
  23. package/dist/forge.cjs +1 -1
  24. package/dist/forge.cjs.map +1 -1
  25. package/dist/forge.iife.js +1 -1
  26. package/dist/forge.iife.js.map +1 -1
  27. package/dist/forge.js +1 -1
  28. package/dist/forge.js.map +1 -1
  29. package/dist/form-data.cjs +1 -1
  30. package/dist/form-data.cjs.map +1 -1
  31. package/dist/form-data.js +1 -29
  32. package/dist/form-data.js.map +1 -1
  33. package/dist/form.cjs +1 -1
  34. package/dist/form.cjs.map +1 -1
  35. package/dist/form.js +1 -236
  36. package/dist/form.js.map +1 -1
  37. package/dist/index.js +1 -4
  38. package/dist/persist.cjs +1 -1
  39. package/dist/persist.cjs.map +1 -1
  40. package/dist/persist.js +1 -18
  41. package/dist/persist.js.map +1 -1
  42. package/dist/plain.cjs +1 -1
  43. package/dist/plain.cjs.map +1 -1
  44. package/dist/plain.js +1 -8
  45. package/dist/plain.js.map +1 -1
  46. package/dist/schema.cjs +1 -1
  47. package/dist/schema.cjs.map +1 -1
  48. package/dist/schema.js +1 -25
  49. package/dist/schema.js.map +1 -1
  50. package/package.json +6 -6
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./core/path.cjs");let n=require("@vielzeug/arsenal");function r(i,a){let o={};for(let[s,c]of Object.entries(i)){if(t.isUnsafeKey(s))continue;if(s.includes(`.`))throw new e.ForgeConfigError(`FormData keys must not contain dots: '${s}'.`);let i=a?`${a}.${s}`:s;(0,n.isPlainObject)(c)?Object.assign(o,r(c,i)):o[i]=c}return o}function i(t){let i=new FormData,a=e=>typeof Blob<`u`&&e instanceof Blob,o=e=>typeof File<`u`&&e instanceof File,s=e=>typeof FileList<`u`&&e instanceof FileList;for(let[c,l]of Object.entries(r(t,``)))if(l!=null){if(o(l)||a(l))i.append(c,l);else if(s(l))for(let e=0;e<l.length;e++)i.append(c,l[e]);else if(Array.isArray(l))for(let t of l){if((0,n.isPlainObject)(t)||Array.isArray(t))throw new e.ForgeConfigError(`FormData array '${c}' must contain only scalar or binary values.`);i.append(c,o(t)||a(t)?t:String(t))}else i.append(c,String(l))}return i}exports.toFormData=i;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./core/path.cjs");let n=require("@vielzeug/arsenal");function flatten(r,i){let a={};for(let[o,s]of Object.entries(r)){if(t.isUnsafeKey(o))continue;if(o.includes(`.`))throw new e.ForgeConfigError(`FormData keys must not contain dots: '${o}'.`);let r=i?`${i}.${o}`:o;(0,n.isPlainObject)(s)?Object.assign(a,flatten(s,r)):a[r]=s}return a}function toFormData(t){let r=new FormData,isBlob=e=>typeof Blob<`u`&&e instanceof Blob,isFile=e=>typeof File<`u`&&e instanceof File,isFileList=e=>typeof FileList<`u`&&e instanceof FileList;for(let[i,a]of Object.entries(flatten(t,``)))if(a!=null){if(isFile(a)||isBlob(a))r.append(i,a);else if(isFileList(a))for(let e=0;e<a.length;e++)r.append(i,a[e]);else if(Array.isArray(a))for(let t of a){if((0,n.isPlainObject)(t)||Array.isArray(t))throw new e.ForgeConfigError(`FormData array '${i}' must contain only scalar or binary values.`);r.append(i,isFile(t)||isBlob(t)?t:String(t))}else r.append(i,String(a))}return r}exports.toFormData=toFormData;
2
2
  //# sourceMappingURL=form-data.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-data.cjs","names":[],"sources":["../src/form-data.ts"],"sourcesContent":["import { isRecord, isUnsafeKey } from './core/path.js';\nimport { ForgeConfigError } from './errors.js';\n\nfunction flatten(obj: Record<string, unknown>, prefix: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isUnsafeKey(key)) continue;\n if (key.includes('.')) throw new ForgeConfigError(`FormData keys must not contain dots: '${key}'.`);\n\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (isRecord(value)) {\n Object.assign(result, flatten(value, fullKey));\n } else {\n result[fullKey] = value;\n }\n }\n\n return result;\n}\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; scalar arrays use repeated keys.\n * `null` and `undefined` values are omitted. Ambiguous dotted keys and nested array containers reject.\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(flatten(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) {\n if (isRecord(item) || Array.isArray(item)) {\n throw new ForgeConfigError(`FormData array '${name}' must contain only scalar or binary values.`);\n }\n fd.append(name, isFile(item) || isBlob(item) ? item : String(item));\n }\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":"mKAGA,SAAS,EAAQ,EAA8B,EAAyC,CACtF,IAAM,EAAkC,CAAC,EAEzC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAG,EAAG,CAC9C,GAAI,EAAA,YAAY,CAAG,EAAG,SACtB,GAAI,EAAI,SAAS,GAAG,EAAG,MAAM,IAAI,EAAA,iBAAiB,yCAAyC,EAAI,GAAG,EAElG,IAAM,EAAU,EAAS,GAAG,EAAO,GAAG,IAAQ,GAE9C,EAAI,EAAA,cAAA,CAAS,CAAK,EAChB,OAAO,OAAO,EAAQ,EAAQ,EAAO,CAAO,CAAC,EAE7C,EAAO,GAAW,CAEtB,CAEA,OAAO,CACT,CAQA,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,QAAQ,EAAQ,EAAQ,EAAE,CAAC,EACxD,MAAU,KAEd,IAAI,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,CACxB,IAAA,EAAI,EAAA,cAAA,CAAS,CAAI,GAAK,MAAM,QAAQ,CAAI,EACtC,MAAM,IAAI,EAAA,iBAAiB,mBAAmB,EAAK,6CAA6C,EAElG,EAAG,OAAO,EAAM,EAAO,CAAI,GAAK,EAAO,CAAI,EAAI,EAAO,OAAO,CAAI,CAAC,CACpE,MAEA,EAAG,OAAO,EAAM,OAAO,CAAK,CAAC,CAAA,CAIjC,OAAO,CACT"}
1
+ {"version":3,"file":"form-data.cjs","names":[],"sources":["../src/form-data.ts"],"sourcesContent":["import { isRecord, isUnsafeKey } from './core/path.js';\nimport { ForgeConfigError } from './errors.js';\n\nfunction flatten(obj: Record<string, unknown>, prefix: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isUnsafeKey(key)) continue;\n if (key.includes('.')) throw new ForgeConfigError(`FormData keys must not contain dots: '${key}'.`);\n\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (isRecord(value)) {\n Object.assign(result, flatten(value, fullKey));\n } else {\n result[fullKey] = value;\n }\n }\n\n return result;\n}\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; scalar arrays use repeated keys.\n * `null` and `undefined` values are omitted. Ambiguous dotted keys and nested array containers reject.\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(flatten(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) {\n if (isRecord(item) || Array.isArray(item)) {\n throw new ForgeConfigError(`FormData array '${name}' must contain only scalar or binary values.`);\n }\n fd.append(name, isFile(item) || isBlob(item) ? item : String(item));\n }\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":"mKAGA,SAAS,QAAQ,EAA8B,EAAyC,CACtF,IAAM,EAAkC,CAAC,EAEzC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAG,EAAG,CAC9C,GAAI,EAAA,YAAY,CAAG,EAAG,SACtB,GAAI,EAAI,SAAS,GAAG,EAAG,MAAM,IAAI,EAAA,iBAAiB,yCAAyC,EAAI,GAAG,EAElG,IAAM,EAAU,EAAS,GAAG,EAAO,GAAG,IAAQ,GAE9C,EAAI,EAAA,cAAA,CAAS,CAAK,EAChB,OAAO,OAAO,EAAQ,QAAQ,EAAO,CAAO,CAAC,EAE7C,EAAO,GAAW,CAEtB,CAEA,OAAO,CACT,CAQA,SAAgB,WAAW,EAA2C,CACpE,IAAM,EAAK,IAAI,SAET,OAAU,GAAkC,OAAO,KAAS,KAAe,aAAiB,KAC5F,OAAU,GAAkC,OAAO,KAAS,KAAe,aAAiB,KAC5F,WAAc,GAClB,OAAO,SAAa,KAAe,aAAiB,SAEtD,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,QAAQ,EAAQ,EAAE,CAAC,EACxD,MAAU,KAEd,IAAI,OAAO,CAAK,GAAK,OAAO,CAAK,EAC/B,EAAG,OAAO,EAAM,CAAK,OAChB,GAAI,WAAW,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,CACxB,IAAA,EAAI,EAAA,cAAA,CAAS,CAAI,GAAK,MAAM,QAAQ,CAAI,EACtC,MAAM,IAAI,EAAA,iBAAiB,mBAAmB,EAAK,6CAA6C,EAElG,EAAG,OAAO,EAAM,OAAO,CAAI,GAAK,OAAO,CAAI,EAAI,EAAO,OAAO,CAAI,CAAC,CACpE,MAEA,EAAG,OAAO,EAAM,OAAO,CAAK,CAAC,CAAA,CAIjC,OAAO,CACT"}
package/dist/form-data.js CHANGED
@@ -1,30 +1,2 @@
1
- import { ForgeConfigError as e } from "./errors.js";
2
- import { isRecord as t, isUnsafeKey as n } from "./core/path.js";
3
- //#region src/form-data.ts
4
- function r(i, a) {
5
- let o = {};
6
- for (let [s, c] of Object.entries(i)) {
7
- if (n(s)) continue;
8
- if (s.includes(".")) throw new e(`FormData keys must not contain dots: '${s}'.`);
9
- let i = a ? `${a}.${s}` : s;
10
- t(c) ? Object.assign(o, r(c, i)) : o[i] = c;
11
- }
12
- return o;
13
- }
14
- function i(n) {
15
- let i = new FormData(), a = (e) => typeof Blob < "u" && e instanceof Blob, o = (e) => typeof File < "u" && e instanceof File, s = (e) => typeof FileList < "u" && e instanceof FileList;
16
- for (let [c, l] of Object.entries(r(n, ""))) if (l != null) {
17
- if (o(l) || a(l)) i.append(c, l);
18
- else if (s(l)) for (let e = 0; e < l.length; e++) i.append(c, l[e]);
19
- else if (Array.isArray(l)) for (let n of l) {
20
- if (t(n) || Array.isArray(n)) throw new e(`FormData array '${c}' must contain only scalar or binary values.`);
21
- i.append(c, o(n) || a(n) ? n : String(n));
22
- }
23
- else i.append(c, String(l));
24
- }
25
- return i;
26
- }
27
- //#endregion
28
- export { i as toFormData };
29
-
1
+ import{ForgeConfigError as e}from"./errors.js";import{isRecord as t,isUnsafeKey as n}from"./core/path.js";function flatten(r,i){let a={};for(let[o,s]of Object.entries(r)){if(n(o))continue;if(o.includes(`.`))throw new e(`FormData keys must not contain dots: '${o}'.`);let r=i?`${i}.${o}`:o;t(s)?Object.assign(a,flatten(s,r)):a[r]=s}return a}function toFormData(n){let r=new FormData,isBlob=e=>typeof Blob<`u`&&e instanceof Blob,isFile=e=>typeof File<`u`&&e instanceof File,isFileList=e=>typeof FileList<`u`&&e instanceof FileList;for(let[i,a]of Object.entries(flatten(n,``)))if(a!=null){if(isFile(a)||isBlob(a))r.append(i,a);else if(isFileList(a))for(let e=0;e<a.length;e++)r.append(i,a[e]);else if(Array.isArray(a))for(let n of a){if(t(n)||Array.isArray(n))throw new e(`FormData array '${i}' must contain only scalar or binary values.`);r.append(i,isFile(n)||isBlob(n)?n:String(n))}else r.append(i,String(a))}return r}export{toFormData};
30
2
  //# sourceMappingURL=form-data.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-data.js","names":[],"sources":["../src/form-data.ts"],"sourcesContent":["import { isRecord, isUnsafeKey } from './core/path.js';\nimport { ForgeConfigError } from './errors.js';\n\nfunction flatten(obj: Record<string, unknown>, prefix: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isUnsafeKey(key)) continue;\n if (key.includes('.')) throw new ForgeConfigError(`FormData keys must not contain dots: '${key}'.`);\n\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (isRecord(value)) {\n Object.assign(result, flatten(value, fullKey));\n } else {\n result[fullKey] = value;\n }\n }\n\n return result;\n}\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; scalar arrays use repeated keys.\n * `null` and `undefined` values are omitted. Ambiguous dotted keys and nested array containers reject.\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(flatten(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) {\n if (isRecord(item) || Array.isArray(item)) {\n throw new ForgeConfigError(`FormData array '${name}' must contain only scalar or binary values.`);\n }\n fd.append(name, isFile(item) || isBlob(item) ? item : String(item));\n }\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":";;;AAGA,SAAS,EAAQ,GAA8B,GAAyC;CACtF,IAAM,IAAkC,CAAC;CAEzC,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAG,GAAG;EAC9C,IAAI,EAAY,CAAG,GAAG;EACtB,IAAI,EAAI,SAAS,GAAG,GAAG,MAAM,IAAI,EAAiB,yCAAyC,EAAI,GAAG;EAElG,IAAM,IAAU,IAAS,GAAG,EAAO,GAAG,MAAQ;EAE9C,AAAI,EAAS,CAAK,IAChB,OAAO,OAAO,GAAQ,EAAQ,GAAO,CAAO,CAAC,IAE7C,EAAO,KAAW;CAEtB;CAEA,OAAO;AACT;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,EAAQ,GAAQ,EAAE,CAAC,GACxD,SAAU,MAEd;MAAI,EAAO,CAAK,KAAK,EAAO,CAAK,GAC/B,EAAG,OAAO,GAAM,CAAK;OAChB,IAAI,EAAW,CAAK,GACzB,KAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK,EAAG,OAAO,GAAM,EAAM,EAAE;OAC1D,IAAI,MAAM,QAAQ,CAAK,GAC5B,KAAK,IAAM,KAAQ,GAAO;GACxB,IAAI,EAAS,CAAI,KAAK,MAAM,QAAQ,CAAI,GACtC,MAAM,IAAI,EAAiB,mBAAmB,EAAK,6CAA6C;GAElG,EAAG,OAAO,GAAM,EAAO,CAAI,KAAK,EAAO,CAAI,IAAI,IAAO,OAAO,CAAI,CAAC;EACpE;OAEA,EAAG,OAAO,GAAM,OAAO,CAAK,CAAC;CAAA;CAIjC,OAAO;AACT"}
1
+ {"version":3,"file":"form-data.js","names":[],"sources":["../src/form-data.ts"],"sourcesContent":["import { isRecord, isUnsafeKey } from './core/path.js';\nimport { ForgeConfigError } from './errors.js';\n\nfunction flatten(obj: Record<string, unknown>, prefix: string): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n if (isUnsafeKey(key)) continue;\n if (key.includes('.')) throw new ForgeConfigError(`FormData keys must not contain dots: '${key}'.`);\n\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (isRecord(value)) {\n Object.assign(result, flatten(value, fullKey));\n } else {\n result[fullKey] = value;\n }\n }\n\n return result;\n}\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; scalar arrays use repeated keys.\n * `null` and `undefined` values are omitted. Ambiguous dotted keys and nested array containers reject.\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(flatten(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) {\n if (isRecord(item) || Array.isArray(item)) {\n throw new ForgeConfigError(`FormData array '${name}' must contain only scalar or binary values.`);\n }\n fd.append(name, isFile(item) || isBlob(item) ? item : String(item));\n }\n } else {\n fd.append(name, String(value));\n }\n }\n\n return fd;\n}\n"],"mappings":"0GAGA,SAAS,QAAQ,EAA8B,EAAyC,CACtF,IAAM,EAAkC,CAAC,EAEzC,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAG,EAAG,CAC9C,GAAI,EAAY,CAAG,EAAG,SACtB,GAAI,EAAI,SAAS,GAAG,EAAG,MAAM,IAAI,EAAiB,yCAAyC,EAAI,GAAG,EAElG,IAAM,EAAU,EAAS,GAAG,EAAO,GAAG,IAAQ,EAE1C,EAAS,CAAK,EAChB,OAAO,OAAO,EAAQ,QAAQ,EAAO,CAAO,CAAC,EAE7C,EAAO,GAAW,CAEtB,CAEA,OAAO,CACT,CAQA,SAAgB,WAAW,EAA2C,CACpE,IAAM,EAAK,IAAI,SAET,OAAU,GAAkC,OAAO,KAAS,KAAe,aAAiB,KAC5F,OAAU,GAAkC,OAAO,KAAS,KAAe,aAAiB,KAC5F,WAAc,GAClB,OAAO,SAAa,KAAe,aAAiB,SAEtD,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,QAAQ,EAAQ,EAAE,CAAC,EACxD,MAAU,KAEd,IAAI,OAAO,CAAK,GAAK,OAAO,CAAK,EAC/B,EAAG,OAAO,EAAM,CAAK,OAChB,GAAI,WAAW,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,CACxB,GAAI,EAAS,CAAI,GAAK,MAAM,QAAQ,CAAI,EACtC,MAAM,IAAI,EAAiB,mBAAmB,EAAK,6CAA6C,EAElG,EAAG,OAAO,EAAM,OAAO,CAAI,GAAK,OAAO,CAAI,EAAI,EAAO,OAAO,CAAI,CAAC,CACpE,MAEA,EAAG,OAAO,EAAM,OAAO,CAAK,CAAC,CAAA,CAIjC,OAAO,CACT"}
package/dist/form.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./core/path.cjs"),n=require("./_field.cjs"),r=require("./_notify.cjs");let i=require("@vielzeug/arsenal");function a(n){if(n!==void 0&&n.length!==0)return Object.freeze(n.map(n=>{if(!n||typeof n.message!=`string`||!Array.isArray(n.path))throw new e.ForgeConfigError(`Validators must return issues with a string message and array path.`);let r=n.path.map(n=>{if(typeof n==`number`){if(!Number.isSafeInteger(n)||n<0)throw new e.ForgeConfigError(`Invalid issue path index ${n}.`);return n}if(typeof n==`string`)return t.assertSafeKey(n),n;throw new e.ForgeConfigError(`Issue paths may contain only string keys and non-negative integer indexes.`)});return Object.freeze({message:n.message,path:Object.freeze(r)})}))}function o(e){return Object.freeze({formError:e.issues?.find(e=>e.path.length===0)?.message,hasErrors:(e.issues?.length??0)>0,issues:e.issues,submitCount:e.submitCount,submitting:e.isSubmitting,touched:Object.keys(e.touched).length>0,validating:e.isValidating,validity:e.validity})}function s(s){let c=s.normalize,l=e=>c?c(e):e,u=t.immutable(l(s.initialValues)),d=r.createNotifier(s.onSubscriberError),f=new AbortController,p=!1,m=!1,h=[],g,_=0,v,y={baseline:u,isSubmitting:!1,issues:void 0,isValidating:!1,submitCount:0,touched:{},validity:`unknown`,value:u},b=o(y);function x(t){if(p)throw new e.ForgeDisposedError(t)}function S(e){if(h.push(e),!m){m=!0;try{for(;h.length>0;)y=Object.freeze(h.shift()(y)),b=o(y),d.notify(y)}finally{m=!1}}}function C(){g?.abort()}let w={abortValidation:C,addListener:d.add,ensureActive:x,invokeListener:d.call,readState:()=>y,resetValue(e){S(n=>(_++,v=void 0,{...n,touched:t.writeMeta(n.touched,e,!1),validity:`unknown`,value:t.resetAtPath(n.value,n.baseline,e)}))},setTouched(e,n){S(r=>({...r,touched:t.writeMeta(r.touched,e,n)}))},setValue(e,n){S(r=>{let i=t.readAtPath(r.value,e),a=typeof n==`function`?n(i):n,o=l(a);return _++,v=void 0,{...r,validity:`unknown`,value:t.writeAtPath(r.value,e,o)}})}};async function T(t){x(`validate`),C();let n=new AbortController,r=t?AbortSignal.any([n.signal,t]):n.signal;if(r.aborted)return Object.freeze({status:`aborted`});let o=y.value,c=_;g=n,v=void 0,S(e=>({...e,isValidating:!0}));try{if(r.aborted||_!==c)return Object.freeze({status:`aborted`});let e=s.validate?await(0,i.abortable)(Promise.resolve(s.validate(o,r)),r):void 0;if(r.aborted||g!==n||_!==c)return Object.freeze({status:`aborted`});let t=a(e),l=t===void 0?`valid`:`invalid`;return g=void 0,S(e=>({...e,issues:t,isValidating:!1,validity:l})),r.aborted||_!==c||g!==void 0?Object.freeze({status:`aborted`}):(v=l===`valid`?{revision:_,value:o}:void 0,Object.freeze(l===`valid`?{status:`valid`}:{issues:t,status:`invalid`}))}catch(t){if(r.aborted||g!==n)return Object.freeze({status:`aborted`});throw new e.ForgeValidationError(`Form validation failed.`,{cause:t})}finally{g===n&&(g=void 0,p||S(e=>({...e,isValidating:!1})))}}let E={get disposalSignal(){return f.signal},dispose(){p||(p=!0,h.length=0,v=void 0,f.abort(),C(),d.clear())},get disposed(){return p},field(r){if(t.assertSafeKey(r),!(0,i.isPlainObject)(y.value))throw new e.ForgeConfigError(`Form value must be an object.`);return n.createField([r],w)},patch(e){x(`patch`),C(),S(n=>{let r=typeof e==`function`?e(n.value):e,i={...n.value,...r};return _++,v=void 0,{...n,issues:void 0,validity:`unknown`,value:t.immutable(l(i))}})},reset(e){x(`reset`),C();let n=e===void 0?void 0:t.immutable(l(e));S(e=>{let t=n??e.baseline;return _++,v=void 0,{...e,baseline:t,issues:void 0,touched:{},validity:`unknown`,value:t}})},set(e){x(`set`),C(),S(n=>{let r=typeof e==`function`?e(n.value):e;return _++,v=void 0,{...n,issues:void 0,validity:`unknown`,value:t.immutable(l(r))}})},get state(){return b},async submit(n,r){if(x(`submit`),y.isSubmitting)throw new e.ForgeSubmitError(`submit() called while a submission is already in progress`);let i=r?AbortSignal.any([f.signal,r]):f.signal;S(e=>({...e,isSubmitting:!0,submitCount:e.submitCount+1,touched:t.touchAll(e.value)}));try{let e=await T(r);if(e.status===`aborted`)return Object.freeze({status:`aborted`});if(e.status===`invalid`)return Object.freeze({issues:e.issues,status:`invalid`});let t=v;if(!t||t.revision!==_||i.aborted)return Object.freeze({status:`aborted`});try{return Object.freeze({status:`ok`,value:await n(t.value,i)})}catch(e){if(i.aborted)return Object.freeze({status:`aborted`});throw e}}finally{p||S(e=>({...e,isSubmitting:!1}))}},subscribe(e,t={}){return x(`subscribe`),t.immediate&&d.call(()=>e(b)),d.add(()=>e(b))},[Symbol.dispose](){E.dispose()},validate:T,get value(){return y.value}};return E}exports.createForm=s;
1
+ const e=require("./errors.cjs"),t=require("./core/path.cjs"),n=require("./_field.cjs"),r=require("./_notify.cjs");let i=require("@vielzeug/arsenal");function normalizeIssues(n){if(n!==void 0&&n.length!==0)return Object.freeze(n.map(n=>{if(!n||typeof n.message!=`string`||!Array.isArray(n.path))throw new e.ForgeConfigError(`Validators must return issues with a string message and array path.`);let r=n.path.map(n=>{if(typeof n==`number`){if(!Number.isSafeInteger(n)||n<0)throw new e.ForgeConfigError(`Invalid issue path index ${n}.`);return n}if(typeof n==`string`)return t.assertSafeKey(n),n;throw new e.ForgeConfigError(`Issue paths may contain only string keys and non-negative integer indexes.`)});return Object.freeze({message:n.message,path:Object.freeze(r)})}))}function makeFormState(e){return Object.freeze({formError:e.issues?.find(e=>e.path.length===0)?.message,hasErrors:(e.issues?.length??0)>0,issues:e.issues,submitCount:e.submitCount,submitting:e.isSubmitting,touched:Object.keys(e.touched).length>0,validating:e.isValidating,validity:e.validity})}function createForm(a){let o=a.normalize,prepare=e=>o?o(e):e,s=t.immutable(prepare(a.initialValues)),c=r.createNotifier(a.onSubscriberError),l=new AbortController,u=!1,d=!1,f=[],p,m=0,h,g={baseline:s,isSubmitting:!1,issues:void 0,isValidating:!1,submitCount:0,touched:{},validity:`unknown`,value:s},_=makeFormState(g);function ensureActive(t){if(u)throw new e.ForgeDisposedError(t)}function write(e){if(f.push(e),!d){d=!0;try{for(;f.length>0;)g=Object.freeze(f.shift()(g)),_=makeFormState(g),c.notify(g)}finally{d=!1}}}function abortValidation(){p?.abort()}let v={abortValidation,addListener:c.add,ensureActive,invokeListener:c.call,readState:()=>g,resetValue(e){write(n=>(m++,h=void 0,{...n,touched:t.writeMeta(n.touched,e,!1),validity:`unknown`,value:t.resetAtPath(n.value,n.baseline,e)}))},setTouched(e,n){write(r=>({...r,touched:t.writeMeta(r.touched,e,n)}))},setValue(e,n){write(r=>{let i=t.readAtPath(r.value,e),a=typeof n==`function`?n(i):n,o=prepare(a);return m++,h=void 0,{...r,validity:`unknown`,value:t.writeAtPath(r.value,e,o)}})}};async function validate(t){ensureActive(`validate`),abortValidation();let n=new AbortController,r=t?AbortSignal.any([n.signal,t]):n.signal;if(r.aborted)return Object.freeze({status:`aborted`});let o=g.value,s=m;p=n,h=void 0,write(e=>({...e,isValidating:!0}));try{if(r.aborted||m!==s)return Object.freeze({status:`aborted`});let e=a.validate?await(0,i.abortable)(Promise.resolve(a.validate(o,r)),r):void 0;if(r.aborted||p!==n||m!==s)return Object.freeze({status:`aborted`});let t=normalizeIssues(e),c=t===void 0?`valid`:`invalid`;return p=void 0,write(e=>({...e,issues:t,isValidating:!1,validity:c})),r.aborted||m!==s||p!==void 0?Object.freeze({status:`aborted`}):(h=c===`valid`?{revision:m,value:o}:void 0,Object.freeze(c===`valid`?{status:`valid`}:{issues:t,status:`invalid`}))}catch(t){if(r.aborted||p!==n)return Object.freeze({status:`aborted`});throw new e.ForgeValidationError(`Form validation failed.`,{cause:t})}finally{p===n&&(p=void 0,u||write(e=>({...e,isValidating:!1})))}}let y={get disposalSignal(){return l.signal},dispose(){u||(u=!0,f.length=0,h=void 0,l.abort(),abortValidation(),c.clear())},get disposed(){return u},field(r){if(t.assertSafeKey(r),!(0,i.isPlainObject)(g.value))throw new e.ForgeConfigError(`Form value must be an object.`);return n.createField([r],v)},patch(e){ensureActive(`patch`),abortValidation(),write(n=>{let r=typeof e==`function`?e(n.value):e,i={...n.value,...r};return m++,h=void 0,{...n,issues:void 0,validity:`unknown`,value:t.immutable(prepare(i))}})},reset(e){ensureActive(`reset`),abortValidation();let n=e===void 0?void 0:t.immutable(prepare(e));write(e=>{let t=n??e.baseline;return m++,h=void 0,{...e,baseline:t,issues:void 0,touched:{},validity:`unknown`,value:t}})},set(e){ensureActive(`set`),abortValidation(),write(n=>{let r=typeof e==`function`?e(n.value):e;return m++,h=void 0,{...n,issues:void 0,validity:`unknown`,value:t.immutable(prepare(r))}})},get state(){return _},async submit(n,r){if(ensureActive(`submit`),g.isSubmitting)throw new e.ForgeSubmitError(`submit() called while a submission is already in progress`);let i=r?AbortSignal.any([l.signal,r]):l.signal;write(e=>({...e,isSubmitting:!0,submitCount:e.submitCount+1,touched:t.touchAll(e.value)}));try{let e=await validate(r);if(e.status===`aborted`)return Object.freeze({status:`aborted`});if(e.status===`invalid`)return Object.freeze({issues:e.issues,status:`invalid`});let t=h;if(!t||t.revision!==m||i.aborted)return Object.freeze({status:`aborted`});try{return Object.freeze({status:`ok`,value:await n(t.value,i)})}catch(e){if(i.aborted)return Object.freeze({status:`aborted`});throw e}}finally{u||write(e=>({...e,isSubmitting:!1}))}},subscribe(e,t={}){return ensureActive(`subscribe`),t.immediate&&c.call(()=>e(_)),c.add(()=>e(_))},[Symbol.dispose](){y.dispose()},validate,get value(){return g.value}};return y}exports.createForm=createForm;
2
2
  //# sourceMappingURL=form.cjs.map
package/dist/form.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"form.cjs","names":[],"sources":["../src/form.ts"],"sourcesContent":["import { abortable } from '@vielzeug/arsenal';\n\nimport { createField, type FieldAccess } from './_field.js';\nimport { createNotifier } from './_notify.js';\nimport {\n assertSafeKey,\n immutable,\n isRecord,\n type MetaRoot,\n readAtPath,\n resetAtPath,\n touchAll,\n writeAtPath,\n writeMeta,\n} from './core/path.js';\nimport { ForgeConfigError, ForgeDisposedError, ForgeSubmitError, ForgeValidationError } from './errors.js';\nimport type {\n Form,\n FormOptions,\n FormState,\n FormValidator,\n MaybePromise,\n SubmitResult,\n Unsubscribe,\n ValidationIssue,\n ValidationResult,\n} from './types.js';\n\ntype Validity = 'invalid' | 'unknown' | 'valid';\n\ntype InternalState<TValues extends Record<string, unknown>> = Readonly<{\n baseline: TValues;\n issues: readonly ValidationIssue[] | undefined;\n isSubmitting: boolean;\n isValidating: boolean;\n submitCount: number;\n touched: MetaRoot;\n validity: Validity;\n value: TValues;\n}>;\n\nfunction normalizeIssues(result: readonly ValidationIssue[] | undefined): readonly ValidationIssue[] | undefined {\n if (result === undefined || result.length === 0) return undefined;\n\n return Object.freeze(\n result.map((issue) => {\n if (!issue || typeof issue.message !== 'string' || !Array.isArray(issue.path)) {\n throw new ForgeConfigError('Validators must return issues with a string message and array path.');\n }\n\n const path = issue.path.map((part) => {\n if (typeof part === 'number') {\n if (!Number.isSafeInteger(part) || part < 0) throw new ForgeConfigError(`Invalid issue path index ${part}.`);\n return part;\n }\n if (typeof part === 'string') {\n assertSafeKey(part);\n return part;\n }\n throw new ForgeConfigError('Issue paths may contain only string keys and non-negative integer indexes.');\n });\n\n return Object.freeze({ message: issue.message, path: Object.freeze(path) });\n }),\n );\n}\n\nfunction makeFormState<TValues extends Record<string, unknown>>(current: InternalState<TValues>): FormState {\n return Object.freeze({\n formError: current.issues?.find((issue) => issue.path.length === 0)?.message,\n hasErrors: (current.issues?.length ?? 0) > 0,\n issues: current.issues,\n submitCount: current.submitCount,\n submitting: current.isSubmitting,\n touched: Object.keys(current.touched).length > 0,\n validating: current.isValidating,\n validity: current.validity,\n });\n}\n\n/** One immutable value tree and one explicit full-form validator keep form behavior locally understandable. */\nexport function createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues> {\n const normalize = options.normalize;\n const prepare = (value: unknown): unknown => (normalize ? normalize(value) : value);\n const initial = immutable(prepare(options.initialValues) as TValues);\n const notifier = createNotifier<InternalState<TValues>>(options.onSubscriberError);\n const disposalController = new AbortController();\n let disposed = false;\n let dispatching = false;\n const pendingWrites: Array<(current: InternalState<TValues>) => InternalState<TValues>> = [];\n let validationController: AbortController | undefined;\n let revision = 0;\n let validatedSnapshot: { revision: number; value: TValues } | undefined;\n let current: InternalState<TValues> = {\n baseline: initial,\n isSubmitting: false,\n issues: undefined,\n isValidating: false,\n submitCount: 0,\n touched: {},\n validity: 'unknown',\n value: initial,\n };\n let publicState = makeFormState(current);\n\n function ensureActive(operation: string): void {\n if (disposed) throw new ForgeDisposedError(operation);\n }\n\n function write(update: (current: InternalState<TValues>) => InternalState<TValues>): void {\n pendingWrites.push(update);\n if (dispatching) return;\n\n dispatching = true;\n try {\n while (pendingWrites.length > 0) {\n current = Object.freeze(pendingWrites.shift()!(current));\n publicState = makeFormState(current);\n notifier.notify(current);\n }\n } finally {\n dispatching = false;\n }\n }\n\n function abortValidation(): void {\n validationController?.abort();\n }\n\n const access: FieldAccess = {\n abortValidation,\n addListener: notifier.add,\n ensureActive,\n invokeListener: notifier.call,\n readState: () => current,\n resetValue(path) {\n write((c) => {\n revision++;\n validatedSnapshot = undefined;\n return {\n ...c,\n touched: writeMeta(c.touched, path, false),\n validity: 'unknown',\n value: resetAtPath(c.value, c.baseline, path),\n };\n });\n },\n setTouched(path, touched) {\n write((c) => ({ ...c, touched: writeMeta(c.touched, path, touched) }));\n },\n setValue(path, next) {\n write((c) => {\n const previous = readAtPath(c.value, path);\n const raw = typeof next === 'function' ? next(previous) : next;\n const value = prepare(raw);\n revision++;\n validatedSnapshot = undefined;\n return { ...c, validity: 'unknown', value: writeAtPath(c.value, path, value) };\n });\n },\n };\n\n async function validate(externalSignal?: AbortSignal): Promise<ValidationResult> {\n ensureActive('validate');\n abortValidation();\n\n const controller = new AbortController();\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n const snapshot = current.value;\n const snapshotRevision = revision;\n validationController = controller;\n validatedSnapshot = undefined;\n write((c) => ({ ...c, isValidating: true }));\n\n try {\n if (signal.aborted || revision !== snapshotRevision) return Object.freeze({ status: 'aborted' });\n const result: readonly ValidationIssue[] | undefined = options.validate\n ? await abortable(\n Promise.resolve(options.validate(snapshot as Parameters<FormValidator<TValues>>[0], signal)),\n signal,\n )\n : undefined;\n\n if (signal.aborted || validationController !== controller || revision !== snapshotRevision) {\n return Object.freeze({ status: 'aborted' });\n }\n\n const issues = normalizeIssues(result);\n const validity: Validity = issues === undefined ? 'valid' : 'invalid';\n validationController = undefined;\n write((c) => ({ ...c, issues, isValidating: false, validity }));\n\n if (signal.aborted || revision !== snapshotRevision || validationController !== undefined) {\n return Object.freeze({ status: 'aborted' });\n }\n\n validatedSnapshot = validity === 'valid' ? { revision, value: snapshot } : undefined;\n return validity === 'valid'\n ? Object.freeze({ status: 'valid' })\n : Object.freeze({ issues: issues as readonly ValidationIssue[], status: 'invalid' });\n } catch (error) {\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n throw new ForgeValidationError('Form validation failed.', { cause: error });\n } finally {\n if (validationController === controller) {\n validationController = undefined;\n if (!disposed) write((c) => ({ ...c, isValidating: false }));\n }\n }\n }\n\n const form: Form<TValues> = {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose() {\n if (disposed) return;\n\n disposed = true;\n pendingWrites.length = 0;\n validatedSnapshot = undefined;\n disposalController.abort();\n abortValidation();\n notifier.clear();\n },\n get disposed() {\n return disposed;\n },\n field(key) {\n assertSafeKey(key);\n if (!isRecord(current.value)) throw new ForgeConfigError('Form value must be an object.');\n\n return createField<TValues[typeof key]>([key], access);\n },\n patch(next) {\n ensureActive('patch');\n abortValidation();\n\n write((c) => {\n const partial =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => Partial<TValues>)(c.value as Form<TValues>['value'])\n : next;\n const merged = { ...c.value, ...partial };\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(merged) as TValues) };\n });\n },\n reset(next) {\n ensureActive('reset');\n abortValidation();\n\n const replacement = next === undefined ? undefined : immutable(prepare(next) as TValues);\n write((c) => {\n const baseline = replacement ?? c.baseline;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, baseline, issues: undefined, touched: {}, validity: 'unknown', value: baseline };\n });\n },\n set(next) {\n ensureActive('set');\n abortValidation();\n\n write((c) => {\n const value =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => TValues)(c.value as Form<TValues>['value'])\n : next;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(value) as TValues) };\n });\n },\n get state() {\n return publicState;\n },\n async submit<TResult = void>(\n handler: (values: Form<TValues>['value'], signal: AbortSignal) => MaybePromise<TResult>,\n externalSignal?: AbortSignal,\n ): Promise<SubmitResult<TResult>> {\n ensureActive('submit');\n\n if (current.isSubmitting) {\n throw new ForgeSubmitError('submit() called while a submission is already in progress');\n }\n\n const signal = externalSignal\n ? AbortSignal.any([disposalController.signal, externalSignal])\n : disposalController.signal;\n\n write((c) => ({\n ...c,\n isSubmitting: true,\n submitCount: c.submitCount + 1,\n touched: touchAll(c.value),\n }));\n\n try {\n const result = await validate(externalSignal);\n\n if (result.status === 'aborted') return Object.freeze({ status: 'aborted' });\n\n if (result.status === 'invalid') {\n return Object.freeze({ issues: result.issues, status: 'invalid' });\n }\n\n const validated = validatedSnapshot;\n if (!validated || validated.revision !== revision || signal.aborted) {\n return Object.freeze({ status: 'aborted' });\n }\n\n try {\n return Object.freeze({\n status: 'ok',\n value: await handler(validated.value as Form<TValues>['value'], signal),\n });\n } catch (error) {\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n throw error;\n }\n } finally {\n if (!disposed) write((c) => ({ ...c, isSubmitting: false }));\n }\n },\n subscribe(listener, subscribeOptions = {}): Unsubscribe {\n ensureActive('subscribe');\n\n if (subscribeOptions.immediate) notifier.call(() => listener(publicState));\n\n return notifier.add(() => listener(publicState));\n },\n [Symbol.dispose]() {\n form.dispose();\n },\n validate,\n get value() {\n return current.value as Form<TValues>['value'];\n },\n };\n\n return form;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"form.cjs","names":[],"sources":["../src/form.ts"],"sourcesContent":["import { abortable } from '@vielzeug/arsenal';\n\nimport { createField, type FieldAccess } from './_field.js';\nimport { createNotifier } from './_notify.js';\nimport {\n assertSafeKey,\n immutable,\n isRecord,\n type MetaRoot,\n readAtPath,\n resetAtPath,\n touchAll,\n writeAtPath,\n writeMeta,\n} from './core/path.js';\nimport { ForgeConfigError, ForgeDisposedError, ForgeSubmitError, ForgeValidationError } from './errors.js';\nimport type {\n Form,\n FormOptions,\n FormState,\n FormValidator,\n MaybePromise,\n SubmitResult,\n Unsubscribe,\n ValidationIssue,\n ValidationResult,\n} from './types.js';\n\ntype Validity = 'invalid' | 'unknown' | 'valid';\n\ntype InternalState<TValues extends Record<string, unknown>> = Readonly<{\n baseline: TValues;\n issues: readonly ValidationIssue[] | undefined;\n isSubmitting: boolean;\n isValidating: boolean;\n submitCount: number;\n touched: MetaRoot;\n validity: Validity;\n value: TValues;\n}>;\n\nfunction normalizeIssues(result: readonly ValidationIssue[] | undefined): readonly ValidationIssue[] | undefined {\n if (result === undefined || result.length === 0) return undefined;\n\n return Object.freeze(\n result.map((issue) => {\n if (!issue || typeof issue.message !== 'string' || !Array.isArray(issue.path)) {\n throw new ForgeConfigError('Validators must return issues with a string message and array path.');\n }\n\n const path = issue.path.map((part) => {\n if (typeof part === 'number') {\n if (!Number.isSafeInteger(part) || part < 0) throw new ForgeConfigError(`Invalid issue path index ${part}.`);\n return part;\n }\n if (typeof part === 'string') {\n assertSafeKey(part);\n return part;\n }\n throw new ForgeConfigError('Issue paths may contain only string keys and non-negative integer indexes.');\n });\n\n return Object.freeze({ message: issue.message, path: Object.freeze(path) });\n }),\n );\n}\n\nfunction makeFormState<TValues extends Record<string, unknown>>(current: InternalState<TValues>): FormState {\n return Object.freeze({\n formError: current.issues?.find((issue) => issue.path.length === 0)?.message,\n hasErrors: (current.issues?.length ?? 0) > 0,\n issues: current.issues,\n submitCount: current.submitCount,\n submitting: current.isSubmitting,\n touched: Object.keys(current.touched).length > 0,\n validating: current.isValidating,\n validity: current.validity,\n });\n}\n\n/** One immutable value tree and one explicit full-form validator keep form behavior locally understandable. */\nexport function createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues> {\n const normalize = options.normalize;\n const prepare = (value: unknown): unknown => (normalize ? normalize(value) : value);\n const initial = immutable(prepare(options.initialValues) as TValues);\n const notifier = createNotifier<InternalState<TValues>>(options.onSubscriberError);\n const disposalController = new AbortController();\n let disposed = false;\n let dispatching = false;\n const pendingWrites: Array<(current: InternalState<TValues>) => InternalState<TValues>> = [];\n let validationController: AbortController | undefined;\n let revision = 0;\n let validatedSnapshot: { revision: number; value: TValues } | undefined;\n let current: InternalState<TValues> = {\n baseline: initial,\n isSubmitting: false,\n issues: undefined,\n isValidating: false,\n submitCount: 0,\n touched: {},\n validity: 'unknown',\n value: initial,\n };\n let publicState = makeFormState(current);\n\n function ensureActive(operation: string): void {\n if (disposed) throw new ForgeDisposedError(operation);\n }\n\n function write(update: (current: InternalState<TValues>) => InternalState<TValues>): void {\n pendingWrites.push(update);\n if (dispatching) return;\n\n dispatching = true;\n try {\n while (pendingWrites.length > 0) {\n current = Object.freeze(pendingWrites.shift()!(current));\n publicState = makeFormState(current);\n notifier.notify(current);\n }\n } finally {\n dispatching = false;\n }\n }\n\n function abortValidation(): void {\n validationController?.abort();\n }\n\n const access: FieldAccess = {\n abortValidation,\n addListener: notifier.add,\n ensureActive,\n invokeListener: notifier.call,\n readState: () => current,\n resetValue(path) {\n write((c) => {\n revision++;\n validatedSnapshot = undefined;\n return {\n ...c,\n touched: writeMeta(c.touched, path, false),\n validity: 'unknown',\n value: resetAtPath(c.value, c.baseline, path),\n };\n });\n },\n setTouched(path, touched) {\n write((c) => ({ ...c, touched: writeMeta(c.touched, path, touched) }));\n },\n setValue(path, next) {\n write((c) => {\n const previous = readAtPath(c.value, path);\n const raw = typeof next === 'function' ? next(previous) : next;\n const value = prepare(raw);\n revision++;\n validatedSnapshot = undefined;\n return { ...c, validity: 'unknown', value: writeAtPath(c.value, path, value) };\n });\n },\n };\n\n async function validate(externalSignal?: AbortSignal): Promise<ValidationResult> {\n ensureActive('validate');\n abortValidation();\n\n const controller = new AbortController();\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n const snapshot = current.value;\n const snapshotRevision = revision;\n validationController = controller;\n validatedSnapshot = undefined;\n write((c) => ({ ...c, isValidating: true }));\n\n try {\n if (signal.aborted || revision !== snapshotRevision) return Object.freeze({ status: 'aborted' });\n const result: readonly ValidationIssue[] | undefined = options.validate\n ? await abortable(\n Promise.resolve(options.validate(snapshot as Parameters<FormValidator<TValues>>[0], signal)),\n signal,\n )\n : undefined;\n\n if (signal.aborted || validationController !== controller || revision !== snapshotRevision) {\n return Object.freeze({ status: 'aborted' });\n }\n\n const issues = normalizeIssues(result);\n const validity: Validity = issues === undefined ? 'valid' : 'invalid';\n validationController = undefined;\n write((c) => ({ ...c, issues, isValidating: false, validity }));\n\n if (signal.aborted || revision !== snapshotRevision || validationController !== undefined) {\n return Object.freeze({ status: 'aborted' });\n }\n\n validatedSnapshot = validity === 'valid' ? { revision, value: snapshot } : undefined;\n return validity === 'valid'\n ? Object.freeze({ status: 'valid' })\n : Object.freeze({ issues: issues as readonly ValidationIssue[], status: 'invalid' });\n } catch (error) {\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n throw new ForgeValidationError('Form validation failed.', { cause: error });\n } finally {\n if (validationController === controller) {\n validationController = undefined;\n if (!disposed) write((c) => ({ ...c, isValidating: false }));\n }\n }\n }\n\n const form: Form<TValues> = {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose() {\n if (disposed) return;\n\n disposed = true;\n pendingWrites.length = 0;\n validatedSnapshot = undefined;\n disposalController.abort();\n abortValidation();\n notifier.clear();\n },\n get disposed() {\n return disposed;\n },\n field(key) {\n assertSafeKey(key);\n if (!isRecord(current.value)) throw new ForgeConfigError('Form value must be an object.');\n\n return createField<TValues[typeof key]>([key], access);\n },\n patch(next) {\n ensureActive('patch');\n abortValidation();\n\n write((c) => {\n const partial =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => Partial<TValues>)(c.value as Form<TValues>['value'])\n : next;\n const merged = { ...c.value, ...partial };\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(merged) as TValues) };\n });\n },\n reset(next) {\n ensureActive('reset');\n abortValidation();\n\n const replacement = next === undefined ? undefined : immutable(prepare(next) as TValues);\n write((c) => {\n const baseline = replacement ?? c.baseline;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, baseline, issues: undefined, touched: {}, validity: 'unknown', value: baseline };\n });\n },\n set(next) {\n ensureActive('set');\n abortValidation();\n\n write((c) => {\n const value =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => TValues)(c.value as Form<TValues>['value'])\n : next;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(value) as TValues) };\n });\n },\n get state() {\n return publicState;\n },\n async submit<TResult = void>(\n handler: (values: Form<TValues>['value'], signal: AbortSignal) => MaybePromise<TResult>,\n externalSignal?: AbortSignal,\n ): Promise<SubmitResult<TResult>> {\n ensureActive('submit');\n\n if (current.isSubmitting) {\n throw new ForgeSubmitError('submit() called while a submission is already in progress');\n }\n\n const signal = externalSignal\n ? AbortSignal.any([disposalController.signal, externalSignal])\n : disposalController.signal;\n\n write((c) => ({\n ...c,\n isSubmitting: true,\n submitCount: c.submitCount + 1,\n touched: touchAll(c.value),\n }));\n\n try {\n const result = await validate(externalSignal);\n\n if (result.status === 'aborted') return Object.freeze({ status: 'aborted' });\n\n if (result.status === 'invalid') {\n return Object.freeze({ issues: result.issues, status: 'invalid' });\n }\n\n const validated = validatedSnapshot;\n if (!validated || validated.revision !== revision || signal.aborted) {\n return Object.freeze({ status: 'aborted' });\n }\n\n try {\n return Object.freeze({\n status: 'ok',\n value: await handler(validated.value as Form<TValues>['value'], signal),\n });\n } catch (error) {\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n throw error;\n }\n } finally {\n if (!disposed) write((c) => ({ ...c, isSubmitting: false }));\n }\n },\n subscribe(listener, subscribeOptions = {}): Unsubscribe {\n ensureActive('subscribe');\n\n if (subscribeOptions.immediate) notifier.call(() => listener(publicState));\n\n return notifier.add(() => listener(publicState));\n },\n [Symbol.dispose]() {\n form.dispose();\n },\n validate,\n get value() {\n return current.value as Form<TValues>['value'];\n },\n };\n\n return form;\n}\n"],"mappings":"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"}
package/dist/form.js CHANGED
@@ -1,237 +1,2 @@
1
- import { ForgeConfigError as e, ForgeDisposedError as t, ForgeSubmitError as n, ForgeValidationError as r } from "./errors.js";
2
- import { assertSafeKey as i, immutable as a, isRecord as o, readAtPath as s, resetAtPath as c, touchAll as l, writeAtPath as u, writeMeta as d } from "./core/path.js";
3
- import { createField as f } from "./_field.js";
4
- import { createNotifier as p } from "./_notify.js";
5
- import { abortable as m } from "@vielzeug/arsenal";
6
- //#region src/form.ts
7
- function h(t) {
8
- if (t !== void 0 && t.length !== 0) return Object.freeze(t.map((t) => {
9
- if (!t || typeof t.message != "string" || !Array.isArray(t.path)) throw new e("Validators must return issues with a string message and array path.");
10
- let n = t.path.map((t) => {
11
- if (typeof t == "number") {
12
- if (!Number.isSafeInteger(t) || t < 0) throw new e(`Invalid issue path index ${t}.`);
13
- return t;
14
- }
15
- if (typeof t == "string") return i(t), t;
16
- throw new e("Issue paths may contain only string keys and non-negative integer indexes.");
17
- });
18
- return Object.freeze({
19
- message: t.message,
20
- path: Object.freeze(n)
21
- });
22
- }));
23
- }
24
- function g(e) {
25
- return Object.freeze({
26
- formError: e.issues?.find((e) => e.path.length === 0)?.message,
27
- hasErrors: (e.issues?.length ?? 0) > 0,
28
- issues: e.issues,
29
- submitCount: e.submitCount,
30
- submitting: e.isSubmitting,
31
- touched: Object.keys(e.touched).length > 0,
32
- validating: e.isValidating,
33
- validity: e.validity
34
- });
35
- }
36
- function _(_) {
37
- let v = _.normalize, y = (e) => v ? v(e) : e, b = a(y(_.initialValues)), x = p(_.onSubscriberError), S = new AbortController(), C = !1, w = !1, T = [], E, D = 0, O, k = {
38
- baseline: b,
39
- isSubmitting: !1,
40
- issues: void 0,
41
- isValidating: !1,
42
- submitCount: 0,
43
- touched: {},
44
- validity: "unknown",
45
- value: b
46
- }, A = g(k);
47
- function j(e) {
48
- if (C) throw new t(e);
49
- }
50
- function M(e) {
51
- if (T.push(e), !w) {
52
- w = !0;
53
- try {
54
- for (; T.length > 0;) k = Object.freeze(T.shift()(k)), A = g(k), x.notify(k);
55
- } finally {
56
- w = !1;
57
- }
58
- }
59
- }
60
- function N() {
61
- E?.abort();
62
- }
63
- let P = {
64
- abortValidation: N,
65
- addListener: x.add,
66
- ensureActive: j,
67
- invokeListener: x.call,
68
- readState: () => k,
69
- resetValue(e) {
70
- M((t) => (D++, O = void 0, {
71
- ...t,
72
- touched: d(t.touched, e, !1),
73
- validity: "unknown",
74
- value: c(t.value, t.baseline, e)
75
- }));
76
- },
77
- setTouched(e, t) {
78
- M((n) => ({
79
- ...n,
80
- touched: d(n.touched, e, t)
81
- }));
82
- },
83
- setValue(e, t) {
84
- M((n) => {
85
- let r = s(n.value, e), i = typeof t == "function" ? t(r) : t, a = y(i);
86
- return D++, O = void 0, {
87
- ...n,
88
- validity: "unknown",
89
- value: u(n.value, e, a)
90
- };
91
- });
92
- }
93
- };
94
- async function F(e) {
95
- j("validate"), N();
96
- let t = new AbortController(), n = e ? AbortSignal.any([t.signal, e]) : t.signal;
97
- if (n.aborted) return Object.freeze({ status: "aborted" });
98
- let i = k.value, a = D;
99
- E = t, O = void 0, M((e) => ({
100
- ...e,
101
- isValidating: !0
102
- }));
103
- try {
104
- if (n.aborted || D !== a) return Object.freeze({ status: "aborted" });
105
- let e = _.validate ? await m(Promise.resolve(_.validate(i, n)), n) : void 0;
106
- if (n.aborted || E !== t || D !== a) return Object.freeze({ status: "aborted" });
107
- let r = h(e), o = r === void 0 ? "valid" : "invalid";
108
- return E = void 0, M((e) => ({
109
- ...e,
110
- issues: r,
111
- isValidating: !1,
112
- validity: o
113
- })), n.aborted || D !== a || E !== void 0 ? Object.freeze({ status: "aborted" }) : (O = o === "valid" ? {
114
- revision: D,
115
- value: i
116
- } : void 0, Object.freeze(o === "valid" ? { status: "valid" } : {
117
- issues: r,
118
- status: "invalid"
119
- }));
120
- } catch (e) {
121
- if (n.aborted || E !== t) return Object.freeze({ status: "aborted" });
122
- throw new r("Form validation failed.", { cause: e });
123
- } finally {
124
- E === t && (E = void 0, C || M((e) => ({
125
- ...e,
126
- isValidating: !1
127
- })));
128
- }
129
- }
130
- let I = {
131
- get disposalSignal() {
132
- return S.signal;
133
- },
134
- dispose() {
135
- C || (C = !0, T.length = 0, O = void 0, S.abort(), N(), x.clear());
136
- },
137
- get disposed() {
138
- return C;
139
- },
140
- field(t) {
141
- if (i(t), !o(k.value)) throw new e("Form value must be an object.");
142
- return f([t], P);
143
- },
144
- patch(e) {
145
- j("patch"), N(), M((t) => {
146
- let n = typeof e == "function" ? e(t.value) : e, r = {
147
- ...t.value,
148
- ...n
149
- };
150
- return D++, O = void 0, {
151
- ...t,
152
- issues: void 0,
153
- validity: "unknown",
154
- value: a(y(r))
155
- };
156
- });
157
- },
158
- reset(e) {
159
- j("reset"), N();
160
- let t = e === void 0 ? void 0 : a(y(e));
161
- M((e) => {
162
- let n = t ?? e.baseline;
163
- return D++, O = void 0, {
164
- ...e,
165
- baseline: n,
166
- issues: void 0,
167
- touched: {},
168
- validity: "unknown",
169
- value: n
170
- };
171
- });
172
- },
173
- set(e) {
174
- j("set"), N(), M((t) => {
175
- let n = typeof e == "function" ? e(t.value) : e;
176
- return D++, O = void 0, {
177
- ...t,
178
- issues: void 0,
179
- validity: "unknown",
180
- value: a(y(n))
181
- };
182
- });
183
- },
184
- get state() {
185
- return A;
186
- },
187
- async submit(e, t) {
188
- if (j("submit"), k.isSubmitting) throw new n("submit() called while a submission is already in progress");
189
- let r = t ? AbortSignal.any([S.signal, t]) : S.signal;
190
- M((e) => ({
191
- ...e,
192
- isSubmitting: !0,
193
- submitCount: e.submitCount + 1,
194
- touched: l(e.value)
195
- }));
196
- try {
197
- let n = await F(t);
198
- if (n.status === "aborted") return Object.freeze({ status: "aborted" });
199
- if (n.status === "invalid") return Object.freeze({
200
- issues: n.issues,
201
- status: "invalid"
202
- });
203
- let i = O;
204
- if (!i || i.revision !== D || r.aborted) return Object.freeze({ status: "aborted" });
205
- try {
206
- return Object.freeze({
207
- status: "ok",
208
- value: await e(i.value, r)
209
- });
210
- } catch (e) {
211
- if (r.aborted) return Object.freeze({ status: "aborted" });
212
- throw e;
213
- }
214
- } finally {
215
- C || M((e) => ({
216
- ...e,
217
- isSubmitting: !1
218
- }));
219
- }
220
- },
221
- subscribe(e, t = {}) {
222
- return j("subscribe"), t.immediate && x.call(() => e(A)), x.add(() => e(A));
223
- },
224
- [Symbol.dispose]() {
225
- I.dispose();
226
- },
227
- validate: F,
228
- get value() {
229
- return k.value;
230
- }
231
- };
232
- return I;
233
- }
234
- //#endregion
235
- export { _ as createForm };
236
-
1
+ import{ForgeConfigError as e,ForgeDisposedError as t,ForgeSubmitError as n,ForgeValidationError as r}from"./errors.js";import{assertSafeKey as i,immutable as a,isRecord as o,readAtPath as s,resetAtPath as c,touchAll as l,writeAtPath as u,writeMeta as d}from"./core/path.js";import{createField as f}from"./_field.js";import{createNotifier as p}from"./_notify.js";import{abortable as m}from"@vielzeug/arsenal";function normalizeIssues(t){if(t!==void 0&&t.length!==0)return Object.freeze(t.map(t=>{if(!t||typeof t.message!=`string`||!Array.isArray(t.path))throw new e(`Validators must return issues with a string message and array path.`);let n=t.path.map(t=>{if(typeof t==`number`){if(!Number.isSafeInteger(t)||t<0)throw new e(`Invalid issue path index ${t}.`);return t}if(typeof t==`string`)return i(t),t;throw new e(`Issue paths may contain only string keys and non-negative integer indexes.`)});return Object.freeze({message:t.message,path:Object.freeze(n)})}))}function makeFormState(e){return Object.freeze({formError:e.issues?.find(e=>e.path.length===0)?.message,hasErrors:(e.issues?.length??0)>0,issues:e.issues,submitCount:e.submitCount,submitting:e.isSubmitting,touched:Object.keys(e.touched).length>0,validating:e.isValidating,validity:e.validity})}function createForm(h){let g=h.normalize,prepare=e=>g?g(e):e,_=a(prepare(h.initialValues)),v=p(h.onSubscriberError),y=new AbortController,b=!1,x=!1,S=[],C,w=0,T,E={baseline:_,isSubmitting:!1,issues:void 0,isValidating:!1,submitCount:0,touched:{},validity:`unknown`,value:_},D=makeFormState(E);function ensureActive(e){if(b)throw new t(e)}function write(e){if(S.push(e),!x){x=!0;try{for(;S.length>0;)E=Object.freeze(S.shift()(E)),D=makeFormState(E),v.notify(E)}finally{x=!1}}}function abortValidation(){C?.abort()}let O={abortValidation,addListener:v.add,ensureActive,invokeListener:v.call,readState:()=>E,resetValue(e){write(t=>(w++,T=void 0,{...t,touched:d(t.touched,e,!1),validity:`unknown`,value:c(t.value,t.baseline,e)}))},setTouched(e,t){write(n=>({...n,touched:d(n.touched,e,t)}))},setValue(e,t){write(n=>{let r=s(n.value,e),i=typeof t==`function`?t(r):t,a=prepare(i);return w++,T=void 0,{...n,validity:`unknown`,value:u(n.value,e,a)}})}};async function validate(e){ensureActive(`validate`),abortValidation();let t=new AbortController,n=e?AbortSignal.any([t.signal,e]):t.signal;if(n.aborted)return Object.freeze({status:`aborted`});let i=E.value,a=w;C=t,T=void 0,write(e=>({...e,isValidating:!0}));try{if(n.aborted||w!==a)return Object.freeze({status:`aborted`});let e=h.validate?await m(Promise.resolve(h.validate(i,n)),n):void 0;if(n.aborted||C!==t||w!==a)return Object.freeze({status:`aborted`});let r=normalizeIssues(e),o=r===void 0?`valid`:`invalid`;return C=void 0,write(e=>({...e,issues:r,isValidating:!1,validity:o})),n.aborted||w!==a||C!==void 0?Object.freeze({status:`aborted`}):(T=o===`valid`?{revision:w,value:i}:void 0,Object.freeze(o===`valid`?{status:`valid`}:{issues:r,status:`invalid`}))}catch(e){if(n.aborted||C!==t)return Object.freeze({status:`aborted`});throw new r(`Form validation failed.`,{cause:e})}finally{C===t&&(C=void 0,b||write(e=>({...e,isValidating:!1})))}}let k={get disposalSignal(){return y.signal},dispose(){b||(b=!0,S.length=0,T=void 0,y.abort(),abortValidation(),v.clear())},get disposed(){return b},field(t){if(i(t),!o(E.value))throw new e(`Form value must be an object.`);return f([t],O)},patch(e){ensureActive(`patch`),abortValidation(),write(t=>{let n=typeof e==`function`?e(t.value):e,r={...t.value,...n};return w++,T=void 0,{...t,issues:void 0,validity:`unknown`,value:a(prepare(r))}})},reset(e){ensureActive(`reset`),abortValidation();let t=e===void 0?void 0:a(prepare(e));write(e=>{let n=t??e.baseline;return w++,T=void 0,{...e,baseline:n,issues:void 0,touched:{},validity:`unknown`,value:n}})},set(e){ensureActive(`set`),abortValidation(),write(t=>{let n=typeof e==`function`?e(t.value):e;return w++,T=void 0,{...t,issues:void 0,validity:`unknown`,value:a(prepare(n))}})},get state(){return D},async submit(e,t){if(ensureActive(`submit`),E.isSubmitting)throw new n(`submit() called while a submission is already in progress`);let r=t?AbortSignal.any([y.signal,t]):y.signal;write(e=>({...e,isSubmitting:!0,submitCount:e.submitCount+1,touched:l(e.value)}));try{let n=await validate(t);if(n.status===`aborted`)return Object.freeze({status:`aborted`});if(n.status===`invalid`)return Object.freeze({issues:n.issues,status:`invalid`});let i=T;if(!i||i.revision!==w||r.aborted)return Object.freeze({status:`aborted`});try{return Object.freeze({status:`ok`,value:await e(i.value,r)})}catch(e){if(r.aborted)return Object.freeze({status:`aborted`});throw e}}finally{b||write(e=>({...e,isSubmitting:!1}))}},subscribe(e,t={}){return ensureActive(`subscribe`),t.immediate&&v.call(()=>e(D)),v.add(()=>e(D))},[Symbol.dispose](){k.dispose()},validate,get value(){return E.value}};return k}export{createForm};
237
2
  //# sourceMappingURL=form.js.map
package/dist/form.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","names":[],"sources":["../src/form.ts"],"sourcesContent":["import { abortable } from '@vielzeug/arsenal';\n\nimport { createField, type FieldAccess } from './_field.js';\nimport { createNotifier } from './_notify.js';\nimport {\n assertSafeKey,\n immutable,\n isRecord,\n type MetaRoot,\n readAtPath,\n resetAtPath,\n touchAll,\n writeAtPath,\n writeMeta,\n} from './core/path.js';\nimport { ForgeConfigError, ForgeDisposedError, ForgeSubmitError, ForgeValidationError } from './errors.js';\nimport type {\n Form,\n FormOptions,\n FormState,\n FormValidator,\n MaybePromise,\n SubmitResult,\n Unsubscribe,\n ValidationIssue,\n ValidationResult,\n} from './types.js';\n\ntype Validity = 'invalid' | 'unknown' | 'valid';\n\ntype InternalState<TValues extends Record<string, unknown>> = Readonly<{\n baseline: TValues;\n issues: readonly ValidationIssue[] | undefined;\n isSubmitting: boolean;\n isValidating: boolean;\n submitCount: number;\n touched: MetaRoot;\n validity: Validity;\n value: TValues;\n}>;\n\nfunction normalizeIssues(result: readonly ValidationIssue[] | undefined): readonly ValidationIssue[] | undefined {\n if (result === undefined || result.length === 0) return undefined;\n\n return Object.freeze(\n result.map((issue) => {\n if (!issue || typeof issue.message !== 'string' || !Array.isArray(issue.path)) {\n throw new ForgeConfigError('Validators must return issues with a string message and array path.');\n }\n\n const path = issue.path.map((part) => {\n if (typeof part === 'number') {\n if (!Number.isSafeInteger(part) || part < 0) throw new ForgeConfigError(`Invalid issue path index ${part}.`);\n return part;\n }\n if (typeof part === 'string') {\n assertSafeKey(part);\n return part;\n }\n throw new ForgeConfigError('Issue paths may contain only string keys and non-negative integer indexes.');\n });\n\n return Object.freeze({ message: issue.message, path: Object.freeze(path) });\n }),\n );\n}\n\nfunction makeFormState<TValues extends Record<string, unknown>>(current: InternalState<TValues>): FormState {\n return Object.freeze({\n formError: current.issues?.find((issue) => issue.path.length === 0)?.message,\n hasErrors: (current.issues?.length ?? 0) > 0,\n issues: current.issues,\n submitCount: current.submitCount,\n submitting: current.isSubmitting,\n touched: Object.keys(current.touched).length > 0,\n validating: current.isValidating,\n validity: current.validity,\n });\n}\n\n/** One immutable value tree and one explicit full-form validator keep form behavior locally understandable. */\nexport function createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues> {\n const normalize = options.normalize;\n const prepare = (value: unknown): unknown => (normalize ? normalize(value) : value);\n const initial = immutable(prepare(options.initialValues) as TValues);\n const notifier = createNotifier<InternalState<TValues>>(options.onSubscriberError);\n const disposalController = new AbortController();\n let disposed = false;\n let dispatching = false;\n const pendingWrites: Array<(current: InternalState<TValues>) => InternalState<TValues>> = [];\n let validationController: AbortController | undefined;\n let revision = 0;\n let validatedSnapshot: { revision: number; value: TValues } | undefined;\n let current: InternalState<TValues> = {\n baseline: initial,\n isSubmitting: false,\n issues: undefined,\n isValidating: false,\n submitCount: 0,\n touched: {},\n validity: 'unknown',\n value: initial,\n };\n let publicState = makeFormState(current);\n\n function ensureActive(operation: string): void {\n if (disposed) throw new ForgeDisposedError(operation);\n }\n\n function write(update: (current: InternalState<TValues>) => InternalState<TValues>): void {\n pendingWrites.push(update);\n if (dispatching) return;\n\n dispatching = true;\n try {\n while (pendingWrites.length > 0) {\n current = Object.freeze(pendingWrites.shift()!(current));\n publicState = makeFormState(current);\n notifier.notify(current);\n }\n } finally {\n dispatching = false;\n }\n }\n\n function abortValidation(): void {\n validationController?.abort();\n }\n\n const access: FieldAccess = {\n abortValidation,\n addListener: notifier.add,\n ensureActive,\n invokeListener: notifier.call,\n readState: () => current,\n resetValue(path) {\n write((c) => {\n revision++;\n validatedSnapshot = undefined;\n return {\n ...c,\n touched: writeMeta(c.touched, path, false),\n validity: 'unknown',\n value: resetAtPath(c.value, c.baseline, path),\n };\n });\n },\n setTouched(path, touched) {\n write((c) => ({ ...c, touched: writeMeta(c.touched, path, touched) }));\n },\n setValue(path, next) {\n write((c) => {\n const previous = readAtPath(c.value, path);\n const raw = typeof next === 'function' ? next(previous) : next;\n const value = prepare(raw);\n revision++;\n validatedSnapshot = undefined;\n return { ...c, validity: 'unknown', value: writeAtPath(c.value, path, value) };\n });\n },\n };\n\n async function validate(externalSignal?: AbortSignal): Promise<ValidationResult> {\n ensureActive('validate');\n abortValidation();\n\n const controller = new AbortController();\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n const snapshot = current.value;\n const snapshotRevision = revision;\n validationController = controller;\n validatedSnapshot = undefined;\n write((c) => ({ ...c, isValidating: true }));\n\n try {\n if (signal.aborted || revision !== snapshotRevision) return Object.freeze({ status: 'aborted' });\n const result: readonly ValidationIssue[] | undefined = options.validate\n ? await abortable(\n Promise.resolve(options.validate(snapshot as Parameters<FormValidator<TValues>>[0], signal)),\n signal,\n )\n : undefined;\n\n if (signal.aborted || validationController !== controller || revision !== snapshotRevision) {\n return Object.freeze({ status: 'aborted' });\n }\n\n const issues = normalizeIssues(result);\n const validity: Validity = issues === undefined ? 'valid' : 'invalid';\n validationController = undefined;\n write((c) => ({ ...c, issues, isValidating: false, validity }));\n\n if (signal.aborted || revision !== snapshotRevision || validationController !== undefined) {\n return Object.freeze({ status: 'aborted' });\n }\n\n validatedSnapshot = validity === 'valid' ? { revision, value: snapshot } : undefined;\n return validity === 'valid'\n ? Object.freeze({ status: 'valid' })\n : Object.freeze({ issues: issues as readonly ValidationIssue[], status: 'invalid' });\n } catch (error) {\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n throw new ForgeValidationError('Form validation failed.', { cause: error });\n } finally {\n if (validationController === controller) {\n validationController = undefined;\n if (!disposed) write((c) => ({ ...c, isValidating: false }));\n }\n }\n }\n\n const form: Form<TValues> = {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose() {\n if (disposed) return;\n\n disposed = true;\n pendingWrites.length = 0;\n validatedSnapshot = undefined;\n disposalController.abort();\n abortValidation();\n notifier.clear();\n },\n get disposed() {\n return disposed;\n },\n field(key) {\n assertSafeKey(key);\n if (!isRecord(current.value)) throw new ForgeConfigError('Form value must be an object.');\n\n return createField<TValues[typeof key]>([key], access);\n },\n patch(next) {\n ensureActive('patch');\n abortValidation();\n\n write((c) => {\n const partial =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => Partial<TValues>)(c.value as Form<TValues>['value'])\n : next;\n const merged = { ...c.value, ...partial };\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(merged) as TValues) };\n });\n },\n reset(next) {\n ensureActive('reset');\n abortValidation();\n\n const replacement = next === undefined ? undefined : immutable(prepare(next) as TValues);\n write((c) => {\n const baseline = replacement ?? c.baseline;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, baseline, issues: undefined, touched: {}, validity: 'unknown', value: baseline };\n });\n },\n set(next) {\n ensureActive('set');\n abortValidation();\n\n write((c) => {\n const value =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => TValues)(c.value as Form<TValues>['value'])\n : next;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(value) as TValues) };\n });\n },\n get state() {\n return publicState;\n },\n async submit<TResult = void>(\n handler: (values: Form<TValues>['value'], signal: AbortSignal) => MaybePromise<TResult>,\n externalSignal?: AbortSignal,\n ): Promise<SubmitResult<TResult>> {\n ensureActive('submit');\n\n if (current.isSubmitting) {\n throw new ForgeSubmitError('submit() called while a submission is already in progress');\n }\n\n const signal = externalSignal\n ? AbortSignal.any([disposalController.signal, externalSignal])\n : disposalController.signal;\n\n write((c) => ({\n ...c,\n isSubmitting: true,\n submitCount: c.submitCount + 1,\n touched: touchAll(c.value),\n }));\n\n try {\n const result = await validate(externalSignal);\n\n if (result.status === 'aborted') return Object.freeze({ status: 'aborted' });\n\n if (result.status === 'invalid') {\n return Object.freeze({ issues: result.issues, status: 'invalid' });\n }\n\n const validated = validatedSnapshot;\n if (!validated || validated.revision !== revision || signal.aborted) {\n return Object.freeze({ status: 'aborted' });\n }\n\n try {\n return Object.freeze({\n status: 'ok',\n value: await handler(validated.value as Form<TValues>['value'], signal),\n });\n } catch (error) {\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n throw error;\n }\n } finally {\n if (!disposed) write((c) => ({ ...c, isSubmitting: false }));\n }\n },\n subscribe(listener, subscribeOptions = {}): Unsubscribe {\n ensureActive('subscribe');\n\n if (subscribeOptions.immediate) notifier.call(() => listener(publicState));\n\n return notifier.add(() => listener(publicState));\n },\n [Symbol.dispose]() {\n form.dispose();\n },\n validate,\n get value() {\n return current.value as Form<TValues>['value'];\n },\n };\n\n return form;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"form.js","names":[],"sources":["../src/form.ts"],"sourcesContent":["import { abortable } from '@vielzeug/arsenal';\n\nimport { createField, type FieldAccess } from './_field.js';\nimport { createNotifier } from './_notify.js';\nimport {\n assertSafeKey,\n immutable,\n isRecord,\n type MetaRoot,\n readAtPath,\n resetAtPath,\n touchAll,\n writeAtPath,\n writeMeta,\n} from './core/path.js';\nimport { ForgeConfigError, ForgeDisposedError, ForgeSubmitError, ForgeValidationError } from './errors.js';\nimport type {\n Form,\n FormOptions,\n FormState,\n FormValidator,\n MaybePromise,\n SubmitResult,\n Unsubscribe,\n ValidationIssue,\n ValidationResult,\n} from './types.js';\n\ntype Validity = 'invalid' | 'unknown' | 'valid';\n\ntype InternalState<TValues extends Record<string, unknown>> = Readonly<{\n baseline: TValues;\n issues: readonly ValidationIssue[] | undefined;\n isSubmitting: boolean;\n isValidating: boolean;\n submitCount: number;\n touched: MetaRoot;\n validity: Validity;\n value: TValues;\n}>;\n\nfunction normalizeIssues(result: readonly ValidationIssue[] | undefined): readonly ValidationIssue[] | undefined {\n if (result === undefined || result.length === 0) return undefined;\n\n return Object.freeze(\n result.map((issue) => {\n if (!issue || typeof issue.message !== 'string' || !Array.isArray(issue.path)) {\n throw new ForgeConfigError('Validators must return issues with a string message and array path.');\n }\n\n const path = issue.path.map((part) => {\n if (typeof part === 'number') {\n if (!Number.isSafeInteger(part) || part < 0) throw new ForgeConfigError(`Invalid issue path index ${part}.`);\n return part;\n }\n if (typeof part === 'string') {\n assertSafeKey(part);\n return part;\n }\n throw new ForgeConfigError('Issue paths may contain only string keys and non-negative integer indexes.');\n });\n\n return Object.freeze({ message: issue.message, path: Object.freeze(path) });\n }),\n );\n}\n\nfunction makeFormState<TValues extends Record<string, unknown>>(current: InternalState<TValues>): FormState {\n return Object.freeze({\n formError: current.issues?.find((issue) => issue.path.length === 0)?.message,\n hasErrors: (current.issues?.length ?? 0) > 0,\n issues: current.issues,\n submitCount: current.submitCount,\n submitting: current.isSubmitting,\n touched: Object.keys(current.touched).length > 0,\n validating: current.isValidating,\n validity: current.validity,\n });\n}\n\n/** One immutable value tree and one explicit full-form validator keep form behavior locally understandable. */\nexport function createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues> {\n const normalize = options.normalize;\n const prepare = (value: unknown): unknown => (normalize ? normalize(value) : value);\n const initial = immutable(prepare(options.initialValues) as TValues);\n const notifier = createNotifier<InternalState<TValues>>(options.onSubscriberError);\n const disposalController = new AbortController();\n let disposed = false;\n let dispatching = false;\n const pendingWrites: Array<(current: InternalState<TValues>) => InternalState<TValues>> = [];\n let validationController: AbortController | undefined;\n let revision = 0;\n let validatedSnapshot: { revision: number; value: TValues } | undefined;\n let current: InternalState<TValues> = {\n baseline: initial,\n isSubmitting: false,\n issues: undefined,\n isValidating: false,\n submitCount: 0,\n touched: {},\n validity: 'unknown',\n value: initial,\n };\n let publicState = makeFormState(current);\n\n function ensureActive(operation: string): void {\n if (disposed) throw new ForgeDisposedError(operation);\n }\n\n function write(update: (current: InternalState<TValues>) => InternalState<TValues>): void {\n pendingWrites.push(update);\n if (dispatching) return;\n\n dispatching = true;\n try {\n while (pendingWrites.length > 0) {\n current = Object.freeze(pendingWrites.shift()!(current));\n publicState = makeFormState(current);\n notifier.notify(current);\n }\n } finally {\n dispatching = false;\n }\n }\n\n function abortValidation(): void {\n validationController?.abort();\n }\n\n const access: FieldAccess = {\n abortValidation,\n addListener: notifier.add,\n ensureActive,\n invokeListener: notifier.call,\n readState: () => current,\n resetValue(path) {\n write((c) => {\n revision++;\n validatedSnapshot = undefined;\n return {\n ...c,\n touched: writeMeta(c.touched, path, false),\n validity: 'unknown',\n value: resetAtPath(c.value, c.baseline, path),\n };\n });\n },\n setTouched(path, touched) {\n write((c) => ({ ...c, touched: writeMeta(c.touched, path, touched) }));\n },\n setValue(path, next) {\n write((c) => {\n const previous = readAtPath(c.value, path);\n const raw = typeof next === 'function' ? next(previous) : next;\n const value = prepare(raw);\n revision++;\n validatedSnapshot = undefined;\n return { ...c, validity: 'unknown', value: writeAtPath(c.value, path, value) };\n });\n },\n };\n\n async function validate(externalSignal?: AbortSignal): Promise<ValidationResult> {\n ensureActive('validate');\n abortValidation();\n\n const controller = new AbortController();\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n const snapshot = current.value;\n const snapshotRevision = revision;\n validationController = controller;\n validatedSnapshot = undefined;\n write((c) => ({ ...c, isValidating: true }));\n\n try {\n if (signal.aborted || revision !== snapshotRevision) return Object.freeze({ status: 'aborted' });\n const result: readonly ValidationIssue[] | undefined = options.validate\n ? await abortable(\n Promise.resolve(options.validate(snapshot as Parameters<FormValidator<TValues>>[0], signal)),\n signal,\n )\n : undefined;\n\n if (signal.aborted || validationController !== controller || revision !== snapshotRevision) {\n return Object.freeze({ status: 'aborted' });\n }\n\n const issues = normalizeIssues(result);\n const validity: Validity = issues === undefined ? 'valid' : 'invalid';\n validationController = undefined;\n write((c) => ({ ...c, issues, isValidating: false, validity }));\n\n if (signal.aborted || revision !== snapshotRevision || validationController !== undefined) {\n return Object.freeze({ status: 'aborted' });\n }\n\n validatedSnapshot = validity === 'valid' ? { revision, value: snapshot } : undefined;\n return validity === 'valid'\n ? Object.freeze({ status: 'valid' })\n : Object.freeze({ issues: issues as readonly ValidationIssue[], status: 'invalid' });\n } catch (error) {\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n throw new ForgeValidationError('Form validation failed.', { cause: error });\n } finally {\n if (validationController === controller) {\n validationController = undefined;\n if (!disposed) write((c) => ({ ...c, isValidating: false }));\n }\n }\n }\n\n const form: Form<TValues> = {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose() {\n if (disposed) return;\n\n disposed = true;\n pendingWrites.length = 0;\n validatedSnapshot = undefined;\n disposalController.abort();\n abortValidation();\n notifier.clear();\n },\n get disposed() {\n return disposed;\n },\n field(key) {\n assertSafeKey(key);\n if (!isRecord(current.value)) throw new ForgeConfigError('Form value must be an object.');\n\n return createField<TValues[typeof key]>([key], access);\n },\n patch(next) {\n ensureActive('patch');\n abortValidation();\n\n write((c) => {\n const partial =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => Partial<TValues>)(c.value as Form<TValues>['value'])\n : next;\n const merged = { ...c.value, ...partial };\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(merged) as TValues) };\n });\n },\n reset(next) {\n ensureActive('reset');\n abortValidation();\n\n const replacement = next === undefined ? undefined : immutable(prepare(next) as TValues);\n write((c) => {\n const baseline = replacement ?? c.baseline;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, baseline, issues: undefined, touched: {}, validity: 'unknown', value: baseline };\n });\n },\n set(next) {\n ensureActive('set');\n abortValidation();\n\n write((c) => {\n const value =\n typeof next === 'function'\n ? (next as (previous: Form<TValues>['value']) => TValues)(c.value as Form<TValues>['value'])\n : next;\n revision++;\n validatedSnapshot = undefined;\n return { ...c, issues: undefined, validity: 'unknown', value: immutable(prepare(value) as TValues) };\n });\n },\n get state() {\n return publicState;\n },\n async submit<TResult = void>(\n handler: (values: Form<TValues>['value'], signal: AbortSignal) => MaybePromise<TResult>,\n externalSignal?: AbortSignal,\n ): Promise<SubmitResult<TResult>> {\n ensureActive('submit');\n\n if (current.isSubmitting) {\n throw new ForgeSubmitError('submit() called while a submission is already in progress');\n }\n\n const signal = externalSignal\n ? AbortSignal.any([disposalController.signal, externalSignal])\n : disposalController.signal;\n\n write((c) => ({\n ...c,\n isSubmitting: true,\n submitCount: c.submitCount + 1,\n touched: touchAll(c.value),\n }));\n\n try {\n const result = await validate(externalSignal);\n\n if (result.status === 'aborted') return Object.freeze({ status: 'aborted' });\n\n if (result.status === 'invalid') {\n return Object.freeze({ issues: result.issues, status: 'invalid' });\n }\n\n const validated = validatedSnapshot;\n if (!validated || validated.revision !== revision || signal.aborted) {\n return Object.freeze({ status: 'aborted' });\n }\n\n try {\n return Object.freeze({\n status: 'ok',\n value: await handler(validated.value as Form<TValues>['value'], signal),\n });\n } catch (error) {\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n throw error;\n }\n } finally {\n if (!disposed) write((c) => ({ ...c, isSubmitting: false }));\n }\n },\n subscribe(listener, subscribeOptions = {}): Unsubscribe {\n ensureActive('subscribe');\n\n if (subscribeOptions.immediate) notifier.call(() => listener(publicState));\n\n return notifier.add(() => listener(publicState));\n },\n [Symbol.dispose]() {\n form.dispose();\n },\n validate,\n get value() {\n return current.value as Form<TValues>['value'];\n },\n };\n\n return form;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1 @@
1
- import { ForgeConfigError as e, ForgeDisposedError as t, ForgeError as n, ForgeSubmitError as r, ForgeValidationError as i } from "./errors.js";
2
- import { createForm as a } from "./form.js";
3
- import { toPlainValues as o } from "./plain.js";
4
- export { e as ForgeConfigError, t as ForgeDisposedError, n as ForgeError, r as ForgeSubmitError, i as ForgeValidationError, a as createForm, o as toPlainValues };
1
+ import{ForgeConfigError as e,ForgeDisposedError as t,ForgeError as n,ForgeSubmitError as r,ForgeValidationError as i}from"./errors.js";import{createForm as a}from"./form.js";import{toPlainValues as o}from"./plain.js";export{e as ForgeConfigError,t as ForgeDisposedError,n as ForgeError,r as ForgeSubmitError,i as ForgeValidationError,a as createForm,o as toPlainValues};
package/dist/persist.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");async function t(t,n,r,i,a,o={}){let s=o.signal?AbortSignal.any([t.disposalSignal,o.signal]):t.disposalSignal;if(s.aborted)throw(0,e.abortError)(s);let c=t.value,l=await(0,e.abortable)(n.get(r,i),s);if(l===void 0||t.value!==c)return!1;let u=a.fromRecord(l);return u===void 0||t.value!==c?!1:(t.reset(u),!0)}async function n(t,n,r,i,a={}){let o=a.signal?AbortSignal.any([t.disposalSignal,a.signal]):t.disposalSignal;if(o.aborted)throw(0,e.abortError)(o);await(0,e.abortable)(n.put(r,i.toRecord(t.value)),o)}exports.loadForm=t,exports.saveForm=n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");async function loadForm(t,n,r,i,a,o={}){let s=o.signal?AbortSignal.any([t.disposalSignal,o.signal]):t.disposalSignal;if(s.aborted)throw(0,e.abortError)(s);let c=t.value,l=await(0,e.abortable)(n.get(r,i),s);if(l===void 0||t.value!==c)return!1;let u=a.fromRecord(l);return u===void 0||t.value!==c?!1:(t.reset(u),!0)}async function saveForm(t,n,r,i,a={}){let o=a.signal?AbortSignal.any([t.disposalSignal,a.signal]):t.disposalSignal;if(o.aborted)throw(0,e.abortError)(o);await(0,e.abortable)(n.put(r,i.toRecord(t.value)),o)}exports.loadForm=loadForm,exports.saveForm=saveForm;
2
2
  //# sourceMappingURL=persist.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"persist.cjs","names":[],"sources":["../src/persist.ts"],"sourcesContent":["import { abortable, abortError } from '@vielzeug/arsenal';\n\nimport type { Form } from './types.js';\n\nexport type FormDraftCodec<TValues extends Record<string, unknown>, RecordValue> = Readonly<{\n fromRecord(record: RecordValue): TValues | undefined;\n toRecord(values: Form<TValues>['value']): RecordValue;\n}>;\n\nexport type FormDraftStore<Table extends string, Key, RecordValue> = {\n get(table: Table, key: Key): Promise<RecordValue | undefined>;\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport type FormPersistenceOptions = Readonly<{ signal?: AbortSignal }>;\n\nexport type FormDraftWriteStore<Table extends string, RecordValue> = {\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport async function loadForm<TValues extends Record<string, unknown>, Table extends string, Key, RecordValue>(\n form: Form<TValues>,\n store: FormDraftStore<Table, Key, RecordValue>,\n table: Table,\n key: Key,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<boolean> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n const snapshot = form.value;\n const record = await abortable(store.get(table, key), signal);\n if (record === undefined || form.value !== snapshot) return false;\n\n const values = codec.fromRecord(record);\n if (values === undefined || form.value !== snapshot) return false;\n\n form.reset(values);\n return true;\n}\n\nexport async function saveForm<TValues extends Record<string, unknown>, Table extends string, RecordValue>(\n form: Form<TValues>,\n store: FormDraftWriteStore<Table, RecordValue>,\n table: Table,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<void> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n await abortable(store.put(table, codec.toRecord(form.value)), signal);\n}\n"],"mappings":"sGAoBA,eAAsB,EACpB,EACA,EACA,EACA,EACA,EACA,EAAkC,CAAC,EACjB,CAClB,IAAM,EAAS,EAAQ,OAAS,YAAY,IAAI,CAAC,EAAK,eAAgB,EAAQ,MAAM,CAAC,EAAI,EAAK,eAC9F,GAAI,EAAO,QAAS,MAAA,EAAM,EAAA,WAAA,CAAW,CAAM,EAC3C,IAAM,EAAW,EAAK,MAChB,EAAS,MAAA,EAAM,EAAA,UAAA,CAAU,EAAM,IAAI,EAAO,CAAG,EAAG,CAAM,EAC5D,GAAI,IAAW,IAAA,IAAa,EAAK,QAAU,EAAU,MAAO,GAE5D,IAAM,EAAS,EAAM,WAAW,CAAM,EAItC,OAHI,IAAW,IAAA,IAAa,EAAK,QAAU,EAAiB,IAE5D,EAAK,MAAM,CAAM,EACV,GACT,CAEA,eAAsB,EACpB,EACA,EACA,EACA,EACA,EAAkC,CAAC,EACpB,CACf,IAAM,EAAS,EAAQ,OAAS,YAAY,IAAI,CAAC,EAAK,eAAgB,EAAQ,MAAM,CAAC,EAAI,EAAK,eAC9F,GAAI,EAAO,QAAS,MAAA,EAAM,EAAA,WAAA,CAAW,CAAM,EAC3C,MAAA,EAAM,EAAA,UAAA,CAAU,EAAM,IAAI,EAAO,EAAM,SAAS,EAAK,KAAK,CAAC,EAAG,CAAM,CACtE"}
1
+ {"version":3,"file":"persist.cjs","names":[],"sources":["../src/persist.ts"],"sourcesContent":["import { abortable, abortError } from '@vielzeug/arsenal';\n\nimport type { Form } from './types.js';\n\nexport type FormDraftCodec<TValues extends Record<string, unknown>, RecordValue> = Readonly<{\n fromRecord(record: RecordValue): TValues | undefined;\n toRecord(values: Form<TValues>['value']): RecordValue;\n}>;\n\nexport type FormDraftStore<Table extends string, Key, RecordValue> = {\n get(table: Table, key: Key): Promise<RecordValue | undefined>;\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport type FormPersistenceOptions = Readonly<{ signal?: AbortSignal }>;\n\nexport type FormDraftWriteStore<Table extends string, RecordValue> = {\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport async function loadForm<TValues extends Record<string, unknown>, Table extends string, Key, RecordValue>(\n form: Form<TValues>,\n store: FormDraftStore<Table, Key, RecordValue>,\n table: Table,\n key: Key,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<boolean> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n const snapshot = form.value;\n const record = await abortable(store.get(table, key), signal);\n if (record === undefined || form.value !== snapshot) return false;\n\n const values = codec.fromRecord(record);\n if (values === undefined || form.value !== snapshot) return false;\n\n form.reset(values);\n return true;\n}\n\nexport async function saveForm<TValues extends Record<string, unknown>, Table extends string, RecordValue>(\n form: Form<TValues>,\n store: FormDraftWriteStore<Table, RecordValue>,\n table: Table,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<void> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n await abortable(store.put(table, codec.toRecord(form.value)), signal);\n}\n"],"mappings":"sGAoBA,eAAsB,SACpB,EACA,EACA,EACA,EACA,EACA,EAAkC,CAAC,EACjB,CAClB,IAAM,EAAS,EAAQ,OAAS,YAAY,IAAI,CAAC,EAAK,eAAgB,EAAQ,MAAM,CAAC,EAAI,EAAK,eAC9F,GAAI,EAAO,QAAS,MAAA,EAAM,EAAA,WAAA,CAAW,CAAM,EAC3C,IAAM,EAAW,EAAK,MAChB,EAAS,MAAA,EAAM,EAAA,UAAA,CAAU,EAAM,IAAI,EAAO,CAAG,EAAG,CAAM,EAC5D,GAAI,IAAW,IAAA,IAAa,EAAK,QAAU,EAAU,MAAO,GAE5D,IAAM,EAAS,EAAM,WAAW,CAAM,EAItC,OAHI,IAAW,IAAA,IAAa,EAAK,QAAU,EAAiB,IAE5D,EAAK,MAAM,CAAM,EACV,GACT,CAEA,eAAsB,SACpB,EACA,EACA,EACA,EACA,EAAkC,CAAC,EACpB,CACf,IAAM,EAAS,EAAQ,OAAS,YAAY,IAAI,CAAC,EAAK,eAAgB,EAAQ,MAAM,CAAC,EAAI,EAAK,eAC9F,GAAI,EAAO,QAAS,MAAA,EAAM,EAAA,WAAA,CAAW,CAAM,EAC3C,MAAA,EAAM,EAAA,UAAA,CAAU,EAAM,IAAI,EAAO,EAAM,SAAS,EAAK,KAAK,CAAC,EAAG,CAAM,CACtE"}
package/dist/persist.js CHANGED
@@ -1,19 +1,2 @@
1
- import { abortError as e, abortable as t } from "@vielzeug/arsenal";
2
- //#region src/persist.ts
3
- async function n(n, r, i, a, o, s = {}) {
4
- let c = s.signal ? AbortSignal.any([n.disposalSignal, s.signal]) : n.disposalSignal;
5
- if (c.aborted) throw e(c);
6
- let l = n.value, u = await t(r.get(i, a), c);
7
- if (u === void 0 || n.value !== l) return !1;
8
- let d = o.fromRecord(u);
9
- return d === void 0 || n.value !== l ? !1 : (n.reset(d), !0);
10
- }
11
- async function r(n, r, i, a, o = {}) {
12
- let s = o.signal ? AbortSignal.any([n.disposalSignal, o.signal]) : n.disposalSignal;
13
- if (s.aborted) throw e(s);
14
- await t(r.put(i, a.toRecord(n.value)), s);
15
- }
16
- //#endregion
17
- export { n as loadForm, r as saveForm };
18
-
1
+ import{abortError as e,abortable as t}from"@vielzeug/arsenal";async function loadForm(n,r,i,a,o,s={}){let c=s.signal?AbortSignal.any([n.disposalSignal,s.signal]):n.disposalSignal;if(c.aborted)throw e(c);let l=n.value,u=await t(r.get(i,a),c);if(u===void 0||n.value!==l)return!1;let d=o.fromRecord(u);return d===void 0||n.value!==l?!1:(n.reset(d),!0)}async function saveForm(n,r,i,a,o={}){let s=o.signal?AbortSignal.any([n.disposalSignal,o.signal]):n.disposalSignal;if(s.aborted)throw e(s);await t(r.put(i,a.toRecord(n.value)),s)}export{loadForm,saveForm};
19
2
  //# sourceMappingURL=persist.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"persist.js","names":[],"sources":["../src/persist.ts"],"sourcesContent":["import { abortable, abortError } from '@vielzeug/arsenal';\n\nimport type { Form } from './types.js';\n\nexport type FormDraftCodec<TValues extends Record<string, unknown>, RecordValue> = Readonly<{\n fromRecord(record: RecordValue): TValues | undefined;\n toRecord(values: Form<TValues>['value']): RecordValue;\n}>;\n\nexport type FormDraftStore<Table extends string, Key, RecordValue> = {\n get(table: Table, key: Key): Promise<RecordValue | undefined>;\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport type FormPersistenceOptions = Readonly<{ signal?: AbortSignal }>;\n\nexport type FormDraftWriteStore<Table extends string, RecordValue> = {\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport async function loadForm<TValues extends Record<string, unknown>, Table extends string, Key, RecordValue>(\n form: Form<TValues>,\n store: FormDraftStore<Table, Key, RecordValue>,\n table: Table,\n key: Key,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<boolean> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n const snapshot = form.value;\n const record = await abortable(store.get(table, key), signal);\n if (record === undefined || form.value !== snapshot) return false;\n\n const values = codec.fromRecord(record);\n if (values === undefined || form.value !== snapshot) return false;\n\n form.reset(values);\n return true;\n}\n\nexport async function saveForm<TValues extends Record<string, unknown>, Table extends string, RecordValue>(\n form: Form<TValues>,\n store: FormDraftWriteStore<Table, RecordValue>,\n table: Table,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<void> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n await abortable(store.put(table, codec.toRecord(form.value)), signal);\n}\n"],"mappings":";;AAoBA,eAAsB,EACpB,GACA,GACA,GACA,GACA,GACA,IAAkC,CAAC,GACjB;CAClB,IAAM,IAAS,EAAQ,SAAS,YAAY,IAAI,CAAC,EAAK,gBAAgB,EAAQ,MAAM,CAAC,IAAI,EAAK;CAC9F,IAAI,EAAO,SAAS,MAAM,EAAW,CAAM;CAC3C,IAAM,IAAW,EAAK,OAChB,IAAS,MAAM,EAAU,EAAM,IAAI,GAAO,CAAG,GAAG,CAAM;CAC5D,IAAI,MAAW,KAAA,KAAa,EAAK,UAAU,GAAU,OAAO;CAE5D,IAAM,IAAS,EAAM,WAAW,CAAM;CAItC,OAHI,MAAW,KAAA,KAAa,EAAK,UAAU,IAAiB,MAE5D,EAAK,MAAM,CAAM,GACV;AACT;AAEA,eAAsB,EACpB,GACA,GACA,GACA,GACA,IAAkC,CAAC,GACpB;CACf,IAAM,IAAS,EAAQ,SAAS,YAAY,IAAI,CAAC,EAAK,gBAAgB,EAAQ,MAAM,CAAC,IAAI,EAAK;CAC9F,IAAI,EAAO,SAAS,MAAM,EAAW,CAAM;CAC3C,MAAM,EAAU,EAAM,IAAI,GAAO,EAAM,SAAS,EAAK,KAAK,CAAC,GAAG,CAAM;AACtE"}
1
+ {"version":3,"file":"persist.js","names":[],"sources":["../src/persist.ts"],"sourcesContent":["import { abortable, abortError } from '@vielzeug/arsenal';\n\nimport type { Form } from './types.js';\n\nexport type FormDraftCodec<TValues extends Record<string, unknown>, RecordValue> = Readonly<{\n fromRecord(record: RecordValue): TValues | undefined;\n toRecord(values: Form<TValues>['value']): RecordValue;\n}>;\n\nexport type FormDraftStore<Table extends string, Key, RecordValue> = {\n get(table: Table, key: Key): Promise<RecordValue | undefined>;\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport type FormPersistenceOptions = Readonly<{ signal?: AbortSignal }>;\n\nexport type FormDraftWriteStore<Table extends string, RecordValue> = {\n put(table: Table, value: RecordValue): Promise<void>;\n};\n\nexport async function loadForm<TValues extends Record<string, unknown>, Table extends string, Key, RecordValue>(\n form: Form<TValues>,\n store: FormDraftStore<Table, Key, RecordValue>,\n table: Table,\n key: Key,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<boolean> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n const snapshot = form.value;\n const record = await abortable(store.get(table, key), signal);\n if (record === undefined || form.value !== snapshot) return false;\n\n const values = codec.fromRecord(record);\n if (values === undefined || form.value !== snapshot) return false;\n\n form.reset(values);\n return true;\n}\n\nexport async function saveForm<TValues extends Record<string, unknown>, Table extends string, RecordValue>(\n form: Form<TValues>,\n store: FormDraftWriteStore<Table, RecordValue>,\n table: Table,\n codec: FormDraftCodec<TValues, RecordValue>,\n options: FormPersistenceOptions = {},\n): Promise<void> {\n const signal = options.signal ? AbortSignal.any([form.disposalSignal, options.signal]) : form.disposalSignal;\n if (signal.aborted) throw abortError(signal);\n await abortable(store.put(table, codec.toRecord(form.value)), signal);\n}\n"],"mappings":"8DAoBA,eAAsB,SACpB,EACA,EACA,EACA,EACA,EACA,EAAkC,CAAC,EACjB,CAClB,IAAM,EAAS,EAAQ,OAAS,YAAY,IAAI,CAAC,EAAK,eAAgB,EAAQ,MAAM,CAAC,EAAI,EAAK,eAC9F,GAAI,EAAO,QAAS,MAAM,EAAW,CAAM,EAC3C,IAAM,EAAW,EAAK,MAChB,EAAS,MAAM,EAAU,EAAM,IAAI,EAAO,CAAG,EAAG,CAAM,EAC5D,GAAI,IAAW,IAAA,IAAa,EAAK,QAAU,EAAU,MAAO,GAE5D,IAAM,EAAS,EAAM,WAAW,CAAM,EAItC,OAHI,IAAW,IAAA,IAAa,EAAK,QAAU,EAAiB,IAE5D,EAAK,MAAM,CAAM,EACV,GACT,CAEA,eAAsB,SACpB,EACA,EACA,EACA,EACA,EAAkC,CAAC,EACpB,CACf,IAAM,EAAS,EAAQ,OAAS,YAAY,IAAI,CAAC,EAAK,eAAgB,EAAQ,MAAM,CAAC,EAAI,EAAK,eAC9F,GAAI,EAAO,QAAS,MAAM,EAAW,CAAM,EAC3C,MAAM,EAAU,EAAM,IAAI,EAAO,EAAM,SAAS,EAAK,KAAK,CAAC,EAAG,CAAM,CACtE"}
package/dist/plain.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./core/path.cjs");function t(t){return e.toPlainValues(t)}exports.toPlainValues=t;
1
+ const e=require("./core/path.cjs");function toPlainValues(t){return e.toPlainValues(t)}exports.toPlainValues=toPlainValues;
2
2
  //# sourceMappingURL=plain.cjs.map