schema-components 0.0.0 → 1.0.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 (39) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.md +526 -0
  4. package/dist/core/adapter.d.mts +19 -0
  5. package/dist/core/adapter.mjs +140 -0
  6. package/dist/core/errors.d.mts +2 -0
  7. package/dist/core/errors.mjs +74 -0
  8. package/dist/core/guards.d.mts +44 -0
  9. package/dist/core/guards.mjs +58 -0
  10. package/dist/core/renderer.d.mts +2 -0
  11. package/dist/core/renderer.mjs +71 -0
  12. package/dist/core/types.d.mts +3 -0
  13. package/dist/core/types.mjs +40 -0
  14. package/dist/core/walker.d.mts +14 -0
  15. package/dist/core/walker.mjs +366 -0
  16. package/dist/errors-DIKI2C78.d.mts +57 -0
  17. package/dist/html/a11y.d.mts +47 -0
  18. package/dist/html/a11y.mjs +81 -0
  19. package/dist/html/html.d.mts +135 -0
  20. package/dist/html/html.mjs +168 -0
  21. package/dist/html/renderToHtml.d.mts +32 -0
  22. package/dist/html/renderToHtml.mjs +352 -0
  23. package/dist/html/renderToHtmlStream.d.mts +58 -0
  24. package/dist/html/renderToHtmlStream.mjs +285 -0
  25. package/dist/html/styles.css +151 -0
  26. package/dist/openapi/components.d.mts +76 -0
  27. package/dist/openapi/components.mjs +223 -0
  28. package/dist/openapi/parser.d.mts +45 -0
  29. package/dist/openapi/parser.mjs +159 -0
  30. package/dist/react/SchemaComponent.d.mts +96 -0
  31. package/dist/react/SchemaComponent.mjs +283 -0
  32. package/dist/react/SchemaErrorBoundary.d.mts +26 -0
  33. package/dist/react/SchemaErrorBoundary.mjs +47 -0
  34. package/dist/react/headless.d.mts +13 -0
  35. package/dist/react/headless.mjs +163 -0
  36. package/dist/themes/shadcn.d.mts +6 -0
  37. package/dist/themes/shadcn.mjs +166 -0
  38. package/dist/types-BU0ETFHk.d.mts +326 -0
  39. package/package.json +113 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ ## 1.0.0 (2026-05-14)
