@scalar/openapi-to-markdown 1.0.2 → 1.2.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 (36) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +20 -4
  3. package/dist/browser.d.ts +9 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/browser.js +8 -0
  6. package/dist/create-markdown-from-openapi.d.ts +0 -3
  7. package/dist/create-markdown-from-openapi.d.ts.map +1 -1
  8. package/dist/create-markdown-from-openapi.js +1 -18
  9. package/dist/get-markdown-examples.d.ts +29 -0
  10. package/dist/get-markdown-examples.d.ts.map +1 -0
  11. package/dist/get-markdown-examples.js +105 -0
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/load-document.d.ts.map +1 -1
  16. package/dist/load-document.js +8 -1
  17. package/dist/render-document.d.ts.map +1 -1
  18. package/dist/render-document.js +8 -3
  19. package/dist/render-examples.d.ts +6 -0
  20. package/dist/render-examples.d.ts.map +1 -0
  21. package/dist/render-examples.js +41 -0
  22. package/dist/render-operation-details.d.ts +11 -0
  23. package/dist/render-operation-details.d.ts.map +1 -0
  24. package/dist/render-operation-details.js +85 -0
  25. package/dist/render-operation.d.ts.map +1 -1
  26. package/dist/render-operation.js +26 -15
  27. package/dist/render-schema.d.ts +55 -13
  28. package/dist/render-schema.d.ts.map +1 -1
  29. package/dist/render-schema.js +227 -69
  30. package/dist/restore-boolean-schemas.d.ts +7 -0
  31. package/dist/restore-boolean-schemas.d.ts.map +1 -0
  32. package/dist/restore-boolean-schemas.js +73 -0
  33. package/dist/select-document.d.ts +3 -3
  34. package/dist/select-document.d.ts.map +1 -1
  35. package/dist/select-document.js +57 -36
  36. package/package.json +13 -10
@@ -1,7 +1,7 @@
1
+ import { HTTP_METHODS } from '@scalar/helpers/http/http-methods';
1
2
  import { isObject } from '@scalar/helpers/object/is-object';
2
- import { getPathItemOperation, getResolvedPathItem } from '@scalar/workspace-store/helpers/for-each-path-item-operation';
3
+ import { forEachPathItemOperation, getPathItemOperation, getResolvedPathItem, setPathItemOperation, } from '@scalar/workspace-store/helpers/for-each-path-item-operation';
3
4
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
4
- const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
5
5
  const HTTP_METHOD_SET = new Set(HTTP_METHODS);
