@xyd-js/openapi 0.1.0-xyd.10 → 0.1.0-xyd.13

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 (49) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/__fixtures__/-2.complex.openai/input.yaml +39848 -0
  3. package/__fixtures__/-2.complex.openai/output.json +321646 -0
  4. package/__fixtures__/-2.complex.openai/pluginOasOpenai.ts +553 -0
  5. package/__fixtures__/1.basic/input.yaml +226 -0
  6. package/__fixtures__/1.basic/output.json +1919 -0
  7. package/__fixtures__/2.more/input.yaml +76 -0
  8. package/__fixtures__/2.more/output.json +292 -0
  9. package/__fixtures__/3.multiple-responses/input.yaml +48 -0
  10. package/__fixtures__/3.multiple-responses/output.json +266 -0
  11. package/__fixtures__/4.abc/input.yaml +639 -0
  12. package/__fixtures__/4.abc/output.json +3828 -0
  13. package/__fixtures__/5.xdocs.codeLanguages/input.yaml +231 -0
  14. package/__fixtures__/5.xdocs.codeLanguages/output.json +1879 -0
  15. package/__fixtures__/5.xdocs.sidebar/input.yaml +256 -0
  16. package/__fixtures__/5.xdocs.sidebar/output.json +843 -0
  17. package/__fixtures__/6.codeSamples/input.yaml +75 -0
  18. package/__fixtures__/6.codeSamples/output.json +293 -0
  19. package/__tests__/oapSchemaToReferences.test.ts +88 -0
  20. package/__tests__/utils.ts +81 -0
  21. package/dist/index.cjs +1859 -162
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +36 -4
  24. package/dist/index.d.ts +36 -4
  25. package/dist/index.js +1855 -155
  26. package/dist/index.js.map +1 -1
  27. package/index.ts +10 -2
  28. package/package.json +11 -6
  29. package/src/const.ts +5 -1
  30. package/src/converters/oas-componentSchemas.ts +205 -0
  31. package/src/converters/oas-examples.ts +417 -0
  32. package/src/{parameters.ts → converters/oas-parameters.ts} +17 -3
  33. package/src/converters/oas-paths.ts +354 -0
  34. package/src/{requestBody.ts → converters/oas-requestBody.ts} +30 -10
  35. package/src/converters/oas-responses.ts +76 -0
  36. package/src/converters/oas-schema.ts +141 -0
  37. package/src/index.ts +13 -5
  38. package/src/oas-core.ts +579 -0
  39. package/src/types.ts +18 -0
  40. package/src/utils.ts +103 -90
  41. package/src/xdocs/index.ts +18 -0
  42. package/src/xdocs/pluginSidebar.ts +580 -0
  43. package/src/xdocs/types.ts +26 -0
  44. package/vitest.config.ts +7 -0
  45. package/src/examples.ts +0 -116
  46. package/src/paths.ts +0 -103
  47. package/src/properties.ts +0 -37
  48. package/src/responses.ts +0 -38
  49. package/src/schema.ts +0 -62
