schema-components 1.22.0 → 1.24.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 +3 -1
- package/dist/core/adapter.d.mts +97 -3
- package/dist/core/adapter.mjs +260 -111
- package/dist/core/constraints.d.mts +2 -2
- package/dist/core/constraints.mjs +0 -7
- package/dist/core/cssClasses.d.mts +52 -0
- package/dist/core/cssClasses.mjs +51 -0
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +5 -13
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +9 -2
- package/dist/core/formats.mjs +12 -1
- package/dist/core/idPath.d.mts +54 -0
- package/dist/core/idPath.mjs +66 -0
- package/dist/core/merge.d.mts +10 -1
- package/dist/core/merge.mjs +49 -10
- package/dist/core/normalise.d.mts +14 -3
- package/dist/core/normalise.mjs +2 -2
- package/dist/core/openapi30.d.mts +15 -1
- package/dist/core/openapi30.mjs +2 -2
- package/dist/core/openapiConstants.d.mts +67 -0
- package/dist/core/openapiConstants.mjs +90 -0
- package/dist/core/ref.d.mts +2 -2
- package/dist/core/ref.mjs +83 -6
- package/dist/core/refChain.d.mts +70 -0
- package/dist/core/refChain.mjs +44 -0
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +982 -2
- package/dist/core/types.d.mts +1 -1
- package/dist/core/unionMatch.d.mts +36 -0
- package/dist/core/unionMatch.mjs +53 -0
- package/dist/core/version.d.mts +1 -1
- package/dist/core/version.mjs +29 -17
- package/dist/core/walkBuilders.d.mts +23 -4
- package/dist/core/walkBuilders.mjs +27 -7
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +44 -45
- package/dist/{diagnostics-D0QCYGv0.d.mts → diagnostics-Cbwak-ZX.d.mts} +1 -1
- package/dist/{errors-DpFwqs5C.d.mts → errors-g_MCTQel.d.mts} +9 -15
- package/dist/html/a11y.d.mts +9 -4
- package/dist/html/a11y.mjs +10 -19
- package/dist/html/renderToHtml.d.mts +2 -2
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderToHtmlStream.mjs +12 -1
- package/dist/html/renderers.d.mts +32 -8
- package/dist/html/renderers.mjs +125 -111
- package/dist/html/streamRenderers.d.mts +4 -5
- package/dist/html/streamRenderers.mjs +40 -61
- package/dist/{normalise-DVEJQmF7.mjs → normalise-DCYp06Sr.mjs} +352 -162
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/components.d.mts +116 -37
- package/dist/openapi/components.mjs +54 -37
- package/dist/openapi/parser.d.mts +19 -9
- package/dist/openapi/parser.mjs +262 -86
- package/dist/openapi/resolve.d.mts +20 -11
- package/dist/openapi/resolve.mjs +135 -75
- package/dist/react/SchemaComponent.d.mts +32 -7
- package/dist/react/SchemaComponent.mjs +45 -21
- package/dist/react/SchemaView.d.mts +30 -10
- package/dist/react/a11y.d.mts +21 -0
- package/dist/react/a11y.mjs +24 -0
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +8 -9
- package/dist/react/headlessRenderers.mjs +41 -72
- package/dist/{ref-D-_JBZkF.d.mts → ref-DCDuswPe.d.mts} +38 -3
- package/dist/{renderer-BaRlQIuN.d.mts → renderer-CXJ8y0qw.d.mts} +1 -1
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +2 -1
- package/dist/{types-BrRMV0en.d.mts → types-BTB73MB8.d.mts} +32 -4
- package/dist/{version-D2jfdX6E.d.mts → version-BFTVLsdb.d.mts} +7 -1
- package/package.json +1 -1
- package/dist/typeInference-DkcUHfaM.d.mts +0 -982
package/dist/openapi/resolve.mjs
CHANGED
|
@@ -3,8 +3,9 @@ import "../core/limits.mjs";
|
|
|
3
3
|
import { emitDiagnostic } from "../core/diagnostics.mjs";
|
|
4
4
|
import { isPrototypePollutingKey } from "../core/uri.mjs";
|
|
5
5
|
import { detectOpenApiVersion } from "../core/version.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { o as normaliseOpenApiSchemas, r as documentContainsKeyword } from "../normalise-DCYp06Sr.mjs";
|
|
7
|
+
import { resolveRefChain } from "../core/refChain.mjs";
|
|
8
|
+
import { getParameters, getRequestBody, getResponses, listAllOperations, parseOpenApiDocument } from "./parser.mjs";
|
|
8
9
|
//#region src/openapi/resolve.ts
|
|
9
10
|
/**
|
|
10
11
|
* OpenAPI document resolution and caching.
|
|
@@ -27,25 +28,55 @@ const docCache = /* @__PURE__ */ new WeakMap();
|
|
|
27
28
|
* same form `<SchemaComponent>` does, keeping the OpenAPI components on
|
|
28
29
|
* the same pipeline as the top-level adapter.
|
|
29
30
|
*
|
|
30
|
-
*
|
|
31
|
-
* (`duplicate-body-parameter`, `dropped-swagger-feature`,
|
|
32
|
-
* `unknown-json-schema-dialect`, `divisible-by-conflict`,
|
|
33
|
-
* `relative-ref-resolved`, etc.) are forwarded to the sink. Passing
|
|
34
|
-
* diagnostics also bypasses the cache so each call observes the
|
|
35
|
-
* normalisation pipeline running against the supplied sink — caching
|
|
36
|
-
* would silently swallow every emission after the first.
|
|
31
|
+
* ### Caching and diagnostics
|
|
37
32
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
33
|
+
* Normalisation runs at most once per document identity. The full set
|
|
34
|
+
* of doc-level diagnostics emitted during that single run is captured
|
|
35
|
+
* into the cache alongside the parsed result. Each caller-supplied
|
|
36
|
+
* sink receives the captured diagnostics exactly once per cached
|
|
37
|
+
* entry, no matter how many times `getParsed` is called with that
|
|
38
|
+
* `(doc, sink)` pair.
|
|
39
|
+
*
|
|
40
|
+
* The previous implementation bypassed the cache whenever
|
|
41
|
+
* `diagnostics` was supplied and re-ran the entire normalisation
|
|
42
|
+
* pipeline against the new sink. That fired every doc-level
|
|
43
|
+
* diagnostic once per call, so a parent like `ApiWebhooks` that
|
|
44
|
+
* renders `ApiWebhook` per webhook entry caused N-fold emission of a
|
|
45
|
+
* single real cause. With the new strategy, cardinality stays at one
|
|
46
|
+
* per real cause regardless of how many child renders share the
|
|
47
|
+
* sink.
|
|
48
|
+
*
|
|
49
|
+
* Strict mode is treated as a per-call invariant — see
|
|
50
|
+
* {@link replayCapturedDiagnostics} for the rationale.
|
|
41
51
|
*/
|
|
42
52
|
function getParsed(doc, diagnostics) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
let cached = docCache.get(doc);
|
|
54
|
+
if (cached === void 0) {
|
|
55
|
+
cached = buildCachedParse(doc);
|
|
56
|
+
docCache.set(doc, cached);
|
|
57
|
+
if (cached.parsed.doc !== doc) docCache.set(cached.parsed.doc, cached);
|
|
46
58
|
}
|
|
59
|
+
if (diagnostics?.diagnostics !== void 0 || diagnostics?.strict === true) replayCapturedDiagnostics(cached, diagnostics);
|
|
60
|
+
return cached.parsed;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Run the normalisation, validation, and parse pipeline against a doc
|
|
64
|
+
* once, capturing every emitted diagnostic into a private array. The
|
|
65
|
+
* private array becomes the source of truth for later replay through
|
|
66
|
+
* caller-supplied sinks.
|
|
67
|
+
*
|
|
68
|
+
* The internal capturing sink does NOT set `strict`. Letting strict
|
|
69
|
+
* mode throw mid-walk would leave the cache empty and force every
|
|
70
|
+
* subsequent caller to re-run the pipeline from scratch — and the
|
|
71
|
+
* defect this caching strategy fixes was precisely that kind of
|
|
72
|
+
* re-running. Strict is enforced instead during replay
|
|
73
|
+
* (see {@link replayCapturedDiagnostics}).
|
|
74
|
+
*/
|
|
75
|
+
function buildCachedParse(doc) {
|
|
76
|
+
const captured = [];
|
|
77
|
+
const captureOpts = { diagnostics: (d) => captured.push(d) };
|
|
47
78
|
const version = detectOpenApiVersion(doc);
|
|
48
|
-
if (
|
|
79
|
+
if (version?.major === 3 && documentContainsKeyword(doc, "xml")) emitDiagnostic(captureOpts, {
|
|
49
80
|
code: "dropped-swagger-feature",
|
|
50
81
|
message: `OpenAPI ${String(version.major)}.${String(version.minor)} xml Schema Object metadata is not rendered and will be ignored`,
|
|
51
82
|
pointer: "",
|
|
@@ -54,17 +85,38 @@ function getParsed(doc, diagnostics) {
|
|
|
54
85
|
source: "openapi-3.x"
|
|
55
86
|
}
|
|
56
87
|
});
|
|
57
|
-
const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version, captureOpts) : doc;
|
|
89
|
+
validateSecuritySchemeTypes(normalisedDoc, captureOpts);
|
|
90
|
+
detectUnsupportedCrossSchemaRefs(normalisedDoc, captureOpts);
|
|
91
|
+
return {
|
|
92
|
+
parsed: parseOpenApiDocument(normalisedDoc),
|
|
93
|
+
diagnostics: captured,
|
|
94
|
+
notifiedSinks: /* @__PURE__ */ new WeakSet()
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Replay each captured diagnostic through the caller-supplied options.
|
|
99
|
+
*
|
|
100
|
+
* Strict mode is treated as a per-call invariant: when `strict` is set
|
|
101
|
+
* we always run the replay so the first captured diagnostic throws,
|
|
102
|
+
* matching the historical fail-fast contract regardless of how many
|
|
103
|
+
* times the cache has previously notified the sink.
|
|
104
|
+
*
|
|
105
|
+
* Non-strict, sink-bearing callers de-duplicate at the function-
|
|
106
|
+
* identity boundary: a second call with the same `(doc, sink)` pair
|
|
107
|
+
* short-circuits because the sink has already seen every captured
|
|
108
|
+
* diagnostic. This is the cardinality-1 guarantee that fixes the
|
|
109
|
+
* N-fold emission caused by parent-fans-out-into-children renders.
|
|
110
|
+
*
|
|
111
|
+
* The sink is only marked notified after a successful replay so a
|
|
112
|
+
* strict throw mid-replay does not silence a follow-up non-strict
|
|
113
|
+
* call that still wants the full captured set.
|
|
114
|
+
*/
|
|
115
|
+
function replayCapturedDiagnostics(cached, opts) {
|
|
116
|
+
const sink = opts.diagnostics;
|
|
117
|
+
if (!(opts.strict === true) && sink !== void 0 && cached.notifiedSinks.has(sink)) return;
|
|
118
|
+
for (const diagnostic of cached.diagnostics) emitDiagnostic(opts, diagnostic);
|
|
119
|
+
if (sink !== void 0) cached.notifiedSinks.add(sink);
|
|
68
120
|
}
|
|
69
121
|
/**
|
|
70
122
|
* Coerce an unknown value to a record, returning `undefined` when the
|
|
@@ -177,25 +229,6 @@ function detectUnsupportedCrossSchemaRefs(doc, diagnostics) {
|
|
|
177
229
|
walk(doc, "");
|
|
178
230
|
}
|
|
179
231
|
/**
|
|
180
|
-
* Recursively check whether any node in an OpenAPI document carries an
|
|
181
|
-
* `xml` annotation. Walks both objects and arrays so the check works
|
|
182
|
-
* for schemas in `components/schemas`, inline `paths`/`webhooks`
|
|
183
|
-
* schemas, request bodies, responses, headers, and parameters. Used
|
|
184
|
-
* by `getParsed` to surface the dropped-feature diagnostic for OAS
|
|
185
|
-
* 3.0/3.1 — the Swagger 2.0 path has its own detection in
|
|
186
|
-
* `swagger2.ts`.
|
|
187
|
-
*/
|
|
188
|
-
function docHasXmlAnywhere(node) {
|
|
189
|
-
if (Array.isArray(node)) {
|
|
190
|
-
for (const item of node) if (docHasXmlAnywhere(item)) return true;
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
if (!isObject(node)) return false;
|
|
194
|
-
if ("xml" in node && isObject(node.xml)) return true;
|
|
195
|
-
for (const value of Object.values(node)) if (docHasXmlAnywhere(value)) return true;
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
232
|
* Look up a Path Item Object on the (already-normalised) parsed document,
|
|
200
233
|
* following a single `$ref` hop into `components/pathItems` (OpenAPI 3.1)
|
|
201
234
|
* if present. Returns `undefined` when the path is not present or the
|
|
@@ -205,44 +238,71 @@ function docHasXmlAnywhere(node) {
|
|
|
205
238
|
* still surfacing path-item-level metadata to the React layer.
|
|
206
239
|
*/
|
|
207
240
|
function lookupPathItemNode(parsed, path, diagnostics) {
|
|
208
|
-
const
|
|
241
|
+
const paths = getProperty(parsed.doc, "paths");
|
|
242
|
+
const webhooks = getProperty(parsed.doc, "webhooks");
|
|
243
|
+
const pathsEntry = getProperty(paths, path);
|
|
244
|
+
const webhooksEntry = getProperty(webhooks, path);
|
|
245
|
+
if (isObject(pathsEntry) && isObject(webhooksEntry)) emitDiagnostic(diagnostics, {
|
|
246
|
+
code: "path-webhook-name-collision",
|
|
247
|
+
message: `Identifier "${path}" appears in both \`paths\` and \`webhooks\`; \`paths\` takes precedence`,
|
|
248
|
+
pointer: `/paths/${path.replace(/~/g, "~0").replace(/\//g, "~1")}`,
|
|
249
|
+
detail: { name: path }
|
|
250
|
+
});
|
|
251
|
+
const fromPaths = resolvePathItemNode(parsed, pathsEntry, diagnostics);
|
|
209
252
|
if (fromPaths !== void 0) return fromPaths;
|
|
210
|
-
return resolvePathItemNode(parsed,
|
|
253
|
+
return resolvePathItemNode(parsed, webhooksEntry, diagnostics);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Resolve a fragment `$ref` (must start with `#/`) against the parsed
|
|
257
|
+
* document by walking the JSON Pointer one segment at a time. Returns
|
|
258
|
+
* the resolved node only when every segment lands on a plain object;
|
|
259
|
+
* returns `undefined` when any intermediate segment is missing or
|
|
260
|
+
* non-object, or when the final node is not an object.
|
|
261
|
+
*
|
|
262
|
+
* Rejects `__proto__`, `constructor`, `prototype` segments — walking
|
|
263
|
+
* into any of these reads `Object.prototype` and would let a crafted
|
|
264
|
+
* pathItems `$ref` smuggle properties from the runtime prototype
|
|
265
|
+
* chain into the resolved Path Item Object.
|
|
266
|
+
*/
|
|
267
|
+
function lookupFragmentRef(parsed, ref) {
|
|
268
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
269
|
+
const parts = ref.slice(2).split("/");
|
|
270
|
+
let node = parsed.doc;
|
|
271
|
+
for (const part of parts) {
|
|
272
|
+
if (!isObject(node)) return void 0;
|
|
273
|
+
const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
274
|
+
if (isPrototypePollutingKey(decoded)) return void 0;
|
|
275
|
+
node = node[decoded];
|
|
276
|
+
}
|
|
277
|
+
return isObject(node) ? node : void 0;
|
|
211
278
|
}
|
|
212
279
|
function resolvePathItemNode(parsed, pathItem, diagnostics) {
|
|
213
280
|
if (!isObject(pathItem)) return void 0;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
281
|
+
return resolveRefChain(pathItem, {
|
|
282
|
+
lookup: (ref) => lookupFragmentRef(parsed, ref),
|
|
283
|
+
extractRef: (node) => {
|
|
284
|
+
const ref = getProperty(node, "$ref");
|
|
285
|
+
if (typeof ref !== "string") return void 0;
|
|
286
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
287
|
+
return ref;
|
|
288
|
+
},
|
|
289
|
+
onCycle: (ref) => {
|
|
221
290
|
emitDiagnostic(diagnostics, {
|
|
222
291
|
code: "cyclic-path-item-ref",
|
|
223
292
|
message: `Cyclic Path Item Object $ref "${ref}"`,
|
|
224
293
|
pointer: ref,
|
|
225
294
|
detail: { ref }
|
|
226
295
|
});
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (!isObject(node)) return current;
|
|
239
|
-
current = node;
|
|
240
|
-
}
|
|
241
|
-
emitDiagnostic(diagnostics, {
|
|
242
|
-
code: "path-item-ref-too-deep",
|
|
243
|
-
message: `Path Item Object $ref chain exceeded ${String(8)} hops`,
|
|
244
|
-
pointer: "",
|
|
245
|
-
detail: { maxHops: 8 }
|
|
296
|
+
},
|
|
297
|
+
onDepthExceeded: () => {
|
|
298
|
+
emitDiagnostic(diagnostics, {
|
|
299
|
+
code: "path-item-ref-too-deep",
|
|
300
|
+
message: `Path Item Object $ref chain exceeded ${String(8)} hops`,
|
|
301
|
+
pointer: "",
|
|
302
|
+
detail: { maxHops: 8 }
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
maxHops: 8
|
|
246
306
|
});
|
|
247
307
|
}
|
|
248
308
|
function extractPathItemInfo(pathItem) {
|
|
@@ -265,7 +325,7 @@ function extractPathItemInfo(pathItem) {
|
|
|
265
325
|
*/
|
|
266
326
|
function resolveOperationFromParsed(parsed, path, method, diagnostics) {
|
|
267
327
|
const pathItemNode = lookupPathItemNode(parsed, path, diagnostics);
|
|
268
|
-
const operation =
|
|
328
|
+
const operation = listAllOperations(parsed).find((op) => op.path === path && op.method === method);
|
|
269
329
|
if (operation === void 0) throw new Error(`Operation not found: ${method.toUpperCase()} ${path}`);
|
|
270
330
|
if (pathItemNode === void 0) throw new Error(`Path item missing for ${method.toUpperCase()} ${path}`);
|
|
271
331
|
return {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { d as FieldOverrides, j as WalkedField, u as FieldOverride, w as SchemaMeta } from "../types-
|
|
2
|
-
import { t as Diagnostic } from "../diagnostics-
|
|
3
|
-
import { t as SchemaError } from "../errors-
|
|
4
|
-
import { l as RenderProps, r as ComponentResolver } from "../renderer-
|
|
5
|
-
import {
|
|
1
|
+
import { d as FieldOverrides, j as WalkedField, u as FieldOverride, w as SchemaMeta } from "../types-BTB73MB8.mjs";
|
|
2
|
+
import { t as Diagnostic } from "../diagnostics-Cbwak-ZX.mjs";
|
|
3
|
+
import { t as SchemaError } from "../errors-g_MCTQel.mjs";
|
|
4
|
+
import { l as RenderProps, r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
|
|
5
|
+
import { FromJSONSchema, FromJSONSchemaMode, IsSwagger2Doc, PathOfType, RejectUnrepresentableZod, ResolveOpenAPIRef, TypeAtPath, __SchemaInferenceFellBack } from "../core/typeInference.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
8
8
|
import { ReactNode } from "react";
|
|
@@ -36,7 +36,7 @@ declare function SchemaProvider({
|
|
|
36
36
|
* or `<SchemaProvider>` instead.
|
|
37
37
|
*/
|
|
38
38
|
declare function registerWidget(name: string, render: (props: RenderProps) => unknown): void;
|
|
39
|
-
type InferFields<T, Ref extends string | undefined> = T extends z.ZodType ? FieldOverrides<z.infer<T>> : T extends {
|
|
39
|
+
type InferFields<T, Ref extends string | undefined> = IsSwagger2Doc<T> extends true ? __SchemaInferenceFellBack : T extends z.ZodType ? FieldOverrides<z.infer<T>> : T extends {
|
|
40
40
|
openapi: unknown;
|
|
41
41
|
} ? Ref extends string ? FieldOverrides<ResolveOpenAPIRef<T & Record<string, unknown>, Ref>> : Record<string, FieldOverride> : T extends object ? unknown extends FromJSONSchema<T> ? Record<string, FieldOverride> : FieldOverrides<FromJSONSchema<T>> : Record<string, FieldOverride>;
|
|
42
42
|
/**
|
|
@@ -55,7 +55,7 @@ type InferFields<T, Ref extends string | undefined> = T extends z.ZodType ? Fiel
|
|
|
55
55
|
* without a ref), the result falls back to `unknown` so callers can
|
|
56
56
|
* still supply arbitrary values.
|
|
57
57
|
*/
|
|
58
|
-
type InferSchemaValue<T, Ref extends string | undefined, Mode extends FromJSONSchemaMode> = T extends z.ZodType ? Mode extends "input" ? z.input<T> : z.output<T> : T extends {
|
|
58
|
+
type InferSchemaValue<T, Ref extends string | undefined, Mode extends FromJSONSchemaMode> = IsSwagger2Doc<T> extends true ? __SchemaInferenceFellBack : T extends z.ZodType ? Mode extends "input" ? z.input<T> : z.output<T> : T extends {
|
|
59
59
|
openapi: unknown;
|
|
60
60
|
} ? Ref extends string ? ResolveOpenAPIRef<T & Record<string, unknown>, Ref, [], Mode> : unknown : T extends object ? FromJSONSchema<T, Record<string, never>, [], Mode> | (unknown extends FromJSONSchema<T> ? unknown : never) extends infer V ? V : unknown : unknown;
|
|
61
61
|
/**
|
|
@@ -132,6 +132,17 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
|
|
|
132
132
|
* The narrowing is fully expressible through the helper alias
|
|
133
133
|
* without forcing every existing caller to update their value
|
|
134
134
|
* shapes for `exactOptionalPropertyTypes` / enum literal widening.
|
|
135
|
+
*
|
|
136
|
+
* TODO(round7-integration): promote to
|
|
137
|
+
* `NarrowAtPath<InferSchemaValue<T, Ref, "output">, P>` once the
|
|
138
|
+
* test fixtures (headless union, discriminated union,
|
|
139
|
+
* schemaview equivalence, type-inference issue fixes) are
|
|
140
|
+
* migrated to either narrow their fixtures or accept the loose
|
|
141
|
+
* boundary. The retype cascades through call sites that
|
|
142
|
+
* intentionally pass invalid values to exercise fallback paths
|
|
143
|
+
* (`value={undefined}`, off-discriminator values, etc.) and
|
|
144
|
+
* through fixtures whose enum/literal types widen at the call
|
|
145
|
+
* site. Coordinated migration is required.
|
|
135
146
|
*/
|
|
136
147
|
value?: unknown;
|
|
137
148
|
/**
|
|
@@ -144,6 +155,15 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
|
|
|
144
155
|
* without an unsafe boundary cast. The {@link InferredInputValue}
|
|
145
156
|
* alias is the recommended way for callers to narrow on the
|
|
146
157
|
* consumer side — `onChange={(v) => { const u = v as InferredInputValue<typeof schema>; ... }}`.
|
|
158
|
+
*
|
|
159
|
+
* TODO(round7-integration): promote to
|
|
160
|
+
* `(value: NarrowAtPath<InferSchemaValue<T, Ref, "input">, P>) => void`
|
|
161
|
+
* alongside the `value` retype. The contravariant boundary needs
|
|
162
|
+
* an internal cast at the only call site (`onChange?.(nextValue)`
|
|
163
|
+
* in `handleChange`) that the project's no-`as` rule disallows
|
|
164
|
+
* without explicit justification. The right place to introduce
|
|
165
|
+
* the cast is a tiny typed boundary helper accompanied by the
|
|
166
|
+
* fixture migration noted above.
|
|
147
167
|
*/
|
|
148
168
|
onChange?: (value: unknown) => void;
|
|
149
169
|
/** Run schema.safeParse() on change and surface errors via onValidationError. */
|
|
@@ -181,6 +201,11 @@ declare function SchemaComponent<T = unknown, Ref extends string | undefined = u
|
|
|
181
201
|
* Append a child path suffix to a parent path. When the suffix is omitted
|
|
182
202
|
* (e.g. transparent wrappers like union options), the parent path is
|
|
183
203
|
* returned unchanged so the child inherits the parent's id.
|
|
204
|
+
*
|
|
205
|
+
* Bracketed array indices like `[0]` append directly so `tags` + `[0]`
|
|
206
|
+
* becomes `tags[0]` rather than `tags.[0]` — matching the canonical form
|
|
207
|
+
* used by `html/a11y.ts` `joinPath` and `react/fieldPath.ts` `resolvePath`,
|
|
208
|
+
* which already parses bracket notation when navigating WalkedField trees.
|
|
184
209
|
*/
|
|
185
210
|
declare function joinPath(parent: string, suffix: string | undefined): string;
|
|
186
211
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
2
|
+
import { getProperty, isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
3
3
|
import "../core/limits.mjs";
|
|
4
4
|
import { SchemaFieldError, SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
|
|
5
5
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
@@ -87,6 +87,7 @@ function SchemaComponent(props) {
|
|
|
87
87
|
}
|
|
88
88
|
throw error;
|
|
89
89
|
}
|
|
90
|
+
const fieldsRecord = toRecordOrUndefined(fields);
|
|
90
91
|
const handleChange = useCallback((nextValue) => {
|
|
91
92
|
if (validate) {
|
|
92
93
|
let error;
|
|
@@ -102,7 +103,7 @@ function SchemaComponent(props) {
|
|
|
102
103
|
}
|
|
103
104
|
if (error !== void 0) {
|
|
104
105
|
onValidationError?.(error);
|
|
105
|
-
dispatchFieldErrors(
|
|
106
|
+
dispatchFieldErrors(fieldsRecord, error);
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
onChange?.(nextValue);
|
|
@@ -112,7 +113,7 @@ function SchemaComponent(props) {
|
|
|
112
113
|
jsonSchema,
|
|
113
114
|
onChange,
|
|
114
115
|
onValidationError,
|
|
115
|
-
|
|
116
|
+
fieldsRecord,
|
|
116
117
|
onDiagnostic,
|
|
117
118
|
onError,
|
|
118
119
|
schemaInput
|
|
@@ -120,7 +121,7 @@ function SchemaComponent(props) {
|
|
|
120
121
|
const walkOptions = {
|
|
121
122
|
componentMeta: mergedMeta,
|
|
122
123
|
rootMeta,
|
|
123
|
-
fieldOverrides:
|
|
124
|
+
fieldOverrides: fieldsRecord,
|
|
124
125
|
rootDocument,
|
|
125
126
|
...diagnostics !== void 0 ? { diagnostics } : {}
|
|
126
127
|
};
|
|
@@ -136,10 +137,16 @@ function SchemaComponent(props) {
|
|
|
136
137
|
* Append a child path suffix to a parent path. When the suffix is omitted
|
|
137
138
|
* (e.g. transparent wrappers like union options), the parent path is
|
|
138
139
|
* returned unchanged so the child inherits the parent's id.
|
|
140
|
+
*
|
|
141
|
+
* Bracketed array indices like `[0]` append directly so `tags` + `[0]`
|
|
142
|
+
* becomes `tags[0]` rather than `tags.[0]` — matching the canonical form
|
|
143
|
+
* used by `html/a11y.ts` `joinPath` and `react/fieldPath.ts` `resolvePath`,
|
|
144
|
+
* which already parses bracket notation when navigating WalkedField trees.
|
|
139
145
|
*/
|
|
140
146
|
function joinPath(parent, suffix) {
|
|
141
147
|
if (suffix === void 0 || suffix.length === 0) return parent;
|
|
142
148
|
if (parent.length === 0) return suffix;
|
|
149
|
+
if (suffix.startsWith("[")) return `${parent}${suffix}`;
|
|
143
150
|
return `${parent}.${suffix}`;
|
|
144
151
|
}
|
|
145
152
|
/**
|
|
@@ -170,9 +177,9 @@ function sanitisePrefix(value) {
|
|
|
170
177
|
*/
|
|
171
178
|
function runValidation(zodSchema, jsonSchema, value, onDiagnostic) {
|
|
172
179
|
if (zodSchema !== void 0 && isObject(zodSchema)) {
|
|
173
|
-
const
|
|
174
|
-
if (isCallable(
|
|
175
|
-
const result =
|
|
180
|
+
const validateFn = isCodecSchema(zodSchema) ? zodSchema.safeEncode : zodSchema.safeParse;
|
|
181
|
+
if (isCallable(validateFn)) {
|
|
182
|
+
const result = validateFn(value);
|
|
176
183
|
if (isObject(result) && "success" in result && result.success !== true) return result.error;
|
|
177
184
|
return;
|
|
178
185
|
}
|
|
@@ -261,12 +268,11 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
|
|
|
261
268
|
if (fieldTree === void 0) throw new SchemaFieldError(`Field not found: ${path}`, schemaInput, path);
|
|
262
269
|
const fieldValue = resolveValue(value, path);
|
|
263
270
|
const handleChange = useCallback((nextFieldValue) => {
|
|
271
|
+
const newRootValue = setNestedValue(value, path, nextFieldValue);
|
|
264
272
|
if (validate) {
|
|
265
|
-
const newRootValue = setNestedValue(value, path, nextFieldValue);
|
|
266
273
|
const error = runValidation(zodSchema, jsonSchema, newRootValue);
|
|
267
274
|
if (error !== void 0) onValidationError?.(error);
|
|
268
275
|
}
|
|
269
|
-
const newRootValue = setNestedValue(value, path, nextFieldValue);
|
|
270
276
|
onChange?.(newRootValue);
|
|
271
277
|
}, [
|
|
272
278
|
validate,
|
|
@@ -288,24 +294,21 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
|
|
|
288
294
|
* Dispatch Zod errors to per-field onValidationError callbacks.
|
|
289
295
|
* Walks the fields override tree and matches errors by path prefix.
|
|
290
296
|
*
|
|
291
|
-
* The `fields`
|
|
292
|
-
* `InferFields<T, Ref>`
|
|
293
|
-
* `FieldOverrides
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* (`
|
|
297
|
-
*
|
|
298
|
-
* `
|
|
299
|
-
* visible to readers without changing runtime behaviour.
|
|
297
|
+
* The runtime shape of `fields` is always `Record<string, FieldOverride>`
|
|
298
|
+
* after `InferFields<T, Ref>` is erased — the typed variants
|
|
299
|
+
* (`FieldOverrides<U>`) and the loose `Record<string, FieldOverride>`
|
|
300
|
+
* fallback share the same structural shape, so the dispatch logic only
|
|
301
|
+
* needs the loose record. The previous parameter union
|
|
302
|
+
* (`Record<string, FieldOverride> | FieldOverrides<unknown> |
|
|
303
|
+
* undefined`) collapsed because `FieldOverrides<unknown>` reduces to
|
|
304
|
+
* `{}`, contributing no extra precision while adding noise to readers.
|
|
300
305
|
*/
|
|
301
306
|
function dispatchFieldErrors(fields, error) {
|
|
302
307
|
if (fields === void 0 || !isObject(error)) return;
|
|
303
308
|
if (!("issues" in error)) return;
|
|
304
309
|
const issues = error.issues;
|
|
305
310
|
if (!Array.isArray(issues)) return;
|
|
306
|
-
const
|
|
307
|
-
if (overrides === void 0) return;
|
|
308
|
-
for (const [key, override] of Object.entries(overrides)) {
|
|
311
|
+
for (const [key, override] of Object.entries(fields)) {
|
|
309
312
|
if (override === void 0 || typeof override !== "object") continue;
|
|
310
313
|
if (override === null) continue;
|
|
311
314
|
if (!("onValidationError" in override)) continue;
|
|
@@ -327,5 +330,26 @@ function isFieldErrorCallback(value) {
|
|
|
327
330
|
function isCallable(value) {
|
|
328
331
|
return typeof value === "function";
|
|
329
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* True when `value` is a Zod schema implemented as a codec.
|
|
335
|
+
*
|
|
336
|
+
* Detection looks for `"$ZodCodec"` in the schema's `_zod.traits`
|
|
337
|
+
* Set, mirroring the runtime detection used inside
|
|
338
|
+
* `core/adapter.ts` (`isCodecSchema` there) and Zod's own
|
|
339
|
+
* `isTransforming` helper. Duplicated here rather than imported
|
|
340
|
+
* because adapter.ts does not export the helper and is outside this
|
|
341
|
+
* fix-cycle's owned files.
|
|
342
|
+
*
|
|
343
|
+
* TODO(round7-integration): replace with an `import` once
|
|
344
|
+
* `isCodecSchema` is exported from `core/adapter.ts` (or promoted to
|
|
345
|
+
* `core/guards.ts`) by a coordinating commit.
|
|
346
|
+
*/
|
|
347
|
+
function isCodecSchema(value) {
|
|
348
|
+
const zod = getProperty(value, "_zod");
|
|
349
|
+
if (!isObject(zod)) return false;
|
|
350
|
+
const traits = zod.traits;
|
|
351
|
+
if (traits instanceof Set) return traits.has("$ZodCodec");
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
330
354
|
//#endregion
|
|
331
355
|
export { SchemaComponent, SchemaField, SchemaProvider, joinPath, registerWidget, renderField, sanitisePrefix };
|
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
import { w as SchemaMeta } from "../types-
|
|
2
|
-
import { t as Diagnostic } from "../diagnostics-
|
|
3
|
-
import { r as ComponentResolver } from "../renderer-
|
|
1
|
+
import { w as SchemaMeta } from "../types-BTB73MB8.mjs";
|
|
2
|
+
import { t as Diagnostic } from "../diagnostics-Cbwak-ZX.mjs";
|
|
3
|
+
import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
|
|
4
|
+
import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
|
|
4
5
|
import { WidgetMap } from "./SchemaComponent.mjs";
|
|
5
6
|
import { ReactNode } from "react";
|
|
6
7
|
|
|
7
8
|
//#region src/react/SchemaView.d.ts
|
|
8
|
-
interface SchemaViewProps {
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefined> {
|
|
10
|
+
/**
|
|
11
|
+
* Zod schema, JSON Schema object, or OpenAPI document.
|
|
12
|
+
*
|
|
13
|
+
* Subject to the same compile-time rejection of unrepresentable
|
|
14
|
+
* Zod 4 types as {@link SchemaComponentProps.schema} — see
|
|
15
|
+
* {@link RejectUnrepresentableZod}.
|
|
16
|
+
*/
|
|
17
|
+
schema: RejectUnrepresentableZod<T>;
|
|
11
18
|
/** For OpenAPI: a ref string like "#/components/schemas/User". */
|
|
12
|
-
ref?:
|
|
13
|
-
/**
|
|
19
|
+
ref?: Ref;
|
|
20
|
+
/**
|
|
21
|
+
* Current value to render.
|
|
22
|
+
*
|
|
23
|
+
* TYPE BOUNDARY NOTE: mirrors `SchemaComponentProps.value` — kept
|
|
24
|
+
* as `unknown` so the same boundary holds for both the editable
|
|
25
|
+
* (`SchemaComponent`) and read-only (`SchemaView`) entry points.
|
|
26
|
+
* The {@link InferredOutputValue} alias is the recommended way
|
|
27
|
+
* for callers to narrow on the consumer side.
|
|
28
|
+
*
|
|
29
|
+
* TODO(round7-integration): promote to
|
|
30
|
+
* `InferSchemaValue<T, Ref, "output">` alongside the matching
|
|
31
|
+
* `SchemaComponent` change. See the type-boundary note on
|
|
32
|
+
* `SchemaComponentProps.value` for the migration coordination.
|
|
33
|
+
*/
|
|
14
34
|
value?: unknown;
|
|
15
35
|
/** Per-field meta overrides. */
|
|
16
36
|
fields?: Record<string, unknown>;
|
|
@@ -44,7 +64,7 @@ interface SchemaViewProps {
|
|
|
44
64
|
* Always renders in read-only mode. For editable forms, use
|
|
45
65
|
* `<SchemaComponent>` with `"use client"`.
|
|
46
66
|
*/
|
|
47
|
-
declare function SchemaView({
|
|
67
|
+
declare function SchemaView<T = unknown, Ref extends string | undefined = undefined>({
|
|
48
68
|
schema: schemaInput,
|
|
49
69
|
ref: refInput,
|
|
50
70
|
value,
|
|
@@ -56,6 +76,6 @@ declare function SchemaView({
|
|
|
56
76
|
onDiagnostic,
|
|
57
77
|
strict,
|
|
58
78
|
idPrefix
|
|
59
|
-
}: SchemaViewProps): ReactNode;
|
|
79
|
+
}: SchemaViewProps<T, Ref>): ReactNode;
|
|
60
80
|
//#endregion
|
|
61
81
|
export { SchemaView, SchemaViewProps };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { j as WalkedField } from "../types-BTB73MB8.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/react/a11y.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build the ARIA attribute bundle for a renderer.
|
|
6
|
+
*
|
|
7
|
+
* - `aria-required="true"` whenever the field is non-optional.
|
|
8
|
+
* - `aria-label=<description>` when a non-empty description is supplied.
|
|
9
|
+
*
|
|
10
|
+
* Returns a plain `Record<string, string>` (rather than a typed
|
|
11
|
+
* attribute interface) so callers can spread the result into any JSX
|
|
12
|
+
* element type without per-element TypeScript widening.
|
|
13
|
+
*
|
|
14
|
+
* Each helper from `html/a11y.ts` returns its corresponding fragment
|
|
15
|
+
* separately. The React renderers merge them into a single object
|
|
16
|
+
* here because the headless renderers compose one attribute bag per
|
|
17
|
+
* `<input>` element rather than threading multiple bags through `h()`.
|
|
18
|
+
*/
|
|
19
|
+
declare function buildAriaAttrs(tree: WalkedField, description?: unknown): Record<string, string>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { buildAriaAttrs };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/react/a11y.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build the ARIA attribute bundle for a renderer.
|
|
4
|
+
*
|
|
5
|
+
* - `aria-required="true"` whenever the field is non-optional.
|
|
6
|
+
* - `aria-label=<description>` when a non-empty description is supplied.
|
|
7
|
+
*
|
|
8
|
+
* Returns a plain `Record<string, string>` (rather than a typed
|
|
9
|
+
* attribute interface) so callers can spread the result into any JSX
|
|
10
|
+
* element type without per-element TypeScript widening.
|
|
11
|
+
*
|
|
12
|
+
* Each helper from `html/a11y.ts` returns its corresponding fragment
|
|
13
|
+
* separately. The React renderers merge them into a single object
|
|
14
|
+
* here because the headless renderers compose one attribute bag per
|
|
15
|
+
* `<input>` element rather than threading multiple bags through `h()`.
|
|
16
|
+
*/
|
|
17
|
+
function buildAriaAttrs(tree, description) {
|
|
18
|
+
const attrs = {};
|
|
19
|
+
if (tree.isOptional === false) attrs["aria-required"] = "true";
|
|
20
|
+
if (typeof description === "string" && description.length > 0) attrs["aria-label"] = description;
|
|
21
|
+
return attrs;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { buildAriaAttrs };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { j as WalkedField } from "../types-
|
|
2
|
-
import { l as RenderProps } from "../renderer-
|
|
1
|
+
import { j as WalkedField } from "../types-BTB73MB8.mjs";
|
|
2
|
+
import { l as RenderProps } from "../renderer-CXJ8y0qw.mjs";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/react/headlessRenderers.d.ts
|
|
@@ -10,15 +10,14 @@ import { ReactNode } from "react";
|
|
|
10
10
|
declare function toReactNode(value: unknown): ReactNode;
|
|
11
11
|
/**
|
|
12
12
|
* Build a stable, unique input ID from the path.
|
|
13
|
-
* Used for `htmlFor`/`id` association between labels and inputs.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Re-exported alias for {@link fieldDomId} so external themes (shadcn,
|
|
15
|
+
* MUI, mantine, radix) keep importing `inputId` from the React entry
|
|
16
|
+
* point. Both the React and HTML renderers must derive the same id from
|
|
17
|
+
* the same path — `fieldDomId` in `core/idPath.ts` is the single
|
|
18
|
+
* source-of-truth.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
* id valid as a CSS selector and predictable in test queries.
|
|
20
|
+
* Throws on an empty path; see `fieldDomId` for the rationale.
|
|
22
21
|
*/
|
|
23
22
|
declare function inputId(path: string): string;
|
|
24
23
|
declare function renderString(props: RenderProps): ReactNode;
|