schema-components 1.23.0 → 1.26.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.
Files changed (48) hide show
  1. package/dist/adapter-MiEFkRVV.d.mts +172 -0
  2. package/dist/core/adapter.d.mts +2 -141
  3. package/dist/core/adapter.mjs +39 -5
  4. package/dist/core/constraints.d.mts +1 -1
  5. package/dist/core/diagnostics.d.mts +1 -1
  6. package/dist/core/errors.d.mts +1 -1
  7. package/dist/core/formats.d.mts +1 -1
  8. package/dist/core/limits.d.mts +1 -1
  9. package/dist/core/merge.d.mts +1 -1
  10. package/dist/core/normalise.d.mts +2 -2
  11. package/dist/core/ref.d.mts +1 -1
  12. package/dist/core/ref.mjs +4 -5
  13. package/dist/core/renderer.d.mts +1 -1
  14. package/dist/core/swagger2.d.mts +1 -1
  15. package/dist/core/typeInference.d.mts +1 -1
  16. package/dist/core/version.d.mts +1 -1
  17. package/dist/core/walkBuilders.d.mts +2 -2
  18. package/dist/{diagnostics-BS2kaUyE.d.mts → diagnostics-Cbwak-ZX.d.mts} +1 -1
  19. package/dist/html/a11y.d.mts +1 -1
  20. package/dist/html/renderToHtml.d.mts +1 -1
  21. package/dist/html/renderToHtmlStream.d.mts +1 -1
  22. package/dist/html/renderers.d.mts +6 -17
  23. package/dist/html/renderers.mjs +5 -16
  24. package/dist/html/streamRenderers.d.mts +2 -2
  25. package/dist/openapi/components.d.mts +1 -1
  26. package/dist/openapi/components.mjs +2 -2
  27. package/dist/openapi/parser.d.mts +13 -4
  28. package/dist/openapi/parser.mjs +59 -33
  29. package/dist/openapi/resolve.d.mts +1 -1
  30. package/dist/openapi/resolve.mjs +2 -2
  31. package/dist/react/SchemaComponent.d.mts +74 -64
  32. package/dist/react/SchemaComponent.mjs +23 -31
  33. package/dist/react/SchemaView.d.mts +24 -20
  34. package/dist/react/SchemaView.mjs +6 -3
  35. package/dist/react/headless.d.mts +1 -1
  36. package/dist/react/headlessRenderers.d.mts +1 -1
  37. package/dist/react/headlessRenderers.mjs +1 -1
  38. package/dist/{ref-DjLEKa_E.d.mts → ref-TdeMfaV_.d.mts} +1 -1
  39. package/dist/themes/mantine.d.mts +1 -1
  40. package/dist/themes/mui.d.mts +1 -1
  41. package/dist/themes/mui.mjs +1 -1
  42. package/dist/themes/radix.d.mts +1 -1
  43. package/dist/themes/shadcn.d.mts +1 -1
  44. package/package.json +6 -2
  45. /package/dist/{errors-g_MCTQel.d.mts → errors-DQSIK4n1.d.mts} +0 -0
  46. /package/dist/{limits-Cw5QZND8.d.mts → limits-DJhgx5Ay.d.mts} +0 -0
  47. /package/dist/{renderer-CXJ8y0qw.d.mts → renderer-Ul9taFYp.d.mts} +0 -0
  48. /package/dist/{version-BFTVLsdb.d.mts → version-ZzL5R6cS.d.mts} +0 -0
@@ -15,16 +15,11 @@ import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAt
15
15
  * `aria-controls`, `aria-labelledby`, and `htmlFor` references resolve
16
16
  * consistently across the React, sync-HTML, and streaming-HTML outputs.
17
17
  *
18
- * The wrapper tolerates an empty path here (returning `sc-`) for the
19
- * sole reason that a leaf renderer at the schema root would otherwise
20
- * throw `renderToHtml(z.string())` is a rare but valid call shape.
21
- * Container renderers thread a non-empty path through `renderChild`, so
22
- * the empty-id fallback can never produce sibling collisions inside a
23
- * structured form.
24
- *
25
- * TODO(round7-integration): once `renderToHtml` always threads a stable
26
- * root path (e.g. `"$"`) into the leaf renderers, drop this wrapper and
27
- * call `fieldDomId` directly so the throw fires as designed.
18
+ * The wrapper tolerates an empty path here (returning `sc-`) so that
19
+ * a leaf renderer at the schema root `renderToHtml(z.string())` — has
20
+ * a usable id without throwing. Container renderers always thread a
21
+ * non-empty path through `renderChild`, so the empty-id fallback can
22
+ * never produce sibling collisions inside a structured form.
28
23
  */
