schema-components 1.12.11 → 1.14.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 +35 -0
- package/dist/core/adapter.d.mts +8 -3
- package/dist/core/adapter.mjs +58 -14
- package/dist/core/constraints.d.mts +17 -0
- package/dist/core/constraints.mjs +150 -0
- package/dist/core/diagnostics.d.mts +2 -0
- package/dist/core/diagnostics.mjs +33 -0
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/formats.d.mts +33 -0
- package/dist/core/formats.mjs +67 -0
- package/dist/core/merge.d.mts +48 -0
- package/dist/core/merge.mjs +125 -0
- package/dist/core/normalise.d.mts +41 -0
- package/dist/core/normalise.mjs +2 -0
- package/dist/core/openapi30.d.mts +41 -0
- package/dist/core/openapi30.mjs +2 -0
- package/dist/core/ref.d.mts +2 -0
- package/dist/core/ref.mjs +165 -0
- package/dist/core/renderer.d.mts +2 -2
- package/dist/core/renderer.mjs +8 -0
- package/dist/core/swagger2.d.mts +11 -0
- package/dist/core/swagger2.mjs +2 -0
- package/dist/core/typeInference.d.mts +2 -0
- package/dist/core/typeInference.mjs +1 -0
- package/dist/core/types.d.mts +2 -3
- package/dist/core/types.mjs +58 -2
- package/dist/core/version.d.mts +2 -0
- package/dist/core/version.mjs +151 -0
- package/dist/core/walkBuilders.d.mts +66 -0
- package/dist/core/walkBuilders.mjs +152 -0
- package/dist/core/walker.d.mts +3 -10
- package/dist/core/walker.mjs +245 -233
- package/dist/diagnostics-DzbZmcLI.d.mts +64 -0
- package/dist/html/a11y.d.mts +5 -4
- package/dist/html/renderToHtml.d.mts +3 -3
- package/dist/html/renderToHtml.mjs +23 -379
- package/dist/html/renderToHtmlStream.d.mts +29 -47
- package/dist/html/renderToHtmlStream.mjs +33 -305
- package/dist/html/renderers.d.mts +14 -0
- package/dist/html/renderers.mjs +406 -0
- package/dist/html/streamRenderers.d.mts +13 -0
- package/dist/html/streamRenderers.mjs +243 -0
- package/dist/normalise-tL9FckAk.mjs +748 -0
- package/dist/openapi/ApiCallbacks.d.mts +16 -0
- package/dist/openapi/ApiCallbacks.mjs +34 -0
- package/dist/openapi/ApiLinks.d.mts +16 -0
- package/dist/openapi/ApiLinks.mjs +42 -0
- package/dist/openapi/ApiResponseHeaders.d.mts +16 -0
- package/dist/openapi/ApiResponseHeaders.mjs +35 -0
- package/dist/openapi/ApiSecurity.d.mts +19 -0
- package/dist/openapi/ApiSecurity.mjs +33 -0
- package/dist/openapi/bundle.d.mts +47 -0
- package/dist/openapi/bundle.mjs +95 -0
- package/dist/openapi/components.d.mts +7 -1
- package/dist/openapi/components.mjs +30 -6
- package/dist/openapi/parser.d.mts +59 -2
- package/dist/openapi/parser.mjs +189 -8
- package/dist/react/SchemaComponent.d.mts +13 -4
- package/dist/react/SchemaComponent.mjs +51 -91
- package/dist/react/SchemaView.d.mts +10 -2
- package/dist/react/SchemaView.mjs +33 -15
- package/dist/react/fieldPath.d.mts +20 -0
- package/dist/react/fieldPath.mjs +81 -0
- package/dist/react/headless.d.mts +2 -4
- package/dist/react/headless.mjs +3 -492
- package/dist/react/headlessRenderers.d.mts +23 -0
- package/dist/react/headlessRenderers.mjs +507 -0
- package/dist/ref-DvWoULcy.d.mts +44 -0
- package/dist/renderer-BdSqllx5.d.mts +160 -0
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mantine.mjs +2 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +3 -2
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +2 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +10 -6
- package/dist/typeInference-k7FXfTVO.d.mts +335 -0
- package/dist/types-D_5ST7SS.d.mts +269 -0
- package/dist/version-B5NV-35j.d.mts +69 -0
- package/package.json +1 -1
- package/dist/types-BJzEgJdX.d.mts +0 -335
- /package/dist/{errors-DIKI2C78.d.mts → errors-C5zRC2PU.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -14,6 +14,10 @@ npm install schema-components
|
|
|
14
14
|
|
|
15
15
|
Peer dependencies: `zod@^4.0.0`, `react@^18.0.0 || ^19.0.0`.
|
|
16
16
|
|
|
17
|
+
### Zod version requirement
|
|
18
|
+
|
|
19
|
+
schema-components requires **Zod 4**. If you are on Zod 3, see the [Zod 4 migration guide](https://zod.dev/v4/migration). The library detects Zod 3 inputs and throws a descriptive error rather than silently misbehaving.
|
|
20
|
+
|
|
17
21
|
## `SchemaComponent`
|
|
18
22
|
|
|
19
23
|
The single entry point. Accepts Zod schemas, JSON Schema objects, or OpenAPI documents:
|
|
@@ -179,6 +183,37 @@ import { SchemaField } from "schema-components/react/SchemaComponent";
|
|
|
179
183
|
|
|
180
184
|
When the schema is a Zod schema or typed `as const`, only valid dot-paths like `"address.city"` are accepted. Invalid paths trigger TypeScript errors. Runtime schemas accept any string.
|
|
181
185
|
|
|
186
|
+
## Spec support
|
|
187
|
+
|
|
188
|
+
The walker reads canonical Draft 2020-12 JSON Schema. Older drafts and OpenAPI documents are normalised to that form transparently.
|
|
189
|
+
|
|
190
|
+
| Spec | Detection | Normalisation | Notes |
|
|
191
|
+
|---|---|---|---|
|
|
192
|
+
| JSON Schema Draft 04 | `http://json-schema.org/draft-04/schema#` | `exclusiveMinimum`/`Maximum` boolean → number; `id` left in place | |
|
|
193
|
+
| JSON Schema Draft 06 | `http://json-schema.org/draft-06/schema#` | Pass-through (canonical for `const`, `examples[]`, `$id`, `propertyNames`, `contains`) | |
|
|
194
|
+
| JSON Schema Draft 07 | `http://json-schema.org/draft-07/schema#` | Pass-through (adds `if`/`then`/`else`, `contentEncoding`, `contentMediaType`) | |
|
|
195
|
+
| JSON Schema Draft 2019-09 | `https://json-schema.org/draft/2019-09/schema` | `$recursiveRef` → `$ref`, `$recursiveAnchor` → `$anchor` | Adds `unevaluatedProperties`/`Items`, `dependentSchemas`/`Required` |
|
|
196
|
+
| JSON Schema Draft 2020-12 | `https://json-schema.org/draft/2020-12/schema` (default) | `$dynamicRef` → `$ref`, `$dynamicAnchor` → `$anchor`; tuple form via `prefixItems` | |
|
|
197
|
+
| OpenAPI 2.0 (Swagger) | `swagger: "2.0"` | Full document restructure → OpenAPI 3.1; `definitions` → `components/schemas`; body params → `requestBody`; `formData` → `multipart/form-data`; `collectionFormat` → `style`/`explode` | |
|
|
198
|
+
| OpenAPI 3.0.x | `openapi: "3.0.x"` | `nullable` → `anyOf [T, null]`; `discriminator` mapping → injected `const`; `example` → `examples[]` | Callbacks, links, security schemes preserved |
|
|
199
|
+
| OpenAPI 3.1.x | `openapi: "3.1.x"` | Direct (already Draft 2020-12) | Webhooks, `components/pathItems`, JSON Schema `type` arrays, `examples[]` |
|
|
200
|
+
|
|
201
|
+
### Documented type-level fallbacks
|
|
202
|
+
|
|
203
|
+
A few JSON Schema keywords can't be expressed in TypeScript's type system. They are handled at runtime by the walker, but `FromJSONSchema<S>` falls back as follows (each pinned by `tests/type-inference-advanced.test.ts`):
|
|
204
|
+
|
|
205
|
+
| Keyword | Type-level result | Why |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `not` | `unknown` | TypeScript has no type negation |
|
|
208
|
+
| `if` / `then` / `else` | Base schema (conditions ignored) | Requires value-dependent conditional evaluation |
|
|
209
|
+
| `propertyNames` | Ignored | Cannot constrain object key *shape* |
|
|
210
|
+
| `dependentSchemas` / `dependentRequired` | Ignored | Cross-field runtime conditionals |
|
|
211
|
+
| `unevaluatedProperties` / `unevaluatedItems` | Ignored | Requires whole-tree evaluation context |
|
|
212
|
+
| `contains` / `minContains` / `maxContains` | Element type unchanged | Constraint metadata only |
|
|
213
|
+
| `$recursiveRef` | `unknown` | Recursive types not expressible |
|
|
214
|
+
|
|
215
|
+
Runtime rendering and validation handle each of these correctly; only the static type widens.
|
|
216
|
+
|
|
182
217
|
## OpenAPI components
|
|
183
218
|
|
|
184
219
|
Render API operations with type-safe field overrides:
|
package/dist/core/adapter.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as SchemaMeta, m as JsonObject } from "../types-D_5ST7SS.mjs";
|
|
2
|
+
import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/core/adapter.d.ts
|
|
4
5
|
type SchemaInput = Record<string, unknown>;
|
|
@@ -14,6 +15,10 @@ interface NormalisedSchema {
|
|
|
14
15
|
/** The root document for $ref resolution. */
|
|
15
16
|
rootDocument: JsonObject;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
interface NormaliseOptions {
|
|
19
|
+
/** Diagnostics channel for surfacing silent fallbacks. */
|
|
20
|
+
diagnostics?: DiagnosticsOptions;
|
|
21
|
+
}
|
|
22
|
+
declare function normaliseSchema(input: unknown, ref?: string, options?: NormaliseOptions): NormalisedSchema;
|
|
18
23
|
//#endregion
|
|
19
|
-
export { type JsonObject, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
|
|
24
|
+
export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
|
package/dist/core/adapter.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { getProperty, hasProperty, isObject } from "./guards.mjs";
|
|
2
|
+
import { emitDiagnostic } from "./diagnostics.mjs";
|
|
3
|
+
import { dereference } from "./ref.mjs";
|
|
4
|
+
import { detectJsonSchemaDraft, detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2 } from "./version.mjs";
|
|
5
|
+
import { i as normaliseOpenApiSchemas, r as normaliseJsonSchema$1 } from "../normalise-tL9FckAk.mjs";
|
|
2
6
|
import { z } from "zod";
|
|
3
7
|
//#region src/core/adapter.ts
|
|
4
8
|
/**
|
|
@@ -16,7 +20,7 @@ const schemaCache = /* @__PURE__ */ new WeakMap();
|
|
|
16
20
|
function detectSchemaKind(input) {
|
|
17
21
|
if (hasProperty(input, "_zod")) return "zod4";
|
|
18
22
|
if (hasProperty(input, "_def") && !hasProperty(input, "_zod")) return "zod3";
|
|
19
|
-
if (hasProperty(input, "openapi")) return "openapi";
|
|
23
|
+
if (hasProperty(input, "openapi") || hasProperty(input, "swagger")) return "openapi";
|
|
20
24
|
return "jsonSchema";
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
@@ -30,7 +34,7 @@ function detectSchemaKind(input) {
|
|
|
30
34
|
function callToJsonSchema(schema) {
|
|
31
35
|
return z.toJSONSchema(schema);
|
|
32
36
|
}
|
|
33
|
-
function normaliseSchema(input, ref) {
|
|
37
|
+
function normaliseSchema(input, ref, options) {
|
|
34
38
|
if (ref === void 0 && isObject(input)) {
|
|
35
39
|
const cached = schemaCache.get(input);
|
|
36
40
|
if (cached !== void 0) return cached;
|
|
@@ -46,11 +50,11 @@ function normaliseSchema(input, ref) {
|
|
|
46
50
|
break;
|
|
47
51
|
case "openapi":
|
|
48
52
|
if (!isObject(input)) throw new Error("Invalid OpenAPI document");
|
|
49
|
-
result = normaliseOpenApi(input, ref);
|
|
53
|
+
result = normaliseOpenApi(input, ref, options);
|
|
50
54
|
break;
|
|
51
55
|
case "jsonSchema":
|
|
52
56
|
if (!isObject(input)) throw new Error("Invalid JSON Schema");
|
|
53
|
-
result = normaliseJsonSchema(input);
|
|
57
|
+
result = normaliseJsonSchema(input, options?.diagnostics);
|
|
54
58
|
break;
|
|
55
59
|
}
|
|
56
60
|
if (ref === void 0 && isObject(input)) schemaCache.set(input, result);
|
|
@@ -69,22 +73,56 @@ function normaliseZod4(input) {
|
|
|
69
73
|
rootDocument: jsonSchema
|
|
70
74
|
};
|
|
71
75
|
}
|
|
72
|
-
function normaliseJsonSchema(jsonSchema) {
|
|
76
|
+
function normaliseJsonSchema(jsonSchema, diagnostics) {
|
|
77
|
+
let draft;
|
|
78
|
+
if (typeof jsonSchema.$schema !== "string") {
|
|
79
|
+
const inferred = inferJsonSchemaDraftWithReason(jsonSchema);
|
|
80
|
+
draft = inferred.draft;
|
|
81
|
+
emitDiagnostic(diagnostics, {
|
|
82
|
+
code: "assumed-draft",
|
|
83
|
+
message: `No $schema present; inferred ${inferred.draft} from keywords (${inferred.inferredFrom})`,
|
|
84
|
+
pointer: "",
|
|
85
|
+
detail: {
|
|
86
|
+
inferredFrom: inferred.inferredFrom,
|
|
87
|
+
draft: inferred.draft
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
} else draft = detectJsonSchemaDraft(jsonSchema);
|
|
91
|
+
const normalised = normaliseJsonSchema$1(jsonSchema, draft);
|
|
73
92
|
return {
|
|
74
|
-
jsonSchema,
|
|
75
|
-
rootMeta: extractRootMetaFromJson(
|
|
76
|
-
rootDocument:
|
|
93
|
+
jsonSchema: normalised,
|
|
94
|
+
rootMeta: extractRootMetaFromJson(normalised),
|
|
95
|
+
rootDocument: normalised
|
|
77
96
|
};
|
|
78
97
|
}
|
|
79
98
|
function normaliseZod3() {
|
|
80
|
-
throw new Error("Zod 3 schemas are not
|
|
99
|
+
throw new Error("Zod 3 schemas are not supported. schema-components requires Zod 4. Detected: Zod 3 (has _def without _zod). See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4");
|
|
81
100
|
}
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Mapping of Swagger 2.0 $ref prefixes to their OpenAPI 3.x equivalents.
|
|
103
|
+
* Used by the adapter to rewrite user-provided ref strings so they
|
|
104
|
+
* resolve correctly against the normalised document.
|
|
105
|
+
*/
|
|
106
|
+
const REF_REWRITES_ADAPTER = [
|
|
107
|
+
["#/definitions/", "#/components/schemas/"],
|
|
108
|
+
["#/parameters/", "#/components/parameters/"],
|
|
109
|
+
["#/responses/", "#/components/responses/"]
|
|
110
|
+
];
|
|
111
|
+
function normaliseOpenApi(doc, ref, options) {
|
|
112
|
+
const version = detectOpenApiVersion(doc);
|
|
113
|
+
const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version, options?.diagnostics) : doc;
|
|
114
|
+
let rewrittenRef = ref;
|
|
115
|
+
if (rewrittenRef !== void 0 && version !== void 0 && isSwagger2(version)) {
|
|
116
|
+
for (const [from, to] of REF_REWRITES_ADAPTER) if (rewrittenRef.startsWith(from)) {
|
|
117
|
+
rewrittenRef = to + rewrittenRef.slice(from.length);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const resolved = resolveOpenApiRef(normalisedDoc, rewrittenRef);
|
|
84
122
|
return {
|
|
85
123
|
jsonSchema: resolved,
|
|
86
124
|
rootMeta: extractRootMetaFromJson(resolved),
|
|
87
|
-
rootDocument:
|
|
125
|
+
rootDocument: normalisedDoc
|
|
88
126
|
};
|
|
89
127
|
}
|
|
90
128
|
function resolveOpenApiRef(doc, ref) {
|
|
@@ -120,11 +158,17 @@ function resolveOpenApiRef(doc, ref) {
|
|
|
120
158
|
const content = getProperty(requestBody, "content");
|
|
121
159
|
if (!isObject(content)) throw new Error(`No content for ${ref}`);
|
|
122
160
|
const json = getProperty(content, "application/json");
|
|
123
|
-
|
|
124
|
-
const
|
|
161
|
+
const multipart = getProperty(content, "multipart/form-data");
|
|
162
|
+
const mediaType = isObject(json) ? json : isObject(multipart) ? multipart : void 0;
|
|
163
|
+
if (mediaType === void 0) throw new Error(`No content for ${ref}`);
|
|
164
|
+
const schema = getProperty(mediaType, "schema");
|
|
125
165
|
if (!isObject(schema)) throw new Error(`Could not resolve request body schema for ${ref}`);
|
|
126
166
|
return schema;
|
|
127
167
|
}
|
|
168
|
+
if (ref.startsWith("#/")) {
|
|
169
|
+
const resolved = dereference(ref, doc);
|
|
170
|
+
if (resolved !== void 0) return resolved;
|
|
171
|
+
}
|
|
128
172
|
throw new Error(`Unsupported OpenAPI ref format: ${ref}`);
|
|
129
173
|
}
|
|
130
174
|
function extractRootMetaFromJson(jsonSchema) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-D_5ST7SS.mjs";
|
|
2
|
+
import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/core/constraints.d.ts
|
|
5
|
+
declare function extractStringConstraints(schema: Record<string, unknown>, diagnostics?: DiagnosticsOptions, pointer?: string): StringConstraints;
|
|
6
|
+
declare function extractNumberConstraints(schema: Record<string, unknown>): NumberConstraints;
|
|
7
|
+
declare function extractArrayConstraints(schema: Record<string, unknown>): ArrayConstraints;
|
|
8
|
+
declare function extractObjectConstraints(schema: Record<string, unknown>): ObjectConstraints;
|
|
9
|
+
declare function extractFileConstraints(schema: Record<string, unknown>): FileConstraints;
|
|
10
|
+
/**
|
|
11
|
+
* Return a copy of the schema with constraint keywords that don't apply
|
|
12
|
+
* to the given type removed. Meta keywords (description, title, etc.)
|
|
13
|
+
* and composition keywords are always preserved.
|
|
14
|
+
*/
|
|
15
|
+
declare function stripInapplicableConstraints(schema: Record<string, unknown>, targetType: string): Record<string, unknown>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { extractArrayConstraints, extractFileConstraints, extractNumberConstraints, extractObjectConstraints, extractStringConstraints, stripInapplicableConstraints };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { isObject } from "./guards.mjs";
|
|
2
|
+
import { emitDiagnostic } from "./diagnostics.mjs";
|
|
3
|
+
import { FORMAT_PATTERNS } from "./formats.mjs";
|
|
4
|
+
//#region src/core/constraints.ts
|
|
5
|
+
function getString(obj, key) {
|
|
6
|
+
const value = obj[key];
|
|
7
|
+
return typeof value === "string" ? value : void 0;
|
|
8
|
+
}
|
|
9
|
+
function getNumber(obj, key) {
|
|
10
|
+
const value = obj[key];
|
|
11
|
+
return typeof value === "number" ? value : void 0;
|
|
12
|
+
}
|
|
13
|
+
function getObject(obj, key) {
|
|
14
|
+
const value = obj[key];
|
|
15
|
+
return isObject(value) ? value : void 0;
|
|
16
|
+
}
|
|
17
|
+
function extractStringConstraints(schema, diagnostics, pointer = "") {
|
|
18
|
+
const c = {};
|
|
19
|
+
const minLength = getNumber(schema, "minLength");
|
|
20
|
+
if (minLength !== void 0) c.minLength = minLength;
|
|
21
|
+
const maxLength = getNumber(schema, "maxLength");
|
|
22
|
+
if (maxLength !== void 0) c.maxLength = maxLength;
|
|
23
|
+
const pattern = getString(schema, "pattern");
|
|
24
|
+
if (pattern !== void 0) c.pattern = pattern;
|
|
25
|
+
const format = getString(schema, "format");
|
|
26
|
+
if (format !== void 0) {
|
|
27
|
+
c.format = format;
|
|
28
|
+
const pattern = FORMAT_PATTERNS[format];
|
|
29
|
+
if (pattern !== void 0) c.formatPattern = pattern;
|
|
30
|
+
else if (format !== "binary") emitDiagnostic(diagnostics, {
|
|
31
|
+
code: "unknown-format",
|
|
32
|
+
message: `Unknown format: ${format}`,
|
|
33
|
+
pointer,
|
|
34
|
+
detail: { format }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const contentEncoding = getString(schema, "contentEncoding");
|
|
38
|
+
if (contentEncoding !== void 0) c.contentEncoding = contentEncoding;
|
|
39
|
+
const contentMediaType = getString(schema, "contentMediaType");
|
|
40
|
+
if (contentMediaType !== void 0) c.contentMediaType = contentMediaType;
|
|
41
|
+
return c;
|
|
42
|
+
}
|
|
43
|
+
function extractNumberConstraints(schema) {
|
|
44
|
+
const c = {};
|
|
45
|
+
const minimum = getNumber(schema, "minimum");
|
|
46
|
+
if (minimum !== void 0) c.minimum = minimum;
|
|
47
|
+
const maximum = getNumber(schema, "maximum");
|
|
48
|
+
if (maximum !== void 0) c.maximum = maximum;
|
|
49
|
+
const exclusiveMinimum = getNumber(schema, "exclusiveMinimum");
|
|
50
|
+
if (exclusiveMinimum !== void 0) c.exclusiveMinimum = exclusiveMinimum;
|
|
51
|
+
const exclusiveMaximum = getNumber(schema, "exclusiveMaximum");
|
|
52
|
+
if (exclusiveMaximum !== void 0) c.exclusiveMaximum = exclusiveMaximum;
|
|
53
|
+
const multipleOf = getNumber(schema, "multipleOf");
|
|
54
|
+
if (multipleOf !== void 0) c.multipleOf = multipleOf;
|
|
55
|
+
return c;
|
|
56
|
+
}
|
|
57
|
+
function extractArrayConstraints(schema) {
|
|
58
|
+
const c = {};
|
|
59
|
+
const minItems = getNumber(schema, "minItems");
|
|
60
|
+
if (minItems !== void 0) c.minItems = minItems;
|
|
61
|
+
const maxItems = getNumber(schema, "maxItems");
|
|
62
|
+
if (maxItems !== void 0) c.maxItems = maxItems;
|
|
63
|
+
if (schema.uniqueItems === true) c.uniqueItems = true;
|
|
64
|
+
const contains = getObject(schema, "contains");
|
|
65
|
+
if (contains !== void 0) c.contains = contains;
|
|
66
|
+
const minContains = getNumber(schema, "minContains");
|
|
67
|
+
if (minContains !== void 0) c.minContains = minContains;
|
|
68
|
+
const maxContains = getNumber(schema, "maxContains");
|
|
69
|
+
if (maxContains !== void 0) c.maxContains = maxContains;
|
|
70
|
+
const unevaluatedItems = getObject(schema, "unevaluatedItems");
|
|
71
|
+
if (unevaluatedItems !== void 0) c.unevaluatedItems = unevaluatedItems;
|
|
72
|
+
return c;
|
|
73
|
+
}
|
|
74
|
+
function extractObjectConstraints(schema) {
|
|
75
|
+
const c = {};
|
|
76
|
+
const minProperties = getNumber(schema, "minProperties");
|
|
77
|
+
if (minProperties !== void 0) c.minProperties = minProperties;
|
|
78
|
+
const maxProperties = getNumber(schema, "maxProperties");
|
|
79
|
+
if (maxProperties !== void 0) c.maxProperties = maxProperties;
|
|
80
|
+
return c;
|
|
81
|
+
}
|
|
82
|
+
function extractFileConstraints(schema) {
|
|
83
|
+
const c = {};
|
|
84
|
+
const contentMediaType = getString(schema, "contentMediaType");
|
|
85
|
+
if (contentMediaType !== void 0) c.mimeTypes = [contentMediaType];
|
|
86
|
+
return c;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Constraint keywords that apply only to specific types.
|
|
90
|
+
* Used to strip inapplicable constraints when expanding type arrays.
|
|
91
|
+
*/
|
|
92
|
+
const STRING_CONSTRAINTS = new Set([
|
|
93
|
+
"minLength",
|
|
94
|
+
"maxLength",
|
|
95
|
+
"pattern"
|
|
96
|
+
]);
|
|
97
|
+
const NUMBER_CONSTRAINTS = new Set([
|
|
98
|
+
"minimum",
|
|
99
|
+
"maximum",
|
|
100
|
+
"exclusiveMinimum",
|
|
101
|
+
"exclusiveMaximum",
|
|
102
|
+
"multipleOf"
|
|
103
|
+
]);
|
|
104
|
+
const ARRAY_CONSTRAINTS = new Set([
|
|
105
|
+
"minItems",
|
|
106
|
+
"maxItems",
|
|
107
|
+
"uniqueItems",
|
|
108
|
+
"contains",
|
|
109
|
+
"minContains",
|
|
110
|
+
"maxContains"
|
|
111
|
+
]);
|
|
112
|
+
const OBJECT_CONSTRAINTS = new Set(["minProperties", "maxProperties"]);
|
|
113
|
+
const ALL_CONSTRAINTS = new Set([
|
|
114
|
+
...STRING_CONSTRAINTS,
|
|
115
|
+
...NUMBER_CONSTRAINTS,
|
|
116
|
+
...ARRAY_CONSTRAINTS,
|
|
117
|
+
...OBJECT_CONSTRAINTS
|
|
118
|
+
]);
|
|
119
|
+
/**
|
|
120
|
+
* Return a copy of the schema with constraint keywords that don't apply
|
|
121
|
+
* to the given type removed. Meta keywords (description, title, etc.)
|
|
122
|
+
* and composition keywords are always preserved.
|
|
123
|
+
*/
|
|
124
|
+
function stripInapplicableConstraints(schema, targetType) {
|
|
125
|
+
let keepForType;
|
|
126
|
+
switch (targetType) {
|
|
127
|
+
case "string":
|
|
128
|
+
keepForType = STRING_CONSTRAINTS;
|
|
129
|
+
break;
|
|
130
|
+
case "number":
|
|
131
|
+
case "integer":
|
|
132
|
+
keepForType = NUMBER_CONSTRAINTS;
|
|
133
|
+
break;
|
|
134
|
+
case "array":
|
|
135
|
+
keepForType = ARRAY_CONSTRAINTS;
|
|
136
|
+
break;
|
|
137
|
+
case "object":
|
|
138
|
+
keepForType = OBJECT_CONSTRAINTS;
|
|
139
|
+
break;
|
|
140
|
+
default: keepForType = /* @__PURE__ */ new Set();
|
|
141
|
+
}
|
|
142
|
+
const result = {};
|
|
143
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
144
|
+
if (ALL_CONSTRAINTS.has(key) && !keepForType.has(key)) continue;
|
|
145
|
+
result[key] = value;
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { extractArrayConstraints, extractFileConstraints, extractNumberConstraints, extractObjectConstraints, extractStringConstraints, stripInapplicableConstraints };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-DzbZmcLI.mjs";
|
|
2
|
+
export { Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticsOptions, appendPointer, emitDiagnostic };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SchemaNormalisationError } from "./errors.mjs";
|
|
2
|
+
//#region src/core/diagnostics.ts
|
|
3
|
+
/**
|
|
4
|
+
* Diagnostics channel for schema-components.
|
|
5
|
+
*
|
|
6
|
+
* Provides a structured way to surface silent fallbacks — unresolved `$ref`,
|
|
7
|
+
* unknown keywords, unknown `format` values, invalid `const` values,
|
|
8
|
+
* unsupported `type` entries, dropped Swagger 2.0 features, external
|
|
9
|
+
* `$ref`, type-negation fallbacks, and conditional fallbacks.
|
|
10
|
+
*
|
|
11
|
+
* Consumers pass a `DiagnosticSink` callback to receive diagnostics
|
|
12
|
+
* as they occur. By default, diagnostics are silently discarded.
|
|
13
|
+
* Setting `strict: true` converts any diagnostic into a thrown
|
|
14
|
+
* `SchemaCompatibilityError`.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Emit a diagnostic through the configured sink.
|
|
18
|
+
* When `strict` is enabled, throws a `SchemaCompatibilityError` instead.
|
|
19
|
+
*/
|
|
20
|
+
function emitDiagnostic(opts, diagnostic) {
|
|
21
|
+
if (opts?.strict === true) throw new SchemaNormalisationError(`[${diagnostic.code}] ${diagnostic.message} (at ${diagnostic.pointer})`, diagnostic.detail, "unknown");
|
|
22
|
+
if (opts?.diagnostics !== void 0) opts.diagnostics(diagnostic);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Append a segment to a JSON Pointer.
|
|
26
|
+
* Encodes `/` and `~` per RFC 6901.
|
|
27
|
+
*/
|
|
28
|
+
function appendPointer(base, segment) {
|
|
29
|
+
const escaped = segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
30
|
+
return base === "" ? `/${escaped}` : `${base}/${escaped}`;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { appendPointer, emitDiagnostic };
|
package/dist/core/errors.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-
|
|
1
|
+
import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-C5zRC2PU.mjs";
|
|
2
2
|
export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/core/formats.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Built-in format patterns for JSON Schema string validation.
|
|
4
|
+
*
|
|
5
|
+
* Maps standard JSON Schema/OpenAPI `format` values to RegExp patterns
|
|
6
|
+
* or predicate validators. Used by the constraint extractor to derive
|
|
7
|
+
* `formatPattern` alongside the existing `format` string, and by the
|
|
8
|
+
* `validate` helper for runtime format checking.
|
|
9
|
+
*
|
|
10
|
+
* Formats that cannot be validated by regex (iri, regex) use predicate
|
|
11
|
+
* functions instead. `validateFormat` dispatches to whichever is available.
|
|
12
|
+
*
|
|
13
|
+
* The user's explicit `pattern` constraint always takes precedence —
|
|
14
|
+
* `formatPattern` is exposed as a separate field for renderers.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A format validator: either a RegExp pattern or a predicate function.
|
|
18
|
+
*/
|
|
19
|
+
type FormatValidator = RegExp | ((value: string) => boolean);
|
|
20
|
+
/**
|
|
21
|
+
* Recognised JSON Schema formats with their validation patterns.
|
|
22
|
+
* Unknown formats emit an `unknown-format` diagnostic and skip derivation.
|
|
23
|
+
*/
|
|
24
|
+
declare const FORMAT_PATTERNS: Readonly<Record<string, RegExp>>;
|
|
25
|
+
/**
|
|
26
|
+
* Validate a string value against format constraints.
|
|
27
|
+
* Returns `true` when the value matches the format,
|
|
28
|
+
* `false` when it does not, and `undefined` when the format
|
|
29
|
+
* is not recognised (no validator available).
|
|
30
|
+
*/
|
|
31
|
+
declare function validateFormat(value: string, format: string): boolean | undefined;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FORMAT_PATTERNS, FormatValidator, validateFormat };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/core/formats.ts
|
|
2
|
+
/**
|
|
3
|
+
* Recognised JSON Schema formats with their validation patterns.
|
|
4
|
+
* Unknown formats emit an `unknown-format` diagnostic and skip derivation.
|
|
5
|
+
*/
|
|
6
|
+
const FORMAT_PATTERNS = {
|
|
7
|
+
uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
8
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
9
|
+
"date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/,
|
|
10
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
11
|
+
time: /^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/,
|
|
12
|
+
ipv4: /^(\d{1,3}\.){3}\d{1,3}$/,
|
|
13
|
+
ipv6: /^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i,
|
|
14
|
+
uri: /^[a-z][a-z0-9+\-.]*:/i,
|
|
15
|
+
hostname: /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,
|
|
16
|
+
"uri-reference": /^(|[a-z][a-z0-9+\-.]*:|[?#][^\s]*)/i,
|
|
17
|
+
"uri-template": /^([^{}]|[{][+#/.;?&=,!@|]([a-zA-Z0-9_%.]+)?(:[1-9][0-9]*)?(,[a-zA-Z0-9_%.]+)*[}])*$/,
|
|
18
|
+
"json-pointer": /^(([/]([^/~]|~0|~1)*)*|)$/,
|
|
19
|
+
"relative-json-pointer": /^(0|[1-9][0-9]*)(#?([/]([^/~]|~0|~1)*)*)?$/,
|
|
20
|
+
duration: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/,
|
|
21
|
+
"idn-email": /^[^\s@]+@[^\s@]+\.[^\s@]+$/u,
|
|
22
|
+
"idn-hostname": /^[a-z0-9\u00a1-\uffff]([a-z0-9\u00a1-\uffff-]{0,61}[a-z0-9\u00a1-\uffff])?(\.[a-z0-9\u00a1-\uffff]([a-z0-9\u00a1-\uffff-]{0,61}[a-z0-9\u00a1-\uffff])?)*$/iu
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Format validators that use predicate functions instead of regex.
|
|
26
|
+
* These are checked in `validateFormat` when no regex pattern exists.
|
|
27
|
+
*/
|
|
28
|
+
const PREDICATE_VALIDATORS = {
|
|
29
|
+
iri: (value) => {
|
|
30
|
+
try {
|
|
31
|
+
return new URL(value).protocol.length > 0;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"iri-reference": (value) => {
|
|
37
|
+
if (value === "") return true;
|
|
38
|
+
try {
|
|
39
|
+
new URL(value);
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return !/\s/.test(value);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
regex: (value) => {
|
|
46
|
+
try {
|
|
47
|
+
new RegExp(value);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Validate a string value against format constraints.
|
|
56
|
+
* Returns `true` when the value matches the format,
|
|
57
|
+
* `false` when it does not, and `undefined` when the format
|
|
58
|
+
* is not recognised (no validator available).
|
|
59
|
+
*/
|
|
60
|
+
function validateFormat(value, format) {
|
|
61
|
+
const pattern = FORMAT_PATTERNS[format];
|
|
62
|
+
if (pattern !== void 0) return pattern.test(value);
|
|
63
|
+
const predicate = PREDICATE_VALIDATORS[format];
|
|
64
|
+
if (predicate !== void 0) return predicate(value);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { FORMAT_PATTERNS, validateFormat };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/core/merge.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Schema merging, nullable detection, and discriminated union detection.
|
|
4
|
+
*
|
|
5
|
+
* Used by the walker to handle `allOf`, `anyOf [T, null]`, and
|
|
6
|
+
* `oneOf` with discriminator properties.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Annotation keywords that can appear as siblings of `$ref` per
|
|
10
|
+
* Draft 2020-12 / OpenAPI 3.1. Structural keywords (type, properties,
|
|
11
|
+
* etc.) are NOT annotation siblings and should not be merged.
|
|
12
|
+
*/
|
|
13
|
+
declare const ANNOTATION_SIBLINGS: ReadonlySet<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Merge annotation siblings from the referencing node onto the
|
|
16
|
+
* resolved target's annotations. The referencer wins for annotations.
|
|
17
|
+
*
|
|
18
|
+
* Structural keywords on the referencer are NOT merged — per spec,
|
|
19
|
+
* `$ref` with structural siblings was invalid pre-2019-09.
|
|
20
|
+
*
|
|
21
|
+
* Returns a new meta object with the merged annotations.
|
|
22
|
+
*/
|
|
23
|
+
declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedMeta: Record<string, unknown>): Record<string, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Merge multiple JSON Schema objects from allOf into one.
|
|
26
|
+
* Merges: properties, required, meta fields, and constraints.
|
|
27
|
+
*/
|
|
28
|
+
declare function mergeAllOf(schemas: unknown[]): Record<string, unknown>;
|
|
29
|
+
interface NormalisedAnyOf {
|
|
30
|
+
inner: Record<string, unknown>;
|
|
31
|
+
isNullable: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect `anyOf: [T, { type: "null" }]` → nullable T.
|
|
35
|
+
* Returns the non-null schema and a nullable flag.
|
|
36
|
+
*/
|
|
37
|
+
declare function normaliseAnyOf(options: unknown[]): NormalisedAnyOf | undefined;
|
|
38
|
+
interface Discriminated {
|
|
39
|
+
options: Record<string, unknown>[];
|
|
40
|
+
discriminator: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Detect oneOf where every option is an object with a property
|
|
44
|
+
* that has a `const` value → discriminated union.
|
|
45
|
+
*/
|
|
46
|
+
declare function detectDiscriminated(options: unknown[]): Discriminated | undefined;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { ANNOTATION_SIBLINGS, Discriminated, NormalisedAnyOf, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf };
|