2
+
3
+ ### Features
4
+
5
+ * add accessibility attributes to HTML renderers ([ea49b72](https://github.com/Mearman/schema-components/commit/ea49b7264b3dd496afba851d6bcee6a103e688fb))
6
+ * add default stylesheet for sc- prefixed HTML classes ([44d1a91](https://github.com/Mearman/schema-components/commit/44d1a91ce9f986567552070f6d698d3f3d78f7b1))
7
+ * add html renderer — render schemas to raw html strings ([a803cfb](https://github.com/Mearman/schema-components/commit/a803cfb4fb8a881115dc224abe15cb4325844fbb))
8
+ * add renderChild to RenderProps for theme adapter recursion ([24305a2](https://github.com/Mearman/schema-components/commit/24305a2793d80d7a29384ba5fda2c46861479952))
9
+ * add shadcn adapter, schema caching, and update readme/package ([fff4082](https://github.com/Mearman/schema-components/commit/fff40825bdb6e3820010dd7faa48a307549af7c9))
10
+ * add Storybook with GitHub Pages deployment ([eae9817](https://github.com/Mearman/schema-components/commit/eae98178e565fa10ba1c2f258887fafb08cafd37))
11
+ * add streaming HTML renderer with three output formats ([5d3fa4a](https://github.com/Mearman/schema-components/commit/5d3fa4a20b3c65af83059ab2b111fda9392085c1))
12
+ * add type-safe openapi components with generic inference ([683e950](https://github.com/Mearman/schema-components/commit/683e950cf08a52538bbfa873400386616ec98756))
13
+ * add typed errors, onError callback, and React error boundary ([893f9a1](https://github.com/Mearman/schema-components/commit/893f9a1fc251159c80a73b45fbc1d6f63f99a857))
14
+ * add unit tests and fix schema passthrough bug ([23092cc](https://github.com/Mearman/schema-components/commit/23092ccdac3cd37068b0eae08753ba654c50359a))
15
+ * flatten nested fields prop for walker field overrides ([a72ffbe](https://github.com/Mearman/schema-components/commit/a72ffbe776feb830830bf7bf68dd6fc9b0deb8d0))
16
+ * implement core library — walker, adapter, renderer, React components ([2c95a2e](https://github.com/Mearman/schema-components/commit/2c95a2e448b2646c4d6c4565ceb30e99500ce732))
17
+ * replace string templates with typed h() builder ([83525b1](https://github.com/Mearman/schema-components/commit/83525b115f77b39c949e566d4838332dae5f92ae))
18
+ * type-safe fields prop with generic SchemaComponent<T, Ref> ([3ea1495](https://github.com/Mearman/schema-components/commit/3ea14950e3d20eb80d8b16186803f3ab0ed28f84))
19
+ * type-safe path prop on generic SchemaField ([8d5fef7](https://github.com/Mearman/schema-components/commit/8d5fef7405d6c4b2f914e0481e501eaf633ddc5b))
20
+ * wire adapter, resolver, and SchemaField into SchemaComponent ([cbc2ce1](https://github.com/Mearman/schema-components/commit/cbc2ce158c080a1a84a940781b81d9d065e055b1))
21
+
22
+ ### Bug Fixes
23
+
24
+ * propagate field key as path in HTML renderChild ([e3f471c](https://github.com/Mearman/schema-components/commit/e3f471c2ee308edf1e6406c1fc6f70bfac8c6b37))
25
+ * readOnly/writeOnly overrides propagate correctly to nested fields ([a809b62](https://github.com/Mearman/schema-components/commit/a809b62bbe08ae44d3fdaa9b277a3ded07c6009d))
26
+
27
+ ### Refactoring
28
+
29
+ * centralise type guards, resolver lookup, and resolver merge ([be354ac](https://github.com/Mearman/schema-components/commit/be354ac35b6d73ec0bbc746fae58c35758991ce0))
30
+ * remove duplicate ComponentResolver types from types.ts ([39702c5](https://github.com/Mearman/schema-components/commit/39702c5ab622be825ad7f1ad222c07dca85c57a4))
31
+ * unify RenderProps and HtmlRenderProps via BaseFieldProps ([a408da9](https://github.com/Mearman/schema-components/commit/a408da9a2f4b4bacf81f0a85f6aad117fa69bb8a))
32
+ * use json schema as authoritative internal representation ([6a996c5](https://github.com/Mearman/schema-components/commit/6a996c53517d18f39cb341f10ed3fafdd7777547))
33
+
34
+ ### Documentation
35
+
36
+ * add README for schema-components design document ([c1251e4](https://github.com/Mearman/schema-components/commit/c1251e4eea39e0154c3e8cc91e5482271bb4ce95))
37
+ * rewrite README to match actual API ([a8fc57f](https://github.com/Mearman/schema-components/commit/a8fc57fe5e650c9f4f886f0525c8c58e309a70aa))
38
+ * update readme for json schema walker architecture ([9649430](https://github.com/Mearman/schema-components/commit/9649430f76f2fbef6d0ea7a8d9fd81ff478f644f))
39
+ * update README with h() builder, streaming, accessibility, errors ([01d2a9f](https://github.com/Mearman/schema-components/commit/01d2a9f32dcdf3827ce117901cd1b6ff9c3e3d26))
40
+
41
+ ### Tests
42
+
43
+ * add parser unit tests and integration tests ([338dba3](https://github.com/Mearman/schema-components/commit/338dba368007dd2d34172a8dacfdb76606a588c6))
44
+
45
+ ### Chores
46
+
47
+ * add mit license and contributing guide ([7d51c29](https://github.com/Mearman/schema-components/commit/7d51c2957ab06fdd6c8bb4a384b3906e453ae786))
48
+ * **build:** remove barrel files, use direct module exports ([08d4f21](https://github.com/Mearman/schema-components/commit/08d4f2148e3e06c95fd1db3a9c5a733a419f3fdc))
49
+ * **build:** replace tsc with tsdown for library bundling ([38951b2](https://github.com/Mearman/schema-components/commit/38951b2ef0b770ec883f18fe9f3d4f4bf7181a5a))
50
+ * pin all GitHub Actions to latest versions, fix CI types ([29c5b6e](https://github.com/Mearman/schema-components/commit/29c5b6e156c9a3b59e656a26c4bf147a1e5b5321))
51
+ * rename to schema-components, adopt OIDC trusted publishing ([61e5c75](https://github.com/Mearman/schema-components/commit/61e5c753d5d103538213157f6fd4810572480d72))
52
+ * set up repo devops ([1042810](https://github.com/Mearman/schema-components/commit/1042810992bf4cb45e77efd680008c184f307210))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Joseph Mearman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,526 @@
1
+ # schema-components
2
+
3
+ React components that render UI from Zod schemas, JSON Schema, and OpenAPI documents.
4
+
5
+ Define your data model once. Get presentational views, input fields, and editable forms — no manual wiring.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install schema-components
11
+ ```
12
+
13
+ Peer dependencies: `zod@^4.0.0`, `react@^18.0.0 || ^19.0.0`.
14
+
15
+ ## Quick start
16
+
17
+ ```tsx
18
+ import { z } from "zod";
19
+ import { SchemaComponent } from "schema-components/react/SchemaComponent";
20
+
21
+ const userSchema = z.object({
22
+ name: z.string().min(1).meta({ description: "Full name" }),
23
+ email: z.email().meta({ description: "Email address" }),
24
+ role: z.enum(["admin", "editor", "viewer"]).meta({ description: "Role" }),
25
+ active: z.boolean().meta({ description: "Active" }),
26
+ });
27
+
28
+ function UserCard() {
29
+ const [user, setUser] = useState({
30
+ name: "Ada Lovelace",
31
+ email: "ada@example.com",
32
+ role: "admin",
33
+ active: true,
34
+ });
35
+
36
+ return (
37
+ <SchemaComponent
38
+ schema={userSchema}
39
+ value={user}
40
+ onChange={setUser}
41
+ />
42
+ );
43
+ }
44
+ ```
45
+
46
+ Renders every field as an editable input. Add `readOnly` to the component for a read-only view:
47
+
48
+ ```tsx
49
+ <SchemaComponent schema={userSchema} value={user} readOnly />
50
+ ```
51
+
52
+ ## How it works
53
+
54
+ ```
55
+ Zod schema ─── z.toJSONSchema() ──→ JSON Schema ──────────┐
56
+
57
+ JSON Schema ─────────────────────────────────────────► JSON Schema ──► walker ──► React
58
+
59
+ OpenAPI doc ── extract schemas ───────────────────────────┘
60
+ ```
61
+
62
+ One walker, one input format. The walker reads standard JSON Schema keywords (Draft 2020-12) — decoupled from Zod's internal API. `z.toJSONSchema()` is lossless: it preserves `readOnly`, `writeOnly`, custom `.meta()` properties, constraints, formats, and defaults.
63
+
64
+ `z.fromJSONSchema()` is used **only for validation** — converting JSON Schema / OpenAPI inputs back to Zod when `validate` is true and the original wasn't a Zod schema.
65
+
66
+ ## Component editability
67
+
68
+ Fields render in one of three states, controlled by `readOnly` and `writeOnly` from three sources:
69
+
70
+ | State | Rendering |
71
+ |---|---|
72
+ | **Presentation** | Read-only display. Formatted text, links, badges. No inputs. |
73
+ | **Input** | Empty field. Blank inputs, "Select…" dropdowns, unchecked toggles. |
74
+ | **Editable** | Pre-populated input the user can change. |
75
+
76
+ ### Three sources, priority order
77
+
78
+ 1. **Schema property** (`.meta({ readOnly: true })`) — always wins
79
+ 2. **Component props** (`readOnly` / `writeOnly` on `<SchemaComponent>`) — rendering context
80
+ 3. **Schema root** (`.meta({ readOnly: true })` on root schema) — fallback default
81
+ 4. Neither → Editable
82
+
83
+ ### Overriding with `readOnly: false`
84
+
85
+ A field override can explicitly opt out of a higher-level `readOnly`:
86
+
87
+ ```tsx
88
+ <SchemaComponent
89
+ schema={userSchema}
90
+ value={user}
91
+ readOnly // everything presentation
92
+ fields={{
93
+ address: {
94
+ readOnly: false, // address subtree: editable
95
+ city: { readOnly: true }, // city: still presentation
96
+ },
97
+ }}
98
+ />
99
+ ```
100
+
101
+ ## Type-safe field overrides
102
+
103
+ The `fields` prop type is inferred from the schema:
104
+
105
+ ```tsx
106
+ // Zod — full autocomplete
107
+ <SchemaComponent
108
+ schema={userSchema}
109
+ fields={{
110
+ name: { readOnly: true }, // ✓ type-safe
111
+ address: {
112
+ city: { description: "City" }, // ✓ nested, type-safe
113
+ },
114
+ // nme: { readOnly: true }, // ✗ TypeScript error: unknown key
115
+ }}
116
+ />
117
+
118
+ // JSON Schema as const — full autocomplete
119
+ const jsonSchema = {
120
+ type: "object" as const,
121
+ properties: {
122
+ name: { type: "string" as const },
123
+ email: { type: "string" as const, format: "email" },
124
+ },
125
+ required: ["name"],
126
+ } as const;
127
+
128
+ <SchemaComponent
129
+ schema={jsonSchema}
130
+ fields={{
131
+ name: { readOnly: true }, // ✓ inferred from as const
132
+ // nme: { readOnly: true }, // ✗ TypeScript error
133
+ }}
134
+ />
135
+
136
+ // OpenAPI as const + ref — full autocomplete
137
+ const spec = {
138
+ openapi: "3.1.0",
139
+ components: {
140
+ schemas: {
141
+ User: {
142
+ type: "object" as const,
143
+ properties: {
144
+ id: { type: "string" as const },
145
+ name: { type: "string" as const },
146
+ },
147
+ required: ["id", "name"],
148
+ },
149
+ },
150
+ },
151
+ } as const;
152
+
153
+ <SchemaComponent
154
+ schema={spec}
155
+ ref="#/components/schemas/User"
156
+ fields={{
157
+ id: { readOnly: true }, // ✓ inferred through ref
158
+ }}
159
+ />
160
+ ```
161
+
162
+ ## Individual fields
163
+
164
+ ```tsx
165
+ import { SchemaField } from "schema-components/react/SchemaComponent";
166
+
167
+ // Type-safe path — only valid dot-paths accepted
168
+ <SchemaField
169
+ schema={userSchema}
170
+ path="address.city" // ✓ type-safe
171
+ // path="address.cty" // ✗ TypeScript error
172
+ value={user}
173
+ onChange={setUser}
174
+ />
175
+ ```
176
+
177
+ 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.
178
+
179
+ ## All input formats
180
+
181
+ `<SchemaComponent>` auto-detects the input format:
182
+
183
+ ```tsx
184
+ // Zod schema
185
+ <SchemaComponent schema={z.object({ name: z.string() })} value={data} />
186
+
187
+ // JSON Schema
188
+ <SchemaComponent
189
+ schema={{ type: "object", properties: { name: { type: "string" } } }}
190
+ value={data}
191
+ />
192
+
193
+ // OpenAPI document + ref
194
+ <SchemaComponent
195
+ schema={openApiSpec}
196
+ ref="#/components/schemas/User"
197
+ value={data}
198
+ />
199
+ ```
200
+
201
+ ## OpenAPI components
202
+
203
+ Render API operations with type-safe field overrides:
204
+
205
+ ```tsx
206
+ import { ApiOperation } from "schema-components/openapi/components";
207
+ import type { ApiRequestBodyProps } from "schema-components/openapi/components";
208
+
209
+ const petStore = {
210
+ openapi: "3.1.0",
211
+ paths: {
212
+ "/pets": {
213
+ post: {
214
+ requestBody: {
215
+ content: {
216
+ "application/json": {
217
+ schema: {
218
+ type: "object" as const,
219
+ properties: {
220
+ name: { type: "string" as const },
221
+ tag: { type: "string" as const },
222
+ },
223
+ required: ["name"],
224
+ },
225
+ },
226
+ },
227
+ },
228
+ responses: { "201": { description: "Created" } },
229
+ },
230
+ },
231
+ },
232
+ } as const;
233
+
234
+ // Full operation — parameters, request body, responses
235
+ <ApiOperation schema={petStore} path="/pets" method="post" />
236
+
237
+ // Just the request body with type-safe fields
238
+ <ApiRequestBody
239
+ schema={petStore}
240
+ path="/pets"
241
+ method="post"
242
+ fields={{
243
+ name: { description: "Pet name" }, // ✓ inferred from as const
244
+ // nme: { description: "X" }, // ✗ TypeScript error
245
+ }}
246
+ />
247
+
248
+ // Just parameters with type-safe overrides
249
+ <ApiParameters
250
+ schema={petStore}
251
+ path="/pets"
252
+ method="get"
253
+ overrides={{
254
+ limit: { description: "Max results" }, // ✓ inferred parameter names
255
+ }}
256
+ />
257
+
258
+ // Response schema
259
+ <ApiResponse schema={petStore} path="/pets" method="get" status="200" />
260
+ ```
261
+
262
+ ## Theme adapters
263
+
264
+ Headless by default (plain HTML). Wrap with a theme adapter for styled components:
265
+
266
+ ```tsx
267
+ import { SchemaProvider } from "schema-components/react/SchemaComponent";
268
+ import { shadcnResolver } from "schema-components/themes/shadcn";
269
+
270
+ <SchemaProvider resolver={shadcnResolver}>
271
+ <SchemaComponent schema={userSchema} value={user} onChange={setUser} />
272
+ </SchemaProvider>
273
+ ```
274
+
275
+ Write a custom adapter:
276
+
277
+ ```tsx
278
+ import type { RenderProps, ComponentResolver } from "schema-components/core/renderer";
279
+
280
+ const myResolver: ComponentResolver = {
281
+ string: (props: RenderProps) => {
282
+ if (props.readOnly) return <span>{props.value}</span>;
283
+ return <input value={props.value} onChange={(e) => props.onChange(e.target.value)} />;
284
+ },
285
+ object: (props: RenderProps) => {
286
+ // props.renderChild recursively renders each field
287
+ return (
288
+ <div>
289
+ {props.fields && Object.entries(props.fields).map(([key, field]) => (
290
+ <div key={key}>
291
+ <label>{field.meta.description}</label>
292
+ {props.renderChild(field, (props.value as Record<string, unknown>)?.[key], (v) => {
293
+ props.onChange({ ...(props.value as object), [key]: v });
294
+ })}
295
+ </div>
296
+ ))}
297
+ </div>
298
+ );
299
+ },
300
+ };
301
+ ```
302
+
303
+ Every render function receives `props.renderChild` for recursive rendering — no need to know about the resolver or rendering context.
304
+
305
+ ## Raw HTML
306
+
307
+ Render schemas to HTML strings — no React needed. Useful for server-side rendering, email templates, static sites, and non-React environments.
308
+
309
+ ```tsx
310
+ import { renderToHtml } from "schema-components/html/renderToHtml";
311
+
312
+ const userSchema = z.object({
313
+ name: z.string().meta({ description: "Name" }),
314
+ email: z.email().meta({ description: "Email" }),
315
+ role: z.enum(["admin", "editor", "viewer"]).meta({ description: "Role" }),
316
+ });
317
+
318
+ // Read-only display
319
+ const html = renderToHtml(userSchema, {
320
+ value: { name: "Ada Lovelace", email: "ada@example.com", role: "admin" },
321
+ readOnly: true,
322
+ });
323
+ // → <dl class="sc-object">
324
+ // <dt class="sc-label">Name</dt><dd class="sc-value"><span class="sc-value">Ada Lovelace</span></dd>
325
+ // <dt class="sc-label">Email</dt><dd class="sc-value"><a class="sc-value" href="mailto:ada@example.com">ada@example.com</a></dd>
326
+ // <dt class="sc-label">Role</dt><dd class="sc-value"><span class="sc-value">admin</span></dd>
327
+ // </dl>
328
+
329
+ // Editable form
330
+ const formHtml = renderToHtml(userSchema, {
331
+ value: { name: "Ada Lovelace", email: "ada@example.com", role: "admin" },
332
+ });
333
+ // → <fieldset class="sc-object">
334
+ // <div class="sc-field">
335
+ // <label class="sc-label" for="sc-name">Name</label>
336
+ // <input class="sc-input" type="text" name="" value="Ada Lovelace">
337
+ // </div>
338
+ // ...
339
+ // </fieldset>
340
+ ```
341
+
342
+ All HTML output uses `sc-` prefixed classes for styling hooks. HTML is properly escaped by the serialiser — no manual escaping needed.
343
+
344
+ A default stylesheet is included:
345
+
346
+ ```html
347
+ <link rel="stylesheet" href="node_modules/schema-components/dist/html/styles.css">
348
+ ```
349
+
350
+ Or import in JS:
351
+
352
+ ```ts
353
+ import "schema-components/styles.css";
354
+ ```
355
+
356
+ ### Structured HTML construction
357
+
358
+ The HTML renderer uses a typed `h()` builder instead of string templates. This gives compile-time safety and automatic escaping:
359
+
360
+ ```ts
361
+ import { h, serialize, raw } from "schema-components/html/html";
362
+
363
+ // Build elements — attrs are type-checked, values auto-escaped
364
+ const input = h("input", { type: "text", id: "name", value: userValue });
365
+ serialize(input); // → <input type="text" id="name" value="Ada">
366
+
367
+ // Embed pre-serialised HTML (from child renderers)
368
+ const div = h("div", { class: "field" }, raw(childHtml));
369
+ serialize(div);
370
+ ```
371
+
372
+ The builder handles void elements (`<input>`, `<br>`, etc.), boolean attributes (`checked`, `disabled`), fragments, and nested children.
373
+
374
+ ### Streaming HTML
375
+
376
+ Three output formats for incremental rendering:
377
+
378
+ ```ts
379
+ import { renderToHtmlChunks } from "schema-components/html/renderToHtmlStream";
380
+ import { renderToHtmlStream } from "schema-components/html/renderToHtmlStream";
381
+ import { renderToHtmlReadable } from "schema-components/html/renderToHtmlStream";
382
+
383
+ // Sync iterable — chunks yielded at field/item/entry boundaries
384
+ const chunks: string[] = [...renderToHtmlChunks(schema, { value })];
385
+
386
+ // Async iterable — yields control to event loop between chunks
387
+ for await (const chunk of renderToHtmlStream(schema, { value })) {
388
+ res.write(chunk);
389
+ }
390
+
391
+ // Web ReadableStream — for Response, TransformStream, etc.
392
+ return new Response(renderToHtmlReadable(schema, { value }), {
393
+ headers: { "Content-Type": "text/html" },
394
+ });
395
+ ```
396
+
397
+ Concatenating all chunks produces identical output to `renderToHtml`.
398
+
399
+ ### Accessibility
400
+
401
+ The HTML renderer produces WAI-ARIA-compliant markup:
402
+
403
+ | Attribute | When |
404
+ |---|---|
405
+ | `id="<key>"` | All editable inputs |
406
+ | `aria-required="true"` | Required fields (`isOptional === false`) |
407
+ | `aria-describedby="<id>-hint"` | Fields with constraints (min/max/length/pattern) |
408
+ | `aria-readonly="true"` | Read-only presentation spans |
409
+ | `aria-label="<description>"` | Checkboxes (no visible text node) |
410
+ | `role="group"` | Record containers |
411
+ | `aria-label` on `<fieldset>` | Object with description |
412
+ | `<small class="sc-hint">` | Constraint hint text |
413
+ | `<span class="sc-required" aria-hidden="true">*` | Required field indicator |
414
+
415
+ ### Custom HTML resolver
416
+
417
+ ```ts
418
+ import { renderToHtml } from "schema-components/html/renderToHtml";
419
+ import type { HtmlResolver, HtmlRenderProps } from "schema-components/html/renderToHtml";
420
+
421
+ const tailwindResolver: HtmlResolver = {
422
+ string: (props: HtmlRenderProps) => {
423
+ if (props.readOnly) {
424
+ return `<span class="text-sm text-gray-700">${typeof props.value === "string" ? props.value : ""}</span>`;
425
+ }
426
+ return `<input class="border rounded px-2 py-1" type="text" value="${typeof props.value === "string" ? props.value : "">">`;
427
+ },
428
+ };
429
+
430
+ const html = renderToHtml(schema, { value, readOnly: true, resolver: tailwindResolver });
431
+ ```
432
+
433
+ Custom resolvers fall back to the default for any type you don't override.
434
+
435
+ ## Custom widgets
436
+
437
+ Register widgets by `.meta({ component })` hint:
438
+
439
+ ```tsx
440
+ import { registerWidget } from "schema-components/react/SchemaComponent";
441
+
442
+ registerWidget("richtext", ({ value, onChange }) => (
443
+ <RichTextEditor value={value} onChange={onChange} />
444
+ ));
445
+
446
+ // In schema
447
+ const schema = z.object({
448
+ bio: z.string().meta({ component: "richtext" }),
449
+ });
450
+ ```
451
+
452
+ Resolution order: `.meta({ component })` → registered widget → theme adapter → headless default.
453
+
454
+ ## Validation
455
+
456
+ ```tsx
457
+ <SchemaComponent
458
+ schema={userSchema}
459
+ value={user}
460
+ onChange={setUser}
461
+ validate
462
+ onValidationError={(error) => console.error(error)}
463
+ />
464
+ ```
465
+
466
+ Validation uses the original Zod schema (if input was Zod) or `z.fromJSONSchema()` (if input was JSON Schema / OpenAPI).
467
+
468
+ ## Error handling
469
+
470
+ Typed errors with `onError` callback for graceful degradation:
471
+
472
+ ```tsx
473
+ import { SchemaErrorBoundary } from "schema-components/react/SchemaErrorBoundary";
474
+ import { SchemaComponent } from "schema-components/react/SchemaComponent";
475
+
476
+ // Error boundary catches render errors from theme adapters
477
+ <SchemaErrorBoundary fallback={(error, reset) => <p>Error: {error.message}</p>}>
478
+ <SchemaComponent schema={schema} value={data} />
479
+ </SchemaErrorBoundary>
480
+
481
+ // Per-component error callback
482
+ <SchemaComponent
483
+ schema={schema}
484
+ value={data}
485
+ onError={(error) => {
486
+ console.error(error);
487
+ return null; // graceful degradation
488
+ }}
489
+ />
490
+ ```
491
+
492
+ Without `onError`, errors re-throw. Error hierarchy: `SchemaError` → `SchemaNormalisationError` | `SchemaRenderError` | `SchemaFieldError`.
493
+
494
+ ## Architecture
495
+
496
+ ```
497
+ schema-components
498
+ ├── core # JSON Schema walker, ComponentResolver, RenderProps, typed errors, type guards
499
+ ├── react # SchemaComponent, SchemaProvider, SchemaField, headless renderer, error boundary
500
+ ├── openapi # Document parser, ApiOperation, ApiParameters, ApiRequestBody, ApiResponse
501
+ ├── html # h() builder, renderToHtml, streaming renderers, ARIA helpers
502
+ └── themes # shadcn, MUI, custom adapters (separate packages)
503
+ ```
504
+
505
+ ## Source files
506
+
507
+ Every module is imported directly — no barrel files.
508
+
509
+ | File | Role |
510
+ |---|---|
511
+ | `core/types.ts` | SchemaMeta, Editability, WalkedField, FieldConstraints, FieldOverrides, FromJSONSchema, PathOfType |
512
+ | `core/walker.ts` | JSON Schema walker (Draft 2020-12), `$ref` resolution, `allOf` merging, nullable/discriminated union detection |
513
+ | `core/adapter.ts` | Normalises all inputs to JSON Schema. WeakMap schema cache. |
514
+ | `core/renderer.ts` | `BaseFieldProps`, `RenderProps`, `HtmlRenderProps`, `ComponentResolver`, `HtmlResolver`, `mergeResolvers` |
515
+ | `core/guards.ts` | Shared type guards: `isObject`, `getProperty`, `hasProperty`, `toRecord` |
516
+ | `core/errors.ts` | `SchemaError`, `SchemaNormalisationError`, `SchemaRenderError`, `SchemaFieldError` |
517
+ | `react/SchemaComponent.tsx` | Generic `<SchemaComponent<T, Ref>>`, `SchemaProvider`, `registerWidget`, `SchemaField<P>` |
518
+ | `react/headless.tsx` | Headless default resolver, `createHeadlessResolver(renderChild)` factory |
519
+ | `react/SchemaErrorBoundary.tsx` | React error boundary catching render errors |
520
+ | `html/html.ts` | Typed `h()` builder — `serialize()`, `serializeChunks()`, `raw()`, `text()`, `fragment()` |
521
+ | `html/a11y.ts` | ARIA helpers — `ariaRequiredAttrs()`, `ariaDescribedByAttrs()`, `buildHintElement()`, `requiredIndicator()` |
522
+ | `html/renderToHtml.ts` | `renderToHtml()`, `defaultHtmlResolver` — schema → HTML string via `h()` builder |
523
+ | `html/renderToHtmlStream.ts` | `renderToHtmlChunks()` (sync), `renderToHtmlStream()` (async), `renderToHtmlReadable()` (web ReadableStream) |
524
+ | `openapi/parser.ts` | OpenAPI document parsing, operation extraction, `$ref` resolution |
525
+ | `openapi/components.tsx` | `ApiOperation`, `ApiParameters`, `ApiRequestBody`, `ApiResponse` with generic type inference |
526
+ | `themes/shadcn.tsx` | shadcn/ui theme adapter |
@@ -0,0 +1,19 @@
1
+ import { l as JsonObject, m as SchemaMeta } from "../types-BU0ETFHk.mjs";
2
+
3
+ //#region src/core/adapter.d.ts
4
+ type SchemaInput = Record<string, unknown>;
5
+ type SchemaKind = "zod4" | "zod3" | "jsonSchema" | "openapi";
6
+ declare function detectSchemaKind(input: unknown): SchemaKind;
7
+ interface NormalisedSchema {
8
+ /** JSON Schema object — the authoritative schema for rendering. */
9
+ jsonSchema: JsonObject;
10
+ /** Original Zod schema, if input was Zod. Used for validation. */
11
+ zodSchema?: unknown;
12
+ /** Root-level metadata. */
13
+ rootMeta: SchemaMeta | undefined;
14
+ /** The root document for $ref resolution. */
15
+ rootDocument: JsonObject;
16
+ }
17
+ declare function normaliseSchema(input: unknown, ref?: string): NormalisedSchema;
18
+ //#endregion
19
+ export { type JsonObject, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };