@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nifra Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @nifrajs/schema
2
+
3
+ The optional, batteries-included schema builder for [nifra](../../README.md): `t` is
4
+ TypeBox-backed, so it **validates** at the boundary *and* gives you **OpenAPI for
5
+ free** (a TypeBox schema is a JSON Schema). Plus `toOpenAPI(contract | app)`.
6
+
7
+ ```sh
8
+ bun add @nifrajs/schema
9
+ ```
10
+
11
+ ```ts
12
+ import { server } from "@nifrajs/core"
13
+ import { t, toOpenAPI } from "@nifrajs/schema"
14
+
15
+ const app = server().post("/users", { body: t.object({ name: t.string(), age: t.integer() }) }, (c) => ({
16
+ id: "u1",
17
+ name: c.body.name, // typed + validated
18
+ }))
19
+
20
+ const openapi = toOpenAPI(app) // OpenAPI 3.1
21
+ ```
22
+
23
+ - **`t` builder** — `string`/`number`/`integer`/`boolean`/`null`/`literal`/`object`/
24
+ `array`/`optional`/`union`/`record`, with options (`min`/`max`, `pattern`, `format`,
25
+ …) that become JSON Schema constraints. Validators are compiled (fast).
26
+ - **Validating string formats** — `email`/`uuid`/`date-time`/`date`/`time`/`uri`/`ipv4`
27
+ validate *and* annotate; register more with `registerFormat`.
28
+ - **`toOpenAPI`** — richest from a contract (it carries `response` schemas + op names →
29
+ `operationId`s); also works on a live app. Routes using a BYO Standard Schema are
30
+ emitted without a detailed schema (Standard Schema exposes no JSON Schema).
31
+
32
+ ### `toOpenAPI` coverage
33
+
34
+ It emits `paths`, `parameters` (path + object query), `requestBody`, and `responses`, plus:
35
+
36
+ - **`servers`**, **top-level `tags`**, and an info **`description`** (document options).
37
+ - **`securitySchemes`** → `components.securitySchemes`, a document-wide **`security`**, and per-operation
38
+ `security` (`[]` marks an operation explicitly public).
39
+ - **Non-200 responses** and **non-JSON content** — a contract op's `responses` map declares extra status
40
+ codes; `requestContentType` / `responseContentType` set media types other than `application/json`.
41
+ - **`$ref` reuse** — a schema with a `$id` (`t.object({…}, { $id: "User" })`) is hoisted into
42
+ `components.schemas` once and referenced by `$ref` everywhere it's used.
43
+ - Per-operation **`summary`**, **`description`**, **`tags`**, **`deprecated`** — declared on the contract op.
44
+
45
+ A **contract** is richest (its ops carry all of the above); in **app** mode a route's response is a
46
+ generic `200` with no schema — declare a `defineContract` with `response` schemas, or pass
47
+ `options.operations` (keyed by `"METHOD /path"`) to enrich app routes. Routes using a BYO Standard Schema
48
+ are still emitted without body/response detail (Standard Schema exposes no JSON Schema).
49
+
50
+ `@nifrajs/core` is a peer dependency. ESM-only. MIT.
@@ -0,0 +1,33 @@
1
+ import type { StandardSchemaV1 } from "@nifrajs/core";
2
+ import type { Static, TSchema } from "@sinclair/typebox";
3
+ /**
4
+ * A `t` schema. It is a Standard Schema (so any nifra route validates it with no
5
+ * special-casing) whose raw TypeBox definition stays reachable as `jsonSchema` —
6
+ * and because a TypeBox schema *is* a JSON Schema, that field is exactly what lets
7
+ * `toOpenAPI` emit a real request/response schema for the route. BYO Standard
8
+ * Schemas (zod/valibot/arktype) validate too but expose no JSON Schema, so only
9
+ * `t`-based routes get full OpenAPI.
10
+ */
11
+ export type NifraSchema<T extends TSchema = TSchema> = StandardSchemaV1<Static<T>, Static<T>> & {
12
+ readonly jsonSchema: T;
13
+ };
14
+ /**
15
+ * Wrap a TypeBox schema as a `NifraSchema`.
16
+ *
17
+ * **Validation is fast where it can be, and works everywhere.** The first `validate`
18
+ * builds a compiled `TypeCompiler` validator (codegen via `new Function`) and memoizes
19
+ * it — composing schemas (`t.object({ a: t.string() })`) never compiles the inner
20
+ * pieces; only a schema actually used to validate a request pays the one-time codegen
21
+ * cost. On Bun and Node this is the only path and it is untouched.
22
+ *
23
+ * Edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy) **forbid dynamic code
24
+ * generation**, so that first compile throws there. We catch it once per schema and
25
+ * fall back to TypeBox's eval-free `Value` checker — same semantics (and same global
26
+ * `FormatRegistry`, so `t.string({ format })` behaves identically), no `new Function`.
27
+ * The branch is decided once and memoized, so the hot path is a single check either way.
28
+ *
29
+ * Exposed (not just used internally by `t`) so a TypeBox schema authored directly can
30
+ * be adapted without leaving the Standard Schema world.
31
+ */
32
+ export declare function fromTypeBox<T extends TSchema>(schema: T): NifraSchema<T>;
33
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiC,gBAAgB,EAAiB,MAAM,eAAe,CAAA;AACnG,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAIxD;;;;;;;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;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CA6BxE"}
@@ -0,0 +1,66 @@
1
+ import { TypeCompiler } from "@sinclair/typebox/compiler";
2
+ import { Value } from "@sinclair/typebox/value";
3
+ /**
4
+ * Map a TypeBox validation error to a Standard Schema issue. TypeBox reports JSON
5
+ * Pointers (`"/name"`, `"/items/0"`); Standard Schema wants a segment array (`""` is
6
+ * the document root → no path). The compiled checker and the eval-free `Value`
7
+ * checker emit the same error shape, so both validation paths share this.
8
+ */
9
+ function toIssue(error) {
10
+ return {
11
+ message: error.message,
12
+ path: error.path === "" ? undefined : error.path.slice(1).split("/"),
13
+ };
14
+ }
15
+ /**
16
+ * Wrap a TypeBox schema as a `NifraSchema`.
17
+ *
18
+ * **Validation is fast where it can be, and works everywhere.** The first `validate`
19
+ * builds a compiled `TypeCompiler` validator (codegen via `new Function`) and memoizes
20
+ * it — composing schemas (`t.object({ a: t.string() })`) never compiles the inner
21
+ * pieces; only a schema actually used to validate a request pays the one-time codegen
22
+ * cost. On Bun and Node this is the only path and it is untouched.
23
+ *
24
+ * Edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy) **forbid dynamic code
25
+ * generation**, so that first compile throws there. We catch it once per schema and
26
+ * fall back to TypeBox's eval-free `Value` checker — same semantics (and same global
27
+ * `FormatRegistry`, so `t.string({ format })` behaves identically), no `new Function`.
28
+ * The branch is decided once and memoized, so the hot path is a single check either way.
29
+ *
30
+ * Exposed (not just used internally by `t`) so a TypeBox schema authored directly can
31
+ * be adapted without leaving the Standard Schema world.
32
+ */
33
+ export function fromTypeBox(schema) {
34
+ let compiled;
35
+ let evalFree = false;
36
+ return {
37
+ "~standard": {
38
+ version: 1,
39
+ vendor: "nifra",
40
+ validate: (value) => {
41
+ if (compiled === undefined && !evalFree) {
42
+ try {
43
+ compiled = TypeCompiler.Compile(schema);
44
+ }
45
+ catch {
46
+ // Dynamic codegen disallowed (edge): take the eval-free path for this schema.
47
+ evalFree = true;
48
+ }
49
+ }
50
+ if (compiled !== undefined) {
51
+ if (compiled.Check(value))
52
+ return { value };
53
+ return { issues: [...compiled.Errors(value)].map(toIssue) };
54
+ }
55
+ if (Value.Check(schema, value))
56
+ return { value };
57
+ return { issues: [...Value.Errors(schema, value)].map(toIssue) };
58
+ },
59
+ // Phantom: `types` carries no runtime value; this cast supplies the
60
+ // compile-time `Static<T>` that nifra's `InferOutput` reads to type `c.body`.
61
+ types: undefined,
62
+ },
63
+ jsonSchema: schema,
64
+ };
65
+ }
66
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAc/C;;;;;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;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CAAoB,MAAS;IACtD,IAAI,QAAkC,CAAA;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,OAAO;QACL,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,OAAO;YACf,QAAQ,EAAE,CAAC,KAAc,EAA6B,EAAE;gBACtD,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,CAAA;oBAC3C,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,CAAA;gBAChD,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"}
@@ -0,0 +1,3 @@
1
+ /** Register (or override) a string format usable as `t.string({ format: name })`. */
2
+ export declare function registerFormat(name: string, validate: (value: string) => boolean): void;
3
+ //# sourceMappingURL=formats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAyBA,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAEvF"}
@@ -0,0 +1,32 @@
1
+ import { FormatRegistry } from "@sinclair/typebox";
2
+ /**
3
+ * Standard JSON Schema string formats, registered with TypeBox so `t.string({
4
+ * format })` both **validates** and **annotates** the JSON Schema (the latter feeds
5
+ * OpenAPI). This registration is the reason it works at all: TypeBox treats an
6
+ * *unregistered* format as a hard failure, so without it `t.string({ format:
7
+ * "email" })` would reject every value.
8
+ *
9
+ * Patterns are pragmatic (not full RFC grammars) but reject obvious garbage. Need a
10
+ * stricter rule or a format not listed here (e.g. `ipv6`, `hostname`)? Register it
11
+ * with {@link registerFormat}. Importing this module performs the registration as a
12
+ * one-time side effect; the `Has` guard means an app that registered its own
13
+ * validator first wins.
14
+ */
15
+ const DEFAULT_FORMATS = {
16
+ email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
17
+ uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
18
+ "date-time": /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/,
19
+ date: /^\d{4}-\d{2}-\d{2}$/,
20
+ time: /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})?$/,
21
+ uri: /^[a-z][a-z0-9+.-]*:\S+$/i,
22
+ ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$/,
23
+ };
24
+ /** Register (or override) a string format usable as `t.string({ format: name })`. */
25
+ export function registerFormat(name, validate) {
26
+ FormatRegistry.Set(name, validate);
27
+ }
28
+ for (const [name, pattern] of Object.entries(DEFAULT_FORMATS)) {
29
+ if (!FormatRegistry.Has(name))
30
+ FormatRegistry.Set(name, (value) => pattern.test(value));
31
+ }
32
+ //# sourceMappingURL=formats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formats.js","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD;;;;;;;;;;;;GAYG;AACH,MAAM,eAAe,GAAqC;IACxD,KAAK,EAAE,4BAA4B;IACnC,IAAI,EAAE,iEAAiE;IACvE,WAAW,EAAE,4EAA4E;IACzF,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,wDAAwD;IAC9D,GAAG,EAAE,0BAA0B;IAC/B,IAAI,EAAE,qEAAqE;CAC5E,CAAA;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,QAAoC;IAC/E,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AACpC,CAAC;AAED,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;IAC9D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AACzF,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@nifrajs/schema` — the optional, batteries-included schema builder for nifra.
3
+ *
4
+ * `t` is TypeBox-backed: validators are compiled (fast) and every schema is a JSON
5
+ * Schema (free OpenAPI). It implements Standard Schema, so `@nifrajs/core` validates
6
+ * it through the exact same path as any BYO library — `@nifrajs/core` never depends on
7
+ * a validator; this package is the opt-in default.
8
+ */
9
+ export { fromTypeBox, type NifraSchema } from "./adapter.js";
10
+ export { registerFormat } from "./formats.js";
11
+ export { type OpenAPIDocument, type OpenAPIInfo, toOpenAPI } from "./openapi.js";
12
+ export { t } from "./t.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAG5D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@nifrajs/schema` — the optional, batteries-included schema builder for nifra.
3
+ *
4
+ * `t` is TypeBox-backed: validators are compiled (fast) and every schema is a JSON
5
+ * Schema (free OpenAPI). It implements Standard Schema, so `@nifrajs/core` validates
6
+ * it through the exact same path as any BYO library — `@nifrajs/core` never depends on
7
+ * a validator; this package is the opt-in default.
8
+ */
9
+ export { fromTypeBox } from "./adapter.js";
10
+ // Re-exporting evaluates ./formats.ts, registering the standard string formats so
11
+ // `t.string({ format })` validates instead of rejecting everything.
12
+ export { registerFormat } from "./formats.js";
13
+ export { toOpenAPI } from "./openapi.js";
14
+ export { t } from "./t.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAoB,MAAM,cAAc,CAAA;AAC5D,kFAAkF;AAClF,oEAAoE;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAA0C,SAAS,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA"}
@@ -0,0 +1,92 @@
1
+ import type { ContractShape, Server } from "@nifrajs/core";
2
+ /**
3
+ * OpenAPI 3.1 generation. We model a practical slice of the spec — enough to feed Swagger UI / codegen
4
+ * and to validate structurally: paths, parameters, request bodies, responses (incl. non-200 and
5
+ * non-JSON), tags, security, servers, and `$ref` reuse via `components.schemas`.
6
+ *
7
+ * Schemas carry full detail only for `t`/TypeBox inputs (they expose a JSON Schema); a BYO Standard
8
+ * Schema validates at runtime but exposes no JSON Schema, so its route is emitted without body/response
9
+ * detail. A **contract** is richest — its operations carry `response`, `tags`, `security`, additional
10
+ * `responses`, etc.; an **app** emits request shapes + a generic `200` (an inline handler has no
11
+ * response *schema* to serialize), enrichable via `options.operations`.
12
+ */
13
+ export interface OpenAPIInfo {
14
+ readonly title: string;
15
+ readonly version: string;
16
+ readonly description?: string;
17
+ }
18
+ export interface OpenAPIServer {
19
+ readonly url: string;
20
+ readonly description?: string;
21
+ }
22
+ export interface OpenAPITag {
23
+ readonly name: string;
24
+ readonly description?: string;
25
+ }
26
+ /** A document-wide / per-operation security requirement: scheme name → required scopes. */
27
+ export type SecurityRequirement = Readonly<Record<string, readonly string[]>>;
28
+ export interface ToOpenAPIOptions {
29
+ readonly title?: string;
30
+ readonly version?: string;
31
+ readonly description?: string;
32
+ /** Server URLs the API is served from (OpenAPI `servers`). */
33
+ readonly servers?: readonly OpenAPIServer[];
34
+ /** Tag definitions (OpenAPI top-level `tags`) — names referenced by an operation's `tags`. */
35
+ readonly tags?: readonly OpenAPITag[];
36
+ /** Reusable security schemes → `components.securitySchemes` (e.g. `{ bearer: { type: "http", scheme: "bearer" } }`). */
37
+ readonly securitySchemes?: Readonly<Record<string, Record<string, unknown>>>;
38
+ /** Document-wide security requirement; a per-operation `security` (incl. `[]` for public) overrides it. */
39
+ readonly security?: readonly SecurityRequirement[];
40
+ /**
41
+ * Per-operation overrides, shallow-merged over the generated operation. Keyed by `operationId`
42
+ * (contract op name) or `"METHOD /path"` (e.g. `"GET /users/:id"`). The escape hatch for detail that
43
+ * can't be introspected — richer response bodies, examples, app-route tags/security.
44
+ */
45
+ readonly operations?: Readonly<Record<string, Record<string, unknown>>>;
46
+ }
47
+ type JsonSchema = Record<string, unknown>;
48
+ interface OpenAPIParameter {
49
+ readonly name: string;
50
+ readonly in: "path" | "query";
51
+ readonly required: boolean;
52
+ readonly schema: JsonSchema;
53
+ }
54
+ interface OpenAPIMediaType {
55
+ readonly schema: JsonSchema;
56
+ }
57
+ interface OpenAPIRequestBody {
58
+ readonly required: boolean;
59
+ readonly content: Record<string, OpenAPIMediaType>;
60
+ }
61
+ interface OpenAPIResponse {
62
+ description: string;
63
+ content?: Record<string, OpenAPIMediaType>;
64
+ }
65
+ interface OpenAPIOperation {
66
+ operationId?: string;
67
+ summary?: string;
68
+ description?: string;
69
+ tags?: readonly string[];
70
+ deprecated?: boolean;
71
+ security?: readonly SecurityRequirement[];
72
+ parameters?: OpenAPIParameter[];
73
+ requestBody?: OpenAPIRequestBody;
74
+ responses: Record<string, OpenAPIResponse>;
75
+ }
76
+ interface OpenAPIComponents {
77
+ schemas?: Record<string, JsonSchema>;
78
+ securitySchemes?: Readonly<Record<string, Record<string, unknown>>>;
79
+ }
80
+ export interface OpenAPIDocument {
81
+ readonly openapi: "3.1.0";
82
+ readonly info: OpenAPIInfo;
83
+ readonly paths: Record<string, Record<string, OpenAPIOperation>>;
84
+ readonly servers?: readonly OpenAPIServer[];
85
+ readonly tags?: readonly OpenAPITag[];
86
+ readonly security?: readonly SecurityRequirement[];
87
+ readonly components?: OpenAPIComponents;
88
+ }
89
+ /** Generate an OpenAPI 3.1 document from a contract or a running app. See the module doc for the detail model. */
90
+ export declare function toOpenAPI(input: ContractShape | Server, options?: ToOpenAPIOptions): OpenAPIDocument;
91
+ export {};
92
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ function hasJsonSchema(schema) {
2
+ return ("jsonSchema" in schema &&
3
+ schema.jsonSchema !== undefined &&
4
+ schema.jsonSchema !== null);
5
+ }
6
+ /**
7
+ * Extract clean JSON Schema from a schema, or `undefined` for a BYO Standard Schema with no JSON
8
+ * Schema. The `JSON` round-trip strips TypeBox's Symbol-keyed metadata; a string `$id` survives it and
9
+ * drives `$ref` reuse.
10
+ */
11
+ function toJsonSchema(schema) {
12
+ if (!hasJsonSchema(schema))
13
+ return undefined;
14
+ return JSON.parse(JSON.stringify(schema.jsonSchema));
15
+ }
16
+ /**
17
+ * Collects schemas that carry a `$id` into `components.schemas`, returning a `$ref` in their place —
18
+ * so a schema used by N operations is emitted once. Schemas without a `$id` stay inline (the existing
19
+ * behavior). The first sighting of an id wins; later ones just `$ref` it.
20
+ */
21
+ class SchemaStore {
22
+ schemas = {};
23
+ /** Hoist a schema with a `$id` into components + return a `$ref`; otherwise return it inline. */
24
+ collect(schema) {
25
+ if (schema === undefined)
26
+ return undefined;
27
+ const id = typeof schema.$id === "string" ? schema.$id : undefined;
28
+ if (id === undefined)
29
+ return schema;
30
+ if (this.schemas[id] === undefined) {
31
+ const hoisted = { ...schema };
32
+ delete hoisted.$id; // the component key is the name; drop the redundant base-URI hint
33
+ this.schemas[id] = hoisted;
34
+ }
35
+ return { $ref: `#/components/schemas/${id}` };
36
+ }
37
+ }
38
+ /** Name of a path segment's parameter, or `undefined` for a static segment. */
39
+ function segmentParam(segment) {
40
+ if (segment.startsWith(":"))
41
+ return segment.slice(1);
42
+ if (segment.startsWith("*"))
43
+ return segment.length > 1 ? segment.slice(1) : "wildcard";
44
+ return undefined;
45
+ }
46
+ /** `/users/:id/*rest` → `/users/{id}/{rest}` (OpenAPI path templating). */
47
+ function toTemplatedPath(path) {
48
+ return path
49
+ .split("/")
50
+ .map((segment) => {
51
+ const param = segmentParam(segment);
52
+ return param === undefined ? segment : `{${param}}`;
53
+ })
54
+ .join("/");
55
+ }
56
+ function pathParameters(path) {
57
+ const params = [];
58
+ for (const segment of path.split("/")) {
59
+ const name = segmentParam(segment);
60
+ // Path params are always strings (decoded from the URL).
61
+ if (name !== undefined)
62
+ params.push({ name, in: "path", required: true, schema: { type: "string" } });
63
+ }
64
+ return params;
65
+ }
66
+ function queryParameters(schema) {
67
+ if (schema === undefined)
68
+ return [];
69
+ const json = toJsonSchema(schema);
70
+ // Only an object schema decomposes into individual query parameters.
71
+ if (json === undefined || json.type !== "object" || typeof json.properties !== "object")
72
+ return [];
73
+ const properties = json.properties;
74
+ const required = Array.isArray(json.required) ? json.required : [];
75
+ return Object.entries(properties).map(([name, propSchema]) => ({
76
+ name,
77
+ in: "query",
78
+ required: required.includes(name),
79
+ schema: propSchema,
80
+ }));
81
+ }
82
+ function buildResponses(input, store) {
83
+ const responses = {};
84
+ if (input.response !== undefined) {
85
+ const schema = store.collect(toJsonSchema(input.response));
86
+ responses["200"] =
87
+ schema !== undefined
88
+ ? {
89
+ description: "OK",
90
+ content: { [input.responseContentType ?? "application/json"]: { schema } },
91
+ }
92
+ : { description: "OK" };
93
+ }
94
+ else {
95
+ responses["200"] = { description: "OK" };
96
+ }
97
+ // Additional (or overriding) responses declared on the contract op.
98
+ if (input.responses !== undefined) {
99
+ for (const [status, def] of Object.entries(input.responses)) {
100
+ const response = { description: def.description ?? "" };
101
+ const schema = def.schema !== undefined ? store.collect(toJsonSchema(def.schema)) : undefined;
102
+ if (schema !== undefined)
103
+ response.content = { [def.contentType ?? "application/json"]: { schema } };
104
+ responses[status] = response;
105
+ }
106
+ }
107
+ return responses;
108
+ }
109
+ function buildOperation(input, store) {
110
+ const operation = { responses: buildResponses(input, store) };
111
+ if (input.operationId !== undefined)
112
+ operation.operationId = input.operationId;
113
+ if (input.summary !== undefined)
114
+ operation.summary = input.summary;
115
+ if (input.description !== undefined)
116
+ operation.description = input.description;
117
+ if (input.tags !== undefined && input.tags.length > 0)
118
+ operation.tags = input.tags;
119
+ if (input.deprecated === true)
120
+ operation.deprecated = true;
121
+ if (input.security !== undefined)
122
+ operation.security = input.security; // `[]` ⇒ explicitly public
123
+ const parameters = [...pathParameters(input.path), ...queryParameters(input.query)];
124
+ if (parameters.length > 0)
125
+ operation.parameters = parameters;
126
+ if (input.body !== undefined) {
127
+ const schema = store.collect(toJsonSchema(input.body));
128
+ if (schema !== undefined) {
129
+ operation.requestBody = {
130
+ required: true,
131
+ content: { [input.requestContentType ?? "application/json"]: { schema } },
132
+ };
133
+ }
134
+ }
135
+ return operation;
136
+ }
137
+ function addOperation(paths, method, input, store, operations) {
138
+ const templated = toTemplatedPath(input.path);
139
+ const pathItem = paths[templated] ?? {};
140
+ paths[templated] = pathItem;
141
+ let operation = buildOperation(input, store);
142
+ // Shallow-merge an override keyed by operationId or "METHOD /path".
143
+ const override = operations?.[input.operationId ?? ""] ?? operations?.[`${method.toUpperCase()} ${input.path}`];
144
+ if (override !== undefined)
145
+ operation = { ...operation, ...override };
146
+ pathItem[method.toLowerCase()] = operation;
147
+ }
148
+ function isApp(input) {
149
+ // Duck-typed so @nifrajs/schema keeps @nifrajs/core a type-only dependency. A contract
150
+ // is a plain record of operations; only a Server exposes a `routes()` method.
151
+ return typeof input.routes === "function";
152
+ }
153
+ /** Generate an OpenAPI 3.1 document from a contract or a running app. See the module doc for the detail model. */
154
+ export function toOpenAPI(input, options = {}) {
155
+ const paths = {};
156
+ const store = new SchemaStore();
157
+ if (isApp(input)) {
158
+ for (const route of input.routes()) {
159
+ addOperation(paths, route.method, {
160
+ path: route.path,
161
+ body: route.schema?.body,
162
+ query: route.schema?.query,
163
+ // A route may now declare a `response` contract — emit it as the 200 body schema.
164
+ response: route.schema?.response,
165
+ operationId: undefined,
166
+ }, store, options.operations);
167
+ }
168
+ }
169
+ else {
170
+ for (const [name, op] of Object.entries(input)) {
171
+ addOperation(paths, op.method, {
172
+ path: op.path,
173
+ body: op.body,
174
+ query: op.query,
175
+ response: op.response,
176
+ operationId: name,
177
+ summary: op.summary,
178
+ description: op.description,
179
+ tags: op.tags,
180
+ deprecated: op.deprecated,
181
+ security: op.security,
182
+ requestContentType: op.requestContentType,
183
+ responseContentType: op.responseContentType,
184
+ responses: op.responses,
185
+ }, store, options.operations);
186
+ }
187
+ }
188
+ const components = {};
189
+ if (Object.keys(store.schemas).length > 0)
190
+ components.schemas = store.schemas;
191
+ if (options.securitySchemes !== undefined)
192
+ components.securitySchemes = options.securitySchemes;
193
+ return {
194
+ openapi: "3.1.0",
195
+ info: {
196
+ title: options.title ?? "API",
197
+ version: options.version ?? "1.0.0",
198
+ ...(options.description !== undefined ? { description: options.description } : {}),
199
+ },
200
+ paths,
201
+ ...(options.servers !== undefined ? { servers: options.servers } : {}),
202
+ ...(options.tags !== undefined ? { tags: options.tags } : {}),
203
+ ...(options.security !== undefined ? { security: options.security } : {}),
204
+ ...(Object.keys(components).length > 0 ? { components } : {}),
205
+ };
206
+ }
207
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
package/dist/t.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { type ArrayOptions, type IntegerOptions, type NumberOptions, type ObjectOptions, type StringOptions, type TLiteralValue, type TSchema, type TUnion } from "@sinclair/typebox";
2
+ import { type NifraSchema } from "./adapter.js";
3
+ type Props = Record<string, NifraSchema>;
4
+ /**
5
+ * The built-in schema builder. Each constructor returns a `NifraSchema` — a
6
+ * Standard Schema whose validated output type flows into `c.body`/`c.query`, and
7
+ * whose `jsonSchema` powers `toOpenAPI`. Options (min/max, length, pattern, …)
8
+ * pass straight through to TypeBox and so become JSON Schema constraints.
9
+ *
10
+ * Composite constructors are generic over the *inner* TypeBox schema `T extends
11
+ * TSchema` (not over `NifraSchema`): reading `.jsonSchema` off a value typed only as
12
+ * `NifraSchema` would erase to the `TSchema` constraint and collapse the output type
13
+ * to `unknown`. Capturing `T` keeps `Static<T>` precise (`string[]`, not
14
+ * `unknown[]`).
15
+ */
16
+ export declare const t: {
17
+ readonly string: (options?: StringOptions) => NifraSchema<import("@sinclair/typebox").TString>;
18
+ readonly number: (options?: NumberOptions) => NifraSchema<import("@sinclair/typebox").TNumber>;
19
+ readonly integer: (options?: IntegerOptions) => NifraSchema<import("@sinclair/typebox").TInteger>;
20
+ readonly boolean: () => NifraSchema<import("@sinclair/typebox").TBoolean>;
21
+ readonly null: () => NifraSchema<import("@sinclair/typebox").TNull>;
22
+ readonly literal: <const L extends TLiteralValue>(value: L) => NifraSchema<import("@sinclair/typebox").TLiteral<L>>;
23
+ readonly object: <P extends Props>(props: P, options?: ObjectOptions) => NifraSchema<import("@sinclair/typebox").TObject<{ [K in keyof P]: P[K]["jsonSchema"]; }>>;
24
+ /** Like `t.object` but ACCEPTS (and passes through) unknown fields — the explicit opt-out of the
25
+ * strict default. Prefer `t.object` unless you genuinely need an open object. */
26
+ readonly looseObject: <P extends Props>(props: P, options?: ObjectOptions) => NifraSchema<import("@sinclair/typebox").TObject<{ [K in keyof P]: P[K]["jsonSchema"]; }>>;
27
+ readonly array: <T extends TSchema>(item: NifraSchema<T>, options?: ArrayOptions) => NifraSchema<import("@sinclair/typebox").TArray<T>>;
28
+ /** Marks a property optional inside `t.object`; standalone it is `T | undefined`. */
29
+ readonly 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>>>;
30
+ readonly union: <const S extends readonly NifraSchema[]>(schemas: S) => NifraSchema<TUnion<{ -readonly [K in keyof S]: S[K]["jsonSchema"]; }>>;
31
+ readonly record: <T extends TSchema>(value: NifraSchema<T>, options?: ObjectOptions) => NifraSchema<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, T>>;
32
+ };
33
+ export {};
34
+ //# sourceMappingURL=t.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"t.d.ts","sourceRoot":"","sources":["../src/t.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,MAAM,EAEZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAE5D,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;AAUxC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,CAAC;gCACO,aAAa;gCACb,aAAa;iCACZ,cAAc;;;6BAGlB,CAAC,SAAS,aAAa,SAAS,CAAC;sBAMxC,CAAC,SAAS,KAAK,SAAS,CAAC,YAAY,aAAa,wDA/Bd,CAAC;IAiC9C;qFACiF;2BACnE,CAAC,SAAS,KAAK,SAAS,CAAC,YAAY,aAAa,wDAnCnB,CAAC;qBAqCtC,CAAC,SAAS,OAAO,QAAQ,WAAW,CAAC,CAAC,CAAC,YAAY,YAAY;IAEvE,qFAAqF;wBAC1E,CAAC,SAAS,OAAO,UAAU,WAAW,CAAC,CAAC,CAAC;2BAOtC,CAAC,SAAS,SAAS,WAAW,EAAE,WAAW,CAAC,KAGnD,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAE,CAAC,CAAC;sBACnE,CAAC,SAAS,OAAO,SAAS,WAAW,CAAC,CAAC,CAAC,YAAY,aAAa;CAElE,CAAA"}