29
24
  function fieldId(path) {
30
25
  if (path.length === 0) return "sc-";
@@ -41,9 +36,6 @@ function fieldId(path) {
41
36
  * Exported because `streamRenderers.ts` needs to derive identical ids
42
37
  * — the panel id on the `<div role="tabpanel">` must match the
43
38
  * `aria-controls` on every tab regardless of which pipeline rendered it.
44
- *
45
- * TODO(round7-integration): drop the empty-path branch once `renderToHtml`
46
- * threads a stable root path so `panelIdFor` can be called directly.
47
39
  */
48
40
  function panelId(path) {
49
41
  if (path.length === 0) return `${fieldId(path)}-panel`;
@@ -52,9 +44,6 @@ function panelId(path) {
52
44
  /**
53
45
  * Tab id for tab `i` within a discriminated union at `path`. Mirror of
54
46
  * `panelId` above — see its comment.
55
- *
56
- * TODO(round7-integration): drop the empty-path branch once `renderToHtml`
57
- * threads a stable root path so `tabIdFor` can be called directly.
58
47
  */
59
48
  function tabId(path, i) {
60
49
  if (path.length === 0) return `${fieldId(path)}-tab-${String(i)}`;
@@ -1,6 +1,6 @@
1
1
  import { j as WalkedField } from "../types-BTB73MB8.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
3
- import { o as HtmlResolver } from "../renderer-CXJ8y0qw.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
3
+ import { o as HtmlResolver } from "../renderer-Ul9taFYp.mjs";
4
4
  import { HtmlElement } from "./html.mjs";
5
5
 
6
6
  //#region src/html/streamRenderers.d.ts
@@ -1,5 +1,5 @@
1
1
  import { u as FieldOverride, w as SchemaMeta } from "../types-BTB73MB8.mjs";
2
- import { r as DiagnosticSink } from "../diagnostics-BS2kaUyE.mjs";
2
+ import { r as DiagnosticSink } from "../diagnostics-Cbwak-ZX.mjs";
3
3
  import { InferParameterOverrides, InferRequestBodyFields, InferResponseFields } from "../core/typeInference.mjs";
4
4
  import { WidgetMap } from "../react/SchemaComponent.mjs";
5
5
  import { ReactNode } from "react";
@@ -2,15 +2,15 @@ import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
2
2
  import { emitDiagnostic } from "../core/diagnostics.mjs";
3
3
  import { extractRootMetaFromJson } from "../core/adapter.mjs";
4
4
  import { walk } from "../core/walker.mjs";
5
+ import { joinPath, renderField, sanitisePrefix } from "../react/SchemaComponent.mjs";
5
6
  import { ApiCallbacks } from "./ApiCallbacks.mjs";
6
7
  import { ApiLinks } from "./ApiLinks.mjs";
7
8
  import { ApiResponseHeaders } from "./ApiResponseHeaders.mjs";
8
9
  import { ApiSecurity } from "./ApiSecurity.mjs";
9
10
  import { getExternalDocs, getLinks, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listCallbacks, listWebhooks } from "./parser.mjs";
10
- import { joinPath, renderField, sanitisePrefix } from "../react/SchemaComponent.mjs";
11
11
  import { getParsed, resolveOperationFromParsed, resolveParametersFromParsed, resolveRequestBodyFromParsed, resolveResponseFromParsed, toDoc } from "./resolve.mjs";
12
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
12
  import { useId } from "react";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
14
  //#region src/openapi/components.tsx
15
15
  /**
16
16
  * OpenAPI React components with type-safe generics.
@@ -1,5 +1,5 @@
1
1
  import { m as JsonObject } from "../types-BTB73MB8.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
3
3
 
4
4
  //#region src/openapi/parser.d.ts
5
5
  interface OpenApiDocument {
@@ -87,17 +87,26 @@ interface LinkInfo {
87
87
  }
88
88
  declare function parseOpenApiDocument(doc: JsonObject): OpenApiDocument;
89
89
  declare function getSchema(parsed: OpenApiDocument, ref: string): JsonObject | undefined;
90
- declare function listOperations(parsed: OpenApiDocument, diagnostics?: DiagnosticsOptions): OperationInfo[];
90
+ declare function listOperations(parsed: OpenApiDocument, diagnostics?: DiagnosticsOptions, seenIds?: Map<string, string>): OperationInfo[];
91
91
  declare function getParameters(parsed: OpenApiDocument, path: string, method: string, diagnostics?: DiagnosticsOptions): ParameterInfo[];
92
92
  declare function getRequestBody(parsed: OpenApiDocument, path: string, method: string): RequestBodyInfo | undefined;
93
93
  declare function getResponses(parsed: OpenApiDocument, path: string, method: string, diagnostics?: DiagnosticsOptions): ResponseInfo[];
94
94
  declare function getSecurityRequirements(parsed: OpenApiDocument, path: string, method: string): SecurityRequirement[];
95
95
  declare function getSecuritySchemes(parsed: OpenApiDocument): Map<string, SecurityScheme>;
96
96
  declare function getResponseHeaders(response: JsonObject, doc?: JsonObject, diagnostics?: DiagnosticsOptions): Map<string, HeaderInfo>;
97
- declare function listWebhooks(parsed: OpenApiDocument, diagnostics?: DiagnosticsOptions): WebhookInfo[];
97
+ declare function listWebhooks(parsed: OpenApiDocument, diagnostics?: DiagnosticsOptions, seenIds?: Map<string, string>): WebhookInfo[];
98
+ /**
99
+ * Enumerate every operation in the document — both the `paths` map and
100
+ * the OpenAPI 3.1 `webhooks` map — sharing a single `seenIds` cache so
101
+ * cross-list `operationId` collisions surface the same way as same-list
102
+ * collisions. Returns the path-operation list followed by webhook
103
+ * operations (flattened); callers that need the structured webhook
104
+ * grouping should call `listWebhooks` directly.
105
+ */
106
+ declare function listAllOperations(parsed: OpenApiDocument, diagnostics?: DiagnosticsOptions): OperationInfo[];
98
107
  declare function getExternalDocs(obj: JsonObject): ExternalDocs | undefined;
99
108
  declare function getXmlInfo(schema: JsonObject): XmlInfo | undefined;
100
109
  declare function listCallbacks(parsed: OpenApiDocument, path: string, method: string, diagnostics?: DiagnosticsOptions): CallbackInfo[];
101
110
  declare function getLinks(parsed: OpenApiDocument, path: string, method: string, statusCode: string, diagnostics?: DiagnosticsOptions): LinkInfo[];
102
111
  //#endregion
103
- export { CallbackInfo, ExternalDocs, HeaderInfo, LinkInfo, OpenApiDocument, OperationInfo, ParameterInfo, ParameterLocation, RequestBodyInfo, ResponseInfo, SecurityRequirement, SecurityScheme, WebhookInfo, XmlInfo, getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
112
+ export { CallbackInfo, ExternalDocs, HeaderInfo, LinkInfo, OpenApiDocument, OperationInfo, ParameterInfo, ParameterLocation, RequestBodyInfo, ResponseInfo, SecurityRequirement, SecurityScheme, WebhookInfo, XmlInfo, getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listAllOperations, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
@@ -96,11 +96,39 @@ function lookupPathItem(parsed, path) {
96
96
  if (resolved !== void 0) return resolved;
97
97
  return resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "webhooks"), path));
98
98
  }
99
- function listOperations(parsed, diagnostics) {
99
+ /**
100
+ * Record an `operationId` against a shared `seenIds` map and emit a
101
+ * `duplicate-operation-id` diagnostic when a subsequent location reuses
102
+ * the same identifier. Returns the original `operationId` so the caller
103
+ * can pass the value straight onto its `OperationInfo`.
104
+ *
105
+ * When the same map is threaded through `listOperations` and
106
+ * `listWebhooks` (see `listAllOperations`), cross-list collisions
107
+ * between a path operation and a webhook operation surface as the same
108
+ * diagnostic class as same-list collisions.
109
+ */
110
+ function recordOperationId(operationId, location, pointer, seenIds, diagnostics) {
111
+ if (operationId === void 0) return;
112
+ const firstSeenAt = seenIds.get(operationId);
113
+ if (firstSeenAt !== void 0) {
114
+ emitDiagnostic(diagnostics, {
115
+ code: "duplicate-operation-id",
116
+ message: `operationId "${operationId}" is declared more than once (first at ${firstSeenAt}, again at ${location})`,
117
+ pointer,
118
+ detail: {
119
+ operationId,
120
+ firstSeenAt,
121
+ duplicateAt: location
122
+ }
123
+ });
124
+ return;
125
+ }
126
+ seenIds.set(operationId, location);
127
+ }
128
+ function listOperations(parsed, diagnostics, seenIds = /* @__PURE__ */ new Map()) {
100
129
  const operations = [];
101
130
  const paths = getProperty(parsed.doc, "paths");
102
131
  if (!isObject(paths)) return operations;
103
- const seenIds = /* @__PURE__ */ new Map();
104
132
  for (const [path, rawPathItem] of Object.entries(paths)) {
105
133
  const pathItem = resolvePathItem(parsed, rawPathItem, diagnostics);
106
134
  if (pathItem === void 0) continue;
@@ -108,20 +136,7 @@ function listOperations(parsed, diagnostics) {
108
136
  const operation = getProperty(pathItem, method);
109
137
  if (!isObject(operation)) continue;
110
138
  const operationId = getString(operation, "operationId");
111
- if (operationId !== void 0) {
112
- const firstSeenAt = seenIds.get(operationId);
113
- if (firstSeenAt !== void 0) emitDiagnostic(diagnostics, {
114
- code: "duplicate-operation-id",
115
- message: `operationId "${operationId}" is declared more than once (first at ${firstSeenAt}, again at ${method.toUpperCase()} ${path})`,
116
- pointer: `/paths/${jsonPointerEscape(path)}/${method}/operationId`,
117
- detail: {
118
- operationId,
119
- firstSeenAt,
120
- duplicateAt: `${method.toUpperCase()} ${path}`
121
- }
122
- });
123
- else seenIds.set(operationId, `${method.toUpperCase()} ${path}`);
124
- }
139
+ recordOperationId(operationId, `${method.toUpperCase()} ${path}`, `/paths/${jsonPointerEscape(path)}/${method}/operationId`, seenIds, diagnostics);
125
140
  operations.push({
126
141
  path,
127
142
  method,
@@ -179,26 +194,21 @@ function extractParameterList(doc, parameters, pointerBase, diagnostics) {
179
194
  * length — a Reference Object whose target is itself a Reference Object
180
195
  * is legal. `resolveRefChain` centralises cycle and depth-cap protection.
181
196
  *
182
- * Cycles and over-deep chains reuse the existing `cyclic-path-item-ref`
183
- * and `path-item-ref-too-deep` diagnostic codes with a
184
- * `detail.kind: "<node-kind>"` discriminator (e.g. `"parameter"`,
185
- * `"header"`, `"link"`). There is no dedicated code per node kind in the
186
- * current `DiagnosticCode` union; consumers filter by `detail.kind` when
187
- * they care to distinguish the source.
188
- *
189
- * TODO(round7-integration): consider adding dedicated `cyclic-*-ref` /
190
- * `*-ref-too-deep` codes per node kind to `core/diagnostics.ts` so the
191
- * kind discriminator on `detail` is not needed. The existing Swagger
192
- * 2.0 path already uses a dedicated `swagger-cyclic-parameter-ref` for
193
- * the equivalent failure mode.
197
+ * Cycles and over-deep chains emit a dedicated diagnostic code per node
198
+ * kind (`cyclic-parameter-ref`, `parameter-ref-too-deep`, and the
199
+ * `header` / `link` equivalents), mirroring the existing
200
+ * `swagger-cyclic-parameter-ref` precedent so consumers can pattern-match
201
+ * directly on the code instead of filtering by `detail.kind`.
194
202
  */
195
203
  function resolveReferenceObjectChain(doc, node, kind, diagnostics) {
196
204
  const kindLabel = kind === "parameter" ? "Parameter Object" : kind === "header" ? "Header Object" : "Link Object";
205
+ const cyclicCode = kind === "parameter" ? "cyclic-parameter-ref" : kind === "header" ? "cyclic-header-ref" : "cyclic-link-ref";
206
+ const tooDeepCode = kind === "parameter" ? "parameter-ref-too-deep" : kind === "header" ? "header-ref-too-deep" : "link-ref-too-deep";
197
207
  return resolveRefChain(node, {
198
208
  lookup: (ref) => ref.startsWith("#/") ? resolveRefInDoc(doc, ref) : void 0,
199
209
  onCycle: (ref) => {
200
210
  emitDiagnostic(diagnostics, {
201
- code: "cyclic-path-item-ref",
211
+ code: cyclicCode,
202
212
  message: `Cyclic ${kindLabel} $ref "${ref}"`,
203
213
  pointer: ref,
204
214
  detail: {
@@ -209,7 +219,7 @@ function resolveReferenceObjectChain(doc, node, kind, diagnostics) {
209
219
  },
210
220
  onDepthExceeded: (ref) => {
211
221
  emitDiagnostic(diagnostics, {
212
- code: "path-item-ref-too-deep",
222
+ code: tooDeepCode,
213
223
  message: `${kindLabel} $ref chain exceeded the hop cap starting from "${ref}"`,
214
224
  pointer: ref,
215
225
  detail: {
@@ -451,7 +461,7 @@ function getResponseHeaders(response, doc, diagnostics) {
451
461
  }
452
462
  return result;
453
463
  }
454
- function listWebhooks(parsed, diagnostics) {
464
+ function listWebhooks(parsed, diagnostics, seenIds = /* @__PURE__ */ new Map()) {
455
465
  const result = [];
456
466
  const webhooks = getProperty(parsed.doc, "webhooks");
457
467
  if (!isObject(webhooks)) return result;
@@ -462,10 +472,12 @@ function listWebhooks(parsed, diagnostics) {
462
472
  for (const method of HTTP_METHODS) {
463
473
  const operation = getProperty(hookItem, method);
464
474
  if (!isObject(operation)) continue;
475
+ const operationId = getString(operation, "operationId");
476
+ recordOperationId(operationId, `${method.toUpperCase()} webhook:${name}`, `/webhooks/${jsonPointerEscape(name)}/${method}/operationId`, seenIds, diagnostics);
465
477
  operations.push({
466
478
  path: name,
467
479
  method,
468
- operationId: getString(operation, "operationId"),
480
+ operationId,
469
481
  summary: getString(operation, "summary"),
470
482
  description: getString(operation, "description"),
471
483
  deprecated: getProperty(operation, "deprecated") === true,
@@ -479,6 +491,20 @@ function listWebhooks(parsed, diagnostics) {
479
491
  }
480
492
  return result;
481
493
  }
494
+ /**
495
+ * Enumerate every operation in the document — both the `paths` map and
496
+ * the OpenAPI 3.1 `webhooks` map — sharing a single `seenIds` cache so
497
+ * cross-list `operationId` collisions surface the same way as same-list
498
+ * collisions. Returns the path-operation list followed by webhook
499
+ * operations (flattened); callers that need the structured webhook
500
+ * grouping should call `listWebhooks` directly.
501
+ */
502
+ function listAllOperations(parsed, diagnostics) {
503
+ const seenIds = /* @__PURE__ */ new Map();
504
+ const pathOps = listOperations(parsed, diagnostics, seenIds);
505
+ const webhookOps = listWebhooks(parsed, diagnostics, seenIds).flatMap((w) => w.operations);
506
+ return [...pathOps, ...webhookOps];
507
+ }
482
508
  function getExternalDocs(obj) {
483
509
  const docs = getProperty(obj, "externalDocs");
484
510
  if (!isObject(docs)) return void 0;
@@ -559,4 +585,4 @@ function getLinks(parsed, path, method, statusCode, diagnostics) {
559
585
  return result;
560
586
  }
561
587
  //#endregion
562
- export { getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
588
+ export { getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listAllOperations, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
2
2
  import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequestBody } from "./parser.mjs";
3
3
 
4
4
  //#region src/openapi/resolve.d.ts
@@ -5,7 +5,7 @@ import { isPrototypePollutingKey } from "../core/uri.mjs";
5
5
  import { detectOpenApiVersion } from "../core/version.mjs";
6
6
  import { o as normaliseOpenApiSchemas, r as documentContainsKeyword } from "../normalise-DCYp06Sr.mjs";
7
7
  import { resolveRefChain } from "../core/refChain.mjs";
8
- import { getParameters, getRequestBody, getResponses, listOperations, listWebhooks, parseOpenApiDocument } from "./parser.mjs";
8
+ import { getParameters, getRequestBody, getResponses, listAllOperations, parseOpenApiDocument } from "./parser.mjs";
9
9
  //#region src/openapi/resolve.ts
10
10
  /**
11
11
  * OpenAPI document resolution and caching.
@@ -325,7 +325,7 @@ function extractPathItemInfo(pathItem) {
325
325
  */
326
326
  function resolveOperationFromParsed(parsed, path, method, diagnostics) {
327
327
  const pathItemNode = lookupPathItemNode(parsed, path, diagnostics);
328
- const operation = [...listOperations(parsed), ...listWebhooks(parsed).flatMap((w) => w.operations)].find((op) => op.path === path && op.method === method);
328
+ const operation = listAllOperations(parsed).find((op) => op.path === path && op.method === method);
329
329
  if (operation === void 0) throw new Error(`Operation not found: ${method.toUpperCase()} ${path}`);
330
330
  if (pathItemNode === void 0) throw new Error(`Path item missing for ${method.toUpperCase()} ${path}`);
331
331
  return {
@@ -1,11 +1,12 @@
1
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-BS2kaUyE.mjs";
3
- import { t as SchemaError } from "../errors-g_MCTQel.mjs";
4
- import { l as RenderProps, r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
2
+ import { t as Diagnostic } from "../diagnostics-Cbwak-ZX.mjs";
3
+ import { i as SchemaIoSide } from "../adapter-MiEFkRVV.mjs";
4
+ import { t as SchemaError } from "../errors-DQSIK4n1.mjs";
5
+ import { l as RenderProps, r as ComponentResolver } from "../renderer-Ul9taFYp.mjs";
5
6
  import { FromJSONSchema, FromJSONSchemaMode, IsSwagger2Doc, PathOfType, RejectUnrepresentableZod, ResolveOpenAPIRef, TypeAtPath, __SchemaInferenceFellBack } from "../core/typeInference.mjs";
6
7
  import { z } from "zod";
7
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
8
8
  import { ReactNode } from "react";
9
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
9
10
 
10
11
  //#region src/react/SchemaComponent.d.ts
11
12
  /**
@@ -65,28 +66,36 @@ type InferSchemaValue<T, Ref extends string | undefined, Mode extends FromJSONSc
65
66
  type NarrowAtPath<V, P extends string | undefined> = P extends string ? TypeAtPath<V, P> : V;
66
67
  /**
67
68
  * Public alias mapping a schema input to the rendered value type.
68
- * Use to narrow a runtime callback inside the body of an `onChange`
69
- * handler:
70
- *
71
- * ```tsx
72
- * <SchemaComponent
73
- * schema={userSchema}
74
- * onChange={(v) => {
75
- * const user = v as InferredOutputValue<typeof userSchema>;
76
- * // ...narrowly typed access on `user`
77
- * }}
78
- * />
79
- * ```
80
69
  *
81
- * The `onChange` argument is typed `unknown` at the props boundary
82
- * because the walker propagates `unknown` values through the render
83
- * pipeline. Narrowing on the consumer side is therefore an explicit
84
- * step and never a silent contract gap.
70
+ * Picks the OUTPUT side (server client) of every transform / pipe /
71
+ * codec. For an `<SchemaComponent io="output">` or `<SchemaView
72
+ * io="output">` (both defaults), this is the inferred shape of
73
+ * `value` and the parameter of `onChange`.
85
74
  */
86
75
  type InferredOutputValue<T, Ref extends string | undefined = undefined, P extends string | undefined = undefined> = NarrowAtPath<InferSchemaValue<T, Ref, "output">, P>;
87
- /** Companion to {@link InferredOutputValue} for `"input"`-mode shapes. */
76
+ /**
77
+ * Companion to {@link InferredOutputValue} for `"input"`-mode shapes.
78
+ *
79
+ * Picks the INPUT side (client → server) of every transform / pipe /
80
+ * codec. Surfaces as the inferred shape of `value` / `onChange` when
81
+ * a consumer renders `<SchemaComponent io="input">`. For JSON Schema
82
+ * inputs with `readOnly`/`writeOnly` annotations, the INPUT mode
83
+ * omits properties marked `readOnly: true`.
84
+ */
88
85
  type InferredInputValue<T, Ref extends string | undefined = undefined, P extends string | undefined = undefined> = NarrowAtPath<InferSchemaValue<T, Ref, "input">, P>;
89
- interface SchemaComponentProps<T = unknown, Ref extends string | undefined = undefined, P extends string | undefined = undefined> {
86
+ /**
87
+ * Resolve the schema-driven value type for either I/O direction.
88
+ *
89
+ * Thin convenience over {@link InferredOutputValue} /
90
+ * {@link InferredInputValue} so consumers that decide between the
91
+ * two at the type level (e.g. a generic wrapper component) can pass
92
+ * the chosen direction as a type argument rather than branch on it
93
+ * with conditional types. Falls back to `unknown` when the schema's
94
+ * value type cannot be statically inferred, identical to the
95
+ * underlying helpers.
96
+ */
97
+ type InferredValue<T, Ref extends string | undefined = undefined, P extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> = NarrowAtPath<InferSchemaValue<T, Ref, Mode>, P>;
98
+ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = undefined, P extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
90
99
  /**
91
100
  * Zod schema, JSON Schema object, or OpenAPI document.
92
101
  *
@@ -116,56 +125,57 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
116
125
  */
117
126
  path?: P;
118
127
  /**
119
- * Current value to render.
128
+ * Which side of every transform / pipe / codec to render.
120
129
  *
121
- * TYPE BOUNDARY NOTE: kept as `unknown` at the props boundary so
122
- * existing call sites including legitimate edge-case fixtures
123
- * that pass deliberately invalid values to exercise fallback code
124
- * paths continue to typecheck. Use {@link InferredOutputValue}
125
- * to narrow on the consumer side:
130
+ * - `"output"` (default) renderer draws the OUTPUT side of the
131
+ * schema. For a `z.codec(z.string(), z.number(), …)` chain
132
+ * this renders a number input. `value` and `onChange` therefore
133
+ * carry the OUTPUT shape, and `validate` runs `safeEncode`
134
+ * (the reverse direction) so user-supplied OUTPUT values are
135
+ * validated against the codec.
136
+ * - `"input"` — renderer draws the INPUT side instead. For the
137
+ * same codec this renders a string input, `value` and
138
+ * `onChange` carry the INPUT shape, and `validate` runs
139
+ * `safeParse` (the forward direction).
140
+ *
141
+ * The choice is propagated through `normaliseSchema` →
142
+ * `normaliseZod4` → `z.toJSONSchema(..., { io })` so a single
143
+ * source of truth drives both the rendered JSON Schema shape and
144
+ * the validation direction. Has no effect for plain JSON Schema
145
+ * or OpenAPI inputs — those advertise a single canonical shape.
146
+ */
147
+ io?: Mode;
148
+ /**
149
+ * Current value to render. Typed against `InferSchemaValue<T,
150
+ * Ref, Mode>` so the prop tracks the schema's inferred shape for
151
+ * the chosen `io` direction. Narrowed further by `P` when a
152
+ * `path` is supplied (currently type-level only — see the JSDoc
153
+ * on {@link SchemaComponentProps.path}).
154
+ *
155
+ * Falls back to `unknown` when the schema's value type cannot be
156
+ * statically inferred (runtime `Record<string, unknown>` JSON
157
+ * Schemas, OpenAPI documents without a ref, etc.), so untyped
158
+ * call sites still compile.
159
+ *
160
+ * Use {@link InferredOutputValue} or {@link InferredInputValue}
161
+ * to narrow a value declared at the call site:
126
162
  *
127
163
  * ```tsx
128
164
  * const user: InferredOutputValue<typeof userSchema> = { ... };
129
165
  * <SchemaComponent schema={userSchema} value={user} readOnly />
130
166
  * ```
131
- *
132
- * The narrowing is fully expressible through the helper alias
133
- * without forcing every existing caller to update their value
134
- * shapes for `exactOptionalPropertyTypes` / enum literal widening.
135
- *
136
- * TODO(round7-integration): promote to
137
- * `NarrowAtPath<InferSchemaValue<T, Ref, "output">, P>` once the
138
- * round-7 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.
146
167
  */
147
- value?: unknown;
168
+ value?: NarrowAtPath<InferSchemaValue<T, Ref, Mode>, P>;
148
169
  /**
149
- * Called when the value changes (editable fields).
170
+ * Called when the value changes (editable fields). The parameter
171
+ * shares the same shape as {@link SchemaComponentProps.value} so
172
+ * a controlled component can round-trip the value through React
173
+ * state without re-shaping.
150
174
  *
151
- * TYPE BOUNDARY NOTE: the parameter is typed `unknown` rather
152
- * than the inferred input shape because the walker pipeline only
153
- * propagates `unknown` values and a narrow contravariant callback
154
- * signature is not assignable from an `unknown`-emitting source
155
- * without an unsafe boundary cast. The {@link InferredInputValue}
156
- * alias is the recommended way for callers to narrow on the
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.
175
+ * Falls back to `unknown` for schemas whose value type cannot be
176
+ * statically inferred see {@link SchemaComponentProps.value}.
167
177
  */
168
- onChange?: (value: unknown) => void;
178
+ onChange?: (value: NarrowAtPath<InferSchemaValue<T, Ref, Mode>, P>) => void;
169
179
  /** Run schema.safeParse() on change and surface errors via onValidationError. */
170
180
  validate?: boolean;
171
181
  /** Called with the ZodError when validation fails. */
@@ -196,7 +206,7 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
196
206
  */
197
207
  idPrefix?: string;
198
208
  }
199
- declare function SchemaComponent<T = unknown, Ref extends string | undefined = undefined, P extends string | undefined = undefined>(props: SchemaComponentProps<T, Ref, P>): ReactNode;
209
+ declare function SchemaComponent<T = unknown, Ref extends string | undefined = undefined, P extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(props: SchemaComponentProps<T, Ref, P, Mode>): ReactNode;
200
210
  /**
201
211
  * Append a child path suffix to a parent path. When the suffix is omitted
202
212
  * (e.g. transparent wrappers like union options), the parent path is
@@ -255,4 +265,4 @@ declare function SchemaField<T = unknown, Ref extends string | undefined = undef
255
265
  onValidationError
256
266
  }: SchemaFieldProps<T, Ref, P>): ReactNode;
257
267
  //#endregion
258
- export { InferredInputValue, InferredOutputValue, SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, WidgetMap, joinPath, registerWidget, renderField, sanitisePrefix };
268
+ export { InferredInputValue, InferredOutputValue, InferredValue, SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, WidgetMap, joinPath, registerWidget, renderField, sanitisePrefix };
@@ -1,15 +1,15 @@
1
1
  "use client";
2
- import { getProperty, isObject, toRecordOrUndefined } from "../core/guards.mjs";
2
+ import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
3
3
  import "../core/limits.mjs";
4
4
  import { SchemaFieldError, SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
5
- import { normaliseSchema } from "../core/adapter.mjs";
5
+ import { isCodecSchema, normaliseSchema } from "../core/adapter.mjs";
6
6
  import { buildRenderProps, getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
7
7
  import { walk } from "../core/walker.mjs";
8
8
  import { headlessResolver } from "./headless.mjs";
9
9
  import { resolvePath, resolveValue, setNestedValue } from "./fieldPath.mjs";
10
10
  import { z } from "zod";
11
- import { jsx, jsxs } from "react/jsx-runtime";
12
11
  import { createContext, isValidElement, useCallback, useContext, useId, useMemo } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
13
  //#region src/react/SchemaComponent.tsx
14
14
  /**
15
15
  * <SchemaComponent> — renders UI from Zod, JSON Schema, or OpenAPI schemas.
@@ -48,7 +48,7 @@ function registerWidget(name, render) {
48
48
  globalWidgets.set(name, render);
49
49
  }
50
50
  function SchemaComponent(props) {
51
- const { schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix } = props;
51
+ const { schema: schemaInput, ref: refInput, io, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix } = props;
52
52
  const userResolver = useContext(UserResolverContext);
53
53
  const contextWidgets = useContext(WidgetsContext);
54
54
  const generatedId = useId();
@@ -74,7 +74,10 @@ function SchemaComponent(props) {
74
74
  let rootMeta;
75
75
  let rootDocument;
76
76
  try {
77
- const normalised = normaliseSchema(schemaInput, refInput, diagnostics !== void 0 ? { diagnostics } : void 0);
77
+ const normalised = normaliseSchema(schemaInput, refInput, diagnostics !== void 0 || io !== void 0 ? {
78
+ ...diagnostics !== void 0 ? { diagnostics } : {},
79
+ ...io !== void 0 ? { io } : {}
80
+ } : void 0);
78
81
  jsonSchema = normalised.jsonSchema;
79
82
  zodSchema = normalised.zodSchema;
80
83
  rootMeta = normalised.rootMeta;
@@ -92,7 +95,7 @@ function SchemaComponent(props) {
92
95
  if (validate) {
93
96
  let error;
94
97
  try {
95
- error = runValidation(zodSchema, jsonSchema, nextValue, onDiagnostic);
98
+ error = runValidation(zodSchema, jsonSchema, nextValue, io, onDiagnostic);
96
99
  } catch (err) {
97
100
  const normalised = err instanceof SchemaNormalisationError ? err : new SchemaNormalisationError(err instanceof Error ? err.message : "Fallback validation failed", schemaInput, "zod-conversion-failed", void 0, err);
98
101
  if (onError !== void 0) {
@@ -106,11 +109,12 @@ function SchemaComponent(props) {
106
109
  dispatchFieldErrors(fieldsRecord, error);
107
110
  }
108
111
  }
109
- onChange?.(nextValue);
112
+ if (onChange !== void 0) onChange(nextValue);
110
113
  }, [
111
114
  validate,
112
115
  zodSchema,
113
116
  jsonSchema,
117
+ io,
114
118
  onChange,
115
119
  onValidationError,
116
120
  fieldsRecord,
@@ -174,10 +178,19 @@ function sanitisePrefix(value) {
174
178
  * to surface somewhere — diagnostics if the consumer opted in, an error
175
179
  * otherwise — so the caller can route it through `onError` / an error
176
180
  * boundary rather than have validation quietly disappear.
181
+ *
182
+ * The `io` argument mirrors the prop on `<SchemaComponent>` and
183
+ * `<SchemaView>`. It determines which Zod entry point validates a
184
+ * codec: `safeEncode` for the OUTPUT side (the default, matching the
185
+ * renderer's default direction), `safeParse` for the INPUT side. For
186
+ * non-codec schemas the choice is irrelevant — both `safeEncode` and
187
+ * `safeParse` behave identically — so `safeParse` is used
188
+ * unconditionally.
177
189
  */
178
- function runValidation(zodSchema, jsonSchema, value, onDiagnostic) {
190
+ function runValidation(zodSchema, jsonSchema, value, io, onDiagnostic) {
179
191
  if (zodSchema !== void 0 && isObject(zodSchema)) {
180
- const validateFn = isCodecSchema(zodSchema) ? zodSchema.safeEncode : zodSchema.safeParse;
192
+ const resolvedIo = io ?? "output";
193
+ const validateFn = isCodecSchema(zodSchema) && resolvedIo === "output" ? zodSchema.safeEncode : zodSchema.safeParse;
181
194
  if (isCallable(validateFn)) {
182
195
  const result = validateFn(value);
183
196
  if (isObject(result) && "success" in result && result.success !== true) return result.error;
@@ -270,7 +283,7 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
270
283
  const handleChange = useCallback((nextFieldValue) => {
271
284
  const newRootValue = setNestedValue(value, path, nextFieldValue);
272
285
  if (validate) {
273
- const error = runValidation(zodSchema, jsonSchema, newRootValue);
286
+ const error = runValidation(zodSchema, jsonSchema, newRootValue, void 0);
274
287
  if (error !== void 0) onValidationError?.(error);
275
288
  }
276
289
  onChange?.(newRootValue);
@@ -330,26 +343,5 @@ function isFieldErrorCallback(value) {
330
343
  function isCallable(value) {
331
344
  return typeof value === "function";
332
345
  }
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
- }
354
346
  //#endregion
355
347
  export { SchemaComponent, SchemaField, SchemaProvider, joinPath, registerWidget, renderField, sanitisePrefix };