@@ -0,0 +1,580 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+
3
+ import {
4
+ CodeBlockTab,
5
+ Example,
6
+ ExampleGroup,
7
+ Reference,
8
+ UniformPluginArgs,
9
+ OpenAPIReferenceContext
10
+ } from "@xyd-js/uniform";
11
+
12
+ import {XDocs, XDocsSidebar, XDocsPage} from "./types";
13
+
14
+ interface NavigationGroup {
15
+ id: string
16
+ title: string
17
+ beta: boolean
18
+ }
19
+
20
+ interface ComponentMeta {
21
+ name: string
22
+
23
+ group: string
24
+
25
+ example: string
26
+ }
27
+
28
+ type ExtensionSchema = OpenAPIV3.Document & {
29
+ "x-docs"?: XDocs
30
+ }
31
+
32
+ type NavigationGroupMap = {
33
+ [id: string]: NavigationGroup & {
34
+ index: number
35
+ }
36
+ }
37
+
38
+ interface OperationExample {
39
+ request: string | { [lang: string]: string }
40
+ response: string | { [lang: string]: string }
41
+ }
42
+
43
+ type Examples = string | OperationExample | OperationExample[]
44
+
45
+ export function uniformPluginXDocsSidebar({
46
+ references,
47
+ defer,
48
+ }: UniformPluginArgs) {
49
+ let schema: ExtensionSchema | undefined
50
+ const refByOperationId: {
51
+ [key: string]: Reference
52
+ } = {}
53
+ const refByComponentSchema: {
54
+ [key: string]: Reference
55
+ } = {}
56
+
57
+ defer(() => {
58
+ // @ts-ignore
59
+ if (typeof references.__internal_options === "function") {
60
+ // @ts-ignore
61
+ const options = references.__internal_options()
62
+
63
+ if (options?.regions?.length) {
64
+ return {}
65
+ }
66
+ }
67
+
68
+ const output: Reference[] = []
69
+ if (!schema) {
70
+ return {}
71
+ }
72
+
73
+ const xDocs = schema["x-docs"]
74
+
75
+ if (!xDocs?.sidebar) {
76
+ return {}
77
+ }
78
+
79
+ const navigationMap: NavigationGroupMap = {}
80
+
81
+ // Process sidebar groups
82
+ for (let i = 0; i < xDocs.sidebar.length; i++) {
83
+ const navGroup: XDocsSidebar = xDocs.sidebar[i]
84
+ if (!navGroup) {
85
+ continue
86
+ }
87
+
88
+ const uniqueKey = `${navGroup.group}-${i}`
89
+ navigationMap[uniqueKey] = {
90
+ id: navGroup.group,
91
+ title: navGroup.group,
92
+ beta: false,
93
+ index: i
94
+ }
95
+ }
96
+
97
+ // Process each group and its nested pages
98
+ for (let i = 0; i < xDocs.sidebar.length; i++) {
99
+ const group: XDocsSidebar = xDocs.sidebar[i]
100
+ const uniqueKey = `${group.group}-${i}`
101
+ const navGroup = navigationMap[uniqueKey]
102
+ if (!navGroup) {
103
+ console.warn(`No navigation group found for group: ${group.group}`)
104
+ continue
105
+ }
106
+
107
+ if (!Array.isArray(group.pages)) {
108
+ continue
109
+ }
110
+
111
+ // Process nested groups and pages
112
+ processGroupPages(xDocs, group.pages, [group.group], navGroup, output)
113
+ }
114
+
115
+ // Clear references and set from output
116
+ if (Array.isArray(references)) {
117
+ references.length = 0
118
+ references.push(...output)
119
+ } else {
120
+ references = output[0] || references
121
+ }
122
+
123
+ return {}
124
+ })
125
+
126
+ function processGroupPages(
127
+ xDocs: XDocs,
128
+ pages: (XDocsSidebar | XDocsPage)[],
129
+ groupPath: string[],
130
+ navGroup: NavigationGroup & { index: number },
131
+ output: Reference[],
132
+ parentPath?: string
133
+ ) {
134
+ for (const page of pages) {
135
+ if ('pages' in page && Array.isArray(page.pages)) {
136
+ // This is a nested group
137
+ processGroupPages(xDocs, page.pages, [...groupPath, page.group], navGroup, output, page.path)
138
+ } else if ('type' in page && 'key' in page) {
139
+ // This is a page
140
+ processPage(xDocs, page, groupPath, navGroup, output, parentPath)
141
+ }
142
+ }
143
+ }
144
+
145
+ function processPage(
146
+ xDocs: XDocs,
147
+ page: XDocsPage,
148
+ groupPath: string[],
149
+ navGroup: NavigationGroup & { index: number },
150
+ output: Reference[],
151
+ parentPath?: string
152
+ ) {
153
+ let uniformRef: Reference | undefined
154
+
155
+ switch (page.type) {
156
+ case "endpoint": {
157
+ const operationRef = refByOperationId[page.key]
158
+ if (!operationRef) {
159
+ console.warn(`No operation found for key: ${page.key} in group ${groupPath.join('/')}`)
160
+ return
161
+ }
162
+ uniformRef = operationRef
163
+ break
164
+ }
165
+
166
+ case "object": {
167
+ const componentRef = refByComponentSchema[page.key]
168
+ if (!componentRef) {
169
+ console.warn(`No component schema found for key: ${page.key} in group ${groupPath.join('/')}`)
170
+ return
171
+ }
172
+
173
+ const selector = componentRef.__UNSAFE_selector
174
+ if (!selector || typeof selector !== "function") {
175
+ return
176
+ }
177
+
178
+ const component = selector("[component]") as OpenAPIV3.SchemaObject | undefined
179
+ if (!component) {
180
+ console.warn(`No component schema found for key: ${page.key} in group ${groupPath.join('/')}`)
181
+ return
182
+ }
183
+
184
+ let componentMeta: ComponentMeta | undefined
185
+ if (component.allOf) {
186
+ let found = false
187
+ for (const item of component.allOf) {
188
+ const docsMeta = (item as any)["x-docs"] as ComponentMeta | undefined
189
+
190
+ if (docsMeta && found) {
191
+ console.warn(`Multiple x-docs found in allOf for component schema: ${page.key} in group ${groupPath.join('/')}`)
192
+ }
193
+
194
+ if (docsMeta) {
195
+ found = true
196
+ componentMeta = docsMeta
197
+ break
198
+ }
199
+ }
200
+
201
+ if (!found) {
202
+ console.warn(`No x-docs found in allOf for component schema: ${page.key} in group ${groupPath.join('/')}`)
203
+ return
204
+ }
205
+ } else {
206
+ const docsMeta = (component as any)["x-docs"] as ComponentMeta | undefined
207
+ if (docsMeta) {
208
+ componentMeta = docsMeta
209
+ }
210
+ }
211
+
212
+ uniformRef = componentRef
213
+ if (!componentMeta) {
214
+ break
215
+ }
216
+
217
+ componentRef.title = componentMeta.name || componentRef.title
218
+
219
+ if (componentMeta.example) {
220
+ const exampleGroups = oasXDocsExamples(componentMeta.example)
221
+ uniformRef.examples = {
222
+ groups: exampleGroups,
223
+ }
224
+ }
225
+ break
226
+ }
227
+
228
+ default: {
229
+ console.warn(`Unknown page type: ${page.type} in group ${groupPath.join('/')}`)
230
+ return
231
+ }
232
+ }
233
+
234
+ if (!uniformRef) {
235
+ return
236
+ }
237
+
238
+ if (xDocs.sidebarPathStrategy === "inherit") {
239
+ const ctx = uniformRef.context as OpenAPIReferenceContext | undefined
240
+ let firstPart = ""
241
+ if (parentPath) {
242
+ firstPart = parentPath
243
+ } else {
244
+ firstPart = ctx?.path || ""
245
+ }
246
+
247
+ const canonical = joinPaths(firstPart || "", page.path)
248
+ if (canonical) {
249
+ uniformRef.canonical = canonical
250
+ }
251
+ } else if (page.path) {
252
+ uniformRef.canonical = joinPaths(parentPath, page.path)
253
+ } else if (parentPath) {
254
+ uniformRef.canonical = parentPath
255
+ }
256
+
257
+ if (!uniformRef.context) {
258
+ uniformRef.context = {}
259
+ }
260
+
261
+ uniformRef.context.group = groupPath
262
+
263
+ output.push(uniformRef)
264
+ }
265
+
266
+ return function pluginXDocsSidebarInner(ref: Reference) {
267
+ // @ts-ignore
268
+ const selector = ref.__UNSAFE_selector
269
+ if (!selector || typeof selector !== "function") {
270
+ return
271
+ }
272
+
273
+ const oapSchema = selector("[schema]")
274
+ if (!oapSchema) {
275
+ return
276
+ }
277
+ schema = oapSchema
278
+
279
+ const ctx = ref.context as OpenAPIReferenceContext | undefined
280
+ if (ctx?.componentSchema) {
281
+ refByComponentSchema[ctx.componentSchema] = ref
282
+ }
283
+
284
+ const methodPath = selector("[method] [path]") as OpenAPIV3.OperationObject | undefined
285
+ if (!methodPath) {
286
+ return
287
+ }
288
+
289
+ const oapMethod = selector("[method]")
290
+ if (!oapMethod) {
291
+ return
292
+ }
293
+
294
+ const operationId = methodPath.operationId
295
+ if (operationId) {
296
+ refByOperationId[operationId] = ref
297
+ }
298
+
299
+ const methodId = (oapMethod?.httpMethod?.toUpperCase() + " " + oapMethod?.path || "").trim()
300
+ if (methodId) {
301
+ refByOperationId[methodId] = ref
302
+ }
303
+
304
+ const meta = (methodPath as any)["x-docs"]
305
+ if (!meta) {
306
+ return
307
+ }
308
+
309
+ if (meta.name) {
310
+ ref.title = meta.name
311
+ }
312
+
313
+ if (meta.group) {
314
+ if (ref.context) {
315
+ ref.context.group = [meta.group]
316
+ }
317
+ }
318
+
319
+ if (!ref.description) {
320
+ ref.description = methodPath.summary || ""
321
+ }
322
+
323
+ if (meta.examples) {
324
+ const exampleGroups = oasXDocsExamples(meta.examples)
325
+ ref.examples = {
326
+ groups: exampleGroups,
327
+ }
328
+ }
329
+
330
+ if (meta.returns) {
331
+ if (ref.definitions?.length) {
332
+ ref.definitions[ref.definitions.length - 1] = {
333
+ title: ref.definitions[ref.definitions.length - 1].title,
334
+ description: meta.returns,
335
+ properties: []
336
+ }
337
+ } else {
338
+ ref.definitions = [
339
+ {
340
+ title: "Response",
341
+ description: meta.returns,
342
+ properties: []
343
+ }
344
+ ]
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ function oasXDocsExamples(examples: Examples) {
351
+ const groups: ExampleGroup[] = []
352
+
353
+ if (examples) {
354
+ if (Array.isArray(examples)) {
355
+ // Create request group
356
+ const requestExamples: Example[] = []
357
+ examples.forEach((example: {
358
+ title?: string;
359
+ request?: string | Record<string, string>;
360
+ response?: string | Record<string, string>;
361
+ }) => {
362
+ if (example.request) {
363
+ const tabs: CodeBlockTab[] = []
364
+ if (typeof example.request === "string") {
365
+ tabs.push({
366
+ title: "",
367
+ language: "json",
368
+ code: example.request
369
+ })
370
+ } else {
371
+ for (let lang of Object.keys(example.request)) {
372
+ const code = example.request[lang] || ""
373
+ const language = lang === "curl" ? "bash" :
374
+ lang === "node.js" ? "js" : lang
375
+
376
+ tabs.push({
377
+ title: lang,
378
+ language,
379
+ code
380
+ })
381
+ }
382
+ }
383
+ if (tabs.length > 0) {
384
+ requestExamples.push({
385
+ description: example.title || "",
386
+ codeblock: {
387
+ title: example.title || "",
388
+ tabs
389
+ }
390
+ })
391
+ }
392
+ }
393
+ })
394
+ if (requestExamples.length > 0) {
395
+ groups.push({
396
+ description: "Example request",
397
+ examples: requestExamples
398
+ })
399
+ }
400
+
401
+ // Create response group
402
+ const responseExamples: Example[] = []
403
+ examples.forEach((example: {
404
+ title?: string;
405
+ request?: string | Record<string, string>;
406
+ response?: string | Record<string, string>;
407
+ }) => {
408
+ if (example.response) {
409
+ const tabs: CodeBlockTab[] = []
410
+ if (typeof example.response === "string") {
411
+ tabs.push({
412
+ title: "",
413
+ language: "json",
414
+ code: example.response
415
+ })
416
+ } else {
417
+ for (let lang of Object.keys(example.response)) {
418
+ const code = example.response[lang] || ""
419
+ const language = lang === "curl" ? "bash" :
420
+ lang === "node.js" ? "js" : lang
421
+
422
+ tabs.push({
423
+ title: lang,
424
+ language,
425
+ code
426
+ })
427
+ }
428
+ }
429
+ if (tabs.length > 0) {
430
+ responseExamples.push({
431
+ description: example.title || "",
432
+ codeblock: {
433
+ title: example.title || "",
434
+ tabs
435
+ }
436
+ })
437
+ }
438
+ }
439
+ })
440
+ if (responseExamples.length > 0) {
441
+ groups.push({
442
+ description: "Example response",
443
+ examples: responseExamples
444
+ })
445
+ }
446
+ } else {
447
+ if (typeof examples === "string") {
448
+ groups.push({
449
+ description: "Example",
450
+ examples: [
451
+ {
452
+ description: "",
453
+ codeblock: {
454
+ tabs: [
455
+ {
456
+ title: "",
457
+ language: "json",
458
+ code: examples
459
+ }
460
+ ]
461
+ }
462
+ }
463
+ ]
464
+ })
465
+ } else {
466
+ if (examples.request) {
467
+ const tabs: CodeBlockTab[] = []
468
+
469
+ if (typeof examples.request === "string") {
470
+ tabs.push({
471
+ title: "",
472
+ language: "json",
473
+ code: examples.request || "",
474
+ })
475
+ } else {
476
+ for (let lang of Object.keys(examples.request)) {
477
+ const code = examples.request[lang] || ""
478
+
479
+ switch (lang) {
480
+ case "curl":
481
+ lang = "bash"
482
+ break
483
+ case "node.js":
484
+ lang = "js"
485
+ break
486
+ default:
487
+ break
488
+ }
489
+
490
+ tabs.push({
491
+ title: lang,
492
+ language: lang,
493
+ code,
494
+ })
495
+ }
496
+ }
497
+
498
+ groups.push({
499
+ description: "Example request",
500
+ examples: [
501
+ {
502
+ description: "",
503
+ codeblock: {
504
+ tabs
505
+ }
506
+ }
507
+ ]
508
+ })
509
+ }
510
+
511
+ if (examples.response) {
512
+ const tabs: CodeBlockTab[] = []
513
+ if (typeof examples.response === "string") {
514
+ tabs.push({
515
+ title: "",
516
+ language: "json",
517
+ code: examples.response || "",
518
+ })
519
+ } else {
520
+ for (let lang of Object.keys(examples.response)) {
521
+ const code = examples.response[lang] || ""
522
+
523
+ switch (lang) {
524
+ case "curl":
525
+ lang = "bash"
526
+ break
527
+ case "node.js":
528
+ lang = "js"
529
+ break
530
+ default:
531
+ break
532
+ }
533
+
534
+ tabs.push({
535
+ title: lang,
536
+ language: lang,
537
+ code,
538
+ })
539
+ }
540
+ }
541
+
542
+ groups.push({
543
+ description: "Example response",
544
+ examples: [
545
+ {
546
+ description: "",
547
+ codeblock: {
548
+ tabs
549
+ }
550
+ }
551
+ ]
552
+ })
553
+ }
554
+ }
555
+ }
556
+ }
557
+
558
+ return groups
559
+ }
560
+
561
+ function sanitizePath(path: string): string {
562
+ // Remove path parameters like {userId} or :userId
563
+ return path.replace(/\/\{[^}]+\}/g, '').replace(/\/:[^/]+/g, '')
564
+ }
565
+
566
+ function joinPaths(...paths: (string | undefined)[]): string {
567
+ return paths
568
+ .filter(Boolean)
569
+ .map(path => {
570
+ // Remove leading and trailing slashes
571
+ path = path!.replace(/^\/+|\/+$/g, '')
572
+ // Ensure path starts with / if it's not empty
573
+ return path ? `/${path}` : ''
574
+ })
575
+ .join('')
576
+ .replace(/\/+/g, '/') // Replace multiple slashes with single slash
577
+ .replace(/\/\{[^}]+\}/g, '') // Remove {param} segments
578
+ .replace(/\/:[^/]+/g, '') // Remove :param segments
579
+ }
580
+
@@ -0,0 +1,26 @@
1
+ export interface XDocs {
2
+ route?: string
3
+
4
+ codeLanguages?: string[]
5
+
6
+ sidebarPathStrategy?: "inherit"
7
+
8
+ sidebar?: XDocsSidebar[]
9
+ }
10
+
11
+ // TODO: move to core settings like SidebarLite<XDocsSidebar>
12
+ export interface XDocsSidebar {
13
+ group: string
14
+
15
+ path?: string
16
+
17
+ pages: XDocsSidebar[] | XDocsPage[]
18
+ }
19
+
20
+ export interface XDocsPage {
21
+ type: "endpoint" | "object"
22
+
23
+ key: string
24
+
25
+ path?: string
26
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ // ...
6
+ },
7
+ })