@vielzeug/forge 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/_utils.cjs +1 -1
  2. package/dist/_utils.cjs.map +1 -1
  3. package/dist/_utils.d.ts.map +1 -1
  4. package/dist/_utils.js +1 -0
  5. package/dist/_utils.js.map +1 -1
  6. package/dist/core/array.cjs +1 -1
  7. package/dist/core/array.cjs.map +1 -1
  8. package/dist/core/array.d.ts.map +1 -1
  9. package/dist/core/array.js +63 -32
  10. package/dist/core/array.js.map +1 -1
  11. package/dist/core/asyncQueue.cjs.map +1 -1
  12. package/dist/core/asyncQueue.d.ts +2 -2
  13. package/dist/core/asyncQueue.js.map +1 -1
  14. package/dist/core/context.cjs +1 -1
  15. package/dist/core/context.cjs.map +1 -1
  16. package/dist/core/context.d.ts +2 -5
  17. package/dist/core/context.d.ts.map +1 -1
  18. package/dist/core/context.js +72 -109
  19. package/dist/core/context.js.map +1 -1
  20. package/dist/core/fields.cjs +2 -0
  21. package/dist/core/fields.cjs.map +1 -0
  22. package/dist/core/fields.d.ts +52 -0
  23. package/dist/core/fields.d.ts.map +1 -0
  24. package/dist/core/fields.js +310 -0
  25. package/dist/core/fields.js.map +1 -0
  26. package/dist/core/lifecycle.cjs +1 -1
  27. package/dist/core/lifecycle.cjs.map +1 -1
  28. package/dist/core/lifecycle.d.ts +3 -1
  29. package/dist/core/lifecycle.d.ts.map +1 -1
  30. package/dist/core/lifecycle.js +10 -14
  31. package/dist/core/lifecycle.js.map +1 -1
  32. package/dist/core/notifier.cjs +2 -0
  33. package/dist/core/notifier.cjs.map +1 -0
  34. package/dist/core/notifier.d.ts +33 -0
  35. package/dist/core/notifier.d.ts.map +1 -0
  36. package/dist/core/notifier.js +78 -0
  37. package/dist/core/notifier.js.map +1 -0
  38. package/dist/core/observe.cjs +1 -1
  39. package/dist/core/observe.cjs.map +1 -1
  40. package/dist/core/observe.d.ts +8 -6
  41. package/dist/core/observe.d.ts.map +1 -1
  42. package/dist/core/observe.js +34 -45
  43. package/dist/core/observe.js.map +1 -1
  44. package/dist/core/scope.cjs +1 -1
  45. package/dist/core/scope.cjs.map +1 -1
  46. package/dist/core/scope.d.ts +6 -3
  47. package/dist/core/scope.d.ts.map +1 -1
  48. package/dist/core/scope.js +105 -156
  49. package/dist/core/scope.js.map +1 -1
  50. package/dist/errors.cjs +1 -1
  51. package/dist/errors.cjs.map +1 -1
  52. package/dist/errors.d.ts +3 -1
  53. package/dist/errors.d.ts.map +1 -1
  54. package/dist/errors.js +2 -2
  55. package/dist/errors.js.map +1 -1
  56. package/dist/forge.cjs +1 -1
  57. package/dist/forge.cjs.map +1 -1
  58. package/dist/forge.iife.js +1 -1
  59. package/dist/forge.iife.js.map +1 -1
  60. package/dist/forge.js +1 -1
  61. package/dist/forge.js.map +1 -1
  62. package/dist/form.cjs +1 -1
  63. package/dist/form.cjs.map +1 -1
  64. package/dist/form.d.ts.map +1 -1
  65. package/dist/form.js +45 -56
  66. package/dist/form.js.map +1 -1
  67. package/dist/index.cjs +1 -1
  68. package/dist/index.js +4 -4
  69. package/dist/types.cjs +1 -1
  70. package/dist/types.cjs.map +1 -1
  71. package/dist/types.d.ts +53 -36
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/types.js +2 -2
  74. package/dist/types.js.map +1 -1
  75. package/package.json +2 -2
  76. package/dist/core/validation.cjs +0 -2
  77. package/dist/core/validation.cjs.map +0 -1
  78. package/dist/core/validation.d.ts +0 -23
  79. package/dist/core/validation.d.ts.map +0 -1
  80. package/dist/core/validation.js +0 -173
  81. package/dist/core/validation.js.map +0 -1
  82. package/dist/core/values.cjs +0 -2
  83. package/dist/core/values.cjs.map +0 -1
  84. package/dist/core/values.d.ts +0 -31
  85. package/dist/core/values.d.ts.map +0 -1
  86. package/dist/core/values.js +0 -149
  87. package/dist/core/values.js.map +0 -1
@@ -1,7 +1,8 @@
1
1
  import { ForgeDisposedError as e } from "../errors.js";
2
- import { assertSafeKey as t, flattenValues as n, isSafeKey as r } from "../_utils.js";
3
2
  import "../types.js";
4
- import { batch as i, signal as a } from "@vielzeug/ripple";
3
+ import { assertSafeKey as t, flattenValues as n, isSafeKey as r } from "../_utils.js";
4
+ import { createNotifier as i } from "./notifier.js";
5
+ import { batch as a } from "@vielzeug/ripple";
5
6
  //#region src/core/context.ts
