@oomfware/forms 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { RouterMiddleware } from "@oomfware/fetch-router";
1
+ import { Middleware } from "@oomfware/fetch-router";
2
2
  import { StandardSchemaV1 } from "@standard-schema/spec";
3
3
 
4
4
  //#region src/lib/types.d.ts
@@ -138,7 +138,7 @@ type FormDefinitions = Record<string, Form<any, any>>;
138
138
  * });
139
139
  * ```
140
140
  */
141
- declare function forms(definitions: FormDefinitions): RouterMiddleware;
141
+ declare function forms(definitions: FormDefinitions): Middleware;
142
142
  //#endregion
143
143
  //#region src/lib/errors.d.ts
144
144
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/lib/form.ts","../src/lib/middleware.ts","../src/lib/errors.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/types.ts","../src/lib/form.ts","../src/lib/middleware.ts","../src/lib/errors.ts"],"sourcesContent":[],"mappings":";;;;KAAY,kBAAkB,IAAI,QAAQ;;;UCgBzB,SAAA;EDhBL,CAAA,GAAA,EAAA,MAAA,CAAA,ECiBI,UDjBQ,CAAA,MAAA,GAAA,MAAA,GAAA,OAAA,GCiB+B,IDjB/B,GCiBsC,SDjBtC,CAAA;;KCoBnB,UDpBqC,CAAA,CAAA,CAAA,GCoBrB,CDpBqB,GCoBjB,CDpBiB,EAAA;AAAR,UCsBjB,SAAA,CDtBiB;EAAO,OAAA,EAAA,MAAA;;;;ACgBzC;;;;;AAEC;AAID;AAoBA;;;;;;;AAGI,KAHQ,YAGR,CAAA,CAAA,CAAA,GAAA,CAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAHgD,gBAAA,CAAiB,KAGjE,CAAA,GAAA,QAAE,MAFO,CAEP,KAFa,CAEb,CAFe,CAEf,CAAA,SAAA,CAAA,KAAA,EAAA,CAAA,EAAA,GADF,iBACE,CADgB,CAChB,CAAA,GAAF,CAAE,CAAA,CAAA,CAAA,SAAA,MAAA,GACD,YADC,CACY,CADZ,CACc,CADd,CAAA,CAAA,GAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAEoB,gBAAA,CAAiB,KAFrC,EACY;KAIb,iBAJe,CAAA,CAAA,CAAA,GAAA;EAAf,CAAA,KAAA,EAAA,MAAA,CAAA,EAKa,CALb,SAAA,MAAA,GAKgC,YALhC,CAK6C,CAL7C,CAAA,GAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAKuE,gBAAA,CAAiB,KALxF;CACqB,GAAA,CAAA,CAAA,OAAA,EAAA,MAAiB,EAAA,GAKjB,gBAAA,CAAiB,KALA,CAAA;;AACzC;;;;;;;AAwIc,UA3EC,IA2ED,CAAA,cA3EoB,SA2EpB,GAAA,IAAA,EAAA,MAAA,CAAA,CAAA;EAAZ;EAAmD,SAAA,MAAA,EAAA,MAAA;EACxB;EAAZ,SAAA,MAAA,EAAA,MAAA;EAAd;EACA,SAAA,MAAA,EAvEa,MAuEb,GAAA,SAAA;EAAqB;EACN,SAAA,MAAA,EAtEF,UAsEE,CAtES,KAsET,CAAA;EAAd;EAAkD,SAAA,WAAA,EApEjC,eAoEiC;;AAGX,UA3D5B,eAAA,CA2D4B;EAAiB,IAAA,EAAA,QAAA;EAAE,SAAA,UAAA,EAAA,MAAA;;;AAyGhD,KA5JJ,cAAA,GA4JQ,MAAA,GAAA,MAAA,EAAA,GAAA,MAAA,GAAA,OAAA,GA5JgD,IA4JhD,GA5JuD,IA4JvD,EAAA;;KAzJf,uBAyJkC,CAAA,CAAA,CAAA,GAAA,OAAA,SAzJW,CAyJX,GAAA,IAAA,GAAA,MAAA,SAAA,MAzJ2C,CAyJ3C,GAAA,IAAA,GAAA,KAAA;;AAAuB,UAtJ7C,gBAsJ6C,CAAA,CAAA,CAAA,CAAA;EAAI;EAKlD,KAAA,EAAI,EAzJV,CAyJU,GAAA,SAAA;EAAe;EAEvB,GAAA,CAAA,KAAA,EAzJA,CAyJA,CAAA,EAzJI,CAyJJ;EAA2B;EAAb,MAAA,EAAA,EAvJf,SAuJe,EAAA,GAAA,SAAA;;;AAClB,KApJI,aAoJJ,CAAA,UApJ4B,cAoJ5B,CAAA,GApJ8C,gBAoJ9C,CApJ+D,CAoJ/D,CAAA,GAAA;EAAO;EAAZ,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAlJgC,MAkJhC,CAAA,MAAA,EAAA,OAAA,CAAA;CAAI;AAKP;KAnJK,kBAmJgD,CAAA,CAAA,CAAA,GAnJxB,gBAmJwB,CAnJP,CAmJO,CAAA,GAAA;EAAW;EAA5B,SAAA,EAAA,EAjJtB,SAiJsB,EAAA,GAAA,SAAA;CACzB;;KA9IN,gBAgJoB,CAAA,CAAA,CAAA,GAhJE,gBAgJF,CAhJmB,CAgJnB,CAAA,GAAA;EACyB;EAA5B,SAAA,EAAA,EA/IR,SA+IyB,EAAA,GAAA,SAAA;EAA9B;EACU,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EA9IgB,MA8IhB,CAAA,MAAA,EAAA,OAAA,CAAA;CAAb,GAAA;EAC8B,CAAA,GAAA,EAAA,MAAA,GAAA,MAAA,CAAA,EA7IX,gBA6IW,CAAA,OAAA,CAAA;CAA5B;;;;;KAtII,gBAAgB,iBACzB,wBACA,wBAAwB,kBACvB,iBAAiB,KACjB,YAAY,uCAAuC,OAClD,cAAc,YAAY,MAC1B,qBAAqB,SACpB,cAAc,sBAAsB,cAAc,YCxKxD,GDyKM,CCzKM,SDyKI,KCzKJ,CAAA,KAAe,EAAA,CAAA,GD0KpB,kBC1K6B,CD0KV,CC1KU,CAAA,GAAA,QA4CpB,MAAK,GD8H2B,UC9Hb,CD8HwB,CC9HxB,CAAA,KD+H5B,mBAAmB,mBAAmB,MAAM,WAAW,EAAE;;;;iBAyGhD,uBAAuB,aAAa,UAAU,WAAW;;;;iBAKzD,mBAAmB,qDAEvB,cAAc,aAAa,WAAW,aAAa,UAC5D,KAAK,OAAO;;;;iBAKC,oBAAoB,iBAAiB,WAAW,4CACrD,mBAEH,gBAAA,CAAiB,YAAY,gBAC5B,aAAa,gBAAA,CAAiB,WAAW,aAC5C,aAAa,UAChB,KAAK,gBAAA,CAAiB,WAAW,SAAS;;;;;AD1T7C;AAA8B,KEmBlB,eAAA,GAAkB,MFnBA,CAAA,MAAA,EEmBe,IFnBf,CAAA,GAAA,EAAA,GAAA,CAAA,CAAA;;;;;;;ACgB9B;;;;;AAEC;AAID;AAoBA;;;;;;;;;;;;;;AAQK,iBCaW,KAAA,CDbM,WAAA,ECaa,eDbb,CAAA,ECa+B,UDb/B;;;;;;ADlDV,cGKC,eAAA,SAAwB,KAAA,CHLb;EAAM,MAAA,EGMrB,gBAAA,CAAiB,KHNI,EAAA;EAAY,WAAA,CAAA,MAAA,EGQrB,gBAAA,CAAiB,KHRI,EAAA;;;;;;ACgB1C;;;;;AAEC;AAID;AAoBA;;;;;;;;;;;AAIK,iBETW,OAAA,CFSX,GAAA,MAAA,EAAA,CET+B,gBAAA,CAAiB,KFShD,GAAA,MAAA,CAAA,EAAA,CAAA,EAAA,KAAA;;;AAEH;AAGgB,iBEPF,iBAAA,CFOE,CAAA,EAAA,OAAA,CAAA,EAAA,CAAA,IEPkC,eFOlC"}
package/dist/index.mjs CHANGED
@@ -481,8 +481,7 @@ function forms(definitions) {
481
481
  formConfig.set(f, { id: name });
482
482
  formsById.set(name, f);
483
483
  }
484
- return async (context, next) => {
485
- const { url, request, store } = context;
484
+ return async ({ request, url, store }, next) => {
486
485
  const formStore = {
487
486
  configs: formConfig,
488
487
  state: /* @__PURE__ */ new WeakMap()
@@ -493,7 +492,7 @@ function forms(definitions) {
493
492
  const formInstance = formsById.get(action);
494
493
  if (formInstance) setFormState(formInstance, await processForm(formInstance, convertFormData(await request.formData())));
495
494
  }
496
- return next(context);
495
+ return next();
497
496
  };
498
497
  }
499
498
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["result: Record<string, unknown>","values: unknown[]","current: Record<string, unknown>","current: unknown","normalized: InternalFormIssue","result: Record<string, InternalFormIssue[]>","target","baseProps: InputProps","form","schema: StandardSchemaV1 | null","info: FormInfo","result: Record<string, unknown>","issue","formStore: FormStore"],"sources":["../src/lib/errors.ts","../src/lib/form-utils.ts","../src/lib/form.ts","../src/lib/middleware.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * error thrown when form validation fails imperatively\n */\nexport class ValidationError extends Error {\n\tissues: StandardSchemaV1.Issue[];\n\n\tconstructor(issues: StandardSchemaV1.Issue[]) {\n\t\tsuper('Validation failed');\n\t\tthis.name = 'ValidationError';\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * use this to throw a validation error to imperatively fail form validation.\n * can be used in combination with `issue` passed to form actions to create field-specific issues.\n *\n * @example\n * ```ts\n * import { invalid, form } from '@oomfware/forms';\n * import * as v from 'valibot';\n *\n * export const login = form(\n * v.object({ name: v.string(), _password: v.string() }),\n * async ({ name, _password }, issue) => {\n * const success = tryLogin(name, _password);\n * if (!success) {\n * invalid('Incorrect username or password');\n * }\n *\n * // ...\n * }\n * );\n * ```\n */\nexport function invalid(...issues: (StandardSchemaV1.Issue | string)[]): never {\n\tthrow new ValidationError(issues.map((issue) => (typeof issue === 'string' ? { message: issue } : issue)));\n}\n\n/**\n * checks whether this is a validation error thrown by {@link invalid}.\n */\nexport function isValidationError(e: unknown): e is ValidationError {\n\treturn e instanceof ValidationError;\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * internal representation of a form validation issue with computed path info\n */\nexport interface InternalFormIssue {\n\t/** dot/bracket notation path string (e.g., \"user.emails[0]\") */\n\tname: string;\n\t/** path segments as array */\n\tpath: (string | number)[];\n\t/** error message */\n\tmessage: string;\n\t/** whether this issue came from server validation */\n\tserver: boolean;\n}\n\n/**\n * sets a value in a nested object using a path string, mutating the original object\n */\nexport function setNestedValue(object: Record<string, unknown>, pathString: string, value: unknown): void {\n\tif (pathString.startsWith('n:')) {\n\t\tpathString = pathString.slice(2);\n\t\tvalue = value === '' ? undefined : parseFloat(value as string);\n\t} else if (pathString.startsWith('b:')) {\n\t\tpathString = pathString.slice(2);\n\t\tvalue = value === 'on';\n\t}\n\n\tdeepSet(object, splitPath(pathString), value);\n}\n\n/**\n * convert `FormData` into a POJO\n */\nexport function convertFormData(data: FormData): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (let key of data.keys()) {\n\t\tconst isArray = key.endsWith('[]');\n\t\tlet values: unknown[] = data.getAll(key);\n\n\t\tif (isArray) {\n\t\t\tkey = key.slice(0, -2);\n\t\t}\n\n\t\tif (values.length > 1 && !isArray) {\n\t\t\tthrow new Error(`Form cannot contain duplicated keys — \"${key}\" has ${values.length} values`);\n\t\t}\n\n\t\t// an empty `<input type=\"file\">` will submit a non-existent file, bizarrely\n\t\tvalues = values.filter(\n\t\t\t(entry) => typeof entry === 'string' || (entry as File).name !== '' || (entry as File).size > 0,\n\t\t);\n\n\t\tif (key.startsWith('n:')) {\n\t\t\tkey = key.slice(2);\n\t\t\tvalues = values.map((v) => (v === '' ? undefined : parseFloat(v as string)));\n\t\t} else if (key.startsWith('b:')) {\n\t\t\tkey = key.slice(2);\n\t\t\tvalues = values.map((v) => v === 'on');\n\t\t}\n\n\t\tsetNestedValue(result, key, isArray ? values : values[0]);\n\t}\n\n\treturn result;\n}\n\nconst PATH_REGEX = /^[a-zA-Z_$]\\w*(\\.[a-zA-Z_$]\\w*|\\[\\d+\\])*$/;\n\n/**\n * splits a path string like \"user.emails[0].address\" into [\"user\", \"emails\", \"0\", \"address\"]\n */\nexport function splitPath(path: string): string[] {\n\tif (!PATH_REGEX.test(path)) {\n\t\tthrow new Error(`Invalid path ${path}`);\n\t}\n\n\treturn path.split(/\\.|\\[|\\]/).filter(Boolean);\n}\n\n/**\n * check if a property key is dangerous and could lead to prototype pollution\n */\nfunction checkPrototypePollution(key: string): void {\n\tif (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n\t\tthrow new Error(`Invalid key \"${key}\": This key is not allowed to prevent prototype pollution.`);\n\t}\n}\n\n/**\n * sets a value in a nested object using an array of keys, mutating the original object.\n */\nexport function deepSet(object: Record<string, unknown>, keys: string[], value: unknown): void {\n\tlet current: Record<string, unknown> = object;\n\n\tfor (let i = 0; i < keys.length - 1; i += 1) {\n\t\tconst key = keys[i]!;\n\n\t\tcheckPrototypePollution(key);\n\n\t\tconst isArray = /^\\d+$/.test(keys[i + 1]!);\n\t\tconst exists = key in current;\n\t\tconst inner = current[key];\n\n\t\tif (exists && isArray !== Array.isArray(inner)) {\n\t\t\tthrow new Error(`Invalid array key ${keys[i + 1]}`);\n\t\t}\n\n\t\tif (!exists) {\n\t\t\tcurrent[key] = isArray ? [] : {};\n\t\t}\n\n\t\tcurrent = current[key] as Record<string, unknown>;\n\t}\n\n\tconst finalKey = keys[keys.length - 1]!;\n\tcheckPrototypePollution(finalKey);\n\tcurrent[finalKey] = value;\n}\n\n/**\n * gets a nested value from an object using a path array\n */\nexport function deepGet(object: Record<string, unknown>, path: (string | number)[]): unknown {\n\tlet current: unknown = object;\n\tfor (const key of path) {\n\t\tif (current == null || typeof current !== 'object') {\n\t\t\treturn current;\n\t\t}\n\t\tcurrent = (current as Record<string | number, unknown>)[key];\n\t}\n\treturn current;\n}\n\n/**\n * normalizes a Standard Schema issue into our internal format\n */\nexport function normalizeIssue(issue: StandardSchemaV1.Issue, server = false): InternalFormIssue {\n\tconst normalized: InternalFormIssue = { name: '', path: [], message: issue.message, server };\n\n\tif (issue.path !== undefined) {\n\t\tlet name = '';\n\n\t\tfor (const segment of issue.path) {\n\t\t\tconst key = typeof segment === 'object' ? (segment.key as string | number) : segment;\n\n\t\t\tnormalized.path.push(key as string | number);\n\n\t\t\tif (typeof key === 'number') {\n\t\t\t\tname += `[${key}]`;\n\t\t\t} else if (typeof key === 'string') {\n\t\t\t\tname += name === '' ? key : '.' + key;\n\t\t\t}\n\t\t}\n\n\t\tnormalized.name = name;\n\t}\n\n\treturn normalized;\n}\n\n/**\n * flattens issues into a lookup object keyed by path\n * includes a special '$' key containing all issues\n */\nexport function flattenIssues(issues: InternalFormIssue[]): Record<string, InternalFormIssue[]> {\n\tconst result: Record<string, InternalFormIssue[]> = {};\n\n\tfor (const issue of issues) {\n\t\t(result.$ ??= []).push(issue);\n\n\t\tlet name = '';\n\n\t\tif (issue.path !== undefined) {\n\t\t\tfor (const key of issue.path) {\n\t\t\t\tif (typeof key === 'number') {\n\t\t\t\t\tname += `[${key}]`;\n\t\t\t\t} else if (typeof key === 'string') {\n\t\t\t\t\tname += name === '' ? key : '.' + key;\n\t\t\t\t}\n\n\t\t\t\t(result[name] ??= []).push(issue);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * builds a path string from an array of path segments\n */\nexport function buildPathString(path: (string | number)[]): string {\n\tlet result = '';\n\n\tfor (const segment of path) {\n\t\tif (typeof segment === 'number') {\n\t\t\tresult += `[${segment}]`;\n\t\t} else {\n\t\t\tresult += result === '' ? segment : '.' + segment;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// #region field proxy\n\nexport interface FieldIssue {\n\tpath: (string | number)[];\n\tmessage: string;\n}\n\nexport interface FieldProxyMethods<T> {\n\t/** get the current value of this field */\n\tvalue(): T | undefined;\n\t/** set the value of this field */\n\tset(value: T): T;\n\t/** get validation issues for this exact field */\n\tissues(): FieldIssue[] | undefined;\n\t/** get all validation issues for this field and its descendants */\n\tallIssues(): FieldIssue[] | undefined;\n\t/**\n\t * get props for binding to an input element.\n\t * returns an object with `name`, `aria-invalid`, and type-specific props.\n\t */\n\tas(type: InputType, value?: string): InputProps;\n}\n\nexport type InputType =\n\t| 'text'\n\t| 'number'\n\t| 'range'\n\t| 'checkbox'\n\t| 'radio'\n\t| 'file'\n\t| 'file multiple'\n\t| 'select'\n\t| 'select multiple'\n\t| 'hidden'\n\t| 'submit'\n\t| 'email'\n\t| 'password'\n\t| 'tel'\n\t| 'url'\n\t| 'date'\n\t| 'time'\n\t| 'datetime-local'\n\t| 'month'\n\t| 'week'\n\t| 'color'\n\t| 'search';\n\nexport interface InputProps {\n\tname: string;\n\t'aria-invalid'?: 'true';\n\ttype?: string;\n\tvalue?: string;\n\tchecked?: boolean;\n\tmultiple?: boolean;\n}\n\n/**\n * creates a proxy-based field accessor for form data.\n * allows type-safe nested field access like `fields.user.emails[0].address.value()`.\n */\nexport function createFieldProxy<T>(\n\ttarget: unknown,\n\tgetInput: () => Record<string, unknown>,\n\tsetInput: (path: (string | number)[], value: unknown) => void,\n\tgetIssues: () => Record<string, InternalFormIssue[]>,\n\tpath: (string | number)[] = [],\n): T {\n\tconst getValue = () => {\n\t\treturn deepGet(getInput(), path);\n\t};\n\n\treturn new Proxy(target as object, {\n\t\tget(target, prop) {\n\t\t\tif (typeof prop === 'symbol') return (target as Record<symbol, unknown>)[prop];\n\n\t\t\t// Handle array access like jobs[0]\n\t\t\tif (/^\\d+$/.test(prop)) {\n\t\t\t\treturn createFieldProxy({}, getInput, setInput, getIssues, [...path, parseInt(prop, 10)]);\n\t\t\t}\n\n\t\t\tconst key = buildPathString(path);\n\n\t\t\tif (prop === 'set') {\n\t\t\t\tconst setFunc = function (newValue: unknown) {\n\t\t\t\t\tsetInput(path, newValue);\n\t\t\t\t\treturn newValue;\n\t\t\t\t};\n\t\t\t\treturn createFieldProxy(setFunc, getInput, setInput, getIssues, [...path, prop]);\n\t\t\t}\n\n\t\t\tif (prop === 'value') {\n\t\t\t\treturn createFieldProxy(getValue, getInput, setInput, getIssues, [...path, prop]);\n\t\t\t}\n\n\t\t\tif (prop === 'issues' || prop === 'allIssues') {\n\t\t\t\tconst issuesFunc = (): FieldIssue[] | undefined => {\n\t\t\t\t\tconst allIssues = getIssues()[key === '' ? '$' : key];\n\n\t\t\t\t\tif (prop === 'allIssues') {\n\t\t\t\t\t\treturn allIssues?.map((issue) => ({\n\t\t\t\t\t\t\tpath: issue.path,\n\t\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\t}));\n\t\t\t\t\t}\n\n\t\t\t\t\treturn allIssues\n\t\t\t\t\t\t?.filter((issue) => issue.name === key)\n\t\t\t\t\t\t?.map((issue) => ({\n\t\t\t\t\t\t\tpath: issue.path,\n\t\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\t}));\n\t\t\t\t};\n\n\t\t\t\treturn createFieldProxy(issuesFunc, getInput, setInput, getIssues, [...path, prop]);\n\t\t\t}\n\n\t\t\tif (prop === 'as') {\n\t\t\t\tconst asFunc = (type: InputType, inputValue?: string): InputProps => {\n\t\t\t\t\tconst isArray =\n\t\t\t\t\t\ttype === 'file multiple' ||\n\t\t\t\t\t\ttype === 'select multiple' ||\n\t\t\t\t\t\t(type === 'checkbox' && typeof inputValue === 'string');\n\n\t\t\t\t\tconst prefix =\n\t\t\t\t\t\ttype === 'number' || type === 'range' ? 'n:' : type === 'checkbox' && !isArray ? 'b:' : '';\n\n\t\t\t\t\t// Base properties for all input types\n\t\t\t\t\tconst baseProps: InputProps = {\n\t\t\t\t\t\tname: prefix + key + (isArray ? '[]' : ''),\n\t\t\t\t\t\tget 'aria-invalid'() {\n\t\t\t\t\t\t\tconst issues = getIssues();\n\t\t\t\t\t\t\treturn key in issues ? 'true' : undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\n\t\t\t\t\t// Add type attribute only for non-text inputs and non-select elements\n\t\t\t\t\tif (type !== 'text' && type !== 'select' && type !== 'select multiple') {\n\t\t\t\t\t\tbaseProps.type = type === 'file multiple' ? 'file' : type;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle submit and hidden inputs\n\t\t\t\t\tif (type === 'submit' || type === 'hidden') {\n\t\t\t\t\t\tif (!inputValue) {\n\t\t\t\t\t\t\tthrow new Error(`\\`${type}\\` inputs must have a value`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tvalue: { value: inputValue, enumerable: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle select inputs\n\t\t\t\t\tif (type === 'select' || type === 'select multiple') {\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tmultiple: { value: isArray, enumerable: true },\n\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\t\treturn getValue();\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle checkbox inputs\n\t\t\t\t\tif (type === 'checkbox' || type === 'radio') {\n\t\t\t\t\t\tif (type === 'radio' && !inputValue) {\n\t\t\t\t\t\t\tthrow new Error('Radio inputs must have a value');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (type === 'checkbox' && isArray && !inputValue) {\n\t\t\t\t\t\t\tthrow new Error('Checkbox array inputs must have a value');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tvalue: { value: inputValue ?? 'on', enumerable: true },\n\t\t\t\t\t\t\tchecked: {\n\t\t\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\t\tconst value = getValue();\n\n\t\t\t\t\t\t\t\t\tif (type === 'radio') {\n\t\t\t\t\t\t\t\t\t\treturn value === inputValue;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (isArray) {\n\t\t\t\t\t\t\t\t\t\treturn ((value as string[] | undefined) ?? []).includes(inputValue!);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle file inputs (can't persist value, just return name/type/multiple)\n\t\t\t\t\tif (type === 'file' || type === 'file multiple') {\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tmultiple: { value: isArray, enumerable: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle all other input types (text, number, etc.)\n\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\tconst value = getValue();\n\t\t\t\t\t\t\t\treturn value != null ? String(value) : '';\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\treturn createFieldProxy(asFunc, getInput, setInput, getIssues, [...path, 'as']);\n\t\t\t}\n\n\t\t\t// Handle property access (nested fields)\n\t\t\treturn createFieldProxy({}, getInput, setInput, getIssues, [...path, prop]);\n\t\t},\n\t}) as T;\n}\n\n// #endregion\n","import { createInjectionKey } from '@oomfware/fetch-router';\nimport { getContext } from '@oomfware/fetch-router/middlewares/async-context';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nimport { ValidationError } from './errors.ts';\nimport {\n\tcreateFieldProxy,\n\tdeepSet,\n\tflattenIssues,\n\tnormalizeIssue,\n\ttype InternalFormIssue,\n} from './form-utils.ts';\nimport type { MaybePromise } from './types.ts';\n\n// #region types\n\nexport interface FormInput {\n\t[key: string]: MaybeArray<string | number | boolean | File | FormInput>;\n}\n\ntype MaybeArray<T> = T | T[];\n\nexport interface FormIssue {\n\tmessage: string;\n\tpath: (string | number)[];\n}\n\n/**\n * the issue creator proxy passed to form callbacks.\n * allows creating field-specific validation issues via property access.\n *\n * @example\n * ```ts\n * form(schema, async (data, issue) => {\n * if (emailTaken(data.email)) {\n * invalid(issue.email('Email already in use'));\n * }\n * // nested fields: issue.user.profile.name('Invalid name')\n * // array fields: issue.items[0].name('Invalid item name')\n * });\n * ```\n */\nexport type InvalidField<T> = ((message: string) => StandardSchemaV1.Issue) & {\n\t[K in keyof T]-?: T[K] extends (infer U)[]\n\t\t? InvalidFieldArray<U>\n\t\t: T[K] extends object\n\t\t\t? InvalidField<T[K]>\n\t\t\t: (message: string) => StandardSchemaV1.Issue;\n};\n\ntype InvalidFieldArray<T> = {\n\t[index: number]: T extends object ? InvalidField<T> : (message: string) => StandardSchemaV1.Issue;\n} & ((message: string) => StandardSchemaV1.Issue);\n\n/**\n * symbol used to identify form instances.\n */\nexport const kForm = Symbol.for('@oomfware/forms');\n\n/**\n * internal info attached to a form instance.\n * used by the forms() middleware to identify and process forms.\n */\nexport interface FormInfo {\n\t/** the schema, if any */\n\tschema: StandardSchemaV1 | null;\n\t/** the handler function */\n\tfn: (data: any, issue: any) => MaybePromise<any>;\n}\n\n/**\n * form config stored by the forms() middleware.\n */\nexport interface FormConfig {\n\t/** the form id, derived from registration name */\n\tid: string;\n}\n\n/**\n * form state stored by the forms() middleware.\n */\nexport interface FormState<Input = unknown, Output = unknown> {\n\t/** the submitted input data (for repopulating form on error) */\n\tinput?: Input;\n\t/** validation issues, flattened by path */\n\tissues?: Record<string, InternalFormIssue[]>;\n\t/** the handler result (if successful) */\n\tresult?: Output;\n}\n\n/**\n * the form store holds registered forms, their configs, and state.\n */\nexport interface FormStore {\n\t/** map of form instance to config */\n\tconfigs: WeakMap<InternalForm<any, any>, FormConfig>;\n\t/** state for each form instance */\n\tstate: WeakMap<InternalForm<any, any>, FormState>;\n}\n\n/**\n * injection key for the form store.\n */\nexport const FORM_STORE_KEY = createInjectionKey<FormStore>();\n\n/**\n * the return value of a form() function.\n * can be spread onto a <form> element.\n */\nexport interface Form<Input extends FormInput | void, Output> {\n\t/** HTTP method */\n\treadonly method: 'POST';\n\t/** the form action URL */\n\treadonly action: string;\n\t/** the handler result, if submission was successful */\n\treadonly result: Output | undefined;\n\t/** access form fields using object notation */\n\treadonly fields: FormFields<Input>;\n\t/** spread this onto a <button> or <input type=\"submit\"> */\n\treadonly buttonProps: FormButtonProps;\n}\n\n/**\n * internal form type with metadata.\n * used internally by middleware; cast Form to this when accessing `__`.\n */\nexport interface InternalForm<Input extends FormInput | void, Output> extends Form<Input, Output> {\n\t/** internal form info, used by forms() middleware */\n\treadonly __: FormInfo;\n}\n\nexport interface FormButtonProps {\n\ttype: 'submit';\n\treadonly formaction: string;\n}\n\n// #region field types\n\n/** valid leaf value types for form fields */\nexport type FormFieldValue = string | string[] | number | boolean | File | File[];\n\n/** guard to prevent infinite recursion when T is unknown or has an index signature */\ntype WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;\n\n/** base methods available on all form fields */\nexport interface FormFieldMethods<T> {\n\t/** get the current value */\n\tvalue(): T | undefined;\n\t/** set the value */\n\tset(value: T): T;\n\t/** get validation issues for this field */\n\tissues(): FormIssue[] | undefined;\n}\n\n/** leaf field (primitives, files) with .as() method */\nexport type FormFieldLeaf<T extends FormFieldValue> = FormFieldMethods<T> & {\n\t/** get props for an input element */\n\tas(type: string, value?: string): Record<string, unknown>;\n};\n\n/** container field (objects, arrays) with allIssues() method */\ntype FormFieldContainer<T> = FormFieldMethods<T> & {\n\t/** get all issues for this field and descendants */\n\tallIssues(): FormIssue[] | undefined;\n};\n\n/** fallback field type when recursion would be infinite */\ntype FormFieldUnknown<T> = FormFieldMethods<T> & {\n\t/** get all issues for this field and descendants */\n\tallIssues(): FormIssue[] | undefined;\n\t/** get props for an input element */\n\tas(type: string, value?: string): Record<string, unknown>;\n} & {\n\t[key: string | number]: FormFieldUnknown<unknown>;\n};\n\n/**\n * recursive type to build form fields structure with proxy access.\n * preserves type information through the object hierarchy.\n */\nexport type FormFields<T> = T extends void\n\t? Record<string, never>\n\t: WillRecurseIndefinitely<T> extends true\n\t\t? FormFieldUnknown<T>\n\t\t: NonNullable<T> extends string | number | boolean | File\n\t\t\t? FormFieldLeaf<NonNullable<T>>\n\t\t\t: T extends string[] | File[]\n\t\t\t\t? FormFieldLeaf<T> & { [K in number]: FormFieldLeaf<T[number]> }\n\t\t\t\t: T extends Array<infer U>\n\t\t\t\t\t? FormFieldContainer<T> & { [K in number]: FormFields<U> }\n\t\t\t\t\t: FormFieldContainer<T> & { [K in keyof T]-?: FormFields<T[K]> };\n\n// #endregion\n\n// #region issue creator\n\n/**\n * creates an issue creator proxy that builds up paths for field-specific issues.\n */\nfunction createIssueCreator<T>(): InvalidField<T> {\n\treturn new Proxy((message: string) => createIssue(message), {\n\t\tget(_target, prop) {\n\t\t\tif (typeof prop === 'symbol') return undefined;\n\t\t\treturn createIssueProxy(prop, []);\n\t\t},\n\t}) as InvalidField<T>;\n\n\tfunction createIssue(message: string, path: (string | number)[] = []): StandardSchemaV1.Issue {\n\t\treturn { message, path };\n\t}\n\n\tfunction createIssueProxy(\n\t\tkey: string | number,\n\t\tpath: (string | number)[],\n\t): (message: string) => StandardSchemaV1.Issue {\n\t\tconst newPath = [...path, key];\n\n\t\tconst issueFunc = (message: string) => createIssue(message, newPath);\n\n\t\treturn new Proxy(issueFunc, {\n\t\t\tget(_target, prop) {\n\t\t\t\tif (typeof prop === 'symbol') return undefined;\n\n\t\t\t\tif (/^\\d+$/.test(prop)) {\n\t\t\t\t\treturn createIssueProxy(parseInt(prop, 10), newPath);\n\t\t\t\t}\n\n\t\t\t\treturn createIssueProxy(prop, newPath);\n\t\t\t},\n\t\t});\n\t}\n}\n\n// #endregion\n\n// #region form state access\n\n/**\n * get the form store from the current request context.\n * @throws if called outside of a request context\n */\nexport function getFormStore(): FormStore {\n\tconst context = getContext();\n\tconst store = context.store.inject(FORM_STORE_KEY);\n\n\tif (!store) {\n\t\tthrow new Error('form store not found. make sure the forms() middleware is installed.');\n\t}\n\n\treturn store;\n}\n\n/**\n * get config for a specific form instance.\n * @throws if form is not registered with forms() middleware\n */\nfunction getFormConfig(form: InternalForm<any, any>): FormConfig {\n\tconst store = getFormStore();\n\tconst config = store.configs.get(form);\n\n\tif (!config) {\n\t\tthrow new Error('form not registered. make sure to pass it to the forms() middleware.');\n\t}\n\n\treturn config;\n}\n\n/**\n * get state for a specific form instance.\n */\nexport function getFormState<Input, Output>(\n\tform: InternalForm<any, any>,\n): FormState<Input, Output> | undefined {\n\tconst store = getFormStore();\n\treturn store.state.get(form) as FormState<Input, Output> | undefined;\n}\n\n/**\n * set state for a specific form instance.\n */\nexport function setFormState<Input, Output>(\n\tform: InternalForm<any, any>,\n\tstate: FormState<Input, Output>,\n): void {\n\tconst store = getFormStore();\n\tstore.state.set(form, state);\n}\n\n// #endregion\n\n// #region form function\n\n/**\n * creates a form without validation.\n */\nexport function form<Output>(fn: () => MaybePromise<Output>): Form<void, Output>;\n\n/**\n * creates a form with unchecked input (no validation).\n */\nexport function form<Input extends FormInput, Output>(\n\tvalidate: 'unchecked',\n\tfn: (data: Input, issue: InvalidField<Input>) => MaybePromise<Output>,\n): Form<Input, Output>;\n\n/**\n * creates a form with Standard Schema validation.\n */\nexport function form<Schema extends StandardSchemaV1<FormInput, Record<string, unknown>>, Output>(\n\tvalidate: Schema,\n\tfn: (\n\t\tdata: StandardSchemaV1.InferOutput<Schema>,\n\t\tissue: InvalidField<StandardSchemaV1.InferInput<Schema>>,\n\t) => MaybePromise<Output>,\n): Form<StandardSchemaV1.InferInput<Schema>, Output>;\n\nexport function form(\n\tvalidateOrFn: StandardSchemaV1 | 'unchecked' | (() => MaybePromise<unknown>),\n\tmaybeFn?: (data: any, issue: any) => MaybePromise<unknown>,\n): Form<any, any> {\n\tconst fn = (maybeFn ?? validateOrFn) as (data: any, issue: any) => MaybePromise<unknown>;\n\n\tconst schema: StandardSchemaV1 | null =\n\t\t!maybeFn || validateOrFn === 'unchecked' ? null : (validateOrFn as StandardSchemaV1);\n\n\tconst instance = {} as InternalForm<any, any>;\n\n\tconst info: FormInfo = {\n\t\tschema,\n\t\tfn,\n\t};\n\n\t// method\n\tObject.defineProperty(instance, 'method', {\n\t\tvalue: 'POST',\n\t\tenumerable: true,\n\t});\n\n\t// action - computed from form store\n\tObject.defineProperty(instance, 'action', {\n\t\tget() {\n\t\t\tconst config = getFormConfig(instance);\n\t\t\treturn `?__action=${config.id}`;\n\t\t},\n\t\tenumerable: true,\n\t});\n\n\t// result - from state store\n\tObject.defineProperty(instance, 'result', {\n\t\tget() {\n\t\t\treturn getFormState(instance)?.result;\n\t\t},\n\t});\n\n\t// fields - proxy for field access\n\tObject.defineProperty(instance, 'fields', {\n\t\tget() {\n\t\t\treturn createFieldProxy(\n\t\t\t\t{},\n\t\t\t\t() => (getFormState(instance)?.input as Record<string, unknown>) ?? {},\n\t\t\t\t(path, value) => {\n\t\t\t\t\tconst currentState = getFormState(instance) ?? { input: {} };\n\t\t\t\t\tif (path.length === 0) {\n\t\t\t\t\t\tsetFormState(instance, { ...currentState, input: value });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst input = (currentState.input as Record<string, unknown>) ?? {};\n\t\t\t\t\t\tdeepSet(input, path.map(String), value);\n\t\t\t\t\t\tsetFormState(instance, { ...currentState, input });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => getFormState(instance)?.issues ?? {},\n\t\t\t);\n\t\t},\n\t});\n\n\t// buttonProps\n\tObject.defineProperty(instance, 'buttonProps', {\n\t\tget() {\n\t\t\tconst config = getFormConfig(instance);\n\t\t\treturn {\n\t\t\t\ttype: 'submit' as const,\n\t\t\t\tformaction: `?__action=${config.id}`,\n\t\t\t};\n\t\t},\n\t});\n\n\t// internal info\n\tObject.defineProperty(instance, '__', {\n\t\tvalue: info,\n\t});\n\n\t// brand symbol for identification\n\tObject.defineProperty(instance, kForm, {\n\t\tvalue: true,\n\t\tenumerable: false,\n\t});\n\n\treturn instance;\n}\n\n// #endregion\n\n// #region form processing\n\n/**\n * redacts sensitive fields (those starting with `_`) from form input.\n * this prevents passwords and other sensitive data from being returned in form state.\n */\nfunction redactSensitiveFields(obj: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const key of Object.keys(obj)) {\n\t\tif (key.startsWith('_')) continue;\n\n\t\tconst value = obj[key];\n\n\t\tif (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof File)) {\n\t\t\tresult[key] = redactSensitiveFields(value as Record<string, unknown>);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tresult[key] = value.map((item) =>\n\t\t\t\titem !== null && typeof item === 'object' && !(item instanceof File)\n\t\t\t\t\t? redactSensitiveFields(item as Record<string, unknown>)\n\t\t\t\t\t: item,\n\t\t\t);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * process a form submission.\n * called by forms() middleware when a matching action is received.\n */\nexport async function processForm(formInstance: InternalForm<any, any>, data: FormInput): Promise<FormState> {\n\tconst { schema, fn } = formInstance.__;\n\n\tlet validatedData = data;\n\n\t// validate with schema if present\n\tif (schema) {\n\t\tconst result = await schema['~standard'].validate(data);\n\n\t\tif (result.issues) {\n\t\t\treturn {\n\t\t\t\tresult: undefined,\n\t\t\t\tissues: flattenIssues(result.issues.map((issue) => normalizeIssue(issue, true))),\n\t\t\t\tinput: redactSensitiveFields(data),\n\t\t\t};\n\t\t}\n\t\tvalidatedData = result.value as FormInput;\n\t}\n\n\t// run handler\n\tconst issue = createIssueCreator();\n\n\ttry {\n\t\treturn {\n\t\t\tresult: await fn(validatedData, issue),\n\t\t\tissues: undefined,\n\t\t\tinput: undefined,\n\t\t};\n\t} catch (e) {\n\t\tif (e instanceof ValidationError) {\n\t\t\treturn {\n\t\t\t\tresult: undefined,\n\t\t\t\tissues: flattenIssues(e.issues.map((issue) => normalizeIssue(issue, true))),\n\t\t\t\tinput: redactSensitiveFields(data),\n\t\t\t};\n\t\t}\n\n\t\tthrow e;\n\t}\n}\n\n// #endregion\n","import type { RouterMiddleware } from '@oomfware/fetch-router';\n\nimport { convertFormData } from './form-utils.ts';\nimport {\n\tFORM_STORE_KEY,\n\tkForm,\n\tprocessForm,\n\tsetFormState,\n\ttype Form,\n\ttype FormConfig,\n\ttype FormStore,\n\ttype InternalForm,\n} from './form.ts';\n\n// #region types\n\n/**\n * a record of form instances to register with the middleware.\n */\nexport type FormDefinitions = Record<string, Form<any, any>>;\n\n// #endregion\n\n// #region helpers\n\n/**\n * checks if a value is a form instance created by form().\n */\nfunction isForm(value: unknown): value is Form<any, any> {\n\treturn value !== null && typeof value === 'object' && kForm in value;\n}\n\n// #endregion\n\n// #region middleware\n\n/**\n * creates a forms middleware that registers forms and handles form submissions.\n *\n * @example\n * ```ts\n * import { form, forms } from '@oomfware/forms';\n * import * as v from 'valibot';\n *\n * const createUserForm = form(\n * v.object({ name: v.string(), password: v.string() }),\n * async (input, issue) => {\n * // handle form submission\n * },\n * );\n *\n * router.map(routes.admin, {\n * middleware: [forms({ createUserForm })],\n * action() {\n * return render(\n * <form {...createUserForm}>\n * <input {...createUserForm.fields.name.as('text')} required />\n * </form>\n * );\n * },\n * });\n * ```\n */\nexport function forms(definitions: FormDefinitions): RouterMiddleware {\n\tconst formConfig = new WeakMap<InternalForm<any, any>, FormConfig>();\n\tconst formsById = new Map<string, InternalForm<any, any>>();\n\n\tfor (const [name, formInstance] of Object.entries(definitions)) {\n\t\tif (!isForm(formInstance)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst f = formInstance as InternalForm<any, any>;\n\n\t\tformConfig.set(f, { id: name });\n\t\tformsById.set(name, f);\n\t}\n\n\treturn async (context, next) => {\n\t\tconst { url, request, store } = context;\n\n\t\t// create form store for this request\n\t\tconst formStore: FormStore = {\n\t\t\tconfigs: formConfig,\n\t\t\tstate: new WeakMap(),\n\t\t};\n\n\t\t// inject form store into context\n\t\tstore.provide(FORM_STORE_KEY, formStore);\n\n\t\t// check if this is a form submission\n\t\tconst action = url.searchParams.get('__action');\n\n\t\tif (action && request.method === 'POST') {\n\t\t\t// find the form\n\t\t\tconst formInstance = formsById.get(action);\n\n\t\t\tif (formInstance) {\n\t\t\t\t// parse form data\n\t\t\t\tconst formData = await request.formData();\n\t\t\t\tconst data = convertFormData(formData as unknown as FormData);\n\n\t\t\t\t// process the form\n\t\t\t\tconst state = await processForm(formInstance, data as any);\n\n\t\t\t\t// store the state\n\t\t\t\tsetFormState(formInstance, state);\n\t\t\t}\n\t\t}\n\n\t\treturn next(context);\n\t};\n}\n\n// #endregion\n"],"mappings":";;;;;;;AAKA,IAAa,kBAAb,cAAqC,MAAM;CAC1C;CAEA,YAAY,QAAkC;AAC7C,QAAM,oBAAoB;AAC1B,OAAK,OAAO;AACZ,OAAK,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;AA0BhB,SAAgB,QAAQ,GAAG,QAAoD;AAC9E,OAAM,IAAI,gBAAgB,OAAO,KAAK,UAAW,OAAO,UAAU,WAAW,EAAE,SAAS,OAAO,GAAG,MAAO,CAAC;;;;;AAM3G,SAAgB,kBAAkB,GAAkC;AACnE,QAAO,aAAa;;;;;;;;AC1BrB,SAAgB,eAAe,QAAiC,YAAoB,OAAsB;AACzG,KAAI,WAAW,WAAW,KAAK,EAAE;AAChC,eAAa,WAAW,MAAM,EAAE;AAChC,UAAQ,UAAU,KAAK,SAAY,WAAW,MAAgB;YACpD,WAAW,WAAW,KAAK,EAAE;AACvC,eAAa,WAAW,MAAM,EAAE;AAChC,UAAQ,UAAU;;AAGnB,SAAQ,QAAQ,UAAU,WAAW,EAAE,MAAM;;;;;AAM9C,SAAgB,gBAAgB,MAAyC;CACxE,MAAMA,SAAkC,EAAE;AAE1C,MAAK,IAAI,OAAO,KAAK,MAAM,EAAE;EAC5B,MAAM,UAAU,IAAI,SAAS,KAAK;EAClC,IAAIC,SAAoB,KAAK,OAAO,IAAI;AAExC,MAAI,QACH,OAAM,IAAI,MAAM,GAAG,GAAG;AAGvB,MAAI,OAAO,SAAS,KAAK,CAAC,QACzB,OAAM,IAAI,MAAM,0CAA0C,IAAI,QAAQ,OAAO,OAAO,SAAS;AAI9F,WAAS,OAAO,QACd,UAAU,OAAO,UAAU,YAAa,MAAe,SAAS,MAAO,MAAe,OAAO,EAC9F;AAED,MAAI,IAAI,WAAW,KAAK,EAAE;AACzB,SAAM,IAAI,MAAM,EAAE;AAClB,YAAS,OAAO,KAAK,MAAO,MAAM,KAAK,SAAY,WAAW,EAAY,CAAE;aAClE,IAAI,WAAW,KAAK,EAAE;AAChC,SAAM,IAAI,MAAM,EAAE;AAClB,YAAS,OAAO,KAAK,MAAM,MAAM,KAAK;;AAGvC,iBAAe,QAAQ,KAAK,UAAU,SAAS,OAAO,GAAG;;AAG1D,QAAO;;AAGR,MAAM,aAAa;;;;AAKnB,SAAgB,UAAU,MAAwB;AACjD,KAAI,CAAC,WAAW,KAAK,KAAK,CACzB,OAAM,IAAI,MAAM,gBAAgB,OAAO;AAGxC,QAAO,KAAK,MAAM,WAAW,CAAC,OAAO,QAAQ;;;;;AAM9C,SAAS,wBAAwB,KAAmB;AACnD,KAAI,QAAQ,eAAe,QAAQ,iBAAiB,QAAQ,YAC3D,OAAM,IAAI,MAAM,gBAAgB,IAAI,4DAA4D;;;;;AAOlG,SAAgB,QAAQ,QAAiC,MAAgB,OAAsB;CAC9F,IAAIC,UAAmC;AAEvC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG;EAC5C,MAAM,MAAM,KAAK;AAEjB,0BAAwB,IAAI;EAE5B,MAAM,UAAU,QAAQ,KAAK,KAAK,IAAI,GAAI;EAC1C,MAAM,SAAS,OAAO;EACtB,MAAM,QAAQ,QAAQ;AAEtB,MAAI,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7C,OAAM,IAAI,MAAM,qBAAqB,KAAK,IAAI,KAAK;AAGpD,MAAI,CAAC,OACJ,SAAQ,OAAO,UAAU,EAAE,GAAG,EAAE;AAGjC,YAAU,QAAQ;;CAGnB,MAAM,WAAW,KAAK,KAAK,SAAS;AACpC,yBAAwB,SAAS;AACjC,SAAQ,YAAY;;;;;AAMrB,SAAgB,QAAQ,QAAiC,MAAoC;CAC5F,IAAIC,UAAmB;AACvB,MAAK,MAAM,OAAO,MAAM;AACvB,MAAI,WAAW,QAAQ,OAAO,YAAY,SACzC,QAAO;AAER,YAAW,QAA6C;;AAEzD,QAAO;;;;;AAMR,SAAgB,eAAe,OAA+B,SAAS,OAA0B;CAChG,MAAMC,aAAgC;EAAE,MAAM;EAAI,MAAM,EAAE;EAAE,SAAS,MAAM;EAAS;EAAQ;AAE5F,KAAI,MAAM,SAAS,QAAW;EAC7B,IAAI,OAAO;AAEX,OAAK,MAAM,WAAW,MAAM,MAAM;GACjC,MAAM,MAAM,OAAO,YAAY,WAAY,QAAQ,MAA0B;AAE7E,cAAW,KAAK,KAAK,IAAuB;AAE5C,OAAI,OAAO,QAAQ,SAClB,SAAQ,IAAI,IAAI;YACN,OAAO,QAAQ,SACzB,SAAQ,SAAS,KAAK,MAAM,MAAM;;AAIpC,aAAW,OAAO;;AAGnB,QAAO;;;;;;AAOR,SAAgB,cAAc,QAAkE;CAC/F,MAAMC,SAA8C,EAAE;AAEtD,MAAK,MAAM,SAAS,QAAQ;AAC3B,GAAC,OAAO,MAAM,EAAE,EAAE,KAAK,MAAM;EAE7B,IAAI,OAAO;AAEX,MAAI,MAAM,SAAS,OAClB,MAAK,MAAM,OAAO,MAAM,MAAM;AAC7B,OAAI,OAAO,QAAQ,SAClB,SAAQ,IAAI,IAAI;YACN,OAAO,QAAQ,SACzB,SAAQ,SAAS,KAAK,MAAM,MAAM;AAGnC,IAAC,OAAO,UAAU,EAAE,EAAE,KAAK,MAAM;;;AAKpC,QAAO;;;;;AAMR,SAAgB,gBAAgB,MAAmC;CAClE,IAAI,SAAS;AAEb,MAAK,MAAM,WAAW,KACrB,KAAI,OAAO,YAAY,SACtB,WAAU,IAAI,QAAQ;KAEtB,WAAU,WAAW,KAAK,UAAU,MAAM;AAI5C,QAAO;;;;;;AA+DR,SAAgB,iBACf,QACA,UACA,UACA,WACA,OAA4B,EAAE,EAC1B;CACJ,MAAM,iBAAiB;AACtB,SAAO,QAAQ,UAAU,EAAE,KAAK;;AAGjC,QAAO,IAAI,MAAM,QAAkB,EAClC,IAAI,UAAQ,MAAM;AACjB,MAAI,OAAO,SAAS,SAAU,QAAQC,SAAmC;AAGzE,MAAI,QAAQ,KAAK,KAAK,CACrB,QAAO,iBAAiB,EAAE,EAAE,UAAU,UAAU,WAAW,CAAC,GAAG,MAAM,SAAS,MAAM,GAAG,CAAC,CAAC;EAG1F,MAAM,MAAM,gBAAgB,KAAK;AAEjC,MAAI,SAAS,OAAO;GACnB,MAAM,UAAU,SAAU,UAAmB;AAC5C,aAAS,MAAM,SAAS;AACxB,WAAO;;AAER,UAAO,iBAAiB,SAAS,UAAU,UAAU,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC;;AAGjF,MAAI,SAAS,QACZ,QAAO,iBAAiB,UAAU,UAAU,UAAU,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC;AAGlF,MAAI,SAAS,YAAY,SAAS,aAAa;GAC9C,MAAM,mBAA6C;IAClD,MAAM,YAAY,WAAW,CAAC,QAAQ,KAAK,MAAM;AAEjD,QAAI,SAAS,YACZ,QAAO,WAAW,KAAK,WAAW;KACjC,MAAM,MAAM;KACZ,SAAS,MAAM;KACf,EAAE;AAGJ,WAAO,WACJ,QAAQ,UAAU,MAAM,SAAS,IAAI,EACrC,KAAK,WAAW;KACjB,MAAM,MAAM;KACZ,SAAS,MAAM;KACf,EAAE;;AAGL,UAAO,iBAAiB,YAAY,UAAU,UAAU,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC;;AAGpF,MAAI,SAAS,MAAM;GAClB,MAAM,UAAU,MAAiB,eAAoC;IACpE,MAAM,UACL,SAAS,mBACT,SAAS,qBACR,SAAS,cAAc,OAAO,eAAe;IAM/C,MAAMC,YAAwB;KAC7B,OAJA,SAAS,YAAY,SAAS,UAAU,OAAO,SAAS,cAAc,CAAC,UAAU,OAAO,MAIzE,OAAO,UAAU,OAAO;KACvC,IAAI,iBAAiB;AAEpB,aAAO,OADQ,WAAW,GACH,SAAS;;KAEjC;AAGD,QAAI,SAAS,UAAU,SAAS,YAAY,SAAS,kBACpD,WAAU,OAAO,SAAS,kBAAkB,SAAS;AAItD,QAAI,SAAS,YAAY,SAAS,UAAU;AAC3C,SAAI,CAAC,WACJ,OAAM,IAAI,MAAM,KAAK,KAAK,6BAA6B;AAGxD,YAAO,OAAO,iBAAiB,WAAW,EACzC,OAAO;MAAE,OAAO;MAAY,YAAY;MAAM,EAC9C,CAAC;;AAIH,QAAI,SAAS,YAAY,SAAS,kBACjC,QAAO,OAAO,iBAAiB,WAAW;KACzC,UAAU;MAAE,OAAO;MAAS,YAAY;MAAM;KAC9C,OAAO;MACN,YAAY;MACZ,MAAM;AACL,cAAO,UAAU;;MAElB;KACD,CAAC;AAIH,QAAI,SAAS,cAAc,SAAS,SAAS;AAC5C,SAAI,SAAS,WAAW,CAAC,WACxB,OAAM,IAAI,MAAM,iCAAiC;AAGlD,SAAI,SAAS,cAAc,WAAW,CAAC,WACtC,OAAM,IAAI,MAAM,0CAA0C;AAG3D,YAAO,OAAO,iBAAiB,WAAW;MACzC,OAAO;OAAE,OAAO,cAAc;OAAM,YAAY;OAAM;MACtD,SAAS;OACR,YAAY;OACZ,MAAM;QACL,MAAM,QAAQ,UAAU;AAExB,YAAI,SAAS,QACZ,QAAO,UAAU;AAGlB,YAAI,QACH,SAAS,SAAkC,EAAE,EAAE,SAAS,WAAY;AAGrE,eAAO;;OAER;MACD,CAAC;;AAIH,QAAI,SAAS,UAAU,SAAS,gBAC/B,QAAO,OAAO,iBAAiB,WAAW,EACzC,UAAU;KAAE,OAAO;KAAS,YAAY;KAAM,EAC9C,CAAC;AAIH,WAAO,OAAO,iBAAiB,WAAW,EACzC,OAAO;KACN,YAAY;KACZ,MAAM;MACL,MAAM,QAAQ,UAAU;AACxB,aAAO,SAAS,OAAO,OAAO,MAAM,GAAG;;KAExC,EACD,CAAC;;AAGH,UAAO,iBAAiB,QAAQ,UAAU,UAAU,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC;;AAIhF,SAAO,iBAAiB,EAAE,EAAE,UAAU,UAAU,WAAW,CAAC,GAAG,MAAM,KAAK,CAAC;IAE5E,CAAC;;;;;;;;AClXH,MAAa,QAAQ,OAAO,IAAI,kBAAkB;;;;AA8ClD,MAAa,iBAAiB,oBAA+B;;;;AAgG7D,SAAS,qBAAyC;AACjD,QAAO,IAAI,OAAO,YAAoB,YAAY,QAAQ,EAAE,EAC3D,IAAI,SAAS,MAAM;AAClB,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAO,iBAAiB,MAAM,EAAE,CAAC;IAElC,CAAC;CAEF,SAAS,YAAY,SAAiB,OAA4B,EAAE,EAA0B;AAC7F,SAAO;GAAE;GAAS;GAAM;;CAGzB,SAAS,iBACR,KACA,MAC8C;EAC9C,MAAM,UAAU,CAAC,GAAG,MAAM,IAAI;EAE9B,MAAM,aAAa,YAAoB,YAAY,SAAS,QAAQ;AAEpE,SAAO,IAAI,MAAM,WAAW,EAC3B,IAAI,SAAS,MAAM;AAClB,OAAI,OAAO,SAAS,SAAU,QAAO;AAErC,OAAI,QAAQ,KAAK,KAAK,CACrB,QAAO,iBAAiB,SAAS,MAAM,GAAG,EAAE,QAAQ;AAGrD,UAAO,iBAAiB,MAAM,QAAQ;KAEvC,CAAC;;;;;;;AAYJ,SAAgB,eAA0B;CAEzC,MAAM,QADU,YAAY,CACN,MAAM,OAAO,eAAe;AAElD,KAAI,CAAC,MACJ,OAAM,IAAI,MAAM,uEAAuE;AAGxF,QAAO;;;;;;AAOR,SAAS,cAAc,QAA0C;CAEhE,MAAM,SADQ,cAAc,CACP,QAAQ,IAAIC,OAAK;AAEtC,KAAI,CAAC,OACJ,OAAM,IAAI,MAAM,uEAAuE;AAGxF,QAAO;;;;;AAMR,SAAgB,aACf,QACuC;AAEvC,QADc,cAAc,CACf,MAAM,IAAIA,OAAK;;;;;AAM7B,SAAgB,aACf,QACA,OACO;AAEP,CADc,cAAc,CACtB,MAAM,IAAIA,QAAM,MAAM;;AA+B7B,SAAgB,KACf,cACA,SACiB;CACjB,MAAM,KAAM,WAAW;CAEvB,MAAMC,SACL,CAAC,WAAW,iBAAiB,cAAc,OAAQ;CAEpD,MAAM,WAAW,EAAE;CAEnB,MAAMC,OAAiB;EACtB;EACA;EACA;AAGD,QAAO,eAAe,UAAU,UAAU;EACzC,OAAO;EACP,YAAY;EACZ,CAAC;AAGF,QAAO,eAAe,UAAU,UAAU;EACzC,MAAM;AAEL,UAAO,aADQ,cAAc,SAAS,CACX;;EAE5B,YAAY;EACZ,CAAC;AAGF,QAAO,eAAe,UAAU,UAAU,EACzC,MAAM;AACL,SAAO,aAAa,SAAS,EAAE;IAEhC,CAAC;AAGF,QAAO,eAAe,UAAU,UAAU,EACzC,MAAM;AACL,SAAO,iBACN,EAAE,QACK,aAAa,SAAS,EAAE,SAAqC,EAAE,GACrE,MAAM,UAAU;GAChB,MAAM,eAAe,aAAa,SAAS,IAAI,EAAE,OAAO,EAAE,EAAE;AAC5D,OAAI,KAAK,WAAW,EACnB,cAAa,UAAU;IAAE,GAAG;IAAc,OAAO;IAAO,CAAC;QACnD;IACN,MAAM,QAAS,aAAa,SAAqC,EAAE;AACnE,YAAQ,OAAO,KAAK,IAAI,OAAO,EAAE,MAAM;AACvC,iBAAa,UAAU;KAAE,GAAG;KAAc;KAAO,CAAC;;WAG9C,aAAa,SAAS,EAAE,UAAU,EAAE,CAC1C;IAEF,CAAC;AAGF,QAAO,eAAe,UAAU,eAAe,EAC9C,MAAM;AAEL,SAAO;GACN,MAAM;GACN,YAAY,aAHE,cAAc,SAAS,CAGL;GAChC;IAEF,CAAC;AAGF,QAAO,eAAe,UAAU,MAAM,EACrC,OAAO,MACP,CAAC;AAGF,QAAO,eAAe,UAAU,OAAO;EACtC,OAAO;EACP,YAAY;EACZ,CAAC;AAEF,QAAO;;;;;;AAWR,SAAS,sBAAsB,KAAuD;CACrF,MAAMC,SAAkC,EAAE;AAE1C,MAAK,MAAM,OAAO,OAAO,KAAK,IAAI,EAAE;AACnC,MAAI,IAAI,WAAW,IAAI,CAAE;EAEzB,MAAM,QAAQ,IAAI;AAElB,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,IAAI,EAAE,iBAAiB,MAC9F,QAAO,OAAO,sBAAsB,MAAiC;WAC3D,MAAM,QAAQ,MAAM,CAC9B,QAAO,OAAO,MAAM,KAAK,SACxB,SAAS,QAAQ,OAAO,SAAS,YAAY,EAAE,gBAAgB,QAC5D,sBAAsB,KAAgC,GACtD,KACH;MAED,QAAO,OAAO;;AAIhB,QAAO;;;;;;AAOR,eAAsB,YAAY,cAAsC,MAAqC;CAC5G,MAAM,EAAE,QAAQ,OAAO,aAAa;CAEpC,IAAI,gBAAgB;AAGpB,KAAI,QAAQ;EACX,MAAM,SAAS,MAAM,OAAO,aAAa,SAAS,KAAK;AAEvD,MAAI,OAAO,OACV,QAAO;GACN,QAAQ;GACR,QAAQ,cAAc,OAAO,OAAO,KAAK,YAAU,eAAeC,SAAO,KAAK,CAAC,CAAC;GAChF,OAAO,sBAAsB,KAAK;GAClC;AAEF,kBAAgB,OAAO;;CAIxB,MAAM,QAAQ,oBAAoB;AAElC,KAAI;AACH,SAAO;GACN,QAAQ,MAAM,GAAG,eAAe,MAAM;GACtC,QAAQ;GACR,OAAO;GACP;UACO,GAAG;AACX,MAAI,aAAa,gBAChB,QAAO;GACN,QAAQ;GACR,QAAQ,cAAc,EAAE,OAAO,KAAK,YAAU,eAAeA,SAAO,KAAK,CAAC,CAAC;GAC3E,OAAO,sBAAsB,KAAK;GAClC;AAGF,QAAM;;;;;;;;;AC7bR,SAAS,OAAO,OAAyC;AACxD,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkChE,SAAgB,MAAM,aAAgD;CACrE,MAAM,6BAAa,IAAI,SAA6C;CACpE,MAAM,4BAAY,IAAI,KAAqC;AAE3D,MAAK,MAAM,CAAC,MAAM,iBAAiB,OAAO,QAAQ,YAAY,EAAE;AAC/D,MAAI,CAAC,OAAO,aAAa,CACxB;EAGD,MAAM,IAAI;AAEV,aAAW,IAAI,GAAG,EAAE,IAAI,MAAM,CAAC;AAC/B,YAAU,IAAI,MAAM,EAAE;;AAGvB,QAAO,OAAO,SAAS,SAAS;EAC/B,MAAM,EAAE,KAAK,SAAS,UAAU;EAGhC,MAAMC,YAAuB;GAC5B,SAAS;GACT,uBAAO,IAAI,SAAS;GACpB;AAGD,QAAM,QAAQ,gBAAgB,UAAU;EAGxC,MAAM,SAAS,IAAI,aAAa,IAAI,WAAW;AAE/C,MAAI,UAAU,QAAQ,WAAW,QAAQ;GAExC,MAAM,eAAe,UAAU,IAAI,OAAO;AAE1C,OAAI,aASH,cAAa,cAHC,MAAM,YAAY,cAHnB,gBADI,MAAM,QAAQ,UAAU,CACoB,CAGH,CAGzB;;AAInC,SAAO,KAAK,QAAQ"}
1
+ {"version":3,"file":"index.mjs","names":["result: Record<string, unknown>","values: unknown[]","current: Record<string, unknown>","current: unknown","normalized: InternalFormIssue","result: Record<string, InternalFormIssue[]>","target","baseProps: InputProps","form","schema: StandardSchemaV1 | null","info: FormInfo","result: Record<string, unknown>","issue","formStore: FormStore"],"sources":["../src/lib/errors.ts","../src/lib/form-utils.ts","../src/lib/form.ts","../src/lib/middleware.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * error thrown when form validation fails imperatively\n */\nexport class ValidationError extends Error {\n\tissues: StandardSchemaV1.Issue[];\n\n\tconstructor(issues: StandardSchemaV1.Issue[]) {\n\t\tsuper('Validation failed');\n\t\tthis.name = 'ValidationError';\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * use this to throw a validation error to imperatively fail form validation.\n * can be used in combination with `issue` passed to form actions to create field-specific issues.\n *\n * @example\n * ```ts\n * import { invalid, form } from '@oomfware/forms';\n * import * as v from 'valibot';\n *\n * export const login = form(\n * v.object({ name: v.string(), _password: v.string() }),\n * async ({ name, _password }, issue) => {\n * const success = tryLogin(name, _password);\n * if (!success) {\n * invalid('Incorrect username or password');\n * }\n *\n * // ...\n * }\n * );\n * ```\n */\nexport function invalid(...issues: (StandardSchemaV1.Issue | string)[]): never {\n\tthrow new ValidationError(issues.map((issue) => (typeof issue === 'string' ? { message: issue } : issue)));\n}\n\n/**\n * checks whether this is a validation error thrown by {@link invalid}.\n */\nexport function isValidationError(e: unknown): e is ValidationError {\n\treturn e instanceof ValidationError;\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\n\n/**\n * internal representation of a form validation issue with computed path info\n */\nexport interface InternalFormIssue {\n\t/** dot/bracket notation path string (e.g., \"user.emails[0]\") */\n\tname: string;\n\t/** path segments as array */\n\tpath: (string | number)[];\n\t/** error message */\n\tmessage: string;\n\t/** whether this issue came from server validation */\n\tserver: boolean;\n}\n\n/**\n * sets a value in a nested object using a path string, mutating the original object\n */\nexport function setNestedValue(object: Record<string, unknown>, pathString: string, value: unknown): void {\n\tif (pathString.startsWith('n:')) {\n\t\tpathString = pathString.slice(2);\n\t\tvalue = value === '' ? undefined : parseFloat(value as string);\n\t} else if (pathString.startsWith('b:')) {\n\t\tpathString = pathString.slice(2);\n\t\tvalue = value === 'on';\n\t}\n\n\tdeepSet(object, splitPath(pathString), value);\n}\n\n/**\n * convert `FormData` into a POJO\n */\nexport function convertFormData(data: FormData): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (let key of data.keys()) {\n\t\tconst isArray = key.endsWith('[]');\n\t\tlet values: unknown[] = data.getAll(key);\n\n\t\tif (isArray) {\n\t\t\tkey = key.slice(0, -2);\n\t\t}\n\n\t\tif (values.length > 1 && !isArray) {\n\t\t\tthrow new Error(`Form cannot contain duplicated keys — \"${key}\" has ${values.length} values`);\n\t\t}\n\n\t\t// an empty `<input type=\"file\">` will submit a non-existent file, bizarrely\n\t\tvalues = values.filter(\n\t\t\t(entry) => typeof entry === 'string' || (entry as File).name !== '' || (entry as File).size > 0,\n\t\t);\n\n\t\tif (key.startsWith('n:')) {\n\t\t\tkey = key.slice(2);\n\t\t\tvalues = values.map((v) => (v === '' ? undefined : parseFloat(v as string)));\n\t\t} else if (key.startsWith('b:')) {\n\t\t\tkey = key.slice(2);\n\t\t\tvalues = values.map((v) => v === 'on');\n\t\t}\n\n\t\tsetNestedValue(result, key, isArray ? values : values[0]);\n\t}\n\n\treturn result;\n}\n\nconst PATH_REGEX = /^[a-zA-Z_$]\\w*(\\.[a-zA-Z_$]\\w*|\\[\\d+\\])*$/;\n\n/**\n * splits a path string like \"user.emails[0].address\" into [\"user\", \"emails\", \"0\", \"address\"]\n */\nexport function splitPath(path: string): string[] {\n\tif (!PATH_REGEX.test(path)) {\n\t\tthrow new Error(`Invalid path ${path}`);\n\t}\n\n\treturn path.split(/\\.|\\[|\\]/).filter(Boolean);\n}\n\n/**\n * check if a property key is dangerous and could lead to prototype pollution\n */\nfunction checkPrototypePollution(key: string): void {\n\tif (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n\t\tthrow new Error(`Invalid key \"${key}\": This key is not allowed to prevent prototype pollution.`);\n\t}\n}\n\n/**\n * sets a value in a nested object using an array of keys, mutating the original object.\n */\nexport function deepSet(object: Record<string, unknown>, keys: string[], value: unknown): void {\n\tlet current: Record<string, unknown> = object;\n\n\tfor (let i = 0; i < keys.length - 1; i += 1) {\n\t\tconst key = keys[i]!;\n\n\t\tcheckPrototypePollution(key);\n\n\t\tconst isArray = /^\\d+$/.test(keys[i + 1]!);\n\t\tconst exists = key in current;\n\t\tconst inner = current[key];\n\n\t\tif (exists && isArray !== Array.isArray(inner)) {\n\t\t\tthrow new Error(`Invalid array key ${keys[i + 1]}`);\n\t\t}\n\n\t\tif (!exists) {\n\t\t\tcurrent[key] = isArray ? [] : {};\n\t\t}\n\n\t\tcurrent = current[key] as Record<string, unknown>;\n\t}\n\n\tconst finalKey = keys[keys.length - 1]!;\n\tcheckPrototypePollution(finalKey);\n\tcurrent[finalKey] = value;\n}\n\n/**\n * gets a nested value from an object using a path array\n */\nexport function deepGet(object: Record<string, unknown>, path: (string | number)[]): unknown {\n\tlet current: unknown = object;\n\tfor (const key of path) {\n\t\tif (current == null || typeof current !== 'object') {\n\t\t\treturn current;\n\t\t}\n\t\tcurrent = (current as Record<string | number, unknown>)[key];\n\t}\n\treturn current;\n}\n\n/**\n * normalizes a Standard Schema issue into our internal format\n */\nexport function normalizeIssue(issue: StandardSchemaV1.Issue, server = false): InternalFormIssue {\n\tconst normalized: InternalFormIssue = { name: '', path: [], message: issue.message, server };\n\n\tif (issue.path !== undefined) {\n\t\tlet name = '';\n\n\t\tfor (const segment of issue.path) {\n\t\t\tconst key = typeof segment === 'object' ? (segment.key as string | number) : segment;\n\n\t\t\tnormalized.path.push(key as string | number);\n\n\t\t\tif (typeof key === 'number') {\n\t\t\t\tname += `[${key}]`;\n\t\t\t} else if (typeof key === 'string') {\n\t\t\t\tname += name === '' ? key : '.' + key;\n\t\t\t}\n\t\t}\n\n\t\tnormalized.name = name;\n\t}\n\n\treturn normalized;\n}\n\n/**\n * flattens issues into a lookup object keyed by path\n * includes a special '$' key containing all issues\n */\nexport function flattenIssues(issues: InternalFormIssue[]): Record<string, InternalFormIssue[]> {\n\tconst result: Record<string, InternalFormIssue[]> = {};\n\n\tfor (const issue of issues) {\n\t\t(result.$ ??= []).push(issue);\n\n\t\tlet name = '';\n\n\t\tif (issue.path !== undefined) {\n\t\t\tfor (const key of issue.path) {\n\t\t\t\tif (typeof key === 'number') {\n\t\t\t\t\tname += `[${key}]`;\n\t\t\t\t} else if (typeof key === 'string') {\n\t\t\t\t\tname += name === '' ? key : '.' + key;\n\t\t\t\t}\n\n\t\t\t\t(result[name] ??= []).push(issue);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * builds a path string from an array of path segments\n */\nexport function buildPathString(path: (string | number)[]): string {\n\tlet result = '';\n\n\tfor (const segment of path) {\n\t\tif (typeof segment === 'number') {\n\t\t\tresult += `[${segment}]`;\n\t\t} else {\n\t\t\tresult += result === '' ? segment : '.' + segment;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// #region field proxy\n\nexport interface FieldIssue {\n\tpath: (string | number)[];\n\tmessage: string;\n}\n\nexport interface FieldProxyMethods<T> {\n\t/** get the current value of this field */\n\tvalue(): T | undefined;\n\t/** set the value of this field */\n\tset(value: T): T;\n\t/** get validation issues for this exact field */\n\tissues(): FieldIssue[] | undefined;\n\t/** get all validation issues for this field and its descendants */\n\tallIssues(): FieldIssue[] | undefined;\n\t/**\n\t * get props for binding to an input element.\n\t * returns an object with `name`, `aria-invalid`, and type-specific props.\n\t */\n\tas(type: InputType, value?: string): InputProps;\n}\n\nexport type InputType =\n\t| 'text'\n\t| 'number'\n\t| 'range'\n\t| 'checkbox'\n\t| 'radio'\n\t| 'file'\n\t| 'file multiple'\n\t| 'select'\n\t| 'select multiple'\n\t| 'hidden'\n\t| 'submit'\n\t| 'email'\n\t| 'password'\n\t| 'tel'\n\t| 'url'\n\t| 'date'\n\t| 'time'\n\t| 'datetime-local'\n\t| 'month'\n\t| 'week'\n\t| 'color'\n\t| 'search';\n\nexport interface InputProps {\n\tname: string;\n\t'aria-invalid'?: 'true';\n\ttype?: string;\n\tvalue?: string;\n\tchecked?: boolean;\n\tmultiple?: boolean;\n}\n\n/**\n * creates a proxy-based field accessor for form data.\n * allows type-safe nested field access like `fields.user.emails[0].address.value()`.\n */\nexport function createFieldProxy<T>(\n\ttarget: unknown,\n\tgetInput: () => Record<string, unknown>,\n\tsetInput: (path: (string | number)[], value: unknown) => void,\n\tgetIssues: () => Record<string, InternalFormIssue[]>,\n\tpath: (string | number)[] = [],\n): T {\n\tconst getValue = () => {\n\t\treturn deepGet(getInput(), path);\n\t};\n\n\treturn new Proxy(target as object, {\n\t\tget(target, prop) {\n\t\t\tif (typeof prop === 'symbol') return (target as Record<symbol, unknown>)[prop];\n\n\t\t\t// Handle array access like jobs[0]\n\t\t\tif (/^\\d+$/.test(prop)) {\n\t\t\t\treturn createFieldProxy({}, getInput, setInput, getIssues, [...path, parseInt(prop, 10)]);\n\t\t\t}\n\n\t\t\tconst key = buildPathString(path);\n\n\t\t\tif (prop === 'set') {\n\t\t\t\tconst setFunc = function (newValue: unknown) {\n\t\t\t\t\tsetInput(path, newValue);\n\t\t\t\t\treturn newValue;\n\t\t\t\t};\n\t\t\t\treturn createFieldProxy(setFunc, getInput, setInput, getIssues, [...path, prop]);\n\t\t\t}\n\n\t\t\tif (prop === 'value') {\n\t\t\t\treturn createFieldProxy(getValue, getInput, setInput, getIssues, [...path, prop]);\n\t\t\t}\n\n\t\t\tif (prop === 'issues' || prop === 'allIssues') {\n\t\t\t\tconst issuesFunc = (): FieldIssue[] | undefined => {\n\t\t\t\t\tconst allIssues = getIssues()[key === '' ? '$' : key];\n\n\t\t\t\t\tif (prop === 'allIssues') {\n\t\t\t\t\t\treturn allIssues?.map((issue) => ({\n\t\t\t\t\t\t\tpath: issue.path,\n\t\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\t}));\n\t\t\t\t\t}\n\n\t\t\t\t\treturn allIssues\n\t\t\t\t\t\t?.filter((issue) => issue.name === key)\n\t\t\t\t\t\t?.map((issue) => ({\n\t\t\t\t\t\t\tpath: issue.path,\n\t\t\t\t\t\t\tmessage: issue.message,\n\t\t\t\t\t\t}));\n\t\t\t\t};\n\n\t\t\t\treturn createFieldProxy(issuesFunc, getInput, setInput, getIssues, [...path, prop]);\n\t\t\t}\n\n\t\t\tif (prop === 'as') {\n\t\t\t\tconst asFunc = (type: InputType, inputValue?: string): InputProps => {\n\t\t\t\t\tconst isArray =\n\t\t\t\t\t\ttype === 'file multiple' ||\n\t\t\t\t\t\ttype === 'select multiple' ||\n\t\t\t\t\t\t(type === 'checkbox' && typeof inputValue === 'string');\n\n\t\t\t\t\tconst prefix =\n\t\t\t\t\t\ttype === 'number' || type === 'range' ? 'n:' : type === 'checkbox' && !isArray ? 'b:' : '';\n\n\t\t\t\t\t// Base properties for all input types\n\t\t\t\t\tconst baseProps: InputProps = {\n\t\t\t\t\t\tname: prefix + key + (isArray ? '[]' : ''),\n\t\t\t\t\t\tget 'aria-invalid'() {\n\t\t\t\t\t\t\tconst issues = getIssues();\n\t\t\t\t\t\t\treturn key in issues ? 'true' : undefined;\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\n\t\t\t\t\t// Add type attribute only for non-text inputs and non-select elements\n\t\t\t\t\tif (type !== 'text' && type !== 'select' && type !== 'select multiple') {\n\t\t\t\t\t\tbaseProps.type = type === 'file multiple' ? 'file' : type;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle submit and hidden inputs\n\t\t\t\t\tif (type === 'submit' || type === 'hidden') {\n\t\t\t\t\t\tif (!inputValue) {\n\t\t\t\t\t\t\tthrow new Error(`\\`${type}\\` inputs must have a value`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tvalue: { value: inputValue, enumerable: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle select inputs\n\t\t\t\t\tif (type === 'select' || type === 'select multiple') {\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tmultiple: { value: isArray, enumerable: true },\n\t\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\t\treturn getValue();\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle checkbox inputs\n\t\t\t\t\tif (type === 'checkbox' || type === 'radio') {\n\t\t\t\t\t\tif (type === 'radio' && !inputValue) {\n\t\t\t\t\t\t\tthrow new Error('Radio inputs must have a value');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (type === 'checkbox' && isArray && !inputValue) {\n\t\t\t\t\t\t\tthrow new Error('Checkbox array inputs must have a value');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tvalue: { value: inputValue ?? 'on', enumerable: true },\n\t\t\t\t\t\t\tchecked: {\n\t\t\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\t\tconst value = getValue();\n\n\t\t\t\t\t\t\t\t\tif (type === 'radio') {\n\t\t\t\t\t\t\t\t\t\treturn value === inputValue;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif (isArray) {\n\t\t\t\t\t\t\t\t\t\treturn ((value as string[] | undefined) ?? []).includes(inputValue!);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle file inputs (can't persist value, just return name/type/multiple)\n\t\t\t\t\tif (type === 'file' || type === 'file multiple') {\n\t\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\t\tmultiple: { value: isArray, enumerable: true },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Handle all other input types (text, number, etc.)\n\t\t\t\t\treturn Object.defineProperties(baseProps, {\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\t\tget() {\n\t\t\t\t\t\t\t\tconst value = getValue();\n\t\t\t\t\t\t\t\treturn value != null ? String(value) : '';\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\treturn createFieldProxy(asFunc, getInput, setInput, getIssues, [...path, 'as']);\n\t\t\t}\n\n\t\t\t// Handle property access (nested fields)\n\t\t\treturn createFieldProxy({}, getInput, setInput, getIssues, [...path, prop]);\n\t\t},\n\t}) as T;\n}\n\n// #endregion\n","import { createInjectionKey } from '@oomfware/fetch-router';\nimport { getContext } from '@oomfware/fetch-router/middlewares/async-context';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\n\nimport { ValidationError } from './errors.ts';\nimport {\n\tcreateFieldProxy,\n\tdeepSet,\n\tflattenIssues,\n\tnormalizeIssue,\n\ttype InternalFormIssue,\n} from './form-utils.ts';\nimport type { MaybePromise } from './types.ts';\n\n// #region types\n\nexport interface FormInput {\n\t[key: string]: MaybeArray<string | number | boolean | File | FormInput>;\n}\n\ntype MaybeArray<T> = T | T[];\n\nexport interface FormIssue {\n\tmessage: string;\n\tpath: (string | number)[];\n}\n\n/**\n * the issue creator proxy passed to form callbacks.\n * allows creating field-specific validation issues via property access.\n *\n * @example\n * ```ts\n * form(schema, async (data, issue) => {\n * if (emailTaken(data.email)) {\n * invalid(issue.email('Email already in use'));\n * }\n * // nested fields: issue.user.profile.name('Invalid name')\n * // array fields: issue.items[0].name('Invalid item name')\n * });\n * ```\n */\nexport type InvalidField<T> = ((message: string) => StandardSchemaV1.Issue) & {\n\t[K in keyof T]-?: T[K] extends (infer U)[]\n\t\t? InvalidFieldArray<U>\n\t\t: T[K] extends object\n\t\t\t? InvalidField<T[K]>\n\t\t\t: (message: string) => StandardSchemaV1.Issue;\n};\n\ntype InvalidFieldArray<T> = {\n\t[index: number]: T extends object ? InvalidField<T> : (message: string) => StandardSchemaV1.Issue;\n} & ((message: string) => StandardSchemaV1.Issue);\n\n/**\n * symbol used to identify form instances.\n */\nexport const kForm = Symbol.for('@oomfware/forms');\n\n/**\n * internal info attached to a form instance.\n * used by the forms() middleware to identify and process forms.\n */\nexport interface FormInfo {\n\t/** the schema, if any */\n\tschema: StandardSchemaV1 | null;\n\t/** the handler function */\n\tfn: (data: any, issue: any) => MaybePromise<any>;\n}\n\n/**\n * form config stored by the forms() middleware.\n */\nexport interface FormConfig {\n\t/** the form id, derived from registration name */\n\tid: string;\n}\n\n/**\n * form state stored by the forms() middleware.\n */\nexport interface FormState<Input = unknown, Output = unknown> {\n\t/** the submitted input data (for repopulating form on error) */\n\tinput?: Input;\n\t/** validation issues, flattened by path */\n\tissues?: Record<string, InternalFormIssue[]>;\n\t/** the handler result (if successful) */\n\tresult?: Output;\n}\n\n/**\n * the form store holds registered forms, their configs, and state.\n */\nexport interface FormStore {\n\t/** map of form instance to config */\n\tconfigs: WeakMap<InternalForm<any, any>, FormConfig>;\n\t/** state for each form instance */\n\tstate: WeakMap<InternalForm<any, any>, FormState>;\n}\n\n/**\n * injection key for the form store.\n */\nexport const FORM_STORE_KEY = createInjectionKey<FormStore>();\n\n/**\n * the return value of a form() function.\n * can be spread onto a <form> element.\n */\nexport interface Form<Input extends FormInput | void, Output> {\n\t/** HTTP method */\n\treadonly method: 'POST';\n\t/** the form action URL */\n\treadonly action: string;\n\t/** the handler result, if submission was successful */\n\treadonly result: Output | undefined;\n\t/** access form fields using object notation */\n\treadonly fields: FormFields<Input>;\n\t/** spread this onto a <button> or <input type=\"submit\"> */\n\treadonly buttonProps: FormButtonProps;\n}\n\n/**\n * internal form type with metadata.\n * used internally by middleware; cast Form to this when accessing `__`.\n */\nexport interface InternalForm<Input extends FormInput | void, Output> extends Form<Input, Output> {\n\t/** internal form info, used by forms() middleware */\n\treadonly __: FormInfo;\n}\n\nexport interface FormButtonProps {\n\ttype: 'submit';\n\treadonly formaction: string;\n}\n\n// #region field types\n\n/** valid leaf value types for form fields */\nexport type FormFieldValue = string | string[] | number | boolean | File | File[];\n\n/** guard to prevent infinite recursion when T is unknown or has an index signature */\ntype WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;\n\n/** base methods available on all form fields */\nexport interface FormFieldMethods<T> {\n\t/** get the current value */\n\tvalue(): T | undefined;\n\t/** set the value */\n\tset(value: T): T;\n\t/** get validation issues for this field */\n\tissues(): FormIssue[] | undefined;\n}\n\n/** leaf field (primitives, files) with .as() method */\nexport type FormFieldLeaf<T extends FormFieldValue> = FormFieldMethods<T> & {\n\t/** get props for an input element */\n\tas(type: string, value?: string): Record<string, unknown>;\n};\n\n/** container field (objects, arrays) with allIssues() method */\ntype FormFieldContainer<T> = FormFieldMethods<T> & {\n\t/** get all issues for this field and descendants */\n\tallIssues(): FormIssue[] | undefined;\n};\n\n/** fallback field type when recursion would be infinite */\ntype FormFieldUnknown<T> = FormFieldMethods<T> & {\n\t/** get all issues for this field and descendants */\n\tallIssues(): FormIssue[] | undefined;\n\t/** get props for an input element */\n\tas(type: string, value?: string): Record<string, unknown>;\n} & {\n\t[key: string | number]: FormFieldUnknown<unknown>;\n};\n\n/**\n * recursive type to build form fields structure with proxy access.\n * preserves type information through the object hierarchy.\n */\nexport type FormFields<T> = T extends void\n\t? Record<string, never>\n\t: WillRecurseIndefinitely<T> extends true\n\t\t? FormFieldUnknown<T>\n\t\t: NonNullable<T> extends string | number | boolean | File\n\t\t\t? FormFieldLeaf<NonNullable<T>>\n\t\t\t: T extends string[] | File[]\n\t\t\t\t? FormFieldLeaf<T> & { [K in number]: FormFieldLeaf<T[number]> }\n\t\t\t\t: T extends Array<infer U>\n\t\t\t\t\t? FormFieldContainer<T> & { [K in number]: FormFields<U> }\n\t\t\t\t\t: FormFieldContainer<T> & { [K in keyof T]-?: FormFields<T[K]> };\n\n// #endregion\n\n// #region issue creator\n\n/**\n * creates an issue creator proxy that builds up paths for field-specific issues.\n */\nfunction createIssueCreator<T>(): InvalidField<T> {\n\treturn new Proxy((message: string) => createIssue(message), {\n\t\tget(_target, prop) {\n\t\t\tif (typeof prop === 'symbol') return undefined;\n\t\t\treturn createIssueProxy(prop, []);\n\t\t},\n\t}) as InvalidField<T>;\n\n\tfunction createIssue(message: string, path: (string | number)[] = []): StandardSchemaV1.Issue {\n\t\treturn { message, path };\n\t}\n\n\tfunction createIssueProxy(\n\t\tkey: string | number,\n\t\tpath: (string | number)[],\n\t): (message: string) => StandardSchemaV1.Issue {\n\t\tconst newPath = [...path, key];\n\n\t\tconst issueFunc = (message: string) => createIssue(message, newPath);\n\n\t\treturn new Proxy(issueFunc, {\n\t\t\tget(_target, prop) {\n\t\t\t\tif (typeof prop === 'symbol') return undefined;\n\n\t\t\t\tif (/^\\d+$/.test(prop)) {\n\t\t\t\t\treturn createIssueProxy(parseInt(prop, 10), newPath);\n\t\t\t\t}\n\n\t\t\t\treturn createIssueProxy(prop, newPath);\n\t\t\t},\n\t\t});\n\t}\n}\n\n// #endregion\n\n// #region form state access\n\n/**\n * get the form store from the current request context.\n * @throws if called outside of a request context\n */\nexport function getFormStore(): FormStore {\n\tconst context = getContext();\n\tconst store = context.store.inject(FORM_STORE_KEY);\n\n\tif (!store) {\n\t\tthrow new Error('form store not found. make sure the forms() middleware is installed.');\n\t}\n\n\treturn store;\n}\n\n/**\n * get config for a specific form instance.\n * @throws if form is not registered with forms() middleware\n */\nfunction getFormConfig(form: InternalForm<any, any>): FormConfig {\n\tconst store = getFormStore();\n\tconst config = store.configs.get(form);\n\n\tif (!config) {\n\t\tthrow new Error('form not registered. make sure to pass it to the forms() middleware.');\n\t}\n\n\treturn config;\n}\n\n/**\n * get state for a specific form instance.\n */\nexport function getFormState<Input, Output>(\n\tform: InternalForm<any, any>,\n): FormState<Input, Output> | undefined {\n\tconst store = getFormStore();\n\treturn store.state.get(form) as FormState<Input, Output> | undefined;\n}\n\n/**\n * set state for a specific form instance.\n */\nexport function setFormState<Input, Output>(\n\tform: InternalForm<any, any>,\n\tstate: FormState<Input, Output>,\n): void {\n\tconst store = getFormStore();\n\tstore.state.set(form, state);\n}\n\n// #endregion\n\n// #region form function\n\n/**\n * creates a form without validation.\n */\nexport function form<Output>(fn: () => MaybePromise<Output>): Form<void, Output>;\n\n/**\n * creates a form with unchecked input (no validation).\n */\nexport function form<Input extends FormInput, Output>(\n\tvalidate: 'unchecked',\n\tfn: (data: Input, issue: InvalidField<Input>) => MaybePromise<Output>,\n): Form<Input, Output>;\n\n/**\n * creates a form with Standard Schema validation.\n */\nexport function form<Schema extends StandardSchemaV1<FormInput, Record<string, unknown>>, Output>(\n\tvalidate: Schema,\n\tfn: (\n\t\tdata: StandardSchemaV1.InferOutput<Schema>,\n\t\tissue: InvalidField<StandardSchemaV1.InferInput<Schema>>,\n\t) => MaybePromise<Output>,\n): Form<StandardSchemaV1.InferInput<Schema>, Output>;\n\nexport function form(\n\tvalidateOrFn: StandardSchemaV1 | 'unchecked' | (() => MaybePromise<unknown>),\n\tmaybeFn?: (data: any, issue: any) => MaybePromise<unknown>,\n): Form<any, any> {\n\tconst fn = (maybeFn ?? validateOrFn) as (data: any, issue: any) => MaybePromise<unknown>;\n\n\tconst schema: StandardSchemaV1 | null =\n\t\t!maybeFn || validateOrFn === 'unchecked' ? null : (validateOrFn as StandardSchemaV1);\n\n\tconst instance = {} as InternalForm<any, any>;\n\n\tconst info: FormInfo = {\n\t\tschema,\n\t\tfn,\n\t};\n\n\t// method\n\tObject.defineProperty(instance, 'method', {\n\t\tvalue: 'POST',\n\t\tenumerable: true,\n\t});\n\n\t// action - computed from form store\n\tObject.defineProperty(instance, 'action', {\n\t\tget() {\n\t\t\tconst config = getFormConfig(instance);\n\t\t\treturn `?__action=${config.id}`;\n\t\t},\n\t\tenumerable: true,\n\t});\n\n\t// result - from state store\n\tObject.defineProperty(instance, 'result', {\n\t\tget() {\n\t\t\treturn getFormState(instance)?.result;\n\t\t},\n\t});\n\n\t// fields - proxy for field access\n\tObject.defineProperty(instance, 'fields', {\n\t\tget() {\n\t\t\treturn createFieldProxy(\n\t\t\t\t{},\n\t\t\t\t() => (getFormState(instance)?.input as Record<string, unknown>) ?? {},\n\t\t\t\t(path, value) => {\n\t\t\t\t\tconst currentState = getFormState(instance) ?? { input: {} };\n\t\t\t\t\tif (path.length === 0) {\n\t\t\t\t\t\tsetFormState(instance, { ...currentState, input: value });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst input = (currentState.input as Record<string, unknown>) ?? {};\n\t\t\t\t\t\tdeepSet(input, path.map(String), value);\n\t\t\t\t\t\tsetFormState(instance, { ...currentState, input });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => getFormState(instance)?.issues ?? {},\n\t\t\t);\n\t\t},\n\t});\n\n\t// buttonProps\n\tObject.defineProperty(instance, 'buttonProps', {\n\t\tget() {\n\t\t\tconst config = getFormConfig(instance);\n\t\t\treturn {\n\t\t\t\ttype: 'submit' as const,\n\t\t\t\tformaction: `?__action=${config.id}`,\n\t\t\t};\n\t\t},\n\t});\n\n\t// internal info\n\tObject.defineProperty(instance, '__', {\n\t\tvalue: info,\n\t});\n\n\t// brand symbol for identification\n\tObject.defineProperty(instance, kForm, {\n\t\tvalue: true,\n\t\tenumerable: false,\n\t});\n\n\treturn instance;\n}\n\n// #endregion\n\n// #region form processing\n\n/**\n * redacts sensitive fields (those starting with `_`) from form input.\n * this prevents passwords and other sensitive data from being returned in form state.\n */\nfunction redactSensitiveFields(obj: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const key of Object.keys(obj)) {\n\t\tif (key.startsWith('_')) continue;\n\n\t\tconst value = obj[key];\n\n\t\tif (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof File)) {\n\t\t\tresult[key] = redactSensitiveFields(value as Record<string, unknown>);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tresult[key] = value.map((item) =>\n\t\t\t\titem !== null && typeof item === 'object' && !(item instanceof File)\n\t\t\t\t\t? redactSensitiveFields(item as Record<string, unknown>)\n\t\t\t\t\t: item,\n\t\t\t);\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * process a form submission.\n * called by forms() middleware when a matching action is received.\n */\nexport async function processForm(formInstance: InternalForm<any, any>, data: FormInput): Promise<FormState> {\n\tconst { schema, fn } = formInstance.__;\n\n\tlet validatedData = data;\n\n\t// validate with schema if present\n\tif (schema) {\n\t\tconst result = await schema['~standard'].validate(data);\n\n\t\tif (result.issues) {\n\t\t\treturn {\n\t\t\t\tresult: undefined,\n\t\t\t\tissues: flattenIssues(result.issues.map((issue) => normalizeIssue(issue, true))),\n\t\t\t\tinput: redactSensitiveFields(data),\n\t\t\t};\n\t\t}\n\t\tvalidatedData = result.value as FormInput;\n\t}\n\n\t// run handler\n\tconst issue = createIssueCreator();\n\n\ttry {\n\t\treturn {\n\t\t\tresult: await fn(validatedData, issue),\n\t\t\tissues: undefined,\n\t\t\tinput: undefined,\n\t\t};\n\t} catch (e) {\n\t\tif (e instanceof ValidationError) {\n\t\t\treturn {\n\t\t\t\tresult: undefined,\n\t\t\t\tissues: flattenIssues(e.issues.map((issue) => normalizeIssue(issue, true))),\n\t\t\t\tinput: redactSensitiveFields(data),\n\t\t\t};\n\t\t}\n\n\t\tthrow e;\n\t}\n}\n\n// #endregion\n","import type { Middleware } from '@oomfware/fetch-router';\n\nimport { convertFormData } from './form-utils.ts';\nimport {\n\tFORM_STORE_KEY,\n\tkForm,\n\tprocessForm,\n\tsetFormState,\n\ttype Form,\n\ttype FormConfig,\n\ttype FormStore,\n\ttype InternalForm,\n} from './form.ts';\n\n// #region types\n\n/**\n * a record of form instances to register with the middleware.\n */\nexport type FormDefinitions = Record<string, Form<any, any>>;\n\n// #endregion\n\n// #region helpers\n\n/**\n * checks if a value is a form instance created by form().\n */\nfunction isForm(value: unknown): value is Form<any, any> {\n\treturn value !== null && typeof value === 'object' && kForm in value;\n}\n\n// #endregion\n\n// #region middleware\n\n/**\n * creates a forms middleware that registers forms and handles form submissions.\n *\n * @example\n * ```ts\n * import { form, forms } from '@oomfware/forms';\n * import * as v from 'valibot';\n *\n * const createUserForm = form(\n * v.object({ name: v.string(), password: v.string() }),\n * async (input, issue) => {\n * // handle form submission\n * },\n * );\n *\n * router.map(routes.admin, {\n * middleware: [forms({ createUserForm })],\n * action() {\n * return render(\n * <form {...createUserForm}>\n * <input {...createUserForm.fields.name.as('text')} required />\n * </form>\n * );\n * },\n * });\n * ```\n */\nexport function forms(definitions: FormDefinitions): Middleware {\n\tconst formConfig = new WeakMap<InternalForm<any, any>, FormConfig>();\n\tconst formsById = new Map<string, InternalForm<any, any>>();\n\n\tfor (const [name, formInstance] of Object.entries(definitions)) {\n\t\tif (!isForm(formInstance)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst f = formInstance as InternalForm<any, any>;\n\n\t\tformConfig.set(f, { id: name });\n\t\tformsById.set(name, f);\n\t}\n\n\treturn async ({ request, url, store }, next) => {\n\t\t// create form store for this request\n\t\tconst formStore: FormStore = {\n\t\t\tconfigs: formConfig,\n\t\t\tstate: new WeakMap(),\n\t\t};\n\n\t\t// inject form store into context\n\t\tstore.provide(FORM_STORE_KEY, formStore);\n\n\t\t// check if this is a form submission\n\t\tconst action = url.searchParams.get('__action');\n\n\t\tif (action && request.method === 'POST') {\n\t\t\t// find the form\n\t\t\tconst formInstance = formsById.get(action);\n\n\t\t\tif (formInstance) {\n\t\t\t\t// parse form data\n\t\t\t\tconst formData = await request.formData();\n\t\t\t\tconst data = convertFormData(formData as unknown as FormData);\n\n\t\t\t\t// process the form\n\t\t\t\tconst state = await processForm(formInstance, data as any);\n\n\t\t\t\t// store the state\n\t\t\t\tsetFormState(formInstance, state);\n\t\t\t}\n\t\t}\n\n\t\treturn next();\n\t};\n}\n\n// #endregion\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oomfware/forms",
4
- "version": "0.1.1",
4
+ "version": "0.2.0",
5
5
  "description": "form validation middleware",
6
6
  "license": "0BSD",
7
7
  "repository": {
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "devDependencies": {
28
28
  "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
29
- "@oomfware/fetch-router": "^0.1.0",
29
+ "@oomfware/fetch-router": "^0.2.1",
30
30
  "@prettier/plugin-oxc": "^0.1.3",
31
31
  "@types/bun": "^1.3.5",
32
32
  "bumpp": "^10.3.2",
@@ -37,7 +37,7 @@
37
37
  "valibot": "^1.2.0"
38
38
  },
39
39
  "peerDependencies": {
40
- "@oomfware/fetch-router": "^0.1.0"
40
+ "@oomfware/fetch-router": "^0.2.1"
41
41
  },
42
42
  "dependencies": {
43
43
  "@standard-schema/spec": "^1.1.0"
@@ -1,4 +1,4 @@
1
- import type { RouterMiddleware } from '@oomfware/fetch-router';
1
+ import type { Middleware } from '@oomfware/fetch-router';
2
2
 
3
3
  import { convertFormData } from './form-utils.ts';
4
4
  import {
@@ -61,7 +61,7 @@ function isForm(value: unknown): value is Form<any, any> {
61
61
  * });
62
62
  * ```
63
63
  */
64
- export function forms(definitions: FormDefinitions): RouterMiddleware {
64
+ export function forms(definitions: FormDefinitions): Middleware {
65
65
  const formConfig = new WeakMap<InternalForm<any, any>, FormConfig>();
66
66
  const formsById = new Map<string, InternalForm<any, any>>();
67
67
 
@@ -76,9 +76,7 @@ export function forms(definitions: FormDefinitions): RouterMiddleware {
76
76
  formsById.set(name, f);
77
77
  }
78
78
 
79
- return async (context, next) => {
80
- const { url, request, store } = context;
81
-
79
+ return async ({ request, url, store }, next) => {
82
80
  // create form store for this request
83
81
  const formStore: FormStore = {
84
82
  configs: formConfig,
@@ -108,7 +106,7 @@ export function forms(definitions: FormDefinitions): RouterMiddleware {
108
106
  }
109
107
  }
110
108
 
111
- return next(context);
109
+ return next();
112
110
  };
113
111
  }
114
112