schema-components 1.3.2 → 1.3.3
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/CHANGELOG.md +13 -0
- package/dist/openapi/components.mjs +17 -37
- package/dist/openapi/resolve.d.mts +42 -0
- package/dist/openapi/resolve.mjs +72 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [1.3.3](https://github.com/Mearman/schema-components/compare/v1.3.2...v1.3.3) (2026-05-14)
|
|
2
|
+
|
|
3
|
+
### Refactoring
|
|
4
|
+
|
|
5
|
+
* extract pure resolution layer from openapi components ([5e0ab32](https://github.com/Mearman/schema-components/commit/5e0ab32de44b41b23e74082886a5ae881b418883))
|
|
6
|
+
|
|
7
|
+
### Tests
|
|
8
|
+
|
|
9
|
+
* add guards unit tests ([39f53ab](https://github.com/Mearman/schema-components/commit/39f53abf4cf30f1751a09267f34990c4a2532567))
|
|
10
|
+
* add openapi resolution and component unit tests ([75a5892](https://github.com/Mearman/schema-components/commit/75a589220f05b5649a531bec946a63093e824687))
|
|
11
|
+
* add renderer utility unit tests ([3ca3677](https://github.com/Mearman/schema-components/commit/3ca367709acf6324a33a79adf3799528971defc2))
|
|
12
|
+
* expand html and streaming coverage, tighten vitest config ([ac480cd](https://github.com/Mearman/schema-components/commit/ac480cdeb39cf2ae0fa3107de2abae39102cfa26))
|
|
13
|
+
|
|
1
14
|
## [1.3.2](https://github.com/Mearman/schema-components/compare/v1.3.1...v1.3.2) (2026-05-14)
|
|
2
15
|
|
|
3
16
|
### Refactoring
|
|
@@ -1,23 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toRecordOrUndefined } from "../core/guards.mjs";
|
|
2
2
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
3
3
|
import { SchemaNormalisationError } from "../core/errors.mjs";
|
|
4
4
|
import { walk } from "../core/walker.mjs";
|
|
5
|
-
import { getParameters, getRequestBody, getResponses, listOperations, parseOpenApiDocument } from "./parser.mjs";
|
|
6
5
|
import { renderField } from "../react/SchemaComponent.mjs";
|
|
6
|
+
import { resolveOperation, resolveParameters, resolveRequestBody, resolveResponse, toDoc } from "./resolve.mjs";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/openapi/components.tsx
|
|
9
|
-
const docCache = /* @__PURE__ */ new WeakMap();
|
|
10
|
-
function getParsed(doc) {
|
|
11
|
-
const cached = docCache.get(doc);
|
|
12
|
-
if (cached !== void 0) return cached;
|
|
13
|
-
const parsed = parseOpenApiDocument(doc);
|
|
14
|
-
docCache.set(doc, parsed);
|
|
15
|
-
return parsed;
|
|
16
|
-
}
|
|
17
9
|
function noop() {}
|
|
18
|
-
function toDoc(value) {
|
|
19
|
-
return isObject(value) ? value : {};
|
|
20
|
-
}
|
|
21
10
|
function renderSchema(schema, rootDocument, options) {
|
|
22
11
|
let jsonSchema;
|
|
23
12
|
let rootMeta;
|
|
@@ -42,38 +31,33 @@ function renderSchema(schema, rootDocument, options) {
|
|
|
42
31
|
return renderField(tree, options.value, options.onChange ?? noop, void 0, renderChild);
|
|
43
32
|
}
|
|
44
33
|
function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBodyChange, responseValue, meta, requestBodyFields }) {
|
|
45
|
-
const parsed = getParsed(toDoc(doc));
|
|
46
34
|
const rootDoc = toDoc(doc);
|
|
47
|
-
const
|
|
48
|
-
if (operation === void 0) throw new SchemaNormalisationError(`Operation not found: ${method.toUpperCase()} ${path}`, doc, "openapi-invalid");
|
|
49
|
-
const params = getParameters(parsed, path, method);
|
|
50
|
-
const requestBody = getRequestBody(parsed, path, method);
|
|
51
|
-
const responses = getResponses(parsed, path, method);
|
|
35
|
+
const resolved = resolveOperation(rootDoc, path, method);
|
|
52
36
|
return /* @__PURE__ */ jsxs("section", {
|
|
53
37
|
"data-operation": `${method.toUpperCase()} ${path}`,
|
|
54
38
|
children: [
|
|
55
|
-
/* @__PURE__ */ jsx(OperationHeader, { operation }),
|
|
56
|
-
|
|
39
|
+
/* @__PURE__ */ jsx(OperationHeader, { operation: resolved.operation }),
|
|
40
|
+
resolved.parameters.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
57
41
|
"data-parameters": true,
|
|
58
42
|
children: [/* @__PURE__ */ jsx("h4", { children: "Parameters" }), /* @__PURE__ */ jsx(ParameterList, {
|
|
59
|
-
parameters:
|
|
43
|
+
parameters: resolved.parameters,
|
|
60
44
|
rootDoc,
|
|
61
45
|
meta
|
|
62
46
|
})]
|
|
63
47
|
}),
|
|
64
|
-
requestBody?.schema !== void 0 && /* @__PURE__ */ jsxs("section", {
|
|
48
|
+
resolved.requestBody?.schema !== void 0 && /* @__PURE__ */ jsxs("section", {
|
|
65
49
|
"data-request-body": true,
|
|
66
50
|
children: [
|
|
67
|
-
/* @__PURE__ */ jsxs("h4", { children: ["Request Body", requestBody.required && /* @__PURE__ */ jsx("span", {
|
|
51
|
+
/* @__PURE__ */ jsxs("h4", { children: ["Request Body", resolved.requestBody.required && /* @__PURE__ */ jsx("span", {
|
|
68
52
|
"data-required": true,
|
|
69
53
|
children: "*"
|
|
70
54
|
})] }),
|
|
71
|
-
requestBody.description && /* @__PURE__ */ jsx("p", { children: requestBody.description }),
|
|
72
|
-
requestBody.contentTypes.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
55
|
+
resolved.requestBody.description && /* @__PURE__ */ jsx("p", { children: resolved.requestBody.description }),
|
|
56
|
+
resolved.requestBody.contentTypes.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
73
57
|
"data-content-type": true,
|
|
74
|
-
children: requestBody.contentTypes[0]
|
|
58
|
+
children: resolved.requestBody.contentTypes[0]
|
|
75
59
|
}),
|
|
76
|
-
renderSchema(requestBody.schema, rootDoc, {
|
|
60
|
+
renderSchema(resolved.requestBody.schema, rootDoc, {
|
|
77
61
|
value: requestBodyValue,
|
|
78
62
|
onChange: onRequestBodyChange,
|
|
79
63
|
fields: requestBodyFields,
|
|
@@ -81,9 +65,9 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
81
65
|
})
|
|
82
66
|
]
|
|
83
67
|
}),
|
|
84
|
-
responses.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
68
|
+
resolved.responses.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
85
69
|
"data-responses": true,
|
|
86
|
-
children: [/* @__PURE__ */ jsx("h4", { children: "Responses" }), responses.map((response) => /* @__PURE__ */ jsx(ResponseCard, {
|
|
70
|
+
children: [/* @__PURE__ */ jsx("h4", { children: "Responses" }), resolved.responses.map((response) => /* @__PURE__ */ jsx(ResponseCard, {
|
|
87
71
|
response,
|
|
88
72
|
rootDoc,
|
|
89
73
|
value: responseValue,
|
|
@@ -94,9 +78,8 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
94
78
|
});
|
|
95
79
|
}
|
|
96
80
|
function ApiParameters({ schema: doc, path, method, meta, overrides }) {
|
|
97
|
-
const parsed = getParsed(toDoc(doc));
|
|
98
81
|
const rootDoc = toDoc(doc);
|
|
99
|
-
const params =
|
|
82
|
+
const params = resolveParameters(rootDoc, path, method);
|
|
100
83
|
if (params.length === 0) return null;
|
|
101
84
|
return /* @__PURE__ */ jsxs("section", {
|
|
102
85
|
"data-parameters": true,
|
|
@@ -109,9 +92,8 @@ function ApiParameters({ schema: doc, path, method, meta, overrides }) {
|
|
|
109
92
|
});
|
|
110
93
|
}
|
|
111
94
|
function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fields }) {
|
|
112
|
-
const parsed = getParsed(toDoc(doc));
|
|
113
95
|
const rootDoc = toDoc(doc);
|
|
114
|
-
const requestBody =
|
|
96
|
+
const requestBody = resolveRequestBody(rootDoc, path, method);
|
|
115
97
|
if (requestBody?.schema === void 0) return null;
|
|
116
98
|
return /* @__PURE__ */ jsxs("section", {
|
|
117
99
|
"data-request-body": true,
|
|
@@ -135,10 +117,8 @@ function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fiel
|
|
|
135
117
|
});
|
|
136
118
|
}
|
|
137
119
|
function ApiResponse({ schema: doc, path, method, status, value, meta, fields }) {
|
|
138
|
-
const parsed = getParsed(toDoc(doc));
|
|
139
120
|
const rootDoc = toDoc(doc);
|
|
140
|
-
const response =
|
|
141
|
-
if (response === void 0) throw new SchemaNormalisationError(`Response not found: ${status}`, doc, "openapi-invalid");
|
|
121
|
+
const response = resolveResponse(rootDoc, path, method, status);
|
|
142
122
|
if (response.schema === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
143
123
|
"data-status": status,
|
|
144
124
|
children: [
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequestBody } from "./parser.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/openapi/resolve.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parse and cache an OpenAPI document. Returns cached version if
|
|
6
|
+
* the same object identity has been seen before.
|
|
7
|
+
*/
|
|
8
|
+
declare function getParsed(doc: Record<string, unknown>): OpenApiDocument;
|
|
9
|
+
/**
|
|
10
|
+
* Coerce an unknown value to a record, returning an empty record
|
|
11
|
+
* for non-objects.
|
|
12
|
+
*/
|
|
13
|
+
declare function toDoc(value: unknown): Record<string, unknown>;
|
|
14
|
+
interface ResolvedOperation {
|
|
15
|
+
operation: OperationInfo;
|
|
16
|
+
parameters: ParameterInfo[];
|
|
17
|
+
requestBody: ReturnType<typeof getRequestBody>;
|
|
18
|
+
responses: ResponseInfo[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve an operation from an OpenAPI document by path and method.
|
|
22
|
+
* Throws if the operation is not found.
|
|
23
|
+
*/
|
|
24
|
+
declare function resolveOperation(doc: Record<string, unknown>, path: string, method: string): ResolvedOperation;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve parameters for an operation. Returns empty array if none.
|
|
27
|
+
*/
|
|
28
|
+
declare function resolveParameters(doc: Record<string, unknown>, path: string, method: string): ParameterInfo[];
|
|
29
|
+
/**
|
|
30
|
+
* Resolve request body for an operation. Returns undefined if none.
|
|
31
|
+
*/
|
|
32
|
+
declare function resolveRequestBody(doc: Record<string, unknown>, path: string, method: string): ReturnType<typeof getRequestBody>;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a specific response by status code. Throws if not found.
|
|
35
|
+
*/
|
|
36
|
+
declare function resolveResponse(doc: Record<string, unknown>, path: string, method: string, statusCode: string): ResponseInfo;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve all responses for an operation.
|
|
39
|
+
*/
|
|
40
|
+
declare function resolveResponses(doc: Record<string, unknown>, path: string, method: string): ResponseInfo[];
|
|
41
|
+
//#endregion
|
|
42
|
+
export { ResolvedOperation, getParsed, resolveOperation, resolveParameters, resolveRequestBody, resolveResponse, resolveResponses, toDoc };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isObject } from "../core/guards.mjs";
|
|
2
|
+
import { getParameters, getRequestBody, getResponses, listOperations, parseOpenApiDocument } from "./parser.mjs";
|
|
3
|
+
//#region src/openapi/resolve.ts
|
|
4
|
+
/**
|
|
5
|
+
* OpenAPI document resolution and caching.
|
|
6
|
+
*
|
|
7
|
+
* Pure functions for looking up operations, parameters, request bodies,
|
|
8
|
+
* and responses from parsed OpenAPI documents. Extracted from components
|
|
9
|
+
* for testability without React.
|
|
10
|
+
*/
|
|
11
|
+
const docCache = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
/**
|
|
13
|
+
* Parse and cache an OpenAPI document. Returns cached version if
|
|
14
|
+
* the same object identity has been seen before.
|
|
15
|
+
*/
|
|
16
|
+
function getParsed(doc) {
|
|
17
|
+
const cached = docCache.get(doc);
|
|
18
|
+
if (cached !== void 0) return cached;
|
|
19
|
+
const parsed = parseOpenApiDocument(doc);
|
|
20
|
+
docCache.set(doc, parsed);
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Coerce an unknown value to a record, returning an empty record
|
|
25
|
+
* for non-objects.
|
|
26
|
+
*/
|
|
27
|
+
function toDoc(value) {
|
|
28
|
+
return isObject(value) ? value : {};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve an operation from an OpenAPI document by path and method.
|
|
32
|
+
* Throws if the operation is not found.
|
|
33
|
+
*/
|
|
34
|
+
function resolveOperation(doc, path, method) {
|
|
35
|
+
const parsed = getParsed(doc);
|
|
36
|
+
const operation = listOperations(parsed).find((op) => op.path === path && op.method === method);
|
|
37
|
+
if (operation === void 0) throw new Error(`Operation not found: ${method.toUpperCase()} ${path}`);
|
|
38
|
+
return {
|
|
39
|
+
operation,
|
|
40
|
+
parameters: getParameters(parsed, path, method),
|
|
41
|
+
requestBody: getRequestBody(parsed, path, method),
|
|
42
|
+
responses: getResponses(parsed, path, method)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve parameters for an operation. Returns empty array if none.
|
|
47
|
+
*/
|
|
48
|
+
function resolveParameters(doc, path, method) {
|
|
49
|
+
return getParameters(getParsed(doc), path, method);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve request body for an operation. Returns undefined if none.
|
|
53
|
+
*/
|
|
54
|
+
function resolveRequestBody(doc, path, method) {
|
|
55
|
+
return getRequestBody(getParsed(doc), path, method);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a specific response by status code. Throws if not found.
|
|
59
|
+
*/
|
|
60
|
+
function resolveResponse(doc, path, method, statusCode) {
|
|
61
|
+
const response = getResponses(getParsed(doc), path, method).find((r) => r.statusCode === statusCode);
|
|
62
|
+
if (response === void 0) throw new Error(`Response not found: ${statusCode}`);
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve all responses for an operation.
|
|
67
|
+
*/
|
|
68
|
+
function resolveResponses(doc, path, method) {
|
|
69
|
+
return getResponses(getParsed(doc), path, method);
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { getParsed, resolveOperation, resolveParameters, resolveRequestBody, resolveResponse, resolveResponses, toDoc };
|