6
7
  function o(e) {
7
8
  if (!e) return;
@@ -22,151 +23,113 @@ function o(e) {
22
23
  function s(r) {
23
24
  let s = /* @__PURE__ */ new Map();
24
25
  for (let [e, n] of Object.entries(r.validators ?? {})) t(e), s.set(e, n);
25
- let c = o(r.validator), l = r.connect ?? {}, u = typeof r.defaultValues == "function", d = typeof r.defaultValues == "function" ? void 0 : r.defaultValues, f = new Map(Object.entries(n(d ?? {}))), p = new Map(f), m = /* @__PURE__ */ new Map(), h = /* @__PURE__ */ new Set(), g = /* @__PURE__ */ new Set(), _ = /* @__PURE__ */ new Map(), v = /* @__PURE__ */ new Set(), y = /* @__PURE__ */ new Map(), b = new AbortController(), x = null, S = null, C = /* @__PURE__ */ new Map(), w = /* @__PURE__ */ new Map(), T = /* @__PURE__ */ new Map(), E = !1, D = 0, O = !1, k = /* @__PURE__ */ new Map(), A = /* @__PURE__ */ new Set();
26
- function j(e) {
27
- return k.has(e) || k.set(e, a(R(e), { equals: (e, t) => e.value === t.value && e.error === t.error && e.hasError === t.hasError && e.touched === t.touched && e.dirty === t.dirty })), k.get(e);
26
+ let c = o(r.validator), l = typeof r.defaultValues == "function", u = typeof r.defaultValues == "function" ? void 0 : r.defaultValues, d = new Map(Object.entries(n(u ?? {}))), f = new Map(d), p = /* @__PURE__ */ new Map(), m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set(), g = /* @__PURE__ */ new Map(), _ = /* @__PURE__ */ new Set(), v = /* @__PURE__ */ new Map(), y = new AbortController(), b = null, x = null, S = /* @__PURE__ */ new Map(), C = !1, w = 0, T = !1;
27
+ function E() {
28
+ b = null;
28
29
  }
29
- function M() {
30
+ function D() {
30
31
  x = null;
31
32
  }
32
- function N() {
33
- S = null;
34
- }
35
- function P() {
36
- return S ??= Object.freeze(Object.fromEntries(m));
33
+ function O() {
34
+ return x ??= Object.freeze(Object.fromEntries(p));
37
35
  }
38
- function F() {
36
+ function k() {
39
37
  return Object.freeze({
40
- errors: P(),
41
- isDirty: g.size > 0,
42
- isLoading: u,
43
- isSubmitting: E,
44
- isTouched: h.size > 0,
45
- isValid: m.size === 0,
46
- isValidating: _.size > 0,
47
- submitCount: D,
48
- touchedFields: Object.freeze([...h]),
49
- validatingFields: Object.freeze([..._.keys()])
38
+ errors: O(),
39
+ isDirty: h.size > 0,
40
+ isLoading: l,
41
+ isSubmitting: C,
42
+ isTouched: m.size > 0,
43
+ isValid: p.size === 0,
44
+ isValidating: g.size > 0,
45
+ submitCount: w,
46
+ touchedFields: Object.freeze([...m]),
47
+ validatingFields: Object.freeze([...g.keys()])
50
48
  });
51
49
  }
52
- let I = a(F());
53
- function L() {
54
- return I.value;
55
- }
56
- function R(e) {
57
- let t = m.get(e);
50
+ function A(e) {
51
+ let t = p.get(e);
58
52
  return {
59
- dirty: g.has(e),
53
+ dirty: h.has(e),
60
54
  error: t,
61
55
  hasError: t !== void 0,
62
- touched: h.has(e),
63
- value: p.get(e)
56
+ touched: m.has(e),
57
+ value: f.get(e)
64
58
  };
65
59
  }
66
- function z(e) {
67
- let t = C.get(e);
68
- if (t) return t;
69
- let n = Object.freeze(R(e));
70
- return C.set(e, n), n;
71
- }
72
- function B(e) {
73
- if (!O) if (M(), S = null, e === void 0) C.clear(), i(() => {
74
- I.value = F();
75
- for (let [e, t] of k) t.value = R(e);
76
- });
77
- else if (typeof e == "string") C.delete(e), i(() => {
78
- I.value = F();
79
- let t = k.get(e);
80
- t && (t.value = R(e));
81
- });
82
- else {
83
- let t = [...e];
84
- for (let e of t) C.delete(e);
85
- i(() => {
86
- I.value = F();
87
- for (let e of t) {
88
- let t = k.get(e);
89
- t && (t.value = R(e));
90
- }
91
- });
60
+ let j = i({
61
+ buildFieldState: A,
62
+ computeState: k,
63
+ invalidateCaches() {
64
+ E(), D();
92
65
  }
66
+ });
67
+ function M(t) {
68
+ if (T) throw new e(t);
93
69
  }
94
- function V() {
95
- if (O) throw new e();
96
- }
97
- function H(e) {
98
- V();
70
+ function N(e) {
71
+ M("batch");
99
72
  try {
100
- i(e);
73
+ a(e);
101
74
  } catch (e) {
102
- throw B(), e;
75
+ throw j.requestNotify(), e;
103
76
  }
104
77
  }
78
+ function P() {
79
+ T = !0, y.abort();
80
+ for (let e of v.values()) e.abort();
81
+ v.clear(), _.clear(), S.clear(), j.dispose();
82
+ }
105
83
  return {
106
- arrayCache: w,
107
- baseline: f,
108
- batch: H,
109
- buildFieldState: R,
110
- get cachedErrors() {
111
- return S;
112
- },
113
- set cachedErrors(e) {
114
- S = e;
115
- },
84
+ arrayCache: S,
85
+ baseline: d,
86
+ batch: N,
116
87
  get cachedValues() {
117
- return x;
88
+ return b;
118
89
  },
119
90
  set cachedValues(e) {
120
- x = e;
91
+ b = e;
121
92
  },
122
- computeErrors: P,
123
- computeState: F,
124
- connectDefaults: l,
125
- dirty: g,
126
- disposeController: b,
93
+ computeErrors: O,
94
+ dirty: h,
95
+ dispose: P,
96
+ disposeController: y,
127
97
  get disposed() {
128
- return O;
129
- },
130
- set disposed(e) {
131
- O = e;
98
+ return T;
132
99
  },
133
- ensureNotDisposed: V,
134
- fieldCtrls: y,
135
- fieldErrors: m,
136
- fieldSignals: k,
137
- fieldStateCache: C,
138
- formStateSignal: I,
100
+ ensureNotDisposed: M,
101
+ fieldCtrls: v,
102
+ fieldErrors: p,
139
103
  formValidator: c,
140
- getFieldSnapshot: z,
141
- getOrCreateFieldSignal: j,
142
- getStateSnapshot: L,
143
- invalidateErrors: N,
144
- invalidateValues: M,
104
+ getFieldSnapshot: j.getFieldSnapshot,
105
+ getStateSnapshot: j.getStateSnapshot,
106
+ invalidateErrors: D,
107
+ invalidateValues: E,
145
108
  get isSubmittingState() {
146
- return E;
109
+ return C;
147
110
  },
148
111
  set isSubmittingState(e) {
149
- E = e;
112
+ C = e;
150
113
  },
151
114
  get loadingState() {
152
- return u;
115
+ return l;
153
116
  },
154
117
  set loadingState(e) {
155
- u = e;
118
+ l = e;
156
119
  },
157
- requestNotify: B,
158
- rippleSubs: A,
159
- runCtrls: v,
160
- scopeCache: T,
161
- store: p,
120
+ requestNotify: j.requestNotify,
121
+ runCtrls: _,
122
+ store: f,
162
123
  get submitCount() {
163
- return D;
124
+ return w;
164
125
  },
165
126
  set submitCount(e) {
166
- D = e;
127
+ w = e;
167
128
  },
168
- touched: h,
169
- validatingRuns: _,
129
+ subscribe: j.subscribe,
130
+ subscribeField: j.subscribeField,
131
+ touched: m,
132
+ validatingRuns: g,
170
133
  validators: s
171
134
  };
172
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../src/core/context.ts"],"sourcesContent":["import { batch as rippleBatch, signal, type Signal } from '@vielzeug/ripple';\n\nimport { assertSafeKey, flattenValues, isSafeKey } from '../_utils';\nimport { ForgeDisposedError } from '../errors';\nimport {\n type ArrayField,\n type ConnectOptions,\n type FieldState,\n type FieldValidator,\n FORM_ERROR,\n type Form,\n type FormOptions,\n type FormState,\n type FormValidator,\n type SafeParseSchema,\n} from '../types';\n\n/**\n * Resolves the `validator` option to a FormValidator.\n * Auto-detects SafeParseSchema objects (duck-typed via `safeParse`) and wraps them,\n * enabling direct use of @vielzeug/spell, Zod, Valibot schemas without an explicit wrapper call.\n */\nfunction resolveFormValidator<TValues extends Record<string, unknown>>(\n raw: FormValidator<TValues> | SafeParseSchema | undefined,\n): FormValidator<TValues> | undefined {\n if (!raw) return undefined;\n\n if (typeof raw === 'function') return raw;\n\n const schema = raw as SafeParseSchema;\n\n return (values, signal) => {\n if (signal?.aborted) return undefined;\n\n const result = schema.safeParse(values);\n\n if (result.success) return undefined;\n\n const errors: Partial<Record<string, string>> = {};\n\n for (const issue of result.error.issues) {\n const key = issue.path.join('.') || FORM_ERROR;\n\n // Security: `issue.path` is schema-controlled (untrusted duck-typed input, per\n // SafeParseSchema) — reject reserved segments before it can become an error key.\n if (!isSafeKey(key)) continue;\n\n if (!errors[key]) errors[key] = issue.message;\n }\n\n return errors as Partial<Record<string, string>>;\n };\n}\n\n/**\n * Shared mutable state + primitives passed to every createForm() sub-module factory\n * (`createValueOps`, `createValidationOps`, `createObserveOps`, `createLifecycleOps`).\n * Mirrors the `ScopeContext` pattern already established in `internal/scope.ts`.\n * @internal\n */\nexport type FormContext<TValues extends Record<string, unknown> = Record<string, unknown>> = {\n arrayCache: Map<string, ArrayField>;\n baseline: Map<string, unknown>;\n batch(fn: () => void): void;\n buildFieldState(name: string): FieldState<unknown>;\n cachedErrors: Readonly<Record<string, string>> | null;\n cachedValues: TValues | null;\n computeErrors(): Readonly<Record<string, string>>;\n computeState(): FormState;\n connectDefaults: ConnectOptions;\n dirty: Set<string>;\n disposeController: AbortController;\n disposed: boolean;\n ensureNotDisposed(): void;\n fieldCtrls: Map<string, AbortController>;\n fieldErrors: Map<string, string>;\n fieldSignals: Map<string, Signal<FieldState<unknown>>>;\n fieldStateCache: Map<string, FieldState<unknown>>;\n formStateSignal: Signal<FormState>;\n formValidator: FormValidator<TValues> | undefined;\n getFieldSnapshot(name: string): FieldState<unknown>;\n getOrCreateFieldSignal(key: string): Signal<FieldState<unknown>>;\n getStateSnapshot(): FormState;\n invalidateErrors(): void;\n invalidateValues(): void;\n isSubmittingState: boolean;\n loadingState: boolean;\n requestNotify(target?: string | Iterable<string>): void;\n rippleSubs: Set<{ dispose(): void }>;\n runCtrls: Set<AbortController>;\n scopeCache: Map<string, Form<never>>;\n store: Map<string, unknown>;\n submitCount: number;\n touched: Set<string>;\n validatingRuns: Map<string, Set<symbol>>;\n validators: Map<string, FieldValidator<unknown>>;\n};\n\n/**\n * Builds the raw mutable state + shared primitives for a form. This is the \"Core state\" +\n * \"Notification helpers\" sections of the original monolithic `createForm()` body, factored\n * out so `internal/values.ts`, `internal/validation.ts`, `internal/observe.ts`, and\n * `internal/lifecycle.ts` can each operate on the same shared state without closing over\n * ad-hoc free variables.\n */\nexport function createFormContext<TValues extends Record<string, unknown> = Record<string, unknown>>(\n init: FormOptions<TValues>,\n): FormContext<TValues> {\n /* ---- Validators ---- */\n\n const validators = new Map<string, FieldValidator<unknown>>();\n\n for (const [name, validator] of Object.entries(init.validators ?? {})) {\n assertSafeKey(name);\n validators.set(name, validator as FieldValidator<unknown>);\n }\n\n const formValidator: FormValidator<TValues> | undefined = resolveFormValidator(init.validator);\n const connectDefaults: ConnectOptions = init.connect ?? {};\n\n /* ---- Core state ---- */\n\n let loadingState = typeof init.defaultValues === 'function';\n\n const staticValues = typeof init.defaultValues !== 'function' ? init.defaultValues : undefined;\n const baseline = new Map<string, unknown>(Object.entries(flattenValues(staticValues ?? {})));\n const store = new Map<string, unknown>(baseline);\n const fieldErrors = new Map<string, string>();\n const touched = new Set<string>();\n const dirty = new Set<string>();\n\n /* ---- Validation tracking ---- */\n\n /**\n * Symbol-based ref-counting for concurrent validation runs per field.\n * Each runValidationCore() call mints a unique symbol and adds it to the field's Set.\n * The field leaves `validatingFields` only when its Set is empty — impossible to miscount.\n */\n const validatingRuns = new Map<string, Set<symbol>>();\n const runCtrls = new Set<AbortController>();\n const fieldCtrls = new Map<string, AbortController>();\n const disposeController = new AbortController();\n\n /* ---- Caches ---- */\n\n let cachedValues: TValues | null = null;\n let cachedErrors: Readonly<Record<string, string>> | null = null;\n const fieldStateCache = new Map<string, FieldState<unknown>>();\n const arrayCache = new Map<string, ArrayField>();\n const scopeCache = new Map<string, Form<never>>();\n\n /* ---- Submission state ---- */\n\n let isSubmittingState = false;\n let submitCount = 0;\n\n /* ---- Lifecycle ---- */\n\n let disposed = false;\n\n /* ---- Reactive signals ---- */\n\n const fieldSignals = new Map<string, Signal<FieldState<unknown>>>();\n const rippleSubs = new Set<{ dispose(): void }>();\n\n function getOrCreateFieldSignal(key: string): Signal<FieldState<unknown>> {\n if (!fieldSignals.has(key)) {\n fieldSignals.set(\n key,\n signal<FieldState<unknown>>(buildFieldState(key), {\n equals: (a, b) =>\n a.value === b.value &&\n a.error === b.error &&\n a.hasError === b.hasError &&\n a.touched === b.touched &&\n a.dirty === b.dirty,\n }),\n );\n }\n\n return fieldSignals.get(key)!;\n }\n\n /* ======== Notification helpers ======== */\n\n function invalidateValues(): void {\n cachedValues = null;\n }\n\n function invalidateErrors(): void {\n cachedErrors = null;\n }\n\n function computeErrors(): Readonly<Record<string, string>> {\n return (cachedErrors ??= Object.freeze(Object.fromEntries(fieldErrors)));\n }\n\n function computeState(): FormState {\n return Object.freeze({\n errors: computeErrors(),\n isDirty: dirty.size > 0,\n isLoading: loadingState,\n isSubmitting: isSubmittingState,\n isTouched: touched.size > 0,\n isValid: fieldErrors.size === 0,\n isValidating: validatingRuns.size > 0,\n submitCount,\n touchedFields: Object.freeze([...touched]) as readonly string[],\n validatingFields: Object.freeze([...validatingRuns.keys()]) as readonly string[],\n });\n }\n\n const formStateSignal = signal<FormState>(computeState());\n\n function getStateSnapshot(): FormState {\n return formStateSignal.value;\n }\n\n function buildFieldState(name: string): FieldState<unknown> {\n const error = fieldErrors.get(name);\n\n return {\n dirty: dirty.has(name),\n error,\n hasError: error !== undefined,\n touched: touched.has(name),\n value: store.get(name),\n };\n }\n\n function getFieldSnapshot(name: string): FieldState<unknown> {\n const cached = fieldStateCache.get(name);\n\n if (cached) return cached;\n\n const snap = Object.freeze(buildFieldState(name));\n\n fieldStateCache.set(name, snap);\n\n return snap;\n }\n\n /**\n * R4: Unified notification — replaces requestNotify(field?) + requestNotifyFields(fields).\n * - undefined → full-form refresh (all field signals + form signal)\n * - string → single field + form signal\n * - Iterable → targeted set of fields + form signal\n * rippleBatch deduplicates signal writes within the synchronous call stack.\n */\n function requestNotify(target?: string | Iterable<string>): void {\n if (disposed) return;\n\n invalidateValues();\n cachedErrors = null;\n\n if (target === undefined) {\n fieldStateCache.clear();\n rippleBatch(() => {\n formStateSignal.value = computeState();\n\n for (const [name, sig] of fieldSignals) sig.value = buildFieldState(name);\n });\n } else if (typeof target === 'string') {\n fieldStateCache.delete(target);\n rippleBatch(() => {\n formStateSignal.value = computeState();\n\n const sig = fieldSignals.get(target);\n\n if (sig) sig.value = buildFieldState(target);\n });\n } else {\n // Materialize before rippleBatch so the iterable isn't consumed inside the callback.\n const fields = [...target];\n\n for (const field of fields) fieldStateCache.delete(field);\n\n rippleBatch(() => {\n formStateSignal.value = computeState();\n\n for (const field of fields) {\n const sig = fieldSignals.get(field);\n\n if (sig) sig.value = buildFieldState(field);\n }\n });\n }\n }\n\n /* ======== Lifecycle guards ======== */\n\n function ensureNotDisposed(): void {\n if (disposed) throw new ForgeDisposedError();\n }\n\n /* ======== Batch ======== */\n\n function batch(fn: () => void): void {\n ensureNotDisposed();\n\n try {\n rippleBatch(fn);\n } catch (e) {\n // rippleBatch clears its pending-subscriber queue on error to prevent\n // stale flushes on future unrelated writes. Re-notify here so subscribers\n // still see the partial mutations that succeeded before the throw.\n requestNotify();\n throw e;\n }\n }\n\n return {\n arrayCache,\n baseline,\n batch,\n buildFieldState,\n get cachedErrors() {\n return cachedErrors;\n },\n set cachedErrors(value) {\n cachedErrors = value;\n },\n get cachedValues() {\n return cachedValues;\n },\n set cachedValues(value) {\n cachedValues = value;\n },\n computeErrors,\n computeState,\n connectDefaults,\n dirty,\n disposeController,\n get disposed() {\n return disposed;\n },\n set disposed(value) {\n disposed = value;\n },\n ensureNotDisposed,\n fieldCtrls,\n fieldErrors,\n fieldSignals,\n fieldStateCache,\n formStateSignal,\n formValidator,\n getFieldSnapshot,\n getOrCreateFieldSignal,\n getStateSnapshot,\n invalidateErrors,\n invalidateValues,\n get isSubmittingState() {\n return isSubmittingState;\n },\n set isSubmittingState(value) {\n isSubmittingState = value;\n },\n get loadingState() {\n return loadingState;\n },\n set loadingState(value) {\n loadingState = value;\n },\n requestNotify,\n rippleSubs,\n runCtrls,\n scopeCache,\n store,\n get submitCount() {\n return submitCount;\n },\n set submitCount(value) {\n submitCount = value;\n },\n touched,\n validatingRuns,\n validators,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../../src/core/context.ts"],"sourcesContent":["import { batch as rippleBatch } from '@vielzeug/ripple';\n\nimport { assertSafeKey, flattenValues, isSafeKey } from '../_utils';\nimport { ForgeDisposedError } from '../errors';\nimport {\n type ArrayField,\n type FieldState,\n type FieldValidator,\n FORM_ERROR,\n type FormOptions,\n type FormState,\n type FormValidator,\n type SafeParseSchema,\n type SubscribeOptions,\n type Unsubscribe,\n} from '../types';\nimport { createNotifier } from './notifier';\n\n/**\n * Resolves the `validator` option to a FormValidator.\n * Auto-detects SafeParseSchema objects (duck-typed via `safeParse`) and wraps them,\n * enabling direct use of @vielzeug/spell, Zod, Valibot schemas without an explicit wrapper call.\n */\nfunction resolveFormValidator<TValues extends Record<string, unknown>>(\n raw: FormValidator<TValues> | SafeParseSchema | undefined,\n): FormValidator<TValues> | undefined {\n if (!raw) return undefined;\n\n if (typeof raw === 'function') return raw;\n\n const schema = raw as SafeParseSchema;\n\n return (values, signal) => {\n if (signal?.aborted) return undefined;\n\n const result = schema.safeParse(values);\n\n if (result.success) return undefined;\n\n const errors: Partial<Record<string, string>> = {};\n\n for (const issue of result.error.issues) {\n const key = issue.path.join('.') || FORM_ERROR;\n\n // Security: `issue.path` is schema-controlled (untrusted duck-typed input, per\n // SafeParseSchema) — reject reserved segments before it can become an error key.\n if (!isSafeKey(key)) continue;\n\n if (!errors[key]) errors[key] = issue.message;\n }\n\n return errors as Partial<Record<string, string>>;\n };\n}\n\n/**\n * Shared mutable state for the field/validator Maps + lifecycle primitives, passed to every\n * `createForm()` sub-module factory (`createFieldOps`, `createObserveOps`, `createLifecycleOps`).\n * Mirrors the `ScopeContext` pattern already established in `scope.ts`.\n *\n * Notably absent: signals, subscriptions, and derived-state caching. Those have no dependency\n * on the field/validator Maps below — only on \"what does current state look like,\" computed\n * here via `computeState`/`buildFieldState` — so they live in their own `Notifier` instance\n * (`core/notifier.ts`) instead of this shared bag. `requestNotify`/`getStateSnapshot`/\n * `getFieldSnapshot`/`subscribe`/`subscribeField` below are thin pass-throughs to that\n * notifier, kept on `FormContext` purely so the other ops modules don't need two objects.\n * @internal\n */\nexport type FormContext<TValues extends Record<string, unknown> = Record<string, unknown>> = {\n arrayCache: Map<string, ArrayField>;\n baseline: Map<string, unknown>;\n batch(fn: () => void): void;\n cachedValues: TValues | null;\n computeErrors(): Readonly<Record<string, string>>;\n dirty: Set<string>;\n dispose(): void;\n disposeController: AbortController;\n disposed: boolean;\n ensureNotDisposed(op?: string): void;\n fieldCtrls: Map<string, AbortController>;\n fieldErrors: Map<string, string>;\n formValidator: FormValidator<TValues> | undefined;\n getFieldSnapshot(name: string): FieldState<unknown>;\n getStateSnapshot(): FormState;\n invalidateErrors(): void;\n invalidateValues(): void;\n isSubmittingState: boolean;\n loadingState: boolean;\n requestNotify(target?: string | Iterable<string>): void;\n runCtrls: Set<AbortController>;\n store: Map<string, unknown>;\n submitCount: number;\n subscribe(listener: (state: FormState) => void, options?: SubscribeOptions): Unsubscribe;\n subscribeField(name: string, listener: (state: FieldState<unknown>) => void, options?: SubscribeOptions): Unsubscribe;\n touched: Set<string>;\n validatingRuns: Map<string, Set<symbol>>;\n validators: Map<string, FieldValidator<unknown>>;\n};\n\n/**\n * Builds the raw mutable state + shared primitives for a form: the field/validator Maps,\n * the notifier that turns mutations into signal updates, and the caches derived from both.\n */\nexport function createFormContext<TValues extends Record<string, unknown> = Record<string, unknown>>(\n init: FormOptions<TValues>,\n): FormContext<TValues> {\n /* ---- Validators ---- */\n\n const validators = new Map<string, FieldValidator<unknown>>();\n\n for (const [name, validator] of Object.entries(init.validators ?? {})) {\n assertSafeKey(name);\n validators.set(name, validator as FieldValidator<unknown>);\n }\n\n const formValidator: FormValidator<TValues> | undefined = resolveFormValidator(init.validator);\n\n /* ---- Core state ---- */\n\n let loadingState = typeof init.defaultValues === 'function';\n\n const staticValues = typeof init.defaultValues !== 'function' ? init.defaultValues : undefined;\n const baseline = new Map<string, unknown>(Object.entries(flattenValues(staticValues ?? {})));\n const store = new Map<string, unknown>(baseline);\n const fieldErrors = new Map<string, string>();\n const touched = new Set<string>();\n const dirty = new Set<string>();\n\n /* ---- Validation tracking ---- */\n\n /**\n * Symbol-based ref-counting for concurrent validation runs per field.\n * Each runValidationCore() call mints a unique symbol and adds it to the field's Set.\n * The field leaves `validatingFields` only when its Set is empty — impossible to miscount.\n */\n const validatingRuns = new Map<string, Set<symbol>>();\n const runCtrls = new Set<AbortController>();\n const fieldCtrls = new Map<string, AbortController>();\n const disposeController = new AbortController();\n\n /* ---- Caches ---- */\n\n let cachedValues: TValues | null = null;\n let cachedErrors: Readonly<Record<string, string>> | null = null;\n const arrayCache = new Map<string, ArrayField>();\n\n /* ---- Submission state ---- */\n\n let isSubmittingState = false;\n let submitCount = 0;\n\n /* ---- Lifecycle ---- */\n\n let disposed = false;\n\n /* ======== Derived state (fed to the notifier below) ======== */\n\n function invalidateValues(): void {\n cachedValues = null;\n }\n\n function invalidateErrors(): void {\n cachedErrors = null;\n }\n\n function computeErrors(): Readonly<Record<string, string>> {\n return (cachedErrors ??= Object.freeze(Object.fromEntries(fieldErrors)));\n }\n\n function computeState(): FormState {\n return Object.freeze({\n errors: computeErrors(),\n isDirty: dirty.size > 0,\n isLoading: loadingState,\n isSubmitting: isSubmittingState,\n isTouched: touched.size > 0,\n isValid: fieldErrors.size === 0,\n isValidating: validatingRuns.size > 0,\n submitCount,\n touchedFields: Object.freeze([...touched]) as readonly string[],\n validatingFields: Object.freeze([...validatingRuns.keys()]) as readonly string[],\n });\n }\n\n function buildFieldState(name: string): FieldState<unknown> {\n const error = fieldErrors.get(name);\n\n return {\n dirty: dirty.has(name),\n error,\n hasError: error !== undefined,\n touched: touched.has(name),\n value: store.get(name),\n };\n }\n\n const notifier = createNotifier({\n buildFieldState,\n computeState,\n invalidateCaches() {\n invalidateValues();\n invalidateErrors();\n },\n });\n\n /* ======== Lifecycle guards ======== */\n\n function ensureNotDisposed(op?: string): void {\n if (disposed) throw new ForgeDisposedError(op);\n }\n\n /* ======== Batch ======== */\n\n function batch(fn: () => void): void {\n ensureNotDisposed('batch');\n\n try {\n rippleBatch(fn);\n } catch (e) {\n // rippleBatch clears its pending-subscriber queue on error to prevent\n // stale flushes on future unrelated writes. Re-notify here so subscribers\n // still see the partial mutations that succeeded before the throw.\n notifier.requestNotify();\n throw e;\n }\n }\n\n function dispose(): void {\n disposed = true;\n disposeController.abort();\n\n for (const ctrl of fieldCtrls.values()) ctrl.abort();\n\n fieldCtrls.clear();\n runCtrls.clear();\n arrayCache.clear();\n notifier.dispose();\n }\n\n return {\n arrayCache,\n baseline,\n batch,\n get cachedValues() {\n return cachedValues;\n },\n set cachedValues(value) {\n cachedValues = value;\n },\n computeErrors,\n dirty,\n dispose,\n disposeController,\n get disposed() {\n return disposed;\n },\n ensureNotDisposed,\n fieldCtrls,\n fieldErrors,\n formValidator,\n getFieldSnapshot: notifier.getFieldSnapshot,\n getStateSnapshot: notifier.getStateSnapshot,\n invalidateErrors,\n invalidateValues,\n get isSubmittingState() {\n return isSubmittingState;\n },\n set isSubmittingState(value) {\n isSubmittingState = value;\n },\n get loadingState() {\n return loadingState;\n },\n set loadingState(value) {\n loadingState = value;\n },\n requestNotify: notifier.requestNotify,\n runCtrls,\n store,\n get submitCount() {\n return submitCount;\n },\n set submitCount(value) {\n submitCount = value;\n },\n subscribe: notifier.subscribe,\n subscribeField: notifier.subscribeField,\n touched,\n validatingRuns,\n validators,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_dev.cjs"),t=require("../errors.cjs"),n=require("../_utils.cjs"),r=require("./array.cjs");let i=require("@vielzeug/arsenal");function a(a){function o(e){let t=e;return n.assertSafeKey(t),a.store.get(t)}function s(){return a.cachedValues??=(0,i.unflattenPaths)(Object.fromEntries(a.store))}function c(e,t){a.baseline.get(e)===t?a.dirty.delete(e):a.dirty.add(e)}function l(e,t,r={}){a.ensureNotDisposed(`set`);let i=e;n.assertSafeKey(i),a.store.set(i,t),c(i,t),r.touched&&a.touched.add(i),a.requestNotify(i)}function u(e){let t=e;return n.assertSafeKey(t),a.getFieldSnapshot(t)}function d(e,t){a.ensureNotDisposed(`setError`);let r=e;n.assertSafeKey(r),a.fieldErrors.set(r,t),a.invalidateErrors(),a.requestNotify(r)}function f(e){a.ensureNotDisposed(`clearError`);let t=e;n.assertSafeKey(t),a.fieldErrors.delete(t)&&(a.invalidateErrors(),a.requestNotify(t))}function p(e,t,r){for(let t of[...a.fieldErrors.keys()])e(t)&&a.fieldErrors.delete(t);for(let[e,i]of Object.entries(t)){let t=r(e);n.assertSafeKey(t),typeof i==`string`&&a.fieldErrors.set(t,i)}a.invalidateErrors()}function m(e={}){a.ensureNotDisposed(`resetErrors`),p(()=>!0,e,e=>e),a.requestNotify()}function h(t,r){/\.\d+(\.|$)/.test(t)&&!a.store.has(t)&&e.warn(`${r}('${n.sanitizeForLog(t,80)}'): path looks like an array item key. Array items are stored as whole arrays — register on the parent key instead.`)}function g(e,t){a.ensureNotDisposed(`setValidator`);let r=e;n.assertSafeKey(r),h(r,`setValidator`),a.fieldCtrls.get(r)?.abort(),a.fieldCtrls.delete(r),t?a.validators.set(r,t):(a.validators.delete(r),a.fieldErrors.delete(r)&&(a.invalidateErrors(),a.requestNotify(r)))}function _(e,t={}){a.ensureNotDisposed(`fields.register`);let r=e;return n.assertSafeKey(r),h(r,`fields.register`),t.validator!==void 0&&g(e,t.validator),t.defaultValue!==void 0&&!a.store.has(r)&&(a.store.set(r,t.defaultValue),a.baseline.set(r,t.defaultValue),a.requestNotify(r)),()=>{a.disposed||E(e)}}function v(){let e=new Set;for(let t of a.store.keys())e.add(t);for(let t of a.validators.keys())e.add(t);return Object.freeze([...e])}function y(e){a.ensureNotDisposed(`touch`);let t=e;n.assertSafeKey(t),a.touched.add(t),a.requestNotify(t)}function b(e){a.ensureNotDisposed(`untouch`);let t=e;n.assertSafeKey(t),a.touched.delete(t)&&a.requestNotify(t)}function x(e){for(let t of a.store.keys())e(t)&&a.touched.add(t);for(let t of a.validators.keys())e(t)&&a.touched.add(t)}function S(){a.ensureNotDisposed(`touchAll`),x(()=>!0),a.requestNotify()}function C(e){for(let t of[...a.touched])e(t)&&a.touched.delete(t)}function w(){a.ensureNotDisposed(`untouchAll`),C(()=>!0),a.requestNotify()}function T(e){a.ensureNotDisposed(`resetField`);let t=e;n.assertSafeKey(t),a.fieldCtrls.get(t)?.abort(),a.fieldCtrls.delete(t),a.store.set(t,a.baseline.get(t)),a.dirty.delete(t),a.touched.delete(t),a.fieldErrors.delete(t)&&a.invalidateErrors(),a.requestNotify(t)}function E(e){a.ensureNotDisposed(`fields.remove`);let t=e;n.assertSafeKey(t),a.store.delete(t),a.baseline.delete(t),a.dirty.delete(t),a.touched.delete(t),a.validators.delete(t),a.arrayCache.delete(t),a.fieldCtrls.get(t)?.abort(),a.fieldCtrls.delete(t),a.fieldErrors.delete(t)&&a.invalidateErrors(),a.requestNotify(t)}function D(){return new Set([...a.store.keys(),...a.touched,...a.fieldErrors.keys(),...a.validators.keys()])}function O(e){for(let t of D())e(t)&&(a.fieldCtrls.get(t)?.abort(),a.fieldCtrls.delete(t),a.baseline.has(t)?a.store.set(t,a.baseline.get(t)):a.store.delete(t),a.dirty.delete(t),a.touched.delete(t),a.fieldErrors.delete(t)&&a.invalidateErrors())}function k(){a.ensureNotDisposed(`reset`);for(let e of a.runCtrls)e.abort();a.runCtrls.clear(),a.validatingRuns.clear(),O(()=>!0),a.submitCount=0,a.requestNotify()}function A(e,t,n){for(let t of D())e(t)&&(a.fieldCtrls.get(t)?.abort(),a.fieldCtrls.delete(t),a.store.delete(t),a.baseline.delete(t),a.dirty.delete(t),a.touched.delete(t),a.fieldErrors.delete(t)&&a.invalidateErrors());for(let[e,r]of Object.entries(t)){let t=n(e);a.store.set(t,r),a.baseline.set(t,r)}}function j(e){a.ensureNotDisposed(`replace`);for(let e of a.runCtrls)e.abort();a.runCtrls.clear(),a.validatingRuns.clear(),A(()=>!0,(0,i.flattenPaths)(e),e=>e),a.submitCount=0,a.requestNotify()}function M(e,t){let n=(0,i.flattenPaths)(e),r=[];for(let[e,i]of Object.entries(n)){let n=t(e);a.baseline.set(n,i),a.store.set(n,i),a.dirty.delete(n),r.push(n)}return r}function N(e){a.ensureNotDisposed(`patch`),a.requestNotify(M(e,e=>e))}function P(e){a.ensureNotDisposed(`array`);let t=e;n.assertSafeKey(t);let i=a.arrayCache.get(t);if(i)return i;let o=r.createArrayField(e,a.store,l);return a.arrayCache.set(t,o),o}async function F(e,t){let n=a.validators.get(e);if(!n)return;if(t.aborted)throw t.reason;let r=await n(a.store.get(e),t);return typeof r==`string`?r:void 0}async function I(e){if(!a.formValidator)return{};if(e.aborted)throw e.reason;let t=await a.formValidator(s(),e),r={};if(t)for(let[e,i]of Object.entries(t))n.isSafeKey(e)&&typeof i==`string`&&(r[e]=i);return r}function L(e,t,n,r){if(r===`full`){a.fieldErrors.clear();for(let[e,r]of Object.entries(n))t[e]===void 0&&(t[e]=r);for(let[e,n]of Object.entries(t))a.fieldErrors.set(e,n);a.invalidateErrors(),a.requestNotify()}else{let n=new Set;for(let r of e){let e=t[r],i=a.fieldErrors.get(r);e===void 0?a.fieldErrors.delete(r)&&n.add(r):(a.fieldErrors.set(r,e),i!==e&&n.add(r))}n.size>0&&a.invalidateErrors(),a.requestNotify(n)}}async function R(e,t,r){let o=new Set(e),s=new Map;for(let e of o){a.fieldCtrls.get(e)?.abort();let t=new AbortController;a.fieldCtrls.set(e,t),s.set(e,t)}let c=new AbortController,l=n.anySignal(c.signal,r,a.disposeController.signal);a.runCtrls.add(c);let u=Symbol();for(let e of o){let t=a.validatingRuns.get(e);t||(t=new Set,a.validatingRuns.set(e,t)),t.add(u)}a.requestNotify();try{let e=await Promise.all([...o].map(async e=>[e,await F(e,t===`partial`?n.anySignal(s.get(e).signal,l):l)]));if(l.aborted)return{aborted:!0,errors:Object.fromEntries(a.fieldErrors),valid:a.fieldErrors.size===0};let r={};for(let[t,n]of e)n!==void 0&&(r[t]=n);return L(o,r,t===`full`?await I(l):{},t),{aborted:!1,errors:Object.fromEntries(a.fieldErrors),valid:a.fieldErrors.size===0}}catch(e){if((0,i.isAbortError)(e))return{aborted:!0,errors:Object.fromEntries(a.fieldErrors),valid:a.fieldErrors.size===0};throw e}finally{a.runCtrls.delete(c);for(let e of o){let t=a.validatingRuns.get(e);t&&(t.delete(u),t.size===0&&a.validatingRuns.delete(e))}a.requestNotify();for(let[e,t]of s)a.fieldCtrls.get(e)===t&&a.fieldCtrls.delete(e)}}async function z(e,t){a.ensureNotDisposed(`validate`);let n=await R(e,`partial`,t);return{errors:n.errors,valid:n.valid}}async function B(e,t){if(a.ensureNotDisposed(`validate`),e===void 0||e instanceof AbortSignal){let t=await R([...a.validators.keys()],`full`,e);return{errors:t.errors,valid:t.valid}}if(Array.isArray(e))return z(e,t);await z([e],t);let n=e,r=a.fieldErrors.get(n);return{errors:r===void 0?{}:{[n]:r},valid:r===void 0}}async function V(e){let n=await H(e);if(!n.ok)throw new t.ForgeValidationError(n.errors);return n.value}async function H(e){if(a.ensureNotDisposed(`submit`),a.isSubmittingState)throw new t.ForgeSubmitError(`submit() called while a submission is already in progress`);a.batch(()=>{a.submitCount++,a.isSubmittingState=!0,S()});try{let t=await R([...a.validators.keys()],`full`);return a.ensureNotDisposed(`submit`),t.valid?{ok:!0,value:await e(s())}:{errors:t.errors,ok:!1,type:`validation`}}finally{a.isSubmittingState=!1,a.disposed||a.requestNotify()}}return{array:P,clearError:f,field:u,get:o,listFields:v,patch:N,patchKeys:M,registerField:_,removeField:E,replace:j,replaceKeys:A,reset:k,resetErrors:m,resetErrorsKeys:p,resetField:T,resetKeys:O,runValidationCore:R,set:l,setError:d,setValidator:g,submit:H,submitOrThrow:V,touch:y,touchAll:S,touchAllKeys:x,untouch:b,untouchAll:w,untouchAllKeys:C,validate:B,validateFields:z,values:s}}exports.createFieldOps=a;
2
+ //# sourceMappingURL=fields.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.cjs","names":[],"sources":["../../src/core/fields.ts"],"sourcesContent":["import { isAbortError } from '@vielzeug/arsenal';\n\nimport type { FormContext } from './context';\n\nimport { warn } from '../_dev';\nimport { anySignal, assertSafeKey, flattenValues, isSafeKey, sanitizeForLog, unflattenValues } from '../_utils';\nimport { ForgeSubmitError, ForgeValidationError } from '../errors';\nimport {\n type ArrayField,\n type ErrorKeyOf,\n type FieldState,\n type FieldValidator,\n type FlatKeyOf,\n type MaybePromise,\n type RegisterFieldOptions,\n type SetOptions,\n type SubmitResult,\n type TypeAtPath,\n type Unsubscribe,\n type ValidateResult,\n} from '../types';\nimport { createArrayField } from './array';\n\n/**\n * Field values, dirty/touch tracking, field-level errors, dynamic field registration, and\n * validation/submit — one module, not the two (`values.ts`/`validation.ts`) it used to be\n * split across. That split was drawn along \"which section of the original monolithic\n * `createForm()` this code used to live in,\" not along a real boundary: validating a field\n * needs the same store/fieldErrors/fieldCtrls state that setting one does, so the old split\n * required threading `deps: Pick<ValueOps, '...'>` between files just to call each other. One\n * closure removes that threading entirely.\n *\n * The six `*Keys(predicate)` functions below are the shared bulk-mutation primitives behind\n * `reset`/`replace`/`patch`/`touchAll`/`untouchAll`/`resetErrors`. `scope.ts` calls them with\n * an `isScopedKey` predicate instead of maintaining its own second copy of each loop.\n */\nexport function createFieldOps<TValues extends Record<string, unknown>>(ctx: FormContext<TValues>) {\n /* ======== Values ======== */\n\n function get<K extends FlatKeyOf<TValues>>(name: K): TypeAtPath<TValues, K> {\n const key = name as string;\n\n assertSafeKey(key);\n\n return ctx.store.get(key) as TypeAtPath<TValues, K>;\n }\n\n function values(): TValues {\n return (ctx.cachedValues ??= unflattenValues(Object.fromEntries(ctx.store)) as TValues);\n }\n\n function trackDirty(name: string, value: unknown): void {\n if (ctx.baseline.get(name) === value) ctx.dirty.delete(name);\n else ctx.dirty.add(name);\n }\n\n function set<K extends FlatKeyOf<TValues>>(name: K, value: TypeAtPath<TValues, K>, options: SetOptions = {}): void {\n ctx.ensureNotDisposed('set');\n\n const key = name as string;\n\n assertSafeKey(key);\n ctx.store.set(key, value);\n trackDirty(key, value);\n\n if (options.touched) ctx.touched.add(key);\n\n ctx.requestNotify(key);\n }\n\n function field<K extends FlatKeyOf<TValues>>(name: K): FieldState<TypeAtPath<TValues, K>> {\n const key = name as string;\n\n assertSafeKey(key);\n\n return ctx.getFieldSnapshot(key) as FieldState<TypeAtPath<TValues, K>>;\n }\n\n /* ======== Errors ======== */\n\n function setError(name: ErrorKeyOf<TValues>, message: string): void {\n ctx.ensureNotDisposed('setError');\n\n const key = name as string;\n\n assertSafeKey(key);\n ctx.fieldErrors.set(key, message);\n ctx.invalidateErrors();\n ctx.requestNotify(key);\n }\n\n function clearError(name: ErrorKeyOf<TValues>): void {\n ctx.ensureNotDisposed('clearError');\n\n const key = name as string;\n\n assertSafeKey(key);\n\n if (ctx.fieldErrors.delete(key)) {\n ctx.invalidateErrors();\n ctx.requestNotify(key);\n }\n }\n\n /**\n * Shared primitive behind `resetErrors()` (predicate `() => true`) and `scope.ts`'s scoped\n * `resetErrors()` (predicate `isScopedKey`, mapper `pre`). Clears every error matching\n * `predicate`, then applies `nextErrors` through `toFullKey`.\n */\n function resetErrorsKeys(\n predicate: (key: string) => boolean,\n nextErrors: Partial<Record<string, string | undefined>>,\n toFullKey: (key: string) => string,\n ): void {\n for (const key of [...ctx.fieldErrors.keys()]) {\n if (predicate(key)) ctx.fieldErrors.delete(key);\n }\n\n for (const [key, message] of Object.entries(nextErrors)) {\n const fullKey = toFullKey(key);\n\n assertSafeKey(fullKey);\n\n if (typeof message === 'string') ctx.fieldErrors.set(fullKey, message);\n }\n\n ctx.invalidateErrors();\n }\n\n function resetErrors(nextErrors: Partial<Record<ErrorKeyOf<TValues>, string | undefined>> = {}): void {\n ctx.ensureNotDisposed('resetErrors');\n resetErrorsKeys(\n () => true,\n nextErrors,\n (k) => k,\n );\n ctx.requestNotify();\n }\n\n /**\n * Warns when `key` looks like a dot-notation array item path (e.g. \"tags.0\") that isn't\n * already a real store entry — array items are stored as whole arrays, not individual keys.\n * Shared by `setValidator()` and `registerField()`.\n */\n function warnIfArrayItemKey(key: string, apiLabel: string): void {\n if (/\\.\\d+(\\.|$)/.test(key) && !ctx.store.has(key)) {\n const displayKey = sanitizeForLog(key, 80);\n\n warn(\n `${apiLabel}('${displayKey}'): path looks like an array item key. ` +\n `Array items are stored as whole arrays — register on the parent key instead.`,\n );\n }\n }\n\n function setValidator(name: FlatKeyOf<TValues>, validator?: FieldValidator<unknown>): void {\n ctx.ensureNotDisposed('setValidator');\n\n const key = name as string;\n\n assertSafeKey(key);\n warnIfArrayItemKey(key, 'setValidator');\n\n ctx.fieldCtrls.get(key)?.abort();\n ctx.fieldCtrls.delete(key);\n\n if (validator) {\n ctx.validators.set(key, validator);\n } else {\n ctx.validators.delete(key);\n\n if (ctx.fieldErrors.delete(key)) {\n ctx.invalidateErrors();\n ctx.requestNotify(key);\n }\n }\n }\n\n /* ======== Dynamic field registration ======== */\n\n function registerField<K extends FlatKeyOf<TValues>>(\n name: K,\n options: RegisterFieldOptions<TypeAtPath<TValues, K>> = {},\n ): Unsubscribe {\n ctx.ensureNotDisposed('fields.register');\n\n const key = name as string;\n\n assertSafeKey(key);\n warnIfArrayItemKey(key, 'fields.register');\n\n if (options.validator !== undefined) setValidator(name, options.validator as FieldValidator<unknown>);\n\n if (options.defaultValue !== undefined && !ctx.store.has(key)) {\n ctx.store.set(key, options.defaultValue);\n ctx.baseline.set(key, options.defaultValue);\n ctx.requestNotify(key);\n }\n\n return () => {\n if (!ctx.disposed) removeField(name);\n };\n }\n\n function listFields(): readonly string[] {\n const known = new Set<string>();\n\n for (const name of ctx.store.keys()) known.add(name);\n for (const name of ctx.validators.keys()) known.add(name);\n\n return Object.freeze([...known]);\n }\n\n /* ======== Touch ======== */\n\n function touch(name: FlatKeyOf<TValues>): void {\n ctx.ensureNotDisposed('touch');\n\n const key = name as string;\n\n assertSafeKey(key);\n ctx.touched.add(key);\n ctx.requestNotify(key);\n }\n\n function untouch(name: FlatKeyOf<TValues>): void {\n ctx.ensureNotDisposed('untouch');\n\n const key = name as string;\n\n assertSafeKey(key);\n\n if (ctx.touched.delete(key)) ctx.requestNotify(key);\n }\n\n /** Shared by `touchAll()` and scope.ts's scoped `touchAll()`. */\n function touchAllKeys(predicate: (key: string) => boolean): void {\n for (const name of ctx.store.keys()) if (predicate(name)) ctx.touched.add(name);\n for (const name of ctx.validators.keys()) if (predicate(name)) ctx.touched.add(name);\n }\n\n function touchAll(): void {\n ctx.ensureNotDisposed('touchAll');\n touchAllKeys(() => true);\n ctx.requestNotify();\n }\n\n /** Shared by `untouchAll()` and scope.ts's scoped `untouchAll()`. */\n function untouchAllKeys(predicate: (key: string) => boolean): void {\n for (const name of [...ctx.touched]) if (predicate(name)) ctx.touched.delete(name);\n }\n\n function untouchAll(): void {\n ctx.ensureNotDisposed('untouchAll');\n untouchAllKeys(() => true);\n ctx.requestNotify();\n }\n\n /* ======== Lifecycle: field operations ======== */\n\n function resetField(name: FlatKeyOf<TValues>): void {\n ctx.ensureNotDisposed('resetField');\n\n const key = name as string;\n\n assertSafeKey(key);\n ctx.fieldCtrls.get(key)?.abort();\n ctx.fieldCtrls.delete(key);\n ctx.store.set(key, ctx.baseline.get(key));\n ctx.dirty.delete(key);\n ctx.touched.delete(key);\n\n if (ctx.fieldErrors.delete(key)) ctx.invalidateErrors();\n\n ctx.requestNotify(key);\n }\n\n function removeField(name: FlatKeyOf<TValues>): void {\n ctx.ensureNotDisposed('fields.remove');\n\n const key = name as string;\n\n assertSafeKey(key);\n ctx.store.delete(key);\n ctx.baseline.delete(key);\n ctx.dirty.delete(key);\n ctx.touched.delete(key);\n ctx.validators.delete(key);\n ctx.arrayCache.delete(key);\n ctx.fieldCtrls.get(key)?.abort();\n ctx.fieldCtrls.delete(key);\n\n if (ctx.fieldErrors.delete(key)) ctx.invalidateErrors();\n\n ctx.requestNotify(key);\n }\n\n /**\n * Every key a bulk operation might need to touch — not just `store.keys()`. A validator-only\n * field (a validator with no matching store entry, e.g. a cross-field or virtual-name\n * validator) can still be `touched` or hold a `fieldErrors` entry, so `resetKeys`/\n * `replaceKeys` below must consider `touched`/`fieldErrors`/`validators` too, not just\n * `store` — deriving \"everything reset must clear\" from `store.keys()` alone silently leaves\n * such a field's touched/error state behind.\n */\n function allKnownKeys(): Set<string> {\n return new Set([...ctx.store.keys(), ...ctx.touched, ...ctx.fieldErrors.keys(), ...ctx.validators.keys()]);\n }\n\n /**\n * Shared primitive behind `reset()` (predicate `() => true`) and scope.ts's scoped\n * `reset()` (predicate `isScopedKey`). For each known key matching `predicate`: aborts its\n * in-flight field validation, restores its baseline value (or removes it entirely if it has\n * no baseline entry — e.g. a key written via `set()` alone, never through `patch()`), and\n * clears its dirty/touched/error state.\n */\n function resetKeys(predicate: (key: string) => boolean): void {\n for (const key of allKnownKeys()) {\n if (!predicate(key)) continue;\n\n ctx.fieldCtrls.get(key)?.abort();\n ctx.fieldCtrls.delete(key);\n\n // Both branches are no-ops for a validator-only key that was never in `store` to begin\n // with — `Map.delete()` on an absent key is a safe no-op, and `baseline.has(key)` is\n // false for it, so it falls through to `delete` rather than writing an `undefined` entry.\n if (ctx.baseline.has(key)) ctx.store.set(key, ctx.baseline.get(key));\n else ctx.store.delete(key);\n\n ctx.dirty.delete(key);\n ctx.touched.delete(key);\n\n if (ctx.fieldErrors.delete(key)) ctx.invalidateErrors();\n }\n }\n\n function reset(): void {\n ctx.ensureNotDisposed('reset');\n\n for (const ctrl of ctx.runCtrls) ctrl.abort();\n\n ctx.runCtrls.clear();\n ctx.validatingRuns.clear();\n resetKeys(() => true);\n ctx.submitCount = 0;\n ctx.requestNotify();\n }\n\n /**\n * Shared primitive behind `replace()` (predicate `() => true`, mapper `(k) => k`) and\n * scope.ts's scoped `replace()` (predicate `isScopedKey`, mapper `pre`). Removes every\n * store/baseline/touched/error entry matching `predicate` (see `allKnownKeys()` above for\n * why this isn't just `store.keys()`), then writes `flat` back in as both store and baseline\n * (a replace always establishes a new, clean baseline).\n */\n function replaceKeys(\n predicate: (key: string) => boolean,\n flat: Record<string, unknown>,\n toFullKey: (key: string) => string,\n ): void {\n for (const key of allKnownKeys()) {\n if (!predicate(key)) continue;\n\n ctx.fieldCtrls.get(key)?.abort();\n ctx.fieldCtrls.delete(key);\n ctx.store.delete(key);\n ctx.baseline.delete(key);\n ctx.dirty.delete(key);\n ctx.touched.delete(key);\n\n if (ctx.fieldErrors.delete(key)) ctx.invalidateErrors();\n }\n\n for (const [key, value] of Object.entries(flat)) {\n const fullKey = toFullKey(key);\n\n ctx.store.set(fullKey, value);\n ctx.baseline.set(fullKey, value);\n }\n }\n\n function replace(newValues: TValues): void {\n ctx.ensureNotDisposed('replace');\n\n for (const ctrl of ctx.runCtrls) ctrl.abort();\n\n ctx.runCtrls.clear();\n ctx.validatingRuns.clear();\n replaceKeys(\n () => true,\n flattenValues(newValues as Record<string, unknown>),\n (k) => k,\n );\n ctx.submitCount = 0;\n ctx.requestNotify();\n }\n\n /**\n * Shared primitive behind `patch()` and scope.ts's scoped `patch()` (mapper `pre`). Unlike\n * `resetKeys`/`replaceKeys`, `patch()` never needs a predicate — the partial object itself\n * already names exactly the keys to touch. Returns the full (prefixed) keys written, for\n * the caller to pass to `requestNotify()`.\n */\n function patchKeys(partial: Record<string, unknown>, toFullKey: (key: string) => string): string[] {\n const flat = flattenValues(partial);\n const changedKeys: string[] = [];\n\n for (const [key, value] of Object.entries(flat)) {\n const fullKey = toFullKey(key);\n\n ctx.baseline.set(fullKey, value);\n ctx.store.set(fullKey, value);\n ctx.dirty.delete(fullKey);\n changedKeys.push(fullKey);\n }\n\n return changedKeys;\n }\n\n function patch(partial: Record<string, unknown>): void {\n ctx.ensureNotDisposed('patch');\n ctx.requestNotify(patchKeys(partial, (k) => k));\n }\n\n /* ======== Array helpers ======== */\n\n function array(name: FlatKeyOf<TValues>): ArrayField {\n ctx.ensureNotDisposed('array');\n\n const key = name as string;\n\n assertSafeKey(key);\n\n const cached = ctx.arrayCache.get(key);\n\n if (cached) return cached;\n\n const helpers = createArrayField(name, ctx.store, set as (name: string, value: unknown) => void);\n\n ctx.arrayCache.set(key, helpers);\n\n return helpers;\n }\n\n /* ======== Validation ======== */\n\n async function runFieldValidator(name: string, signal: AbortSignal): Promise<string | undefined> {\n const validator = ctx.validators.get(name);\n\n if (!validator) return undefined;\n\n if (signal.aborted) throw signal.reason;\n\n const result = await validator(ctx.store.get(name), signal);\n\n return typeof result === 'string' ? result : undefined;\n }\n\n async function runFormValidator(signal: AbortSignal): Promise<Record<string, string>> {\n if (!ctx.formValidator) return {};\n\n if (signal.aborted) throw signal.reason;\n\n const result = await ctx.formValidator(values(), signal);\n const errors: Record<string, string> = {};\n\n if (result) {\n for (const [key, message] of Object.entries(result)) {\n // Security: a custom FormValidator function is caller-authored code — its returned\n // keys are just as untrusted as a schema's `issue.path`. Reject reserved segments.\n if (isSafeKey(key) && typeof message === 'string') errors[key] = message;\n }\n }\n\n return errors;\n }\n\n function applyFieldErrors(\n fieldSet: Set<string>,\n nextErrors: Record<string, string>,\n formErrors: Record<string, string>,\n scope: 'full' | 'partial',\n ): void {\n if (scope === 'full') {\n ctx.fieldErrors.clear();\n\n for (const [key, message] of Object.entries(formErrors)) {\n if (nextErrors[key] === undefined) nextErrors[key] = message;\n }\n\n for (const [key, message] of Object.entries(nextErrors)) ctx.fieldErrors.set(key, message);\n\n ctx.invalidateErrors();\n ctx.requestNotify();\n } else {\n const changedFields = new Set<string>();\n\n for (const name of fieldSet) {\n const next = nextErrors[name];\n const previous = ctx.fieldErrors.get(name);\n\n if (next !== undefined) {\n ctx.fieldErrors.set(name, next);\n\n if (previous !== next) changedFields.add(name);\n } else if (ctx.fieldErrors.delete(name)) {\n changedFields.add(name);\n }\n }\n\n if (changedFields.size > 0) ctx.invalidateErrors();\n\n ctx.requestNotify(changedFields);\n }\n }\n\n async function runValidationCore(\n fields: string[],\n scope: 'full' | 'partial',\n signal?: AbortSignal,\n ): Promise<ValidateResult & { aborted: boolean }> {\n const fieldSet = new Set(fields);\n const fieldRunCtrls = new Map<string, AbortController>();\n\n for (const name of fieldSet) {\n ctx.fieldCtrls.get(name)?.abort();\n\n const ctrl = new AbortController();\n\n ctx.fieldCtrls.set(name, ctrl);\n fieldRunCtrls.set(name, ctrl);\n }\n\n const runCtrl = new AbortController();\n const runSignal = anySignal(runCtrl.signal, signal, ctx.disposeController.signal)!;\n\n ctx.runCtrls.add(runCtrl);\n\n // Symbol-based ref-counting: each run gets a unique token per field.\n // The field leaves validatingFields only when its Set empties — impossible to miscount.\n const runId = Symbol();\n\n for (const name of fieldSet) {\n let runs = ctx.validatingRuns.get(name);\n\n if (!runs) {\n runs = new Set<symbol>();\n ctx.validatingRuns.set(name, runs);\n }\n\n runs.add(runId);\n }\n\n ctx.requestNotify();\n\n try {\n const results = await Promise.all(\n [...fieldSet].map(async (name) => {\n const fieldSignal = scope === 'partial' ? anySignal(fieldRunCtrls.get(name)!.signal, runSignal)! : runSignal;\n\n return [name, await runFieldValidator(name, fieldSignal)] as const;\n }),\n );\n\n if (runSignal.aborted) {\n return { aborted: true, errors: Object.fromEntries(ctx.fieldErrors), valid: ctx.fieldErrors.size === 0 };\n }\n\n const nextErrors: Record<string, string> = {};\n\n for (const [name, message] of results) {\n if (message !== undefined) nextErrors[name] = message;\n }\n\n const formErrors = scope === 'full' ? await runFormValidator(runSignal) : {};\n\n applyFieldErrors(fieldSet, nextErrors, formErrors, scope);\n\n return { aborted: false, errors: Object.fromEntries(ctx.fieldErrors), valid: ctx.fieldErrors.size === 0 };\n } catch (error) {\n if (isAbortError(error)) {\n return { aborted: true, errors: Object.fromEntries(ctx.fieldErrors), valid: ctx.fieldErrors.size === 0 };\n }\n\n throw error;\n } finally {\n ctx.runCtrls.delete(runCtrl);\n\n for (const name of fieldSet) {\n const runs = ctx.validatingRuns.get(name);\n\n if (runs) {\n runs.delete(runId);\n\n if (runs.size === 0) ctx.validatingRuns.delete(name);\n }\n }\n\n ctx.requestNotify();\n\n for (const [name, ctrl] of fieldRunCtrls) {\n if (ctx.fieldCtrls.get(name) === ctrl) ctx.fieldCtrls.delete(name);\n }\n }\n }\n\n async function validateFields(\n fields: FlatKeyOf<TValues>[] | string[],\n signal?: AbortSignal,\n ): Promise<ValidateResult> {\n ctx.ensureNotDisposed('validate');\n\n const result = await runValidationCore(fields as string[], 'partial', signal);\n\n return { errors: result.errors, valid: result.valid };\n }\n\n async function validate(\n nameOrFieldsOrSignal?: FlatKeyOf<TValues> | FlatKeyOf<TValues>[] | AbortSignal,\n signal?: AbortSignal,\n ): Promise<ValidateResult> {\n ctx.ensureNotDisposed('validate');\n\n if (nameOrFieldsOrSignal === undefined || nameOrFieldsOrSignal instanceof AbortSignal) {\n const result = await runValidationCore(\n [...ctx.validators.keys()],\n 'full',\n nameOrFieldsOrSignal as AbortSignal | undefined,\n );\n\n return { errors: result.errors, valid: result.valid };\n }\n\n if (Array.isArray(nameOrFieldsOrSignal)) {\n return validateFields(nameOrFieldsOrSignal, signal);\n }\n\n await validateFields([nameOrFieldsOrSignal], signal);\n\n const name = nameOrFieldsOrSignal as string;\n const error = ctx.fieldErrors.get(name);\n\n return { errors: error !== undefined ? { [name]: error } : {}, valid: error === undefined };\n }\n\n /* ======== Submit ======== */\n\n async function submitOrThrow<TResult = void>(handler: (values: TValues) => MaybePromise<TResult>): Promise<TResult> {\n const result = await submit(handler);\n\n if (!result.ok) throw new ForgeValidationError(result.errors as Record<string, string>);\n\n return result.value;\n }\n\n async function submit<TResult = void>(\n handler: (values: TValues) => MaybePromise<TResult>,\n ): Promise<SubmitResult<TResult>> {\n ctx.ensureNotDisposed('submit');\n\n if (ctx.isSubmittingState) throw new ForgeSubmitError('submit() called while a submission is already in progress');\n\n ctx.batch(() => {\n ctx.submitCount++;\n ctx.isSubmittingState = true;\n touchAll();\n });\n\n try {\n const validation = await runValidationCore([...ctx.validators.keys()], 'full');\n\n ctx.ensureNotDisposed('submit');\n\n if (!validation.valid) {\n return { errors: validation.errors, ok: false, type: 'validation' };\n }\n\n return { ok: true, value: await handler(values()) };\n } finally {\n ctx.isSubmittingState = false;\n\n if (!ctx.disposed) ctx.requestNotify();\n }\n }\n\n return {\n array,\n clearError,\n field,\n get,\n listFields,\n patch,\n patchKeys,\n registerField,\n removeField,\n replace,\n replaceKeys,\n reset,\n resetErrors,\n resetErrorsKeys,\n resetField,\n resetKeys,\n runValidationCore,\n set,\n setError,\n setValidator,\n submit,\n submitOrThrow,\n touch,\n touchAll,\n touchAllKeys,\n untouch,\n untouchAll,\n untouchAllKeys,\n validate,\n validateFields,\n values,\n };\n}\n\nexport type FieldOps<TValues extends Record<string, unknown>> = ReturnType<typeof createFieldOps<TValues>>;\n"],"mappings":"iJAoCA,SAAgB,EAAwD,EAA2B,CAGjG,SAAS,EAAkC,EAAiC,CAC1E,IAAM,EAAM,EAIZ,OAFA,EAAA,cAAc,CAAG,EAEV,EAAI,MAAM,IAAI,CAAG,CAC1B,CAEA,SAAS,GAAkB,CACzB,MAAQ,GAAI,gBAAA,EAAA,EAAA,eAAA,CAAiC,OAAO,YAAY,EAAI,KAAK,CAAC,CAC5E,CAEA,SAAS,EAAW,EAAc,EAAsB,CAClD,EAAI,SAAS,IAAI,CAAI,IAAM,EAAO,EAAI,MAAM,OAAO,CAAI,EACtD,EAAI,MAAM,IAAI,CAAI,CACzB,CAEA,SAAS,EAAkC,EAAS,EAA+B,EAAsB,CAAC,EAAS,CACjH,EAAI,kBAAkB,KAAK,EAE3B,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EACjB,EAAI,MAAM,IAAI,EAAK,CAAK,EACxB,EAAW,EAAK,CAAK,EAEjB,EAAQ,SAAS,EAAI,QAAQ,IAAI,CAAG,EAExC,EAAI,cAAc,CAAG,CACvB,CAEA,SAAS,EAAoC,EAA6C,CACxF,IAAM,EAAM,EAIZ,OAFA,EAAA,cAAc,CAAG,EAEV,EAAI,iBAAiB,CAAG,CACjC,CAIA,SAAS,EAAS,EAA2B,EAAuB,CAClE,EAAI,kBAAkB,UAAU,EAEhC,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EACjB,EAAI,YAAY,IAAI,EAAK,CAAO,EAChC,EAAI,iBAAiB,EACrB,EAAI,cAAc,CAAG,CACvB,CAEA,SAAS,EAAW,EAAiC,CACnD,EAAI,kBAAkB,YAAY,EAElC,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EAEb,EAAI,YAAY,OAAO,CAAG,IAC5B,EAAI,iBAAiB,EACrB,EAAI,cAAc,CAAG,EAEzB,CAOA,SAAS,EACP,EACA,EACA,EACM,CACN,IAAK,IAAM,IAAO,CAAC,GAAG,EAAI,YAAY,KAAK,CAAC,EACtC,EAAU,CAAG,GAAG,EAAI,YAAY,OAAO,CAAG,EAGhD,IAAK,GAAM,CAAC,EAAK,KAAY,OAAO,QAAQ,CAAU,EAAG,CACvD,IAAM,EAAU,EAAU,CAAG,EAE7B,EAAA,cAAc,CAAO,EAEjB,OAAO,GAAY,UAAU,EAAI,YAAY,IAAI,EAAS,CAAO,CACvE,CAEA,EAAI,iBAAiB,CACvB,CAEA,SAAS,EAAY,EAAuE,CAAC,EAAS,CACpG,EAAI,kBAAkB,aAAa,EACnC,MACQ,GACN,EACC,GAAM,CACT,EACA,EAAI,cAAc,CACpB,CAOA,SAAS,EAAmB,EAAa,EAAwB,CAC3D,cAAc,KAAK,CAAG,GAAK,CAAC,EAAI,MAAM,IAAI,CAAG,GAG/C,EAAA,KACE,GAAG,EAAS,IAHK,EAAA,eAAe,EAAK,EAGrB,EAAW,oHAE7B,CAEJ,CAEA,SAAS,EAAa,EAA0B,EAA2C,CACzF,EAAI,kBAAkB,cAAc,EAEpC,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EACjB,EAAmB,EAAK,cAAc,EAEtC,EAAI,WAAW,IAAI,CAAG,CAAC,EAAE,MAAM,EAC/B,EAAI,WAAW,OAAO,CAAG,EAErB,EACF,EAAI,WAAW,IAAI,EAAK,CAAS,GAEjC,EAAI,WAAW,OAAO,CAAG,EAErB,EAAI,YAAY,OAAO,CAAG,IAC5B,EAAI,iBAAiB,EACrB,EAAI,cAAc,CAAG,GAG3B,CAIA,SAAS,EACP,EACA,EAAwD,CAAC,EAC5C,CACb,EAAI,kBAAkB,iBAAiB,EAEvC,IAAM,EAAM,EAaZ,OAXA,EAAA,cAAc,CAAG,EACjB,EAAmB,EAAK,iBAAiB,EAErC,EAAQ,YAAc,IAAA,IAAW,EAAa,EAAM,EAAQ,SAAoC,EAEhG,EAAQ,eAAiB,IAAA,IAAa,CAAC,EAAI,MAAM,IAAI,CAAG,IAC1D,EAAI,MAAM,IAAI,EAAK,EAAQ,YAAY,EACvC,EAAI,SAAS,IAAI,EAAK,EAAQ,YAAY,EAC1C,EAAI,cAAc,CAAG,OAGV,CACN,EAAI,UAAU,EAAY,CAAI,CACrC,CACF,CAEA,SAAS,GAAgC,CACvC,IAAM,EAAQ,IAAI,IAElB,IAAK,IAAM,KAAQ,EAAI,MAAM,KAAK,EAAG,EAAM,IAAI,CAAI,EACnD,IAAK,IAAM,KAAQ,EAAI,WAAW,KAAK,EAAG,EAAM,IAAI,CAAI,EAExD,OAAO,OAAO,OAAO,CAAC,GAAG,CAAK,CAAC,CACjC,CAIA,SAAS,EAAM,EAAgC,CAC7C,EAAI,kBAAkB,OAAO,EAE7B,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EACjB,EAAI,QAAQ,IAAI,CAAG,EACnB,EAAI,cAAc,CAAG,CACvB,CAEA,SAAS,EAAQ,EAAgC,CAC/C,EAAI,kBAAkB,SAAS,EAE/B,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EAEb,EAAI,QAAQ,OAAO,CAAG,GAAG,EAAI,cAAc,CAAG,CACpD,CAGA,SAAS,EAAa,EAA2C,CAC/D,IAAK,IAAM,KAAQ,EAAI,MAAM,KAAK,EAAO,EAAU,CAAI,GAAG,EAAI,QAAQ,IAAI,CAAI,EAC9E,IAAK,IAAM,KAAQ,EAAI,WAAW,KAAK,EAAO,EAAU,CAAI,GAAG,EAAI,QAAQ,IAAI,CAAI,CACrF,CAEA,SAAS,GAAiB,CACxB,EAAI,kBAAkB,UAAU,EAChC,MAAmB,EAAI,EACvB,EAAI,cAAc,CACpB,CAGA,SAAS,EAAe,EAA2C,CACjE,IAAK,IAAM,IAAQ,CAAC,GAAG,EAAI,OAAO,EAAO,EAAU,CAAI,GAAG,EAAI,QAAQ,OAAO,CAAI,CACnF,CAEA,SAAS,GAAmB,CAC1B,EAAI,kBAAkB,YAAY,EAClC,MAAqB,EAAI,EACzB,EAAI,cAAc,CACpB,CAIA,SAAS,EAAW,EAAgC,CAClD,EAAI,kBAAkB,YAAY,EAElC,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EACjB,EAAI,WAAW,IAAI,CAAG,CAAC,EAAE,MAAM,EAC/B,EAAI,WAAW,OAAO,CAAG,EACzB,EAAI,MAAM,IAAI,EAAK,EAAI,SAAS,IAAI,CAAG,CAAC,EACxC,EAAI,MAAM,OAAO,CAAG,EACpB,EAAI,QAAQ,OAAO,CAAG,EAElB,EAAI,YAAY,OAAO,CAAG,GAAG,EAAI,iBAAiB,EAEtD,EAAI,cAAc,CAAG,CACvB,CAEA,SAAS,EAAY,EAAgC,CACnD,EAAI,kBAAkB,eAAe,EAErC,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EACjB,EAAI,MAAM,OAAO,CAAG,EACpB,EAAI,SAAS,OAAO,CAAG,EACvB,EAAI,MAAM,OAAO,CAAG,EACpB,EAAI,QAAQ,OAAO,CAAG,EACtB,EAAI,WAAW,OAAO,CAAG,EACzB,EAAI,WAAW,OAAO,CAAG,EACzB,EAAI,WAAW,IAAI,CAAG,CAAC,EAAE,MAAM,EAC/B,EAAI,WAAW,OAAO,CAAG,EAErB,EAAI,YAAY,OAAO,CAAG,GAAG,EAAI,iBAAiB,EAEtD,EAAI,cAAc,CAAG,CACvB,CAUA,SAAS,GAA4B,CACnC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAI,MAAM,KAAK,EAAG,GAAG,EAAI,QAAS,GAAG,EAAI,YAAY,KAAK,EAAG,GAAG,EAAI,WAAW,KAAK,CAAC,CAAC,CAC3G,CASA,SAAS,EAAU,EAA2C,CAC5D,IAAK,IAAM,KAAO,EAAa,EACxB,EAAU,CAAG,IAElB,EAAI,WAAW,IAAI,CAAG,CAAC,EAAE,MAAM,EAC/B,EAAI,WAAW,OAAO,CAAG,EAKrB,EAAI,SAAS,IAAI,CAAG,EAAG,EAAI,MAAM,IAAI,EAAK,EAAI,SAAS,IAAI,CAAG,CAAC,EAC9D,EAAI,MAAM,OAAO,CAAG,EAEzB,EAAI,MAAM,OAAO,CAAG,EACpB,EAAI,QAAQ,OAAO,CAAG,EAElB,EAAI,YAAY,OAAO,CAAG,GAAG,EAAI,iBAAiB,EAE1D,CAEA,SAAS,GAAc,CACrB,EAAI,kBAAkB,OAAO,EAE7B,IAAK,IAAM,KAAQ,EAAI,SAAU,EAAK,MAAM,EAE5C,EAAI,SAAS,MAAM,EACnB,EAAI,eAAe,MAAM,EACzB,MAAgB,EAAI,EACpB,EAAI,YAAc,EAClB,EAAI,cAAc,CACpB,CASA,SAAS,EACP,EACA,EACA,EACM,CACN,IAAK,IAAM,KAAO,EAAa,EACxB,EAAU,CAAG,IAElB,EAAI,WAAW,IAAI,CAAG,CAAC,EAAE,MAAM,EAC/B,EAAI,WAAW,OAAO,CAAG,EACzB,EAAI,MAAM,OAAO,CAAG,EACpB,EAAI,SAAS,OAAO,CAAG,EACvB,EAAI,MAAM,OAAO,CAAG,EACpB,EAAI,QAAQ,OAAO,CAAG,EAElB,EAAI,YAAY,OAAO,CAAG,GAAG,EAAI,iBAAiB,GAGxD,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAI,EAAG,CAC/C,IAAM,EAAU,EAAU,CAAG,EAE7B,EAAI,MAAM,IAAI,EAAS,CAAK,EAC5B,EAAI,SAAS,IAAI,EAAS,CAAK,CACjC,CACF,CAEA,SAAS,EAAQ,EAA0B,CACzC,EAAI,kBAAkB,SAAS,EAE/B,IAAK,IAAM,KAAQ,EAAI,SAAU,EAAK,MAAM,EAE5C,EAAI,SAAS,MAAM,EACnB,EAAI,eAAe,MAAM,EACzB,MACQ,IAAA,EAAA,EAAA,aAAA,CACQ,CAAoC,EACjD,GAAM,CACT,EACA,EAAI,YAAc,EAClB,EAAI,cAAc,CACpB,CAQA,SAAS,EAAU,EAAkC,EAA8C,CACjG,IAAM,GAAA,EAAA,EAAA,aAAA,CAAqB,CAAO,EAC5B,EAAwB,CAAC,EAE/B,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAI,EAAG,CAC/C,IAAM,EAAU,EAAU,CAAG,EAE7B,EAAI,SAAS,IAAI,EAAS,CAAK,EAC/B,EAAI,MAAM,IAAI,EAAS,CAAK,EAC5B,EAAI,MAAM,OAAO,CAAO,EACxB,EAAY,KAAK,CAAO,CAC1B,CAEA,OAAO,CACT,CAEA,SAAS,EAAM,EAAwC,CACrD,EAAI,kBAAkB,OAAO,EAC7B,EAAI,cAAc,EAAU,EAAU,GAAM,CAAC,CAAC,CAChD,CAIA,SAAS,EAAM,EAAsC,CACnD,EAAI,kBAAkB,OAAO,EAE7B,IAAM,EAAM,EAEZ,EAAA,cAAc,CAAG,EAEjB,IAAM,EAAS,EAAI,WAAW,IAAI,CAAG,EAErC,GAAI,EAAQ,OAAO,EAEnB,IAAM,EAAU,EAAA,iBAAiB,EAAM,EAAI,MAAO,CAA6C,EAI/F,OAFA,EAAI,WAAW,IAAI,EAAK,CAAO,EAExB,CACT,CAIA,eAAe,EAAkB,EAAc,EAAkD,CAC/F,IAAM,EAAY,EAAI,WAAW,IAAI,CAAI,EAEzC,GAAI,CAAC,EAAW,OAEhB,GAAI,EAAO,QAAS,MAAM,EAAO,OAEjC,IAAM,EAAS,MAAM,EAAU,EAAI,MAAM,IAAI,CAAI,EAAG,CAAM,EAE1D,OAAO,OAAO,GAAW,SAAW,EAAS,IAAA,EAC/C,CAEA,eAAe,EAAiB,EAAsD,CACpF,GAAI,CAAC,EAAI,cAAe,MAAO,CAAC,EAEhC,GAAI,EAAO,QAAS,MAAM,EAAO,OAEjC,IAAM,EAAS,MAAM,EAAI,cAAc,EAAO,EAAG,CAAM,EACjD,EAAiC,CAAC,EAExC,GAAI,MACG,GAAM,CAAC,EAAK,KAAY,OAAO,QAAQ,CAAM,EAG5C,EAAA,UAAU,CAAG,GAAK,OAAO,GAAY,WAAU,EAAO,GAAO,GAIrE,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,GAAI,IAAU,OAAQ,CACpB,EAAI,YAAY,MAAM,EAEtB,IAAK,GAAM,CAAC,EAAK,KAAY,OAAO,QAAQ,CAAU,EAChD,EAAW,KAAS,IAAA,KAAW,EAAW,GAAO,GAGvD,IAAK,GAAM,CAAC,EAAK,KAAY,OAAO,QAAQ,CAAU,EAAG,EAAI,YAAY,IAAI,EAAK,CAAO,EAEzF,EAAI,iBAAiB,EACrB,EAAI,cAAc,CACpB,KAAO,CACL,IAAM,EAAgB,IAAI,IAE1B,IAAK,IAAM,KAAQ,EAAU,CAC3B,IAAM,EAAO,EAAW,GAClB,EAAW,EAAI,YAAY,IAAI,CAAI,EAErC,IAAS,IAAA,GAIF,EAAI,YAAY,OAAO,CAAI,GACpC,EAAc,IAAI,CAAI,GAJtB,EAAI,YAAY,IAAI,EAAM,CAAI,EAE1B,IAAa,GAAM,EAAc,IAAI,CAAI,EAIjD,CAEI,EAAc,KAAO,GAAG,EAAI,iBAAiB,EAEjD,EAAI,cAAc,CAAa,CACjC,CACF,CAEA,eAAe,EACb,EACA,EACA,EACgD,CAChD,IAAM,EAAW,IAAI,IAAI,CAAM,EACzB,EAAgB,IAAI,IAE1B,IAAK,IAAM,KAAQ,EAAU,CAC3B,EAAI,WAAW,IAAI,CAAI,CAAC,EAAE,MAAM,EAEhC,IAAM,EAAO,IAAI,gBAEjB,EAAI,WAAW,IAAI,EAAM,CAAI,EAC7B,EAAc,IAAI,EAAM,CAAI,CAC9B,CAEA,IAAM,EAAU,IAAI,gBACd,EAAY,EAAA,UAAU,EAAQ,OAAQ,EAAQ,EAAI,kBAAkB,MAAM,EAEhF,EAAI,SAAS,IAAI,CAAO,EAIxB,IAAM,EAAQ,OAAO,EAErB,IAAK,IAAM,KAAQ,EAAU,CAC3B,IAAI,EAAO,EAAI,eAAe,IAAI,CAAI,EAEjC,IACH,EAAO,IAAI,IACX,EAAI,eAAe,IAAI,EAAM,CAAI,GAGnC,EAAK,IAAI,CAAK,CAChB,CAEA,EAAI,cAAc,EAElB,GAAI,CACF,IAAM,EAAU,MAAM,QAAQ,IAC5B,CAAC,GAAG,CAAQ,CAAC,CAAC,IAAI,KAAO,IAGhB,CAAC,EAAM,MAAM,EAAkB,EAFlB,IAAU,UAAY,EAAA,UAAU,EAAc,IAAI,CAAI,CAAC,CAAE,OAAQ,CAAS,EAAK,CAE5C,CAAC,CACzD,CACH,EAEA,GAAI,EAAU,QACZ,MAAO,CAAE,QAAS,GAAM,OAAQ,OAAO,YAAY,EAAI,WAAW,EAAG,MAAO,EAAI,YAAY,OAAS,CAAE,EAGzG,IAAM,EAAqC,CAAC,EAE5C,IAAK,GAAM,CAAC,EAAM,KAAY,EACxB,IAAY,IAAA,KAAW,EAAW,GAAQ,GAOhD,OAFA,EAAiB,EAAU,EAFR,IAAU,OAAS,MAAM,EAAiB,CAAS,EAAI,CAAC,EAExB,CAAK,EAEjD,CAAE,QAAS,GAAO,OAAQ,OAAO,YAAY,EAAI,WAAW,EAAG,MAAO,EAAI,YAAY,OAAS,CAAE,CAC1G,OAAS,EAAO,CACd,IAAA,EAAA,EAAA,aAAA,CAAiB,CAAK,EACpB,MAAO,CAAE,QAAS,GAAM,OAAQ,OAAO,YAAY,EAAI,WAAW,EAAG,MAAO,EAAI,YAAY,OAAS,CAAE,EAGzG,MAAM,CACR,QAAU,CACR,EAAI,SAAS,OAAO,CAAO,EAE3B,IAAK,IAAM,KAAQ,EAAU,CAC3B,IAAM,EAAO,EAAI,eAAe,IAAI,CAAI,EAEpC,IACF,EAAK,OAAO,CAAK,EAEb,EAAK,OAAS,GAAG,EAAI,eAAe,OAAO,CAAI,EAEvD,CAEA,EAAI,cAAc,EAElB,IAAK,GAAM,CAAC,EAAM,KAAS,EACrB,EAAI,WAAW,IAAI,CAAI,IAAM,GAAM,EAAI,WAAW,OAAO,CAAI,CAErE,CACF,CAEA,eAAe,EACb,EACA,EACyB,CACzB,EAAI,kBAAkB,UAAU,EAEhC,IAAM,EAAS,MAAM,EAAkB,EAAoB,UAAW,CAAM,EAE5E,MAAO,CAAE,OAAQ,EAAO,OAAQ,MAAO,EAAO,KAAM,CACtD,CAEA,eAAe,EACb,EACA,EACyB,CAGzB,GAFA,EAAI,kBAAkB,UAAU,EAE5B,IAAyB,IAAA,IAAa,aAAgC,YAAa,CACrF,IAAM,EAAS,MAAM,EACnB,CAAC,GAAG,EAAI,WAAW,KAAK,CAAC,EACzB,OACA,CACF,EAEA,MAAO,CAAE,OAAQ,EAAO,OAAQ,MAAO,EAAO,KAAM,CACtD,CAEA,GAAI,MAAM,QAAQ,CAAoB,EACpC,OAAO,EAAe,EAAsB,CAAM,EAGpD,MAAM,EAAe,CAAC,CAAoB,EAAG,CAAM,EAEnD,IAAM,EAAO,EACP,EAAQ,EAAI,YAAY,IAAI,CAAI,EAEtC,MAAO,CAAE,OAAQ,IAAU,IAAA,GAAgC,CAAC,EAArB,EAAG,GAAO,CAAM,EAAQ,MAAO,IAAU,IAAA,EAAU,CAC5F,CAIA,eAAe,EAA8B,EAAuE,CAClH,IAAM,EAAS,MAAM,EAAO,CAAO,EAEnC,GAAI,CAAC,EAAO,GAAI,MAAM,IAAI,EAAA,qBAAqB,EAAO,MAAgC,EAEtF,OAAO,EAAO,KAChB,CAEA,eAAe,EACb,EACgC,CAGhC,GAFA,EAAI,kBAAkB,QAAQ,EAE1B,EAAI,kBAAmB,MAAM,IAAI,EAAA,iBAAiB,2DAA2D,EAEjH,EAAI,UAAY,CACd,EAAI,cACJ,EAAI,kBAAoB,GACxB,EAAS,CACX,CAAC,EAED,GAAI,CACF,IAAM,EAAa,MAAM,EAAkB,CAAC,GAAG,EAAI,WAAW,KAAK,CAAC,EAAG,MAAM,EAQ7E,OANA,EAAI,kBAAkB,QAAQ,EAEzB,EAAW,MAIT,CAAE,GAAI,GAAM,MAAO,MAAM,EAAQ,EAAO,CAAC,CAAE,EAHzC,CAAE,OAAQ,EAAW,OAAQ,GAAI,GAAO,KAAM,YAAa,CAItE,QAAU,CACR,EAAI,kBAAoB,GAEnB,EAAI,UAAU,EAAI,cAAc,CACvC,CACF,CAEA,MAAO,CACL,QACA,aACA,QACA,MACA,aACA,QACA,YACA,gBACA,cACA,UACA,cACA,QACA,cACA,kBACA,aACA,YACA,oBACA,MACA,WACA,eACA,SACA,gBACA,QACA,WACA,eACA,UACA,aACA,iBACA,WACA,iBACA,QACF,CACF"}
@@ -0,0 +1,52 @@
1
+ import type { FormContext } from './context';
2
+ import { type ArrayField, type ErrorKeyOf, type FieldState, type FieldValidator, type FlatKeyOf, type MaybePromise, type RegisterFieldOptions, type SetOptions, type SubmitResult, type TypeAtPath, type Unsubscribe, type ValidateResult } from '../types';
3
+ /**
4
+ * Field values, dirty/touch tracking, field-level errors, dynamic field registration, and
5
+ * validation/submit — one module, not the two (`values.ts`/`validation.ts`) it used to be
6
+ * split across. That split was drawn along "which section of the original monolithic
7
+ * `createForm()` this code used to live in," not along a real boundary: validating a field
8
+ * needs the same store/fieldErrors/fieldCtrls state that setting one does, so the old split
9
+ * required threading `deps: Pick<ValueOps, '...'>` between files just to call each other. One
10
+ * closure removes that threading entirely.
11
+ *
12
+ * The six `*Keys(predicate)` functions below are the shared bulk-mutation primitives behind
13
+ * `reset`/`replace`/`patch`/`touchAll`/`untouchAll`/`resetErrors`. `scope.ts` calls them with
14
+ * an `isScopedKey` predicate instead of maintaining its own second copy of each loop.
15
+ */
16
+ export declare function createFieldOps<TValues extends Record<string, unknown>>(ctx: FormContext<TValues>): {
17
+ array: (name: FlatKeyOf<TValues>) => ArrayField;
18
+ clearError: (name: ErrorKeyOf<TValues>) => void;
19
+ field: <K extends FlatKeyOf<TValues>>(name: K) => FieldState<TypeAtPath<TValues, K>>;
20
+ get: <K extends FlatKeyOf<TValues>>(name: K) => TypeAtPath<TValues, K>;
21
+ listFields: () => readonly string[];
22
+ patch: (partial: Record<string, unknown>) => void;
23
+ patchKeys: (partial: Record<string, unknown>, toFullKey: (key: string) => string) => string[];
24
+ registerField: <K extends FlatKeyOf<TValues>>(name: K, options?: RegisterFieldOptions<TypeAtPath<TValues, K>>) => Unsubscribe;
25
+ removeField: (name: FlatKeyOf<TValues>) => void;
26
+ replace: (newValues: TValues) => void;
27
+ replaceKeys: (predicate: (key: string) => boolean, flat: Record<string, unknown>, toFullKey: (key: string) => string) => void;
28
+ reset: () => void;
29
+ resetErrors: (nextErrors?: Partial<Record<ErrorKeyOf<TValues>, string | undefined>>) => void;
30
+ resetErrorsKeys: (predicate: (key: string) => boolean, nextErrors: Partial<Record<string, string | undefined>>, toFullKey: (key: string) => string) => void;
31
+ resetField: (name: FlatKeyOf<TValues>) => void;
32
+ resetKeys: (predicate: (key: string) => boolean) => void;
33
+ runValidationCore: (fields: string[], scope: "full" | "partial", signal?: AbortSignal) => Promise<ValidateResult & {
34
+ aborted: boolean;
35
+ }>;
36
+ set: <K extends FlatKeyOf<TValues>>(name: K, value: TypeAtPath<TValues, K>, options?: SetOptions) => void;
37
+ setError: (name: ErrorKeyOf<TValues>, message: string) => void;
38
+ setValidator: (name: FlatKeyOf<TValues>, validator?: FieldValidator<unknown>) => void;
39
+ submit: <TResult = void>(handler: (values: TValues) => MaybePromise<TResult>) => Promise<SubmitResult<TResult>>;
40
+ submitOrThrow: <TResult = void>(handler: (values: TValues) => MaybePromise<TResult>) => Promise<TResult>;
41
+ touch: (name: FlatKeyOf<TValues>) => void;
42
+ touchAll: () => void;
43
+ touchAllKeys: (predicate: (key: string) => boolean) => void;
44
+ untouch: (name: FlatKeyOf<TValues>) => void;
45
+ untouchAll: () => void;
46
+ untouchAllKeys: (predicate: (key: string) => boolean) => void;
47
+ validate: (nameOrFieldsOrSignal?: FlatKeyOf<TValues> | FlatKeyOf<TValues>[] | AbortSignal, signal?: AbortSignal) => Promise<ValidateResult>;
48
+ validateFields: (fields: FlatKeyOf<TValues>[] | string[], signal?: AbortSignal) => Promise<ValidateResult>;
49
+ values: () => TValues;
50
+ };
51
+ export type FieldOps<TValues extends Record<string, unknown>> = ReturnType<typeof createFieldOps<TValues>>;
52
+ //# sourceMappingURL=fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../src/core/fields.ts"],"names":[],"mappings":"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"}