@nifrajs/schema 0.1.0-alpha.1

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/src/t.ts ADDED
@@ -0,0 +1,70 @@
1
+ import {
2
+ type ArrayOptions,
3
+ type IntegerOptions,
4
+ type NumberOptions,
5
+ type ObjectOptions,
6
+ type StringOptions,
7
+ type TLiteralValue,
8
+ type TSchema,
9
+ type TUnion,
10
+ Type,
11
+ } from "@sinclair/typebox"
12
+ import { fromTypeBox, type NifraSchema } from "./adapter.ts"
13
+
14
+ type Props = Record<string, NifraSchema>
15
+
16
+ /** Pull each property's raw TypeBox schema out of its `NifraSchema` wrapper. */
17
+ function unwrap<P extends Props>(props: P): { [K in keyof P]: P[K]["jsonSchema"] } {
18
+ const out: Record<string, TSchema> = {}
19
+ // Object.entries → own enumerable string keys only (no prototype walk).
20
+ for (const [key, schema] of Object.entries(props)) out[key] = schema.jsonSchema
21
+ return out as { [K in keyof P]: P[K]["jsonSchema"] }
22
+ }
23
+
24
+ /**
25
+ * The built-in schema builder. Each constructor returns a `NifraSchema` — a
26
+ * Standard Schema whose validated output type flows into `c.body`/`c.query`, and
27
+ * whose `jsonSchema` powers `toOpenAPI`. Options (min/max, length, pattern, …)
28
+ * pass straight through to TypeBox and so become JSON Schema constraints.
29
+ *
30
+ * Composite constructors are generic over the *inner* TypeBox schema `T extends
31
+ * TSchema` (not over `NifraSchema`): reading `.jsonSchema` off a value typed only as
32
+ * `NifraSchema` would erase to the `TSchema` constraint and collapse the output type
33
+ * to `unknown`. Capturing `T` keeps `Static<T>` precise (`string[]`, not
34
+ * `unknown[]`).
35
+ */
36
+ export const t = {
37
+ string: (options?: StringOptions) => fromTypeBox(Type.String(options)),
38
+ number: (options?: NumberOptions) => fromTypeBox(Type.Number(options)),
39
+ integer: (options?: IntegerOptions) => fromTypeBox(Type.Integer(options)),
40
+ boolean: () => fromTypeBox(Type.Boolean()),
41
+ null: () => fromTypeBox(Type.Null()),
42
+ literal: <const L extends TLiteralValue>(value: L) => fromTypeBox(Type.Literal(value)),
43
+
44
+ // `t.object` REJECTS unknown fields by default (`additionalProperties: false`) — the trust-boundary
45
+ // rule. A body with extra keys fails validation (a 400), so `c.body` never carries attacker-supplied
46
+ // properties (no mass-assignment). Use `t.looseObject` (or pass `{ additionalProperties: true }`) to
47
+ // opt into an open object; an explicit `options.additionalProperties` always wins over the default.
48
+ object: <P extends Props>(props: P, options?: ObjectOptions) =>
49
+ fromTypeBox(Type.Object(unwrap(props), { additionalProperties: false, ...options })),
50
+ /** Like `t.object` but ACCEPTS (and passes through) unknown fields — the explicit opt-out of the
51
+ * strict default. Prefer `t.object` unless you genuinely need an open object. */
52
+ looseObject: <P extends Props>(props: P, options?: ObjectOptions) =>
53
+ fromTypeBox(Type.Object(unwrap(props), { additionalProperties: true, ...options })),
54
+ array: <T extends TSchema>(item: NifraSchema<T>, options?: ArrayOptions) =>
55
+ fromTypeBox(Type.Array(item.jsonSchema, options)),
56
+ /** Marks a property optional inside `t.object`; standalone it is `T | undefined`. */
57
+ optional: <T extends TSchema>(schema: NifraSchema<T>) =>
58
+ fromTypeBox(Type.Optional(schema.jsonSchema)),
59
+ // `const S` captures the argument as a tuple; the explicit return type then maps
60
+ // over that captured tuple (`S[K]["jsonSchema"]`) so the union's `Static` is
61
+ // `A | B`, not `unknown` — the value-level `.map` can't preserve per-element
62
+ // types, so the output type is derived from `S` and the result cast to match
63
+ // (order/length are preserved by `map`, so the tuple shape is sound).
64
+ union: <const S extends readonly NifraSchema[]>(schemas: S) =>
65
+ fromTypeBox(
66
+ Type.Union((schemas as readonly NifraSchema[]).map((schema) => schema.jsonSchema)),
67
+ ) as NifraSchema<TUnion<{ -readonly [K in keyof S]: S[K]["jsonSchema"] }>>,
68
+ record: <T extends TSchema>(value: NifraSchema<T>, options?: ObjectOptions) =>
69
+ fromTypeBox(Type.Record(Type.String(), value.jsonSchema, options)),
70
+ } as const