6
6
  const normalizeHttpMethod = (method) => {
7
7
  const normalized = method.toLowerCase();
@@ -28,17 +28,18 @@ const parseJsonPointer = (pointer) => normalizeJsonPointer(pointer)
28
28
  .map((segment) => segment.replaceAll('~1', '/').replaceAll('~0', '~'));
29
29
  const getOperationSelectorFromPointer = (pointer) => {
30
30
  const segments = parseJsonPointer(pointer);
31
- if (segments.length !== 3 || segments[0] !== 'paths') {
31
+ if (segments[0] !== 'paths' ||
32
+ !(segments.length === 3 || (segments.length === 4 && segments[2] === 'additionalOperations'))) {
32
33
  throw new Error(`JSON pointer "${pointer}" must target an operation object under "/paths/{path}/{method}"`);
33
34
  }
34
35
  const path = segments[1];
35
- const method = segments[2];
36
- if (!path || !method) {
36
+ const method = segments.length === 4 ? segments[3] : segments[2];
37
+ if (!path || !method || (segments.length === 3 ? !HTTP_METHOD_SET.has(method) : HTTP_METHOD_SET.has(method))) {
37
38
  throw new Error(`JSON pointer "${pointer}" must target an operation object under "/paths/{path}/{method}"`);
38
39
  }
39
40
  return {
40
41
  path,
41
- method: method,
42
+ method,
42
43
  };
43
44
  };
44
45
  const getPathEntries = (document) => {
@@ -51,12 +52,20 @@ const getPathEntries = (document) => {
51
52
  return pathItem ? [[path, pathItem]] : [];
52
53
  });
53
54
  };
54
- const filterPathItemToSingleOperation = (pathItem, selectedMethod) => Object.fromEntries(Object.entries(pathItem).filter(([key]) => {
55
- const method = normalizeHttpMethod(key);
56
- return !method || method === selectedMethod;
57
- }));
55
+ /** Keep path metadata while excluding every unselected fixed or additional operation. */
56
+ const filterPathItemOperations = (pathItem, methods) => {
57
+ const selected = Object.fromEntries(Object.entries(pathItem).filter(([key]) => !HTTP_METHOD_SET.has(key) && key !== 'additionalOperations'));
58
+ forEachPathItemOperation(pathItem, (method, operation) => {
59
+ if (methods.includes(method)) {
60
+ setPathItemOperation(selected, method, operation);
61
+ }
62
+ });
63
+ return selected;
64
+ };
65
+ /** Exact authored methods take precedence over the legacy uppercase fixed-method aliases. */
66
+ const resolveMethod = (pathItem, method) => getPathItemOperation(pathItem, method) ? method : normalizeHttpMethod(method);
58
67
  const findOperationByPathAndMethod = (document, selector) => {
59
- const method = normalizeHttpMethod(selector.method);
68
+ const method = resolveMethod(getResolvedPathItem(document.paths?.[selector.path]), selector.method);
60
69
  if (!method) {
61
70
  throw new Error(`Invalid HTTP method "${selector.method}". Supported methods: ${HTTP_METHODS.join(', ')}`);
62
71
  }
@@ -69,20 +78,22 @@ const findOperationByPathAndMethod = (document, selector) => {
69
78
  method,
70
79
  };
71
80
  };
72
- const findOperationsByOperationId = (document, operationId) => getPathEntries(document).flatMap(([path, pathItem]) => Object.entries(pathItem).flatMap(([methodKey, operation]) => {
73
- const method = normalizeHttpMethod(methodKey);
74
- if (!method || !isObject(operation)) {
75
- return [];
76
- }
77
- const candidateOperationId = 'operationId' in operation && typeof operation.operationId === 'string' ? operation.operationId : undefined;
78
- if (candidateOperationId !== operationId) {
79
- return [];
80
- }
81
- return [{ path, method }];
82
- }));
81
+ const findOperationsByOperationId = (document, operationId) => getPathEntries(document).flatMap(([path, pathItem]) => {
82
+ const matches = [];
83
+ forEachPathItemOperation(pathItem, (method, operation) => {
84
+ if (getResolvedRef(operation)?.operationId === operationId) {
85
+ matches.push({ path, method });
86
+ }
87
+ });
88
+ return matches;
89
+ });
83
90
  const resolveOperationMatch = (document, selector) => {
84
91
  if ('pointer' in selector) {
85
- return findOperationByPathAndMethod(document, getOperationSelectorFromPointer(selector.pointer));
92
+ const match = getOperationSelectorFromPointer(selector.pointer);
93
+ if (!getPathItemOperation(document.paths?.[match.path], match.method)) {
94
+ throw new Error(`Operation not found at JSON pointer "${selector.pointer}"`);
95
+ }
96
+ return match;
86
97
  }
87
98
  if ('operationId' in selector) {
88
99
  const matches = findOperationsByOperationId(document, selector.operationId);
@@ -106,7 +117,7 @@ const filterDocumentByOperation = (document, selector) => {
106
117
  return {
107
118
  ...document,
108
119
  paths: {
109
- [match.path]: filterPathItemToSingleOperation(pathItem, match.method),
120
+ [match.path]: filterPathItemOperations(pathItem, [match.method]),
110
121
  },
111
122
  };
112
123
  };
@@ -157,9 +168,14 @@ export const selectDocument = (document, options = {}) => {
157
168
  }
158
169
  selected.tags = metadata.length ? metadata : [{ name: options.tag }];
159
170
  for (const [path, item] of getPathEntries(document)) {
160
- const methods = HTTP_METHODS.filter((method) => getPathItemOperation(item, method)?.tags?.includes(options.tag));
171
+ const methods = [];
172
+ forEachPathItemOperation(item, (method, operation) => {
173
+ if (getResolvedRef(operation)?.tags?.includes(options.tag)) {
174
+ methods.push(method);
175
+ }
176
+ });
161
177
  if (methods.length) {
162
- selected.paths[path] = Object.fromEntries(Object.entries(item).filter(([key]) => !HTTP_METHOD_SET.has(key) || methods.includes(key)));
178
+ selected.paths[path] = filterPathItemOperations(item, methods);
163
179
  }
164
180
  }
165
181
  if (!metadata.length && !Object.keys(selected.paths ?? {}).length) {
@@ -182,26 +198,31 @@ export const selectDocument = (document, options = {}) => {
182
198
  Object.keys(selector).length !== 2) {
183
199
  throw new Error('Invalid webhook selector. Use { name, method }');
184
200
  }
185
- const method = normalizeHttpMethod(selector.method);
201
+ const item = getResolvedPathItem(document.webhooks?.[selector.name]);
202
+ const method = resolveMethod(item, selector.method);
186
203
  if (!method) {
187
204
  throw new Error(`Invalid HTTP method "${selector.method}"`);
188
205
  }
189
- const item = getResolvedPathItem(document.webhooks?.[selector.name]);
190
206
  if (!item || !getPathItemOperation(item, method)) {
191
207
  throw new Error(`Webhook "${selector.name}" with method "${method.toUpperCase()}" was not found`);
192
208
  }
193
- selected.webhooks = { [selector.name]: filterPathItemToSingleOperation(item, method) };
209
+ selected.webhooks = { [selector.name]: filterPathItemOperations(item, [method]) };
194
210
  }
195
211
  const securityNames = new Set();
196
212
  const tagNames = new Set();
197
213
  for (const items of [selected.paths, selected.webhooks]) {
198
214
  for (const [path, itemRef] of Object.entries(items ?? {})) {
199
215
  const item = getResolvedPathItem(itemRef);
200
- const scoped = { ...item, parameters: undefined, servers: undefined };
201
- for (const method of HTTP_METHODS) {
202
- const operation = getPathItemOperation(item, method);
216
+ const scoped = {
217
+ ...item,
218
+ additionalOperations: item.additionalOperations ? { ...item.additionalOperations } : undefined,
219
+ parameters: undefined,
220
+ servers: undefined,
221
+ };
222
+ forEachPathItemOperation(item, (method, operationRef) => {
223
+ const operation = getResolvedRef(operationRef);
203
224
  if (!operation) {
204
- continue;
225
+ return;
205
226
  }
206
227
  const parameters = new Map();
207
228
  for (const ref of [...(item.parameters ?? []), ...(operation.parameters ?? [])]) {
@@ -219,14 +240,14 @@ export const selectDocument = (document, options = {}) => {
219
240
  for (const name of operation.tags ?? []) {
220
241
  tagNames.add(name);
221
242
  }
222
- scoped[method] = {
243
+ setPathItemOperation(scoped, method, {
223
244
  ...operation,
224
245
  parameters: [...parameters.values()],
225
246
  servers: operation.servers ?? item.servers ?? document.servers,
226
247
  security,
227
248
  tags: options.tag !== undefined ? [options.tag] : operation.tags,
228
- };
229
- }
249
+ });
250
+ });
230
251
  items[path] = scoped;
231
252
  }
232
253
  }
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "llm",
17
17
  "swagger"
18
18
  ],
19
- "version": "1.0.2",
19
+ "version": "1.2.0",
20
20
  "engines": {
21
21
  "node": ">=22"
22
22
  },
@@ -28,6 +28,11 @@
28
28
  "import": "./dist/index.js",
29
29
  "types": "./dist/index.d.ts",
30
30
  "default": "./dist/index.js"
31
+ },
32
+ "./browser": {
33
+ "import": "./dist/browser.js",
34
+ "types": "./dist/browser.d.ts",
35
+ "default": "./dist/browser.js"
31
36
  }
32
37
  },
33
38
  "files": [
@@ -35,26 +40,24 @@
35
40
  "CHANGELOG.md"
36
41
  ],
37
42
  "dependencies": {
38
- "@scalar/code-highlight": "0.4.6",
39
- "@scalar/helpers": "0.12.0",
40
- "@scalar/json-magic": "0.14.0",
41
- "@scalar/openapi-upgrader": "0.2.17",
42
- "@scalar/workspace-store": "0.64.0",
43
+ "@scalar/code-highlight": "0.4.7",
44
+ "@scalar/helpers": "0.14.0",
45
+ "@scalar/json-magic": "0.15.1",
46
+ "@scalar/openapi-upgrader": "0.3.1",
47
+ "@scalar/workspace-store": "0.66.0",
43
48
  "rehype-parse": "^9.0.1",
44
49
  "rehype-remark": "^10.0.1",
45
50
  "rehype-sanitize": "^6.0.0",
46
- "rehype-stringify": "^10.0.1",
47
51
  "remark-gfm": "^4.0.1",
48
52
  "remark-parse": "^11.0.0",
49
- "remark-rehype": "^11.1.2",
50
53
  "remark-stringify": "^11.0.0",
51
54
  "unified": "^11.0.5"
52
55
  },
53
56
  "devDependencies": {
54
- "@hono/node-server": "^1.19.10",
57
+ "@hono/node-server": "^2.1.1",
55
58
  "@scalar/galaxy": "0.7.1",
56
59
  "@types/mdast": "^4.0.4",
57
- "hono": "^4.13.7",
60
+ "hono": "^4.13.8",
58
61
  "vitest": "4.1.10"
59
62
  },
60
63
  "scripts": {