schema-components 0.0.0 → 1.1.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 +59 -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
@@ -0,0 +1,140 @@
1
+ import { getProperty, hasProperty, isObject } from "./guards.mjs";
2
+ import { z } from "zod";
3
+ //#region src/core/adapter.ts
4
+ /**
5
+ * Schema adapter — normalises all inputs to JSON Schema.
6
+ *
7
+ * - Zod 4 schemas → converted via z.toJSONSchema()
8
+ * - Zod 3 schemas → error (not yet supported)
9
+ * - JSON Schema objects → passed through
10
+ * - OpenAPI documents → schemas extracted and passed through
11
+ *
12
+ * The adapter preserves the original Zod schema for validation.
13
+ * All narrowing uses type guards — no type assertions.
14
+ */
15
+ const schemaCache = /* @__PURE__ */ new WeakMap();
16
+ function detectSchemaKind(input) {
17
+ if (hasProperty(input, "_zod")) return "zod4";
18
+ if (hasProperty(input, "_def") && !hasProperty(input, "_zod")) return "zod3";
19
+ if (hasProperty(input, "openapi")) return "openapi";
20
+ return "jsonSchema";
21
+ }
22
+ /**
23
+ * Wraps z.toJSONSchema() for a runtime-validated Zod schema.
24
+ *
25
+ * The _zod guard in normaliseZod4 has confirmed this is a valid Zod schema,
26
+ * but TypeScript cannot represent "has _zod.def" as the $ZodType parameter
27
+ * that z.toJSONSchema expects. This is the library boundary equivalent of
28
+ * object → Record<string, unknown> — the type mismatch is genuinely unavoidable.
29
+ */
30
+ function callToJsonSchema(schema) {
31
+ return z.toJSONSchema(schema);
32
+ }
33
+ function normaliseSchema(input, ref) {
34
+ if (ref === void 0 && isObject(input)) {
35
+ const cached = schemaCache.get(input);
36
+ if (cached !== void 0) return cached;
37
+ }
38
+ const kind = detectSchemaKind(input);
39
+ let result;
40
+ switch (kind) {
41
+ case "zod4":
42
+ result = normaliseZod4(input);
43
+ break;
44
+ case "zod3":
45
+ result = normaliseZod3();
46
+ break;
47
+ case "openapi":
48
+ if (!isObject(input)) throw new Error("Invalid OpenAPI document");
49
+ result = normaliseOpenApi(input, ref);
50
+ break;
51
+ case "jsonSchema":
52
+ if (!isObject(input)) throw new Error("Invalid JSON Schema");
53
+ result = normaliseJsonSchema(input);
54
+ break;
55
+ }
56
+ if (ref === void 0 && isObject(input)) schemaCache.set(input, result);
57
+ return result;
58
+ }
59
+ function normaliseZod4(input) {
60
+ const zod = getProperty(input, "_zod");
61
+ if (!isObject(zod)) throw new Error("Invalid Zod 4 schema: missing _zod property");
62
+ if (!("def" in zod)) throw new Error("Invalid Zod 4 schema: missing _zod.def");
63
+ const jsonSchema = callToJsonSchema(input);
64
+ if (!isObject(jsonSchema)) throw new Error("z.toJSONSchema() did not produce an object");
65
+ return {
66
+ jsonSchema,
67
+ zodSchema: input,
68
+ rootMeta: extractRootMetaFromJson(jsonSchema),
69
+ rootDocument: jsonSchema
70
+ };
71
+ }
72
+ function normaliseJsonSchema(jsonSchema) {
73
+ return {
74
+ jsonSchema,
75
+ rootMeta: extractRootMetaFromJson(jsonSchema),
76
+ rootDocument: jsonSchema
77
+ };
78
+ }
79
+ function normaliseZod3() {
80
+ throw new Error("Zod 3 schemas are not yet supported. Convert to Zod 4 or provide JSON Schema directly.");
81
+ }
82
+ function normaliseOpenApi(doc, ref) {
83
+ const resolved = resolveOpenApiRef(doc, ref);
84
+ return {
85
+ jsonSchema: resolved,
86
+ rootMeta: extractRootMetaFromJson(resolved),
87
+ rootDocument: doc
88
+ };
89
+ }
90
+ function resolveOpenApiRef(doc, ref) {
91
+ if (ref === void 0) {
92
+ const schemas = getProperty(getProperty(doc, "components"), "schemas");
93
+ if (!isObject(schemas)) throw new Error("OpenAPI document has no components/schemas and no ref was provided.");
94
+ const firstKey = Object.keys(schemas)[0];
95
+ if (firstKey === void 0) throw new Error("OpenAPI document has empty components/schemas.");
96
+ const first = schemas[firstKey];
97
+ if (!isObject(first)) throw new Error("Schema is not an object.");
98
+ return first;
99
+ }
100
+ if (ref.startsWith("#/components/schemas/")) {
101
+ const name = ref.slice(21);
102
+ const schemas = getProperty(getProperty(doc, "components"), "schemas");
103
+ if (!isObject(schemas)) throw new Error(`OpenAPI ref not found: ${ref}`);
104
+ const resolved = schemas[name];
105
+ if (!isObject(resolved)) throw new Error(`OpenAPI ref not found: ${ref}`);
106
+ return resolved;
107
+ }
108
+ const pathMatch = /^\/(.+)\/(get|post|put|patch|delete)$/.exec(ref);
109
+ if (pathMatch?.[1] !== void 0 && pathMatch[2] !== void 0) {
110
+ const pathStr = pathMatch[1];
111
+ const method = pathMatch[2];
112
+ const paths = getProperty(doc, "paths");
113
+ if (!isObject(paths)) throw new Error("OpenAPI document has no paths.");
114
+ const pathObj = paths[`/${pathStr}`];
115
+ if (!isObject(pathObj)) throw new Error(`Path not found: /${pathStr}`);
116
+ const operation = pathObj[method];
117
+ if (!isObject(operation)) throw new Error(`Method ${method} not found on /${pathStr}`);
118
+ const requestBody = getProperty(operation, "requestBody");
119
+ if (!isObject(requestBody)) throw new Error(`No requestBody for ${ref}`);
120
+ const content = getProperty(requestBody, "content");
121
+ if (!isObject(content)) throw new Error(`No content for ${ref}`);
122
+ const json = getProperty(content, "application/json");
123
+ if (!isObject(json)) throw new Error(`No application/json for ${ref}`);
124
+ const schema = getProperty(json, "schema");
125
+ if (!isObject(schema)) throw new Error(`Could not resolve request body schema for ${ref}`);
126
+ return schema;
127
+ }
128
+ throw new Error(`Unsupported OpenAPI ref format: ${ref}`);
129
+ }
130
+ function extractRootMetaFromJson(jsonSchema) {
131
+ const meta = {};
132
+ if (jsonSchema.readOnly === true) meta.readOnly = true;
133
+ if (jsonSchema.writeOnly === true) meta.writeOnly = true;
134
+ if (typeof jsonSchema.description === "string") meta.description = jsonSchema.description;
135
+ if (typeof jsonSchema.title === "string") meta.title = jsonSchema.title;
136
+ if (typeof jsonSchema.deprecated === "boolean") meta.deprecated = jsonSchema.deprecated;
137
+ return Object.keys(meta).length > 0 ? meta : void 0;
138
+ }
139
+ //#endregion
140
+ export { detectSchemaKind, normaliseSchema };
@@ -0,0 +1,2 @@
1
+ import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-DIKI2C78.mjs";
2
+ export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
@@ -0,0 +1,74 @@
1
+ //#region src/core/errors.ts
2
+ /**
3
+ * Structured error types for schema-components.
4
+ *
5
+ * Every error produced by the library is one of these three types:
6
+ *
7
+ * - SchemaNormalisationError — the adapter failed to convert the input
8
+ * to JSON Schema (invalid Zod, bad OpenAPI ref, malformed schema)
9
+ * - SchemaRenderError — a theme adapter's render function threw
10
+ * - SchemaFieldError — a field path couldn't be resolved
11
+ *
12
+ * All extend `SchemaError` so consumers can catch the base class.
13
+ */
14
+ /**
15
+ * Base class for all schema-components errors.
16
+ * Catch this to handle any library error uniformly.
17
+ */
18
+ var SchemaError = class extends Error {
19
+ /** The schema input that caused the error. */
20
+ schema;
21
+ constructor(message, schema) {
22
+ super(message);
23
+ this.name = "SchemaError";
24
+ this.schema = schema;
25
+ }
26
+ };
27
+ /**
28
+ * The adapter failed to convert the input schema to JSON Schema.
29
+ *
30
+ * Causes: invalid Zod schema, Zod 3 schema (unsupported), malformed
31
+ * JSON Schema, missing OpenAPI ref, unsupported ref format.
32
+ */
33
+ var SchemaNormalisationError = class extends SchemaError {
34
+ kind;
35
+ constructor(message, schema, kind) {
36
+ super(message, schema);
37
+ this.name = "SchemaNormalisationError";
38
+ this.kind = kind;
39
+ }
40
+ };
41
+ /**
42
+ * A theme adapter's render function threw during rendering.
43
+ *
44
+ * The `cause` is the original error from the render function.
45
+ */
46
+ var SchemaRenderError = class extends SchemaError {
47
+ /** The schema type being rendered when the error occurred. */
48
+ schemaType;
49
+ /** The original error from the render function. */
50
+ cause;
51
+ constructor(message, schema, schemaType, cause) {
52
+ super(message, schema);
53
+ this.name = "SchemaRenderError";
54
+ this.schemaType = schemaType;
55
+ this.cause = cause;
56
+ }
57
+ };
58
+ /**
59
+ * A field path couldn't be resolved against the walked schema tree.
60
+ *
61
+ * This is produced by `<SchemaField>` when the `path` prop doesn't
62
+ * match any field in the schema.
63
+ */
64
+ var SchemaFieldError = class extends SchemaError {
65
+ /** The unresolvable dot-separated path. */
66
+ path;
67
+ constructor(message, schema, path) {
68
+ super(message, schema);
69
+ this.name = "SchemaFieldError";
70
+ this.path = path;
71
+ }
72
+ };
73
+ //#endregion
74
+ export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
@@ -0,0 +1,44 @@
1
+ //#region src/core/guards.d.ts
2
+ /**
3
+ * Shared type guards and safe property access.
4
+ *
5
+ * Every module in schema-components needs `isObject` and `getProperty`.
6
+ * Defining them once eliminates the six re-implementations that existed
7
+ * across core, react, openapi, html, and themes.
8
+ *
9
+ * The `object → Record<string, unknown>` conversion (`toRecord`) is
10
+ * the one place where a cast is genuinely unavoidable — TypeScript's
11
+ * `object` type has no index signature. See AGENTS.md: "object →
12
+ * Record<string, unknown>".
13
+ */
14
+ /**
15
+ * Narrows `unknown` to a non-null, non-array object.
16
+ * This is the most fundamental narrowing in the library — every module
17
+ * that reads JSON Schema or OpenAPI objects uses this.
18
+ */
19
+ declare function isObject(value: unknown): value is Record<string, unknown>;
20
+ /**
21
+ * Safe property access on unknown values. Returns `undefined` if the
22
+ * value is not an object or the key doesn't exist.
23
+ */
24
+ declare function getProperty(value: unknown, key: string): unknown;
25
+ /**
26
+ * Check if a value is an object with a specific own-property.
27
+ */
28
+ declare function hasProperty(value: unknown, key: string): boolean;
29
+ /**
30
+ * Convert a known `object` to `Record<string, unknown>` by iterating
31
+ * `Object.entries`. This avoids the cast that TypeScript's `object`
32
+ * type (no index signature) otherwise forces.
33
+ *
34
+ * Only use this when you already know `value` is an `object` — it does
35
+ * not perform a type guard.
36
+ */
37
+ declare function toRecord(value: object): Record<string, unknown>;
38
+ /**
39
+ * Convert `unknown` to `Record<string, unknown> | undefined`.
40
+ * Returns `undefined` for non-objects, null, and arrays.
41
+ */
42
+ declare function toRecordOrUndefined(value: unknown): Record<string, unknown> | undefined;
43
+ //#endregion
44
+ export { getProperty, hasProperty, isObject, toRecord, toRecordOrUndefined };
@@ -0,0 +1,58 @@
1
+ //#region src/core/guards.ts
2
+ /**
3
+ * Shared type guards and safe property access.
4
+ *
5
+ * Every module in schema-components needs `isObject` and `getProperty`.
6
+ * Defining them once eliminates the six re-implementations that existed
7
+ * across core, react, openapi, html, and themes.
8
+ *
9
+ * The `object → Record<string, unknown>` conversion (`toRecord`) is
10
+ * the one place where a cast is genuinely unavoidable — TypeScript's
11
+ * `object` type has no index signature. See AGENTS.md: "object →
12
+ * Record<string, unknown>".
13
+ */
14
+ /**
15
+ * Narrows `unknown` to a non-null, non-array object.
16
+ * This is the most fundamental narrowing in the library — every module
17
+ * that reads JSON Schema or OpenAPI objects uses this.
18
+ */
19
+ function isObject(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
22
+ /**
23
+ * Safe property access on unknown values. Returns `undefined` if the
24
+ * value is not an object or the key doesn't exist.
25
+ */
26
+ function getProperty(value, key) {
27
+ if (!isObject(value)) return void 0;
28
+ return value[key];
29
+ }
30
+ /**
31
+ * Check if a value is an object with a specific own-property.
32
+ */
33
+ function hasProperty(value, key) {
34
+ return isObject(value) && key in value;
35
+ }
36
+ /**
37
+ * Convert a known `object` to `Record<string, unknown>` by iterating
38
+ * `Object.entries`. This avoids the cast that TypeScript's `object`
39
+ * type (no index signature) otherwise forces.
40
+ *
41
+ * Only use this when you already know `value` is an `object` — it does
42
+ * not perform a type guard.
43
+ */
44
+ function toRecord(value) {
45
+ const record = {};
46
+ for (const [key, val] of Object.entries(value)) record[key] = val;
47
+ return record;
48
+ }
49
+ /**
50
+ * Convert `unknown` to `Record<string, unknown> | undefined`.
51
+ * Returns `undefined` for non-objects, null, and arrays.
52
+ */
53
+ function toRecordOrUndefined(value) {
54
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
55
+ return toRecord(value);
56
+ }
57
+ //#endregion
58
+ export { getProperty, hasProperty, isObject, toRecord, toRecordOrUndefined };
@@ -0,0 +1,2 @@
1
+ import { A as mergeResolvers, C as HtmlResolver, D as getHtmlRenderFn, E as RenderProps, O as getRenderFunction, S as HtmlRenderProps, T as RenderFunction, b as ComponentResolver, j as typeToKey, k as mergeHtmlResolvers, w as RESOLVER_KEYS, x as HtmlRenderFunction, y as BaseFieldProps } from "../types-BU0ETFHk.mjs";
2
+ export { BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
@@ -0,0 +1,71 @@
1
+ //#region src/core/renderer.ts
2
+ const RESOLVER_KEYS = [
3
+ "string",
4
+ "number",
5
+ "boolean",
6
+ "enum",
7
+ "object",
8
+ "array",
9
+ "record",
10
+ "union",
11
+ "literal",
12
+ "file",
13
+ "unknown"
14
+ ];
15
+ /**
16
+ * Map a schema type to the resolver key that handles it.
17
+ * `discriminatedUnion` → `union`. Unknown types → `unknown`.
18
+ */
19
+ function typeToKey(type) {
20
+ switch (type) {
21
+ case "string":
22
+ case "number":
23
+ case "boolean":
24
+ case "enum":
25
+ case "object":
26
+ case "array":
27
+ case "record":
28
+ case "union":
29
+ case "literal":
30
+ case "file":
31
+ case "unknown": return type;
32
+ case "discriminatedUnion": return "union";
33
+ default: return "unknown";
34
+ }
35
+ }
36
+ /**
37
+ * Look up the render function for a schema type in a ComponentResolver.
38
+ */
39
+ function getRenderFunction(type, resolver) {
40
+ return resolver[typeToKey(type)];
41
+ }
42
+ /**
43
+ * Look up the render function for a schema type in an HtmlResolver.
44
+ */
45
+ function getHtmlRenderFn(type, resolver) {
46
+ return resolver[typeToKey(type)];
47
+ }
48
+ /**
49
+ * Merge two ComponentResolvers — user values take priority, fallback fills gaps.
50
+ */
51
+ function mergeResolvers(user, fallback) {
52
+ const merged = {};
53
+ for (const key of RESOLVER_KEYS) {
54
+ const fn = user[key] ?? fallback[key];
55
+ if (fn !== void 0) merged[key] = fn;
56
+ }
57
+ return merged;
58
+ }
59
+ /**
60
+ * Merge two HtmlResolvers — user values take priority, fallback fills gaps.
61
+ */
62
+ function mergeHtmlResolvers(user, fallback) {
63
+ const merged = {};
64
+ for (const key of RESOLVER_KEYS) {
65
+ const fn = user[key] ?? fallback[key];
66
+ if (fn !== void 0) merged[key] = fn;
67
+ }
68
+ return merged;
69
+ }
70
+ //#endregion
71
+ export { RESOLVER_KEYS, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
@@ -0,0 +1,3 @@
1
+ import { C as HtmlResolver, E as RenderProps, S as HtmlRenderProps, T as RenderFunction, _ as WalkedField, a as FromJSONSchema, b as ComponentResolver, c as InferResponseFields, d as OpenAPIResponseType, f as PathOfType, g as TypeAtPath, h as SchemaType, i as FieldOverrides, l as JsonObject, m as SchemaMeta, n as FieldConstraints, o as InferParameterOverrides, p as ResolveOpenAPIRef, r as FieldOverride, s as InferRequestBodyFields, t as Editability, u as OpenAPIRequestBodyType, v as resolveEditability, x as HtmlRenderFunction, y as BaseFieldProps } from "../types-BU0ETFHk.mjs";
2
+ import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-DIKI2C78.mjs";
3
+ export { BaseFieldProps, ComponentResolver, Editability, FieldConstraints, FieldOverride, FieldOverrides, FromJSONSchema, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, InferParameterOverrides, InferRequestBodyFields, InferResponseFields, JsonObject, OpenAPIRequestBodyType, OpenAPIResponseType, PathOfType, RenderFunction, RenderProps, ResolveOpenAPIRef, SchemaError, SchemaFieldError, SchemaMeta, SchemaNormalisationError, SchemaRenderError, SchemaType, TypeAtPath, WalkedField, resolveEditability };
@@ -0,0 +1,40 @@
1
+ import { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError } from "./errors.mjs";
2
+ //#region src/core/types.ts
3
+ /**
4
+ * Resolved editability state for a single field.
5
+ *
6
+ * Priority (highest wins):
7
+ * 1. Property-level readOnly → presentation
8
+ * 2. Property-level writeOnly → input
9
+ * 3. Component-level readOnly → presentation
10
+ * 4. Component-level writeOnly → input
11
+ * 5. Schema root readOnly → presentation
12
+ * 6. Schema root writeOnly → input
13
+ * 7. Neither → editable
14
+ */
15
+ function resolveEditability(propertyMeta, componentMeta, rootMeta) {
16
+ if (propertyMeta !== void 0 && "readOnly" in propertyMeta) {
17
+ if (propertyMeta.readOnly) return "presentation";
18
+ }
19
+ if (propertyMeta !== void 0 && "writeOnly" in propertyMeta) {
20
+ if (propertyMeta.writeOnly) return "input";
21
+ }
22
+ if (propertyMeta !== void 0 && ("readOnly" in propertyMeta || "writeOnly" in propertyMeta)) return "editable";
23
+ if (componentMeta !== void 0 && "readOnly" in componentMeta) {
24
+ if (componentMeta.readOnly) return "presentation";
25
+ }
26
+ if (componentMeta !== void 0 && "writeOnly" in componentMeta) {
27
+ if (componentMeta.writeOnly) return "input";
28
+ }
29
+ if (componentMeta !== void 0 && ("readOnly" in componentMeta || "writeOnly" in componentMeta)) return "editable";
30
+ if (rootMeta !== void 0 && "readOnly" in rootMeta) {
31
+ if (rootMeta.readOnly) return "presentation";
32
+ }
33
+ if (rootMeta !== void 0 && "writeOnly" in rootMeta) {
34
+ if (rootMeta.writeOnly) return "input";
35
+ }
36
+ if (rootMeta !== void 0 && ("readOnly" in rootMeta || "writeOnly" in rootMeta)) return "editable";
37
+ return "editable";
38
+ }
39
+ //#endregion
40
+ export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError, resolveEditability };
@@ -0,0 +1,14 @@
1
+ import { _ as WalkedField, m as SchemaMeta } from "../types-BU0ETFHk.mjs";
2
+
3
+ //#region src/core/walker.d.ts
4
+ interface WalkOptions {
5
+ componentMeta?: SchemaMeta | undefined;
6
+ rootMeta?: SchemaMeta | undefined;
7
+ /** Nested field overrides — same shape as the schema. */
8
+ fieldOverrides?: Record<string, unknown> | undefined;
9
+ /** The root document for $ref resolution. */
10
+ rootDocument?: Record<string, unknown> | undefined;
11
+ }
12
+ declare function walk(schema: unknown, options?: WalkOptions): WalkedField;
13
+ //#endregion
14
+ export { WalkOptions, walk };