@nifrajs/schema 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,53 @@ const openapi = toOpenAPI(app) // OpenAPI 3.1
29
29
  `operationId`s); also works on a live app. Routes using a BYO Standard Schema are
30
30
  emitted without a detailed schema (Standard Schema exposes no JSON Schema).
31
31
 
32
+ ### Forms and files - `@nifrajs/schema/form`
33
+
34
+ The `t` exported from `@nifrajs/schema/form` is the same builder plus `t.file` and `t.form`. A
35
+ `t.form` is a route `body` that reads `multipart/form-data`: text fields and files validated side by
36
+ side, typed in `c.body`.
37
+
38
+ ```ts
39
+ import { server } from "@nifrajs/core/server"
40
+ import { t } from "@nifrajs/schema/form"
41
+
42
+ const app = server().post(
43
+ "/avatars",
44
+ {
45
+ body: t.form({
46
+ avatar: t.file({ maxBytes: 5_000_000, accept: ["image/png", "image/jpeg"] }),
47
+ photos: t.array(t.file({ accept: ["image/*"] }), { maxItems: 8 }),
48
+ caption: t.optional(t.string({ maxLength: 200 })),
49
+ }),
50
+ bodyLimit: 50_000_000, // the default body cap is 1 MB
51
+ },
52
+ (c) => ({ bytes: c.body.avatar.size, photos: c.body.photos.length }),
53
+ )
54
+ ```
55
+
56
+ - **`t.file({ maxBytes, accept })`** - a `File`. The size is checked before a byte is read, and
57
+ `accept` is matched against the file's leading bytes, never the type the client claimed. With
58
+ `accept` set, the validated file's `type` is the detected one. A type with no signature to check
59
+ (`text/csv`, `image/svg+xml`) throws when the schema is built.
60
+ - **`t.form(fields, options?)`** - text fields are coerced from their string form, a repeated field
61
+ is a list, and an undeclared field fails unless `additionalProperties` is set. `options` also takes
62
+ the body limits: `maxFields` (100), `maxFiles` (10), `maxFieldBytes` (64 KiB), `maxFileBytes`. A
63
+ request over one is a `413`.
64
+ - **Composes with the plain `t`** - `t.array(file)` and `t.optional(file)` work from either import. A
65
+ file anywhere else (`t.object`, `t.union`, `t.record`) throws: a file is not JSON.
66
+ - **`toOpenAPI`** emits a form with a file as `multipart/form-data` with `format: binary` fields.
67
+ - **A form with no required file also accepts JSON and urlencoded bodies**, so one route serves an
68
+ HTML form and a script.
69
+
70
+ It is a subpath so that an app with no uploads carries none of it. Validation proves a file's
71
+ signature and nothing more: generate your own storage key rather than using `file.name`, serve
72
+ uploads with `content-disposition: attachment`, and protect cookie-authenticated upload routes with
73
+ `csrf()` - a browser posts a form cross-origin without a preflight. The
74
+ [security guide](https://nifra.dev/docs/security) has the full list.
75
+
76
+ A hand-rolled Standard Schema can validate a form too: wrap it in `multipartBody(schema, limits?)`
77
+ from `@nifrajs/core/multipart`.
78
+
32
79
  ### `t` vs bring-your-own (bundle size)
33
80
 
34
81
  `t` is the batteries-included default because a TypeBox schema *is* a JSON Schema: it gives you
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,gBAAgB,EAEjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAQ,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAKnE;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9F,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;CACvB,CAAA;AAoJD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAC3C,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,WAAW,CAAC,CAAC,CAAC,CA+ChB"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,gBAAgB,EAEjB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAQ,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAKnE;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;IAC9F,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;CACvB,CAAA;AAmKD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAC3C,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,WAAW,CAAC,CAAC,CAAC,CA+ChB"}
package/dist/adapter.js CHANGED
@@ -14,6 +14,18 @@ function toIssue(error) {
14
14
  path: error.path === "" ? undefined : error.path.slice(1).split("/"),
15
15
  };
16
16
  }
17
+ // The issues past this are never reported (a 422 lists 100), so walking them is wasted work on a
18
+ // large hostile body.
19
+ const MAX_ISSUES = 100;
20
+ function firstIssues(errors) {
21
+ const issues = [];
22
+ for (const error of errors) {
23
+ issues.push(toIssue(error));
24
+ if (issues.length === MAX_ISSUES)
25
+ break;
26
+ }
27
+ return issues;
28
+ }
17
29
  /** Extract a flat conversion plan, or `null` when this schema needs the full recursive Convert. */
18
30
  function flatCoercionPlan(schema) {
19
31
  if (schema[Kind] !== "Object")
@@ -187,11 +199,11 @@ export function fromTypeBox(schema, options) {
187
199
  if (compiled !== undefined) {
188
200
  if (compiled.Check(input))
189
201
  return { value: input };
190
- return { issues: [...compiled.Errors(input)].map(toIssue) };
202
+ return { issues: firstIssues(compiled.Errors(input)) };
191
203
  }
192
204
  if (Value.Check(schema, input))
193
205
  return { value: input };
194
- return { issues: [...Value.Errors(schema, input)].map(toIssue) };
206
+ return { issues: firstIssues(Value.Errors(schema, input)) };
195
207
  },
196
208
  // Phantom: `types` carries no runtime value; this cast supplies the
197
209
  // compile-time `Static<T>` that nifra's `InferOutput` reads to type `c.body`.
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAA6B,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAkB,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAcnD;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAA0D;IACzE,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;KACrE,CAAA;AACH,CAAC;AAsBD,mGAAmG;AACnG,SAAS,gBAAgB,CAAC,MAAe;IACvC,IAAK,MAA6C,CAAC,IAAI,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAClF,MAAM,KAAK,GAAI,MAA4C,CAAC,UAAU,CAAA;IACtE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC5D,MAAM,MAAM,GAAG,KAAgC,CAAA;IAC/C,MAAM,IAAI,GAAyC,EAAE,CAAA;IACrD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAI,MAAM,CAAC,GAAG,CAAoD,EAAE,CAAC,IAAI,CAAC,CAAA;QACpF,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvF,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,0FAA0F;AAC1F,MAAM,eAAe,GAAG,CAAC,CAAS,EAAW,EAAE;AAC7C,oGAAoG;AACpG,CAAC,KAAK,CAAC,CAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;AAEzD,MAAM,WAAW,GAAG,CAAC,CAAU,EAAW,EAAE,CAC1C,CAAC,KAAK,IAAI;IACV,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;AAEtE,MAAM,YAAY,GAAG,CAAC,CAAU,EAAW,EAAE,CAC3C,CAAC,KAAK,KAAK;IACX,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAA;AAErF,SAAS,aAAa,CAAC,IAAgB,EAAE,CAAU;IACjD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,eAAe,CAAC,CAAC,CAAC;gBAChD,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;gBACf,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;oBACd,CAAC,CAAC,CAAC;oBACH,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;wBACf,CAAC,CAAC,CAAC;wBACH,CAAC,CAAC,CAAC,CAAA;QACX,KAAK,SAAS;YACZ,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,eAAe,CAAC,CAAC,CAAC;gBAChD,CAAC,CAAC,oFAAoF;oBACpF,mFAAmF;oBACnF,0FAA0F;oBAC1F,QAAQ,CAAC,CAAC,CAAC;gBACb,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ;oBACrB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;oBACf,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;wBACd,CAAC,CAAC,CAAC;wBACH,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;4BACf,CAAC,CAAC,CAAC;4BACH,CAAC,CAAC,CAAC,CAAA;QACb,KAAK,SAAS;YACZ,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5D,KAAK,QAAQ;YACX,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS;gBACzD,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE;gBAC1B,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,QAAQ;oBACxE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;oBACd,CAAC,CAAC,CAAC,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,gBAAgB,CAAC,IAAa;IACrC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3D,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,IAA+B,CAAA;IAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;QACzB,IACE,CAAC,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,mBAAmB,CAAC;YACrD,KAAK,KAAK,IAAI;YACd,OAAO,KAAK,KAAK,QAAQ;YACzB,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EACrE,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,GAAG,KAAK,OAAO,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QACrF,IAAI,gBAAgB,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;IAC1C,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,qGAAqG;AACrG,SAAS,WAAW,CAAC,IAAkB,EAAE,KAAc;IACrD,8FAA8F;IAC9F,0CAA0C;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACrF,MAAM,MAAM,GAAG,KAAgC,CAAA;IAC/C,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC;YAAE,SAAQ,CAAC,iEAAiE;QAChG,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IAChD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,WAAW,CACzB,MAAS,EACT,OAAuC;IAEvC,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,KAAK,CAAA;IACvC,iGAAiG;IACjG,gGAAgG;IAChG,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC7D,IAAI,QAAkC,CAAA;IACtC,yFAAyF;IACzF,kGAAkG;IAClG,IAAI,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACvC,OAAO;QACL,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,OAAO;YACf,QAAQ,EAAE,CAAC,KAAc,EAA6B,EAAE;gBACtD,uFAAuF;gBACvF,mFAAmF;gBACnF,2FAA2F;gBAC3F,oBAAoB,EAAE,CAAA;gBACtB,gGAAgG;gBAChG,kGAAkG;gBAClG,yDAAyD;gBACzD,MAAM,KAAK,GAAG,MAAM;oBAClB,CAAC,CAAC,YAAY,KAAK,IAAI;wBACrB,CAAC,CAAC,WAAW,CAAC,YAAY,EAAE,KAAK,CAAC;wBAClC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;oBAChC,CAAC,CAAC,KAAK,CAAA;gBACT,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACxC,IAAI,CAAC;wBACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBACzC,CAAC;oBAAC,MAAM,CAAC;wBACP,8EAA8E;wBAC9E,QAAQ,GAAG,IAAI,CAAA;oBACjB,CAAC;gBACH,CAAC;gBACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;wBAAE,OAAO,EAAE,KAAK,EAAE,KAAkB,EAAE,CAAA;oBAC/D,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;gBAC7D,CAAC;gBACD,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;oBAAE,OAAO,EAAE,KAAK,EAAE,KAAkB,EAAE,CAAA;gBACpE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAA;YAClE,CAAC;YACD,oEAAoE;YACpE,8EAA8E;YAC9E,KAAK,EAAE,SAA2D;SACnE;QACD,UAAU,EAAE,MAAM;KACnB,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * How `t` tells a file field apart without carrying the code that validates one.
3
+ *
4
+ * File fields come from `@nifrajs/schema/form`. They mark their JSON Schema, and the schema object
5
+ * brings its own list and optional constructors along, so `t.array` and `t.optional` handle a file
6
+ * while an app that never takes an upload ships none of that code.
7
+ */
8
+ import { type ArrayOptions, type TSchema } from "@sinclair/typebox";
9
+ import type { NifraSchema } from "./adapter.js";
10
+ /** Marks the JSON Schema of a file field. Registered, so two copies of this package agree on it. */
11
+ export declare const FILE: unique symbol;
12
+ /** Carries the file-aware constructors on a file schema. Registered for the same reason. */
13
+ export declare const FILE_OPS: unique symbol;
14
+ export interface FileOps {
15
+ optional(schema: NifraSchema): NifraSchema;
16
+ array(item: NifraSchema, options?: ArrayOptions): NifraSchema;
17
+ }
18
+ /** Whether a JSON Schema is a file field, or a list of them. */
19
+ export declare function isFileSchema(schema: TSchema): boolean;
20
+ /** Throw when a constructor that validates JSON is handed a file field. */
21
+ export declare function refuseFile(where: string, schema: TSchema): void;
22
+ /**
23
+ * The file-aware constructors of a file schema, or `undefined` for any other schema. A schema whose
24
+ * JSON Schema says "file" but which lost its validator is refused: TypeBox cannot check a file.
25
+ */
26
+ export declare function fileOps(where: string, schema: NifraSchema): FileOps | undefined;
27
+ //# sourceMappingURL=file-kind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-kind.d.ts","sourceRoot":"","sources":["../src/file-kind.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,KAAK,YAAY,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,oGAAoG;AACpG,eAAO,MAAM,IAAI,EAAE,OAAO,MAAwC,CAAA;AAElE,4FAA4F;AAC5F,eAAO,MAAM,QAAQ,EAAE,OAAO,MAA2C,CAAA;AAEzE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAAA;IAC1C,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CAAA;CAC9D;AAID,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAMrD;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAI/D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,GAAG,SAAS,CAI/E"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * How `t` tells a file field apart without carrying the code that validates one.
3
+ *
4
+ * File fields come from `@nifrajs/schema/form`. They mark their JSON Schema, and the schema object
5
+ * brings its own list and optional constructors along, so `t.array` and `t.optional` handle a file
6
+ * while an app that never takes an upload ships none of that code.
7
+ */
8
+ import { Kind } from "@sinclair/typebox";
9
+ /** Marks the JSON Schema of a file field. Registered, so two copies of this package agree on it. */
10
+ export const FILE = Symbol.for("nifra.schema.file");
11
+ /** Carries the file-aware constructors on a file schema. Registered for the same reason. */
12
+ export const FILE_OPS = Symbol.for("nifra.schema.fileOps");
13
+ /** Whether a JSON Schema is a file field, or a list of them. */
14
+ export function isFileSchema(schema) {
15
+ const node = schema;
16
+ return (node[FILE] === true ||
17
+ (node[Kind] === "Array" && node.items !== undefined && isFileSchema(node.items)));
18
+ }
19
+ /** Throw when a constructor that validates JSON is handed a file field. */
20
+ export function refuseFile(where, schema) {
21
+ if (isFileSchema(schema)) {
22
+ throw new TypeError(`${where}: a file field belongs in t.form() from "@nifrajs/schema/form"`);
23
+ }
24
+ }
25
+ /**
26
+ * The file-aware constructors of a file schema, or `undefined` for any other schema. A schema whose
27
+ * JSON Schema says "file" but which lost its validator is refused: TypeBox cannot check a file.
28
+ */
29
+ export function fileOps(where, schema) {
30
+ const ops = schema[FILE_OPS];
31
+ if (ops === undefined)
32
+ refuseFile(where, schema.jsonSchema);
33
+ return ops;
34
+ }
35
+ //# sourceMappingURL=file-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-kind.js","sourceRoot":"","sources":["../src/file-kind.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAqB,IAAI,EAAgB,MAAM,mBAAmB,CAAA;AAGzE,oGAAoG;AACpG,MAAM,CAAC,MAAM,IAAI,GAAkB,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;AAElE,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAAkB,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;AASzE,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,MAAe;IAC1C,MAAM,IAAI,GAAG,MAA2B,CAAA;IACxC,OAAO,CACL,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI;QACnB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CACjF,CAAA;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,MAAe;IACvD,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CAAC,GAAG,KAAK,gEAAgE,CAAC,CAAA;IAC/F,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,MAAmB;IACxD,MAAM,GAAG,GAAI,MAA4C,CAAC,QAAQ,CAAC,CAAA;IACnE,IAAI,GAAG,KAAK,SAAS;QAAE,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAC3D,OAAO,GAAG,CAAA;AACZ,CAAC"}
package/dist/form.d.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `@nifrajs/schema/form` - `t` with file fields and `multipart/form-data` bodies.
3
+ *
4
+ * The `t` exported here is the builder from `@nifrajs/schema` plus `t.file` and `t.form`. A file is
5
+ * not JSON, so TypeBox cannot check one: `t.file` carries its own validator and marks its JSON
6
+ * Schema (`{ type: "string", format: "binary" }`) so the other constructors can tell a file field
7
+ * apart. `t.form` is the object constructor that understands those fields - it validates the text
8
+ * fields through TypeBox (with coercion, since every form value arrives as a string) and each file
9
+ * field through its own validator - and it reads the multipart body itself, so it is a route's
10
+ * `body` as is:
11
+ *
12
+ * ```ts
13
+ * import { t } from "@nifrajs/schema/form"
14
+ *
15
+ * app.post(
16
+ * "/avatars",
17
+ * {
18
+ * body: t.form({
19
+ * avatar: t.file({ maxBytes: 5_000_000, accept: ["image/png", "image/jpeg"] }),
20
+ * caption: t.optional(t.string({ maxLength: 200 })),
21
+ * }),
22
+ * bodyLimit: 6_000_000,
23
+ * },
24
+ * (c) => ({ name: c.body.avatar.name, type: c.body.avatar.type }),
25
+ * )
26
+ * ```
27
+ *
28
+ * A subpath on purpose: an app that takes no uploads carries none of this.
29
+ */
30
+ import { type MultipartLimits } from "@nifrajs/core/multipart";
31
+ import { type ArrayOptions, type Static, type TObject, type TSchema, type TUnsafe } from "@sinclair/typebox";
32
+ import { type NifraSchema } from "./adapter.js";
33
+ export interface FileOptions {
34
+ /** Largest accepted size in bytes. Omit for no bound beyond the route's `bodyLimit`. */
35
+ readonly maxBytes?: number;
36
+ /**
37
+ * Accepted types, matched against the file's leading bytes - never against the type the client
38
+ * claimed. Each entry is an exact type (`"image/png"`) or a subtype wildcard (`"image/*"`), and has
39
+ * to be one the bytes can prove: a type with no signature (`text/csv`, `image/svg+xml`) or a
40
+ * catch-all wildcard throws here rather than rejecting every upload later. With `accept` set, the
41
+ * validated file's `type` is the detected one.
42
+ */
43
+ readonly accept?: readonly string[];
44
+ }
45
+ /**
46
+ * The multipart limits (`maxFields`, `maxFiles`, `maxFieldBytes`, `maxFileBytes`) bound what the body
47
+ * may carry before any of it is validated; a request over one is answered `413`.
48
+ */
49
+ export interface FormOptions extends MultipartLimits {
50
+ /** Accept fields the form does not declare. Defaults to `false`: an undeclared field fails validation. */
51
+ readonly additionalProperties?: boolean;
52
+ readonly title?: string;
53
+ readonly description?: string;
54
+ }
55
+ declare function file(options?: FileOptions): NifraSchema<TUnsafe<File>>;
56
+ type Props = Record<string, NifraSchema>;
57
+ type FormShape<P extends Props> = TObject<{
58
+ [K in keyof P]: P[K]["jsonSchema"];
59
+ }>;
60
+ declare function form<P extends Props>(props: P, options?: FormOptions): NifraSchema<FormShape<P>>;
61
+ /** `t` from `@nifrajs/schema`, plus the two constructors a `multipart/form-data` body needs. */
62
+ export declare const t: {
63
+ /**
64
+ * An uploaded file, for a field of `t.form`. `maxBytes` bounds its size; `accept` allow-lists its
65
+ * type by the file's leading bytes (not the type the client claimed) and makes the validated
66
+ * file's `type` the detected one. Without `accept`, `file.type` is whatever the client sent.
67
+ * `file.name` is client-chosen either way - store under a key you generate.
68
+ *
69
+ * Wrap it in `t.optional()` for a file that may be absent, or `t.array()` for several under one
70
+ * name.
71
+ */
72
+ file: typeof file;
73
+ /**
74
+ * A `multipart/form-data` body: text fields and `t.file()` fields side by side. Text values are
75
+ * coerced (`"20"` -> `20`) because a form sends only strings, a name sent once satisfies a list
76
+ * field, and a required list nobody filled in is empty rather than missing. Undeclared fields are
77
+ * rejected unless `additionalProperties: true`.
78
+ *
79
+ * The result reads the multipart body itself - pass it as a route's `body`. JSON and urlencoded
80
+ * bodies still reach it, so a form with no required file accepts those too.
81
+ */
82
+ form: typeof form;
83
+ string: (options?: import("@sinclair/typebox").StringOptions) => NifraSchema<import("@sinclair/typebox").TString>;
84
+ number: (options?: import("@sinclair/typebox").NumberOptions) => NifraSchema<import("@sinclair/typebox").TNumber>;
85
+ integer: (options?: import("@sinclair/typebox").IntegerOptions) => NifraSchema<import("@sinclair/typebox").TInteger>;
86
+ boolean: () => NifraSchema<import("@sinclair/typebox").TBoolean>;
87
+ null: () => NifraSchema<import("@sinclair/typebox").TNull>;
88
+ literal: <const L extends import("@sinclair/typebox").TLiteralValue>(value: L) => NifraSchema<import("@sinclair/typebox").TLiteral<L>>;
89
+ object: <P extends {
90
+ [x: string]: NifraSchema;
91
+ }>(props: P, options?: import("@sinclair/typebox").ObjectOptions) => NifraSchema<TObject<{ [K in keyof P]: P[K]["jsonSchema"]; }>>;
92
+ looseObject: <P extends {
93
+ [x: string]: NifraSchema;
94
+ }>(props: P, options?: import("@sinclair/typebox").ObjectOptions) => NifraSchema<TObject<{ [K in keyof P]: P[K]["jsonSchema"]; }>>;
95
+ array: <T extends TSchema>(item: NifraSchema<T>, options?: ArrayOptions) => NifraSchema<import("@sinclair/typebox").TArray<T>>;
96
+ optional: <T extends TSchema>(schema: NifraSchema<T>) => NifraSchema<T extends import("@sinclair/typebox").TOptional<infer S extends TSchema> ? import("@sinclair/typebox").TOptional<S> : import("@sinclair/typebox").Ensure<import("@sinclair/typebox").TOptional<T>>>;
97
+ union: <const S extends readonly NifraSchema[]>(schemas: S) => NifraSchema<import("@sinclair/typebox").TUnion<{ -readonly [K in keyof S]: S[K]["jsonSchema"]; }>>;
98
+ record: <T extends TSchema>(value: NifraSchema<T>, options?: import("@sinclair/typebox").ObjectOptions) => NifraSchema<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, T>>;
99
+ paginated: <T extends TSchema>(item: NifraSchema<T>, options?: import("@sinclair/typebox").ObjectOptions) => NifraSchema<TObject<{
100
+ items: import("@sinclair/typebox").TArray<T>;
101
+ nextCursor: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TNull]>;
102
+ }>>;
103
+ pageQuery: (options?: {
104
+ maxLimit?: number;
105
+ }) => NifraSchema<TObject<{
106
+ cursor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
107
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
108
+ }>>;
109
+ query: <P extends {
110
+ [x: string]: NifraSchema;
111
+ }>(props: P, options?: import("@sinclair/typebox").ObjectOptions) => NifraSchema<TObject<{ [K in keyof P]: P[K]["jsonSchema"]; }>>;
112
+ cookies: <P extends {
113
+ [x: string]: NifraSchema;
114
+ }>(props: P, options?: import("@sinclair/typebox").ObjectOptions) => NifraSchema<TObject<{ [K in keyof P]: P[K]["jsonSchema"]; }>>;
115
+ deferred: <T extends TSchema>(inner: NifraSchema<T>) => NifraSchema<TUnsafe<import("./t.js").DeferredValue<Static<T>>>>;
116
+ declassified: <T extends TSchema>(reason: string, schema: NifraSchema<T>) => NifraSchema<T>;
117
+ };
118
+ export {};
119
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,yBAAyB,CAAA;AAO7E,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,MAAM,EACX,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,OAAO,EAEb,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAM5D,MAAM,WAAW,WAAW;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,0GAA0G;IAC1G,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAmFD,iBAAS,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAenE;AAgGD,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;AACxC,KAAK,SAAS,CAAC,CAAC,SAAS,KAAK,IAAI,OAAO,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;CAAE,CAAC,CAAA;AAqBjF,iBAAS,IAAI,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CA8EzF;AAED,gGAAgG;AAChG,eAAO,MAAM,CAAC;IAEZ;;;;;;;;OAQG;;IAEH;;;;;;;;OAQG;;;;;;;;;;;;;;;;oHArP2B,CAAC,yBAAyB,IAAI;;;;;;;gBAmC7C,CAAC;;;;;;;;;;;;;CAoNjB,CAAA"}
package/dist/form.js ADDED
@@ -0,0 +1,301 @@
1
+ /**
2
+ * `@nifrajs/schema/form` - `t` with file fields and `multipart/form-data` bodies.
3
+ *
4
+ * The `t` exported here is the builder from `@nifrajs/schema` plus `t.file` and `t.form`. A file is
5
+ * not JSON, so TypeBox cannot check one: `t.file` carries its own validator and marks its JSON
6
+ * Schema (`{ type: "string", format: "binary" }`) so the other constructors can tell a file field
7
+ * apart. `t.form` is the object constructor that understands those fields - it validates the text
8
+ * fields through TypeBox (with coercion, since every form value arrives as a string) and each file
9
+ * field through its own validator - and it reads the multipart body itself, so it is a route's
10
+ * `body` as is:
11
+ *
12
+ * ```ts
13
+ * import { t } from "@nifrajs/schema/form"
14
+ *
15
+ * app.post(
16
+ * "/avatars",
17
+ * {
18
+ * body: t.form({
19
+ * avatar: t.file({ maxBytes: 5_000_000, accept: ["image/png", "image/jpeg"] }),
20
+ * caption: t.optional(t.string({ maxLength: 200 })),
21
+ * }),
22
+ * bodyLimit: 6_000_000,
23
+ * },
24
+ * (c) => ({ name: c.body.avatar.name, type: c.body.avatar.type }),
25
+ * )
26
+ * ```
27
+ *
28
+ * A subpath on purpose: an app that takes no uploads carries none of this.
29
+ */
30
+ import { multipartBody } from "@nifrajs/core/multipart";
31
+ import { DETECTABLE_MIME_TYPES, detectFileType, FILE_TYPE_PREFIX_BYTES, } from "@nifrajs/uploads/detect";
32
+ import { Kind, OptionalKind, Type, } from "@sinclair/typebox";
33
+ import { fromTypeBox } from "./adapter.js";
34
+ import { FILE, FILE_OPS, isFileSchema } from "./file-kind.js";
35
+ import { t as base } from "./t.js";
36
+ const fail = (message) => ({ issues: [{ message }] });
37
+ const matches = (mime, pattern) => pattern === mime || (pattern.endsWith("/*") && mime.startsWith(pattern.slice(0, -1)));
38
+ function custom(jsonSchema, validate) {
39
+ return {
40
+ "~standard": {
41
+ version: 1,
42
+ vendor: "nifra",
43
+ validate: validate,
44
+ // Phantom, as in `fromTypeBox`: read by `InferOutput` at compile time only.
45
+ types: undefined,
46
+ },
47
+ jsonSchema,
48
+ };
49
+ }
50
+ /** A file schema: `t.array` and `t.optional` find their file-aware versions on it. */
51
+ function fileKind(jsonSchema, validate) {
52
+ return Object.defineProperty(custom(jsonSchema, validate), FILE_OPS, { value: OPS });
53
+ }
54
+ function assertCount(value, name) {
55
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
56
+ throw new RangeError(`${name} must be a non-negative safe integer`);
57
+ }
58
+ }
59
+ function acceptList(accept) {
60
+ if (!Array.isArray(accept) || accept.length === 0) {
61
+ throw new TypeError("t.file: accept must list at least one type");
62
+ }
63
+ for (const pattern of accept) {
64
+ if (typeof pattern !== "string" ||
65
+ !DETECTABLE_MIME_TYPES.some((mime) => matches(mime, pattern))) {
66
+ throw new TypeError(`t.file: accept ${JSON.stringify(pattern)} cannot be checked against a file's bytes. ` +
67
+ `Use one of ${DETECTABLE_MIME_TYPES.join(", ")}, or a wildcard over them such as "image/*"`);
68
+ }
69
+ }
70
+ return [...accept];
71
+ }
72
+ /**
73
+ * The same file under a different `type`. The result is verified rather than trusted: a runtime
74
+ * that ignores the requested type must not hand the handler a file still labelled by the client.
75
+ */
76
+ function typedFile(file, type) {
77
+ const options = { type, lastModified: file.lastModified };
78
+ let typed = new File([file], file.name, options);
79
+ if (typed.name !== file.name || typed.type !== type) {
80
+ typed = new File([file, ""], file.name, options);
81
+ }
82
+ if (typed.name !== file.name || typed.type !== type || typed.size !== file.size) {
83
+ throw new Error("t.file: this runtime did not apply a file type");
84
+ }
85
+ return typed;
86
+ }
87
+ async function sniff(file, accept) {
88
+ // Only the leading bytes are read, whatever the file's size.
89
+ const source = file;
90
+ const head = new Uint8Array(await source.slice(0, FILE_TYPE_PREFIX_BYTES).arrayBuffer());
91
+ const detected = detectFileType(head);
92
+ if (detected === null)
93
+ return fail("File type could not be recognized");
94
+ if (!accept.some((pattern) => matches(detected.mime, pattern))) {
95
+ return fail(`File type ${detected.mime} is not accepted`);
96
+ }
97
+ return { value: file.type === detected.mime ? file : typedFile(file, detected.mime) };
98
+ }
99
+ function file(options = {}) {
100
+ const { maxBytes } = options;
101
+ assertCount(maxBytes, "t.file: maxBytes");
102
+ const accept = options.accept === undefined ? undefined : acceptList(options.accept);
103
+ return fileKind(Type.Unsafe({ type: "string", format: "binary", [FILE]: true }), (value) => {
104
+ if (!(value instanceof File))
105
+ return fail("Expected a file");
106
+ if (maxBytes !== undefined && value.size > maxBytes) {
107
+ return fail(`File is larger than ${maxBytes} bytes`);
108
+ }
109
+ // Reading bytes is the only asynchronous step, so a file with no `accept` validates in place.
110
+ return accept === undefined ? { value } : sniff(value, accept);
111
+ });
112
+ }
113
+ function optionalFile(schema) {
114
+ const inner = schema["~standard"].validate;
115
+ return fileKind(Type.Optional(schema.jsonSchema), (value) => value === undefined ? { value } : inner(value));
116
+ }
117
+ /** Run one validator per entry; synchronous unless an entry has bytes to read. */
118
+ function validateEach(count, run, path, issues, keep) {
119
+ // Results are gathered by position and reported in order, so the issue list does not depend on
120
+ // which read finished first.
121
+ const results = new Array(count);
122
+ const pending = [];
123
+ for (let index = 0; index < count; index++) {
124
+ const result = run(index);
125
+ if (result instanceof Promise) {
126
+ pending.push(result.then((settled) => {
127
+ results[index] = settled;
128
+ }));
129
+ }
130
+ else {
131
+ results[index] = result;
132
+ }
133
+ }
134
+ const report = () => {
135
+ for (let index = 0; index < count; index++) {
136
+ const result = results[index];
137
+ if (result.issues === undefined) {
138
+ keep(index, result.value);
139
+ continue;
140
+ }
141
+ for (const issue of result.issues) {
142
+ issues.push({ message: issue.message, path: [path(index), ...(issue.path ?? [])] });
143
+ }
144
+ }
145
+ };
146
+ if (pending.length === 0) {
147
+ report();
148
+ return undefined;
149
+ }
150
+ return Promise.all(pending).then(report);
151
+ }
152
+ const UNSUPPORTED_ARRAY_OPTIONS = ["uniqueItems", "contains", "minContains", "maxContains"];
153
+ function fileArray(item, options) {
154
+ if (item.jsonSchema[FILE] !== true) {
155
+ throw new TypeError("t.array: a list of files cannot be nested inside another list");
156
+ }
157
+ for (const name of UNSUPPORTED_ARRAY_OPTIONS) {
158
+ if (options?.[name] !== undefined) {
159
+ throw new TypeError(`t.array: ${name} is not supported for a list of files`);
160
+ }
161
+ }
162
+ const minItems = options?.minItems;
163
+ const maxItems = options?.maxItems;
164
+ assertCount(minItems, "t.array: minItems");
165
+ assertCount(maxItems, "t.array: maxItems");
166
+ const inner = item["~standard"].validate;
167
+ return fileKind(Type.Array(item.jsonSchema, options), (value) => {
168
+ if (!Array.isArray(value))
169
+ return fail("Expected a list of files");
170
+ if (minItems !== undefined && value.length < minItems) {
171
+ return fail(`Expected at least ${minItems} file(s)`);
172
+ }
173
+ if (maxItems !== undefined && value.length > maxItems) {
174
+ return fail(`Expected at most ${maxItems} file(s)`);
175
+ }
176
+ const issues = [];
177
+ const output = new Array(value.length);
178
+ const finish = () => issues.length > 0 ? { issues } : { value: output };
179
+ const pending = validateEach(value.length, (index) => inner(value[index]), (index) => index, issues, (index, entry) => {
180
+ output[index] = entry;
181
+ });
182
+ return pending === undefined ? finish() : pending.then(finish);
183
+ });
184
+ }
185
+ // Function declarations, so the table can sit above the constructors that hand it out.
186
+ const OPS = { optional: optionalFile, array: fileArray };
187
+ /**
188
+ * What a file field was sent, with the inputs nobody filled in taken out. A browser submits such an
189
+ * input as an empty file, which never reaches a schema; some clients send an empty text part in its
190
+ * place, and that names no file either.
191
+ */
192
+ function given(entry) {
193
+ if (entry === "")
194
+ return undefined;
195
+ if (!Array.isArray(entry) || !entry.includes(""))
196
+ return entry;
197
+ const filled = entry.filter((value) => value !== "");
198
+ return filled.length === 0 ? undefined : filled;
199
+ }
200
+ function form(props, options) {
201
+ const files = [];
202
+ const fileKeys = new Set();
203
+ /** Required text lists: a list nobody filled in is sent as no field at all, which means empty. */
204
+ const lists = [];
205
+ const text = {};
206
+ const all = {};
207
+ for (const [key, schema] of Object.entries(props)) {
208
+ // Assigning this key to an ordinary object would replace its prototype instead of adding a field.
209
+ if (key === "__proto__")
210
+ throw new TypeError('t.form: "__proto__" cannot be a field name');
211
+ const node = schema.jsonSchema;
212
+ const marks = node;
213
+ const list = marks[Kind] === "Array";
214
+ const required = marks[OptionalKind] !== "Optional";
215
+ all[key] = node;
216
+ if (isFileSchema(node)) {
217
+ files.push({ key, validate: schema["~standard"].validate, list, required });
218
+ fileKeys.add(key);
219
+ }
220
+ else {
221
+ text[key] = node;
222
+ if (list && required)
223
+ lists.push(key);
224
+ }
225
+ }
226
+ const objectOptions = {
227
+ ...(options?.title === undefined ? {} : { title: options.title }),
228
+ ...(options?.description === undefined ? {} : { description: options.description }),
229
+ additionalProperties: options?.additionalProperties === true,
230
+ };
231
+ // `fromTypeBox` validates synchronously; only a file with bytes to read makes a form asynchronous.
232
+ const validateText = fromTypeBox(Type.Object(text, objectOptions), { coerce: true })["~standard"]
233
+ .validate;
234
+ const validate = (value) => {
235
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
236
+ return fail("Expected form fields");
237
+ }
238
+ const input = value;
239
+ // Validated on a copy with no prototype: coercion writes into the object it is given, and a
240
+ // field named like an `Object.prototype` member has to be an ordinary field here.
241
+ const fields = Object.create(null);
242
+ for (const key of Object.keys(input)) {
243
+ if (!fileKeys.has(key))
244
+ fields[key] = input[key];
245
+ }
246
+ for (const key of lists) {
247
+ if (!(key in fields))
248
+ fields[key] = [];
249
+ }
250
+ const checked = validateText(fields);
251
+ const issues = checked.issues === undefined ? [] : [...checked.issues];
252
+ const output = Object.create(null);
253
+ if (checked.issues === undefined) {
254
+ for (const key of Object.keys(checked.value))
255
+ output[key] = checked.value[key];
256
+ }
257
+ const finish = () => issues.length > 0 ? { issues } : { value: output };
258
+ const pending = validateEach(files.length, (index) => {
259
+ const field = files[index];
260
+ const entry = Object.hasOwn(input, field.key) ? given(input[field.key]) : undefined;
261
+ if (!field.list)
262
+ return field.validate(entry);
263
+ // One file under a repeatable name is a list of one; none at all is an empty list.
264
+ if (entry === undefined)
265
+ return field.validate(field.required ? [] : undefined);
266
+ return field.validate(Array.isArray(entry) ? entry : [entry]);
267
+ }, (index) => files[index].key, issues, (index, entry) => {
268
+ if (entry !== undefined)
269
+ output[files[index].key] = entry;
270
+ });
271
+ return pending === undefined ? finish() : pending.then(finish);
272
+ };
273
+ // Branded here, so the schema is a route's `body` as is: a form that did not read multipart
274
+ // bodies would refuse every request it exists to accept.
275
+ return multipartBody(custom(Type.Object(all, objectOptions), validate), options);
276
+ }
277
+ /** `t` from `@nifrajs/schema`, plus the two constructors a `multipart/form-data` body needs. */
278
+ export const t = {
279
+ ...base,
280
+ /**
281
+ * An uploaded file, for a field of `t.form`. `maxBytes` bounds its size; `accept` allow-lists its
282
+ * type by the file's leading bytes (not the type the client claimed) and makes the validated
283
+ * file's `type` the detected one. Without `accept`, `file.type` is whatever the client sent.
284
+ * `file.name` is client-chosen either way - store under a key you generate.
285
+ *
286
+ * Wrap it in `t.optional()` for a file that may be absent, or `t.array()` for several under one
287
+ * name.
288
+ */
289
+ file,
290
+ /**
291
+ * A `multipart/form-data` body: text fields and `t.file()` fields side by side. Text values are
292
+ * coerced (`"20"` -> `20`) because a form sends only strings, a name sent once satisfies a list
293
+ * field, and a required list nobody filled in is empty rather than missing. Undeclared fields are
294
+ * rejected unless `additionalProperties: true`.
295
+ *
296
+ * The result reads the multipart body itself - pass it as a route's `body`. JSON and urlencoded
297
+ * bodies still reach it, so a form with no required file accepts those too.
298
+ */
299
+ form,
300
+ };
301
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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"}