@xyd-js/openapi 0.1.0-xyd.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.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # xyd-openapi
2
+
3
+ Transform OpenAPI 3.0 into xyd markdown interface.
@@ -0,0 +1,20 @@
1
+ import path from 'path';
2
+
3
+ import {
4
+ deferencedOpenAPI,
5
+ oapSchemaToReferences,
6
+ } from "../../src";
7
+
8
+ (async () => {
9
+ const openApiPath = path.join(process.cwd(), './examples/livesession/openapi.yaml'); // Ensure this file exists
10
+ const schema = await deferencedOpenAPI(openApiPath)
11
+ const references = oapSchemaToReferences(schema)
12
+ })()
13
+
14
+ // (async () => {
15
+ // const openApiPath = path.join(process.cwd(), './examples/basic/openapi.yaml'); // Ensure this file exists
16
+ // const schema = await deferencedOpenAPI(openApiPath)
17
+ // const references = oapSchemaToReferences(schema)
18
+ //
19
+ // console.log(JSON.stringify(references, null, 2))
20
+ // })()
@@ -0,0 +1,36 @@
1
+ import path from 'path';
2
+
3
+ import Oas from 'oas';
4
+ import {OpenAPIV3} from "openapi-types";
5
+ import oasToSnippet from '@readme/oas-to-snippet';
6
+ import OpenAPISampler from 'openapi-sampler';
7
+ import type {JSONSchema7} from 'json-schema';
8
+
9
+ import {
10
+ deferencedOpenAPI,
11
+ } from "../../src";
12
+
13
+
14
+ (async () => {
15
+ const openApiPath = path.join(process.cwd(), './examples/basic/openapi.yaml'); // Ensure this file exists
16
+ const schema = await deferencedOpenAPI(openApiPath)
17
+
18
+ // TODO: fix any
19
+ const oas = new Oas(schema as any);
20
+ const operation = oas.operation('/todos', 'post');
21
+
22
+ if (operation.schema.requestBody) {
23
+ const body = operation.schema.requestBody as OpenAPIV3.RequestBodyObject
24
+ const schema = body.content["application/json"].schema as JSONSchema7
25
+
26
+ if (!schema) {
27
+ return
28
+ }
29
+
30
+ const fakeData = OpenAPISampler.sample(schema)
31
+
32
+ const {code} = oasToSnippet(oas, operation, {
33
+ body: fakeData
34
+ }, null, "node");
35
+ }
36
+ })()
@@ -0,0 +1,124 @@
1
+ openapi: 3.0.0
2
+ info:
3
+ title: TODO API
4
+ version: 1.0.0
5
+ description: A simple API to manage TODO items.
6
+
7
+ paths:
8
+ /todos:
9
+ get:
10
+ summary: Get all TODO items
11
+ description: "Returns a list of all TODO items."
12
+ responses:
13
+ '200':
14
+ description: A list of TODO items
15
+ content:
16
+ application/json:
17
+ schema:
18
+ $ref: '#/components/schemas/TodoList'
19
+ post:
20
+ summary: Create a new TODO item
21
+ description: "Creates a new TODO item."
22
+ requestBody:
23
+ description: TODO item to create
24
+ required: true
25
+ content:
26
+ application/json:
27
+ schema:
28
+ $ref: '#/components/schemas/TodoItem'
29
+ responses:
30
+ '201':
31
+ description: TODO item created
32
+ content:
33
+ application/json:
34
+ schema:
35
+ $ref: '#/components/schemas/TodoItem'
36
+
37
+ /todos/{id}:
38
+ get:
39
+ summary: Get a TODO item by ID
40
+ description: "Returns a single TODO item."
41
+ parameters:
42
+ - name: id
43
+ in: path
44
+ required: true
45
+ schema:
46
+ type: string
47
+ responses:
48
+ '200':
49
+ description: TODO item found
50
+ content:
51
+ application/json:
52
+ schema:
53
+ $ref: '#/components/schemas/TodoItem'
54
+ '404':
55
+ description: TODO item not found
56
+
57
+ put:
58
+ summary: Update a TODO item by ID
59
+ description: "Updates a single TODO item."
60
+ parameters:
61
+ - name: id
62
+ in: path
63
+ required: true
64
+ schema:
65
+ type: string
66
+ requestBody:
67
+ description: Updated TODO item
68
+ required: true
69
+ content:
70
+ application/json:
71
+ schema:
72
+ $ref: '#/components/schemas/TodoItem'
73
+ responses:
74
+ '200':
75
+ description: TODO item updated
76
+ content:
77
+ application/json:
78
+ schema:
79
+ $ref: '#/components/schemas/TodoItem'
80
+ '404':
81
+ description: TODO item not found
82
+
83
+ delete:
84
+ summary: Delete a TODO item by ID
85
+ description: "Deletes a single TODO item."
86
+ parameters:
87
+ - name: id
88
+ in: path
89
+ required: true
90
+ schema:
91
+ type: string
92
+ responses:
93
+ '204':
94
+ description: TODO item deleted
95
+ '404':
96
+ description: TODO item not found
97
+
98
+ components:
99
+ schemas:
100
+ TodoList:
101
+ type: array
102
+ items:
103
+ $ref: '#/components/schemas/TodoItem'
104
+
105
+ TodoItem:
106
+ type: object
107
+ properties:
108
+ id:
109
+ type: string
110
+ description: Unique identifier for the TODO item
111
+ title:
112
+ type: string
113
+ description: The title of the task
114
+ completed:
115
+ type: boolean
116
+ description: Whether the task is completed
117
+ dueDate:
118
+ type: string
119
+ format: date-time
120
+ description: When the task is due
121
+ required:
122
+ - id
123
+ - title
124
+ - completed
@@ -0,0 +1,16 @@
1
+ import path from 'path';
2
+
3
+ import {
4
+ deferencedOpenAPI,
5
+ oapSchemaToReferences,
6
+ } from "../../src";
7
+ import fs from "fs";
8
+
9
+ (async () => {
10
+ const openApiPath = path.join(process.cwd(), './examples/webhooks/openapi.yaml');
11
+ const schema = await deferencedOpenAPI(openApiPath)
12
+ const references = oapSchemaToReferences(schema)
13
+
14
+ fs.writeFileSync(path.join(process.cwd(), './examples/webhooks/references.json'), JSON.stringify(references, null, 2))
15
+ })()
16
+
@@ -0,0 +1,248 @@
1
+ openapi: 3.1.0
2
+
3
+ info:
4
+ title: Webhooks API
5
+ version: '1.0'
6
+
7
+ paths:
8
+ /session.event.JSError:
9
+ post:
10
+ summary: JSerror
11
+ description: |
12
+ ---
13
+ title: JSError
14
+ group: [EVENT TYPES, session.event]
15
+ ---
16
+ requestBody:
17
+ required: true
18
+ content:
19
+ application/json:
20
+ schema:
21
+ $ref: '#/components/schemas/SessionEventJSErrorPayload'
22
+ responses:
23
+ '200':
24
+ description: Event handled successfully
25
+
26
+ /session.event.NetError:
27
+ post:
28
+ summary: NetError
29
+ description: |
30
+ ---
31
+ title: NetError
32
+ group: [EVENT TYPES, session.event]
33
+ ---
34
+ requestBody:
35
+ required: true
36
+ content:
37
+ application/json:
38
+ schema:
39
+ $ref: '#/components/schemas/SessionEventNetErrorPayload'
40
+ responses:
41
+ '200':
42
+ description: Event handled successfully
43
+
44
+ /session.event.ErrorClick:
45
+ post:
46
+ summary: ErrorClick
47
+ description: |
48
+ ---
49
+ title: ErrorClick
50
+ group: [EVENT TYPES, session.event]
51
+ ---
52
+ requestBody:
53
+ required: true
54
+ content:
55
+ application/json:
56
+ schema:
57
+ $ref: '#/components/schemas/SessionEventErrorClickPayload'
58
+ responses:
59
+ '200':
60
+ description: Event handled successfully
61
+
62
+ /session.event.RageClick:
63
+ post:
64
+ summary: Handle session event
65
+ description: |
66
+ ---
67
+ title: RageClick
68
+ group: [EVENT TYPES, session.event]
69
+ ---
70
+ requestBody:
71
+ required: true
72
+ content:
73
+ application/json:
74
+ schema:
75
+ $ref: '#/components/schemas/SessionEventRageClickPayload'
76
+ responses:
77
+ '200':
78
+ description: Event handled successfully
79
+
80
+ /session.event.Custom:
81
+ post:
82
+ summary: Handle session event
83
+ description: |
84
+ ---
85
+ title: Custom
86
+ group: [EVENT TYPES, session.event]
87
+ ---
88
+ requestBody:
89
+ required: true
90
+ content:
91
+ application/json:
92
+ schema:
93
+ $ref: '#/components/schemas/SessionEventCustomPayload'
94
+ responses:
95
+ '200':
96
+ description: Event handled successfully
97
+
98
+ components:
99
+ schemas:
100
+ CommonProperties:
101
+ type: object
102
+ properties:
103
+ message_id:
104
+ type: string
105
+ webhook_id:
106
+ type: string
107
+ webhook_type:
108
+ type: string
109
+ api_version:
110
+ type: string
111
+ account_id:
112
+ type: string
113
+ website_id:
114
+ type: string
115
+ created_at:
116
+ type: integer
117
+
118
+ CommonPayloadProperties:
119
+ type: object
120
+ properties:
121
+ visitor:
122
+ $ref: '#/components/schemas/Visitor'
123
+ event_name:
124
+ type: string
125
+ time:
126
+ type: integer
127
+ name:
128
+ type: string
129
+
130
+ Visitor:
131
+ type: object
132
+ properties:
133
+ id:
134
+ type: string
135
+ description: The unique ID of the visitor
136
+ name:
137
+ type: string
138
+ description: The name of the visitor
139
+ email:
140
+ type: string
141
+ description: The email of the visitor
142
+ params:
143
+ type: array
144
+ items:
145
+ type: object
146
+ properties:
147
+ name:
148
+ type: string
149
+ value:
150
+ type: string
151
+ geolocation:
152
+ type: object
153
+ properties:
154
+ country_code:
155
+ type: string
156
+ city:
157
+ type: string
158
+ region:
159
+ type: string
160
+
161
+ JSErrorPayload:
162
+ type: object
163
+ properties:
164
+ count:
165
+ type: integer
166
+ value:
167
+ type: string
168
+
169
+ NetErrorPayload:
170
+ type: object
171
+ properties:
172
+ method:
173
+ type: string
174
+ url:
175
+ type: string
176
+ status:
177
+ type: integer
178
+
179
+ ErrorClickPayload:
180
+ type: object
181
+ properties:
182
+ message:
183
+ type: string
184
+
185
+ RageClickPayload:
186
+ type: object
187
+ properties:
188
+ clicks:
189
+ type: integer
190
+
191
+ CustomPayload:
192
+ type: object
193
+ properties:
194
+ properties:
195
+ type: object
196
+ additionalProperties: true
197
+
198
+ SessionEventJSErrorPayload:
199
+ allOf:
200
+ - $ref: '#/components/schemas/CommonProperties'
201
+ - type: object
202
+ properties:
203
+ payload:
204
+ allOf:
205
+ - $ref: '#/components/schemas/CommonPayloadProperties'
206
+ - $ref: '#/components/schemas/JSErrorPayload'
207
+
208
+
209
+ SessionEventNetErrorPayload:
210
+ allOf:
211
+ - $ref: '#/components/schemas/CommonProperties'
212
+ - type: object
213
+ properties:
214
+ payload:
215
+ allOf:
216
+ - $ref: '#/components/schemas/CommonPayloadProperties'
217
+ - $ref: '#/components/schemas/NetErrorPayload'
218
+
219
+
220
+ SessionEventErrorClickPayload:
221
+ allOf:
222
+ - $ref: '#/components/schemas/CommonProperties'
223
+ - type: object
224
+ properties:
225
+ payload:
226
+ allOf:
227
+ - $ref: '#/components/schemas/CommonPayloadProperties'
228
+ - $ref: '#/components/schemas/ErrorClickPayload'
229
+
230
+ SessionEventRageClickPayload:
231
+ allOf:
232
+ - $ref: '#/components/schemas/CommonProperties'
233
+ - type: object
234
+ properties:
235
+ payload:
236
+ allOf:
237
+ - $ref: '#/components/schemas/CommonPayloadProperties'
238
+ - $ref: '#/components/schemas/RageClickPayload'
239
+
240
+ SessionEventCustomPayload:
241
+ allOf:
242
+ - $ref: '#/components/schemas/CommonProperties'
243
+ - type: object
244
+ properties:
245
+ payload:
246
+ allOf:
247
+ - $ref: '#/components/schemas/CommonPayloadProperties'
248
+ - $ref: '#/components/schemas/CustomPayload'
package/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export {
2
+ deferencedOpenAPI,
3
+ oapSchemaToReferences
4
+ } from "./src";
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@xyd-js/openapi",
3
+ "version": "0.1.0-xyd.0",
4
+ "description": "",
5
+ "main": "./dist/index.js",
6
+ "type": "module",
7
+ "dependencies": {
8
+ "@readme/oas-to-snippet": "^26.0.1",
9
+ "js-yaml": "^4.1.0",
10
+ "json-schema-ref-parser": "^9.0.9",
11
+ "oas": "^25.0.3",
12
+ "openapi-sampler": "^1.5.1",
13
+ "openapi-types": "^12.1.3",
14
+ "@xyd-js/uniform": "0.1.0-xyd.2"
15
+ },
16
+ "devDependencies": {
17
+ "@types/js-yaml": "^4.0.9",
18
+ "tsup": "^8.3.0"
19
+ },
20
+ "scripts": {
21
+ "clean": "rimraf build",
22
+ "prebuild": "pnpm clean",
23
+ "build": "tsup",
24
+ "build:examples": "tsup --config tsup.examples-config.ts"
25
+ }
26
+ }
package/src/const.ts ADDED
@@ -0,0 +1,10 @@
1
+ export const SUPPORTED_HTTP_METHODS: string[] = [
2
+ 'get',
3
+ 'put',
4
+ 'patch',
5
+ 'post',
6
+ 'delete',
7
+ // 'options',
8
+ // 'head',
9
+ // 'trace'
10
+ ];
@@ -0,0 +1,116 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import Oas from "oas";
3
+ // @ts-ignore
4
+ import {Operation} from 'oas/operation'; // TODO: fix ts
5
+ import oasToSnippet from "@readme/oas-to-snippet";
6
+ import OpenAPISampler from "openapi-sampler";
7
+ import type {JSONSchema7} from "json-schema";
8
+
9
+ import {ExampleGroup, Example} from "@xyd-js/uniform";
10
+
11
+ // TODO: option with another languages
12
+ // TODO: uniform plugins
13
+ // TODO: fs uniform plugins
14
+ export function oapExamples(
15
+ oas: Oas,
16
+ operation: Operation
17
+ ): ExampleGroup[] {
18
+ const exampleGroups: ExampleGroup[] = []
19
+
20
+ if (operation.schema.requestBody) {
21
+ const body = operation.schema.requestBody as OpenAPIV3.RequestBodyObject
22
+ const schema = fixAllOfBug(
23
+ body.content["application/json"].schema as JSONSchema7
24
+ )
25
+
26
+ if (!schema) {
27
+ return exampleGroups
28
+ }
29
+
30
+ const fakeData = OpenAPISampler.sample(schema)
31
+
32
+ // TODO: snippet languages options
33
+ const {code} = oasToSnippet(oas, operation, {
34
+ body: fakeData
35
+ }, null, "shell");
36
+
37
+ const examples: Example[] = []
38
+
39
+ examples.push({
40
+ codeblock: {
41
+ tabs: [{
42
+ title: "curl",
43
+ language: "curl",
44
+ code: code || "",
45
+ }]
46
+ }
47
+ })
48
+
49
+ exampleGroups.push({
50
+ description: "Example request",
51
+ examples
52
+ })
53
+ }
54
+
55
+ if (operation.schema.responses) {
56
+ const responses = operation.schema.responses as OpenAPIV3.ResponsesObject
57
+
58
+ const examples: Example[] = []
59
+
60
+ Object.entries(responses).forEach(([status, r]) => {
61
+ const response = r as OpenAPIV3.ResponseObject
62
+ const schema = response?.content?.["application/json"].schema as JSONSchema7
63
+
64
+ if (!schema) {
65
+ return
66
+ }
67
+
68
+ const fakeData = OpenAPISampler.sample(schema)
69
+
70
+ examples.push({
71
+ codeblock: {
72
+ title: status,
73
+ tabs: [{
74
+ title: "json",
75
+ language: "json",
76
+ code: JSON.stringify(fakeData, null, 2) || "",
77
+ }]
78
+ }
79
+ })
80
+ })
81
+
82
+ exampleGroups.push({
83
+ description: "Response",
84
+ examples
85
+ })
86
+ }
87
+
88
+ return exampleGroups
89
+ }
90
+
91
+ /*
92
+ fixAllOfBug fixes below case:
93
+
94
+ ```yaml
95
+ allOf:
96
+ - $ref: '#/components/schemas/SomeSchema'
97
+ - type: object
98
+ required:
99
+ properties:
100
+ ```
101
+ */
102
+ function fixAllOfBug(schema: JSONSchema7) {
103
+ const modifiedSchema = {...schema}
104
+
105
+ if (schema.allOf) {
106
+ schema.allOf.forEach((prop, i) => {
107
+ const propObj = prop as object
108
+
109
+ if ("properties" in propObj && !propObj["properties"]) {
110
+ delete modifiedSchema.allOf?.[i]
111
+ }
112
+ })
113
+ }
114
+
115
+ return modifiedSchema
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export * from "./const"
2
+
3
+ export * from "./parameters"
4
+
5
+ export * from "./properties"
6
+
7
+ export * from "./requestBody"
8
+
9
+ export * from "./responses"
10
+
11
+ export * from "./schema"
12
+
13
+ export * from "./utils"
@@ -0,0 +1,27 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import {DefinitionProperty} from "@xyd-js/uniform";
3
+
4
+ // oapParametersToDefinitionProperties converts OpenAPI parameters to uniform DefinitionProperties
5
+ export function oapParametersToDefinitionProperties(
6
+ parameters: OpenAPIV3.ParameterObject[]
7
+ ): { [key: string]: DefinitionProperty[] } {
8
+ const parameterIn: { [key: string]: DefinitionProperty[] } = {}
9
+
10
+ parameters.forEach((param) => {
11
+ if (!parameterIn[param.in]) {
12
+ parameterIn[param.in] = []
13
+ }
14
+
15
+ const schema = param.schema as OpenAPIV3.SchemaObject
16
+
17
+ const property: DefinitionProperty = {
18
+ name: param.name,
19
+ type: schema.type || "",
20
+ description: param.description || ""
21
+ }
22
+
23
+ parameterIn[param.in].push(property)
24
+ })
25
+
26
+ return parameterIn
27
+ }
package/src/paths.ts ADDED
@@ -0,0 +1,104 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import {Definition, ExampleGroup, Reference, ReferenceCategory} from "@xyd-js/uniform";
3
+
4
+ import {oapParametersToDefinitionProperties} from "./parameters";
5
+ import {oapRequestBodyToDefinitionProperties} from "./requestBody";
6
+ import {oapResponseToDefinitionProperties} from "./responses";
7
+ import {
8
+ httpMethodToUniformMethod,
9
+ toPascalCase
10
+ } from "./utils";
11
+
12
+ // oapPathToReference converts an OpenAPI path to a uniform Reference
13
+ export function oapPathToReference(
14
+ httpMethod: "get" | "put" | "post" | "delete", // TODO: ts type
15
+ path: string,
16
+ oapPath: OpenAPIV3.PathItemObject,
17
+ ): Reference | null {
18
+ const mType = httpMethodToUniformMethod(httpMethod)
19
+
20
+ if (!mType) {
21
+ console.error(`Unsupported method: ${httpMethod}`)
22
+ return null
23
+ }
24
+
25
+ const definitions: Definition[] = []
26
+ const exampleGroups: ExampleGroup[] = []
27
+
28
+ const oapMethod = oapPath?.[httpMethod] as OpenAPIV3.OperationObject
29
+
30
+ if (!oapMethod) {
31
+ return null
32
+ }
33
+
34
+ const endpointRef: Reference = {
35
+ title: oapMethod?.summary || "",
36
+ canonical: toPascalCase(oapMethod?.summary || ""),
37
+ description: oapMethod?.description || "",
38
+
39
+ category: ReferenceCategory.REST,
40
+ type: mType,
41
+
42
+ context: {
43
+ method: httpMethod,
44
+
45
+ path
46
+ },
47
+
48
+ examples: {
49
+ groups: exampleGroups,
50
+ },
51
+ definitions: definitions,
52
+ }
53
+
54
+ if (oapMethod.parameters) {
55
+ const parameters = oapMethod.parameters as OpenAPIV3.ParameterObject[]
56
+
57
+ const paramtersMap = oapParametersToDefinitionProperties(parameters) // TODO: finish
58
+
59
+ Object.entries(paramtersMap).forEach(([key, definitionProperties]) => {
60
+ let title: string
61
+
62
+ // TODO: add context to definition
63
+ switch (key) {
64
+ case 'path':
65
+ title = "Paths"
66
+ break
67
+ case 'query':
68
+ title = "Query"
69
+ break
70
+ case 'header':
71
+ title = "Header"
72
+ break
73
+ default:
74
+ console.error(`Unsupported parameter type: ${key} for ${httpMethod} ${path}`)
75
+ return
76
+ }
77
+
78
+ definitions.push({
79
+ title,
80
+ properties: definitionProperties
81
+ })
82
+ })
83
+ }
84
+
85
+ if (oapMethod.requestBody) {
86
+ const reqBody = oapMethod.requestBody as OpenAPIV3.RequestBodyObject
87
+
88
+ definitions.push({
89
+ title: 'Request body',
90
+ properties: oapRequestBodyToDefinitionProperties(reqBody) || []
91
+ })
92
+ }
93
+
94
+ if (oapMethod.responses) {
95
+ const responses = oapMethod.responses as OpenAPIV3.ResponsesObject
96
+
97
+ definitions.push({
98
+ title: 'Response',
99
+ properties: oapResponseToDefinitionProperties(responses) || []
100
+ })
101
+ }
102
+
103
+ return endpointRef
104
+ }
@@ -0,0 +1,31 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import {DefinitionProperty} from "@xyd-js/uniform";
3
+
4
+ // schemaObjectToDefinitionProperties converts OpenAPI schema object to uniform DefinitionProperty[]
5
+ export function schemaObjectToDefinitionProperties(v: OpenAPIV3.SchemaObject): DefinitionProperty[] {
6
+ return Object.entries(v.properties || {}).map(([name, prop]) => {
7
+ let objProp = prop as OpenAPIV3.SchemaObject
8
+
9
+ let merged: DefinitionProperty[] = []
10
+
11
+ if (objProp.allOf) {
12
+ merged = objProp.allOf.reduce((acc, of) => {
13
+ return [
14
+ ...acc,
15
+ ...schemaObjectToDefinitionProperties(of as OpenAPIV3.SchemaObject)
16
+ ]
17
+ }, [] as DefinitionProperty[])
18
+ }
19
+
20
+ return {
21
+ name: name,
22
+ type: objProp.type || "",
23
+ description: objProp.description || "",
24
+ properties: (
25
+ merged?.length
26
+ ? merged
27
+ : objProp.properties ? schemaObjectToDefinitionProperties(objProp) : []
28
+ )
29
+ }
30
+ })
31
+ }
@@ -0,0 +1,55 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import {DefinitionProperty} from "@xyd-js/uniform";
3
+
4
+ import {schemaObjectToDefinitionProperties} from "./properties";
5
+
6
+ // oapRequestBodyToDefinitionProperties converts OpenAPI request body to uniform DefinitionProperties
7
+ export function oapRequestBodyToDefinitionProperties(
8
+ reqBody: OpenAPIV3.RequestBodyObject
9
+ ): DefinitionProperty[] | null {
10
+ // TODO: support other content types ???
11
+ const schema = reqBody.content['application/json'].schema as OpenAPIV3.SchemaObject
12
+
13
+ let schemaObject: OpenAPIV3.SchemaObject | undefined
14
+
15
+ if (schema.allOf) {
16
+ return schema.allOf.reduce((acc, of) => {
17
+ const fakeBody: OpenAPIV3.RequestBodyObject = {
18
+ content: {
19
+ 'application/json': {
20
+ schema: of
21
+ }
22
+ }
23
+ }
24
+
25
+ return [
26
+ ...acc,
27
+ ...oapRequestBodyToDefinitionProperties(fakeBody) || []
28
+ ]
29
+ }, [] as DefinitionProperty[])
30
+ }
31
+
32
+ switch (schema.type) {
33
+ case 'object': {
34
+ schemaObject = schema
35
+ break
36
+ }
37
+ case 'array': {
38
+ const arrSchema = schema as OpenAPIV3.ArraySchemaObject
39
+
40
+ const items = arrSchema.items as OpenAPIV3.SchemaObject
41
+
42
+ schemaObject = items
43
+ break
44
+ }
45
+ default:
46
+ // TODO: primitive types ???
47
+ break
48
+ }
49
+
50
+ if (!schemaObject) {
51
+ return null
52
+ }
53
+
54
+ return schemaObjectToDefinitionProperties(schemaObject)
55
+ }
@@ -0,0 +1,38 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import {DefinitionProperty} from "@xyd-js/uniform";
3
+
4
+ import {schemaObjectToDefinitionProperties} from "./properties";
5
+
6
+ export function oapResponseToDefinitionProperties(
7
+ responses: OpenAPIV3.ResponsesObject
8
+ ): DefinitionProperty[] | null {
9
+ let schemaObject: OpenAPIV3.SchemaObject | undefined
10
+ // TODO: support another statuses
11
+ if (responses["default"]) {
12
+ const w = responses["default"] as OpenAPIV3.ResponseObject
13
+
14
+ schemaObject = w?.content?.['application/json']?.schema as OpenAPIV3.SchemaObject
15
+ } else if (responses["200"]) {
16
+ const w = responses["200"] as OpenAPIV3.ResponseObject
17
+
18
+ schemaObject = w?.content?.['application/json']?.schema as OpenAPIV3.SchemaObject
19
+ }
20
+
21
+ if (!schemaObject) {
22
+ return null
23
+ }
24
+
25
+ switch (schemaObject.type) {
26
+ case 'array':
27
+ const arrSchema = schemaObject as OpenAPIV3.ArraySchemaObject
28
+
29
+ const items = arrSchema.items as OpenAPIV3.SchemaObject
30
+
31
+ schemaObject = items
32
+ break
33
+ default:
34
+ break
35
+ }
36
+
37
+ return schemaObjectToDefinitionProperties(schemaObject)
38
+ }
package/src/schema.ts ADDED
@@ -0,0 +1,56 @@
1
+ import {OpenAPIV3} from "openapi-types";
2
+ import Oas from "oas";
3
+ import {Reference, ReferenceType} from "@xyd-js/uniform";
4
+
5
+ import {SUPPORTED_HTTP_METHODS} from "./const";
6
+ import {oapPathToReference} from "./paths";
7
+ import {oapExamples} from "./examples";
8
+
9
+ // TODO: support one-of
10
+ // TODO: support $ref - currently we use $refParser.dereference that converts $ref into objects
11
+ // TODO: better method check system - currently we need to manually check that in few methods
12
+
13
+ // oapSchemaToReferences converts an OpenAPI schema to a list of uniform References
14
+ export function oapSchemaToReferences(
15
+ schema: OpenAPIV3.Document
16
+ ): Reference[] {
17
+ const references: Reference[] = [];
18
+ const oas = new Oas(schema as any);
19
+
20
+ Object.entries(schema.paths).forEach(([path, oapPath]) => {
21
+ let type: ReferenceType;
22
+
23
+ SUPPORTED_HTTP_METHODS.forEach((eachMethod) => {
24
+ const httpMethod = eachMethod.toLowerCase()
25
+
26
+ switch (httpMethod) {
27
+ case 'get':
28
+ break
29
+ case 'put':
30
+ break
31
+ case 'post':
32
+ break
33
+ case 'delete':
34
+ break
35
+ default:
36
+ console.error(`Unsupported method: ${httpMethod}`)
37
+ return
38
+ }
39
+
40
+ const reference = oapPathToReference(
41
+ httpMethod,
42
+ path,
43
+ oapPath as OpenAPIV3.PathItemObject
44
+ )
45
+
46
+ if (reference) {
47
+ const operation = oas.operation(path, httpMethod);
48
+ reference.examples.groups = oapExamples(oas, operation)
49
+
50
+ references.push(reference)
51
+ }
52
+ })
53
+ })
54
+
55
+ return references
56
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,124 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import yaml from "js-yaml";
4
+ import $refParser from "json-schema-ref-parser";
5
+ import {OpenAPIV3} from "openapi-types";
6
+
7
+ import {ReferenceType} from "@xyd-js/uniform";
8
+
9
+ type Parameters = {
10
+ query?: Record<string, string | number | boolean>;
11
+ headers?: Record<string, string>;
12
+ };
13
+
14
+ export function toPascalCase(str: string): string {
15
+ return str
16
+ .split(/[\s_-]+/)
17
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
18
+ .join('');
19
+ }
20
+
21
+ // readOpenApiSpec reads an OpenAPI spec file and returns the content
22
+ function readOpenApiSpec(filePath: string) {
23
+ const ext = path.extname(filePath).toLowerCase();
24
+ const content = fs.readFileSync(filePath, 'utf-8');
25
+
26
+ if (ext === '.yaml' || ext === '.yml') {
27
+ return yaml.load(content);
28
+ } else if (ext === '.json') {
29
+ return JSON.parse(content);
30
+ } else {
31
+ throw new Error('Unsupported file format. Use JSON or YAML.');
32
+ }
33
+ }
34
+
35
+ // deferencedOpenAPI reads an OpenAPI spec file and returns a dereferenced OpenAPIV3.Document
36
+ // dereferenced means that all $ref references are resolved automatically
37
+ export async function deferencedOpenAPI(openApiPath: string) {
38
+ const openApiSpec = readOpenApiSpec(openApiPath);
39
+
40
+ //@ts-ignore TODO: fix ts
41
+ await $refParser.dereference(openApiSpec);
42
+
43
+ return openApiSpec as OpenAPIV3.Document
44
+ }
45
+
46
+ // httpMethodToUniformMethod converts an HTTP method to a uniform ReferenceType
47
+ export function httpMethodToUniformMethod(method: string): ReferenceType | null {
48
+ switch (method) {
49
+ case 'get':
50
+ return ReferenceType.REST_HTTP_GET
51
+ case 'put':
52
+ return ReferenceType.REST_HTTP_PUT
53
+ case 'patch':
54
+ return ReferenceType.REST_HTTP_PATCH
55
+ case 'post':
56
+ return ReferenceType.REST_HTTP_POST
57
+ case 'delete':
58
+ return ReferenceType.REST_HTTP_DELETE
59
+ // case 'options':
60
+ // return ReferenceType.REST_HTTP_OPTIONS
61
+ // case 'head':
62
+ // return ReferenceType.REST_HTTP_HEAD
63
+ // case 'trace':
64
+ // return ReferenceType.REST_HTTP_TRACE
65
+ default:
66
+ return null
67
+ }
68
+ }
69
+
70
+ // schemaToRequestBody generates a request body from an OpenAPI schema
71
+ function schemaToRequestBody(schema: OpenAPIV3.SchemaObject): string {
72
+ const requestBody: any = {};
73
+
74
+ if (schema.type === 'object' && schema.properties) {
75
+ for (const [key, value] of Object.entries(schema.properties)) {
76
+ if ((value as OpenAPIV3.SchemaObject).default !== undefined) {
77
+ requestBody[key] = (value as OpenAPIV3.SchemaObject).default;
78
+ } else {
79
+ requestBody[key] = null; // or some placeholder value
80
+ }
81
+ }
82
+ }
83
+
84
+ return JSON.stringify(requestBody);
85
+ }
86
+
87
+ // generateRequestInitFromOpenAPIObject generates a RequestInit object from an OpenAPI object
88
+ export function generateRequestInitFromOapOperation(
89
+ urlPath: string,
90
+ operation: OpenAPIV3.OperationObject
91
+ ): { url: string, reqInit: RequestInit } {
92
+ const reqInit: RequestInit = {}
93
+ let queryParams = '';
94
+
95
+ if (operation.parameters) {
96
+ const parameters = operation.parameters as OpenAPIV3.ParameterObject[]
97
+
98
+ const params = new URLSearchParams(
99
+ Object.entries(parameters).map(([key, value]) => [key, String(value)])
100
+ ).toString();
101
+
102
+ queryParams += `?${params}`;
103
+ }
104
+
105
+ if (operation.requestBody) {
106
+ const reqBody = operation.requestBody as OpenAPIV3.RequestBodyObject;
107
+ const contentType = Object.keys(reqBody.content || {})[0];
108
+
109
+ if (contentType === "application/json") {
110
+ const schema = reqBody.content['application/json'].schema as OpenAPIV3.SchemaObject
111
+
112
+ reqInit.body = schemaToRequestBody(schema);
113
+ reqInit.headers = {
114
+ 'Content-Type': 'application/json',
115
+ }
116
+ }
117
+ }
118
+
119
+ return {
120
+ url: `${urlPath}${queryParams}`,
121
+ reqInit,
122
+ };
123
+ }
124
+
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "incremental": true,
14
+ "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
15
+ },
16
+ "include": ["examples/**/*.ts", "src/**/*.ts"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['index.ts'],
5
+ format: ['esm', 'cjs'], // Output both ESM and CJS formats
6
+ target: 'node16', // Ensure compatibility with Node.js 16
7
+ dts: {
8
+ entry: 'index.ts', // Specify the entry for DTS
9
+ resolve: true, // Resolve external types
10
+ },
11
+ splitting: false, // Disable code splitting
12
+ sourcemap: true, // Generate source maps
13
+ clean: true, // Clean the output directory before each build
14
+ esbuildOptions: (options) => {
15
+ options.platform = 'node'; // Ensure the platform is set to Node.js
16
+ options.external = ['node:fs/promises']; // Mark 'node:fs/promises' as external
17
+ options.loader = { '.js': 'jsx' }; // Ensure proper handling of .js files
18
+ },
19
+ });
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ // TODO: multiple entry points + outputs
4
+
5
+ const isDebugMode = !!process.env.DEBUG
6
+
7
+ export default defineConfig({
8
+ entry: [
9
+ "./examples/webhooks/index.ts"
10
+ ],
11
+ format: ['esm', 'cjs'], // Output both ESM and CJS formats
12
+ target: 'node16', // Ensure compatibility with Node.js 16
13
+ dts: {
14
+ entry: "./examples/webhooks/index.ts",
15
+ resolve: true, // Resolve external types
16
+ },
17
+ splitting: false, // Disable code splitting
18
+ sourcemap: true, // Generate source maps
19
+ clean: true, // Clean the output directory before each build
20
+ outDir: './examples/dist', // Output directory
21
+ esbuildOptions(options) {
22
+ options.minify = true;
23
+ if (isDebugMode) {
24
+ options.drop = [];
25
+ } else {
26
+ options.drop = [];
27
+ options.pure = ['console.debug'];
28
+ }
29
+ },
30
+ });