spiceflow 1.1.7 → 1.1.9
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 +177 -92
- package/dist/benchmark.benchmark.js.map +1 -1
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +8 -12
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/utils.js.map +1 -1
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +1 -3
- package/dist/client/ws.js.map +1 -1
- package/dist/client.test.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js.map +1 -1
- package/dist/cors.test.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/middleware.test.js +65 -0
- package/dist/middleware.test.js.map +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +1 -1
- package/dist/openapi.js.map +1 -1
- package/dist/openapi.test.js.map +1 -1
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +12 -5
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/stream.test.js +6 -6
- package/dist/stream.test.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/types.test.js +56 -6
- package/dist/types.test.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/zod.test.js.map +1 -1
- package/package.json +1 -1
- package/src/benchmark.benchmark.ts +8 -8
- package/src/client/errors.ts +17 -17
- package/src/client/index.ts +437 -469
- package/src/client/types.ts +168 -191
- package/src/client/utils.ts +5 -5
- package/src/client/ws.ts +87 -89
- package/src/client.test.ts +176 -183
- package/src/context.ts +82 -82
- package/src/cors.test.ts +38 -38
- package/src/cors.ts +87 -92
- package/src/error.ts +13 -13
- package/src/middleware.test.ts +221 -149
- package/src/openapi.test.ts +97 -97
- package/src/openapi.ts +365 -365
- package/src/spiceflow.test.ts +461 -467
- package/src/spiceflow.ts +1117 -1157
- package/src/stream.test.ts +310 -310
- package/src/types.test.ts +59 -39
- package/src/types.ts +698 -701
- package/src/utils.ts +79 -79
- package/src/zod.test.ts +64 -64
package/src/openapi.ts
CHANGED
|
@@ -13,283 +13,283 @@ import { z } from 'zod'
|
|
|
13
13
|
import zodToJsonSchema from 'zod-to-json-schema'
|
|
14
14
|
|
|
15
15
|
export const toOpenAPIPath = (path: string) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
path
|
|
17
|
+
.split('/')
|
|
18
|
+
.map((x) => {
|
|
19
|
+
if (x.startsWith(':')) {
|
|
20
|
+
x = x.slice(1, x.length)
|
|
21
|
+
if (x.endsWith('?')) x = x.slice(0, -1)
|
|
22
|
+
x = `{${x}}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return x
|
|
26
|
+
})
|
|
27
|
+
.join('/')
|
|
28
28
|
|
|
29
29
|
export const mapProperties = (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
name: string,
|
|
31
|
+
schema: TypeSchema | string | undefined,
|
|
32
|
+
models: Record<string, TypeSchema>,
|
|
33
33
|
) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
if (schema === undefined) return []
|
|
35
|
+
|
|
36
|
+
if (typeof schema === 'string')
|
|
37
|
+
if (schema in models) schema = models[schema]
|
|
38
|
+
else throw new Error(`Can't find model ${schema}`)
|
|
39
|
+
|
|
40
|
+
let jsonSchema = getJsonSchema(schema)
|
|
41
|
+
|
|
42
|
+
return Object.entries(jsonSchema?.properties ?? []).map(([key, value]) => {
|
|
43
|
+
const {
|
|
44
|
+
type: valueType = undefined,
|
|
45
|
+
description,
|
|
46
|
+
examples,
|
|
47
|
+
...schemaKeywords
|
|
48
|
+
} = value as any
|
|
49
|
+
return {
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
description,
|
|
52
|
+
examples,
|
|
53
|
+
schema: { type: valueType, ...schemaKeywords },
|
|
54
|
+
in: name,
|
|
55
|
+
name: key,
|
|
56
|
+
|
|
57
|
+
required: jsonSchema!.required?.includes(key) ?? false,
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const mapTypesResponse = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
types: string[],
|
|
64
|
+
schema:
|
|
65
|
+
| string
|
|
66
|
+
| {
|
|
67
|
+
type: string
|
|
68
|
+
properties: Object
|
|
69
|
+
required: string[]
|
|
70
|
+
},
|
|
71
71
|
) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
72
|
+
if (
|
|
73
|
+
typeof schema === 'object' &&
|
|
74
|
+
['void', 'undefined', 'null'].includes(schema.type)
|
|
75
|
+
)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
const responses: Record<string, OpenAPIV3.MediaTypeObject> = {}
|
|
79
|
+
|
|
80
|
+
for (const type of types)
|
|
81
|
+
responses[type] = {
|
|
82
|
+
schema:
|
|
83
|
+
typeof schema === 'string'
|
|
84
|
+
? {
|
|
85
|
+
$ref: `#/components/schemas/${schema}`,
|
|
86
|
+
}
|
|
87
|
+
: { ...(schema as any) },
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return responses
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export const capitalize = (word: string) =>
|
|
94
|
-
|
|
94
|
+
word.charAt(0).toUpperCase() + word.slice(1)
|
|
95
95
|
|
|
96
96
|
export const generateOperationId = (method: string, paths: string) => {
|
|
97
|
-
|
|
97
|
+
let operationId = method.toLowerCase()
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
if (paths === '/') return operationId + 'Index'
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
for (const path of paths.split('/')) {
|
|
102
|
+
if (path.charCodeAt(0) === 123) {
|
|
103
|
+
operationId += 'By' + capitalize(path.slice(1, -1))
|
|
104
|
+
} else {
|
|
105
|
+
operationId += capitalize(path)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
return operationId
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export const registerSchemaPath = ({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
schema,
|
|
114
|
+
path,
|
|
115
|
+
method,
|
|
116
|
+
hook,
|
|
117
|
+
models,
|
|
118
118
|
}: {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
schema: Partial<OpenAPIV3.PathsObject>
|
|
120
|
+
contentType?: string | string[]
|
|
121
|
+
path: string
|
|
122
|
+
method: HTTPMethod
|
|
123
|
+
hook?: LocalHook<any, any, any, any, any, any, any>
|
|
124
|
+
models: Record<string, TypeSchema>
|
|
125
125
|
}) => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
126
|
+
if (hook) hook = deepClone(hook)
|
|
127
|
+
|
|
128
|
+
// TODO if a route uses an async generator, add text/event-stream. if a roue does not add an explicit schema, use all possible content types
|
|
129
|
+
const contentType = hook?.type ?? [
|
|
130
|
+
'application/json',
|
|
131
|
+
// 'multipart/form-data',
|
|
132
|
+
// 'text/plain',
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
path = toOpenAPIPath(path)
|
|
136
|
+
|
|
137
|
+
const contentTypes =
|
|
138
|
+
typeof contentType === 'string'
|
|
139
|
+
? [contentType]
|
|
140
|
+
: (contentType ?? ['application/json'])
|
|
141
|
+
|
|
142
|
+
const bodySchema = getJsonSchema(hook?.body)
|
|
143
|
+
const paramsSchema = hook?.params
|
|
144
|
+
// const headerSchema = hook?.headers
|
|
145
|
+
const querySchema = hook?.query
|
|
146
|
+
let responseSchema = hook?.response as unknown as TypeSchema
|
|
147
|
+
let openapiResponse: OpenAPIV3.ResponsesObject = {}
|
|
148
|
+
|
|
149
|
+
if (typeof responseSchema === 'object') {
|
|
150
|
+
const isStatusMap = Object.keys(responseSchema).every(
|
|
151
|
+
(key) => typeof key === 'number' || Number.isInteger(Number(key)),
|
|
152
|
+
)
|
|
153
|
+
if (!isStatusMap) {
|
|
154
|
+
let jsonSchema = getJsonSchema(responseSchema)
|
|
155
|
+
const {
|
|
156
|
+
type,
|
|
157
|
+
properties,
|
|
158
|
+
required,
|
|
159
|
+
additionalProperties,
|
|
160
|
+
patternProperties,
|
|
161
|
+
...rest
|
|
162
|
+
} = jsonSchema
|
|
163
|
+
|
|
164
|
+
openapiResponse = {
|
|
165
|
+
'200': {
|
|
166
|
+
...rest,
|
|
167
|
+
description: rest.description as any,
|
|
168
|
+
content: mapTypesResponse(
|
|
169
|
+
contentTypes,
|
|
170
|
+
type === 'object' || type === 'array'
|
|
171
|
+
? ({
|
|
172
|
+
type,
|
|
173
|
+
properties,
|
|
174
|
+
patternProperties,
|
|
175
|
+
items: jsonSchema.items,
|
|
176
|
+
required,
|
|
177
|
+
} as any)
|
|
178
|
+
: jsonSchema,
|
|
179
|
+
),
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
Object.entries(responseSchema as Record<string, TypeSchema>).forEach(
|
|
184
|
+
([key, value]) => {
|
|
185
|
+
if (typeof value === 'string') {
|
|
186
|
+
if (!models[value]) return
|
|
187
|
+
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
189
|
+
const {
|
|
190
|
+
type,
|
|
191
|
+
properties,
|
|
192
|
+
required,
|
|
193
|
+
additionalProperties: _1,
|
|
194
|
+
patternProperties: _2,
|
|
195
|
+
...rest
|
|
196
|
+
} = getJsonSchema(models[value])
|
|
197
|
+
|
|
198
|
+
openapiResponse[key] = {
|
|
199
|
+
...rest,
|
|
200
|
+
description: rest.description as any,
|
|
201
|
+
content: mapTypesResponse(contentTypes, value),
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
const schema = getJsonSchema(value)
|
|
205
|
+
const {
|
|
206
|
+
type,
|
|
207
|
+
properties,
|
|
208
|
+
required,
|
|
209
|
+
additionalProperties,
|
|
210
|
+
patternProperties,
|
|
211
|
+
...rest
|
|
212
|
+
} = schema
|
|
213
|
+
|
|
214
|
+
openapiResponse[key] = {
|
|
215
|
+
...rest,
|
|
216
|
+
description: rest.description as any,
|
|
217
|
+
content: mapTypesResponse(
|
|
218
|
+
contentTypes,
|
|
219
|
+
type === 'object' || type === 'array'
|
|
220
|
+
? ({
|
|
221
|
+
type,
|
|
222
|
+
properties,
|
|
223
|
+
patternProperties,
|
|
224
|
+
items: rest.items,
|
|
225
|
+
required,
|
|
226
|
+
} as any)
|
|
227
|
+
: schema,
|
|
228
|
+
),
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
} else if (typeof responseSchema === 'string') {
|
|
235
|
+
if (!(responseSchema in models)) return
|
|
236
|
+
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
238
|
+
const {
|
|
239
|
+
type,
|
|
240
|
+
properties,
|
|
241
|
+
required,
|
|
242
|
+
additionalProperties: _1,
|
|
243
|
+
patternProperties: _2,
|
|
244
|
+
...rest
|
|
245
|
+
} = getJsonSchema(models[responseSchema])
|
|
246
|
+
|
|
247
|
+
openapiResponse = {
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
'200': {
|
|
250
|
+
...rest,
|
|
251
|
+
content: mapTypesResponse(contentTypes, responseSchema),
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const parameters = [
|
|
257
|
+
// ...mapProperties('header', headerSchema, models),
|
|
258
|
+
...mapProperties('path', paramsSchema, models),
|
|
259
|
+
...mapProperties('query', querySchema, models),
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
schema[path] = {
|
|
263
|
+
...(schema[path] ? schema[path] : {}),
|
|
264
|
+
[method.toLowerCase()]: {
|
|
265
|
+
...((paramsSchema || querySchema || bodySchema
|
|
266
|
+
? ({ parameters } as any)
|
|
267
|
+
: {}) satisfies OpenAPIV3.ParameterObject),
|
|
268
|
+
...(openapiResponse
|
|
269
|
+
? {
|
|
270
|
+
responses: openapiResponse,
|
|
271
|
+
}
|
|
272
|
+
: {}),
|
|
273
|
+
operationId:
|
|
274
|
+
hook?.detail?.operationId ?? generateOperationId(method, path),
|
|
275
|
+
...hook?.detail,
|
|
276
|
+
...(bodySchema
|
|
277
|
+
? {
|
|
278
|
+
requestBody: {
|
|
279
|
+
required: true,
|
|
280
|
+
content: mapTypesResponse(
|
|
281
|
+
contentTypes,
|
|
282
|
+
typeof bodySchema === 'string'
|
|
283
|
+
? {
|
|
284
|
+
$ref: `#/components/schemas/${bodySchema}`,
|
|
285
|
+
}
|
|
286
|
+
: (bodySchema as any),
|
|
287
|
+
),
|
|
288
|
+
},
|
|
289
|
+
}
|
|
290
|
+
: null),
|
|
291
|
+
} satisfies OpenAPIV3.OperationObject,
|
|
292
|
+
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
/**
|
|
@@ -298,117 +298,117 @@ export const registerSchemaPath = ({
|
|
|
298
298
|
* @see https://github.com/elysiajs/elysia-swagger
|
|
299
299
|
*/
|
|
300
300
|
export const openapi = <Path extends string = '/openapi'>({
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
path = '/openapi' as Path,
|
|
302
|
+
documentation = {},
|
|
303
303
|
}: {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
304
|
+
path?: Path
|
|
305
|
+
/**
|
|
306
|
+
* Customize Swagger config, refers to Swagger 2.0 config
|
|
307
|
+
*
|
|
308
|
+
* @see https://swagger.io/specification/v2/
|
|
309
|
+
*/
|
|
310
|
+
documentation?: Omit<
|
|
311
|
+
Partial<OpenAPIV3.Document>,
|
|
312
|
+
| 'x-express-openapi-additional-middleware'
|
|
313
|
+
| 'x-express-openapi-validation-strict'
|
|
314
|
+
>
|
|
315
315
|
} = {}) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
316
|
+
const schema = {}
|
|
317
|
+
let totalRoutes = 0
|
|
318
|
+
|
|
319
|
+
const relativePath = path.startsWith('/') ? path.slice(1) : path
|
|
320
|
+
|
|
321
|
+
const app = new Spiceflow({ name: 'openapi' }).get(path, ({}) => {
|
|
322
|
+
let routes = app.getAllRoutes()
|
|
323
|
+
if (routes.length !== totalRoutes) {
|
|
324
|
+
const ALLOWED_METHODS = [
|
|
325
|
+
'GET',
|
|
326
|
+
'PUT',
|
|
327
|
+
'POST',
|
|
328
|
+
'DELETE',
|
|
329
|
+
'OPTIONS',
|
|
330
|
+
'HEAD',
|
|
331
|
+
'PATCH',
|
|
332
|
+
'TRACE',
|
|
333
|
+
]
|
|
334
|
+
totalRoutes = routes.length
|
|
335
|
+
|
|
336
|
+
routes.forEach((route: InternalRoute) => {
|
|
337
|
+
if (route.hooks?.detail?.hide === true) return
|
|
338
|
+
// TODO: route.hooks?.detail?.hide !== false add ability to hide: false to prevent excluding
|
|
339
|
+
if (excludeMethods.includes(route.method)) return
|
|
340
|
+
if (
|
|
341
|
+
ALLOWED_METHODS.includes(route.method) === false &&
|
|
342
|
+
route.method !== 'ALL'
|
|
343
|
+
)
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
if (route.method === 'ALL') {
|
|
347
|
+
ALLOWED_METHODS.forEach((method) => {
|
|
348
|
+
registerSchemaPath({
|
|
349
|
+
schema,
|
|
350
|
+
hook: route.hooks,
|
|
351
|
+
method,
|
|
352
|
+
path: route.path,
|
|
353
|
+
// @ts-ignore
|
|
354
|
+
models: app.definitions?.type,
|
|
355
|
+
contentType: route.hooks?.type,
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
registerSchemaPath({
|
|
362
|
+
schema,
|
|
363
|
+
hook: route.hooks,
|
|
364
|
+
method: route.method,
|
|
365
|
+
path: route.path,
|
|
366
|
+
// @ts-ignore
|
|
367
|
+
models: app.definitions?.type,
|
|
368
|
+
contentType: route.hooks?.type,
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
openapi: '3.0.3',
|
|
375
|
+
...{
|
|
376
|
+
...documentation,
|
|
377
|
+
// tags: documentation.tags?.filter(
|
|
378
|
+
// (tag) => !excludeTags?.includes(tag?.name),
|
|
379
|
+
// ),
|
|
380
|
+
info: {
|
|
381
|
+
title: 'Spiceflow Documentation',
|
|
382
|
+
description: 'Development documentation',
|
|
383
|
+
version: '0.0.0',
|
|
384
|
+
...documentation.info,
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
paths: {
|
|
388
|
+
...schema,
|
|
389
|
+
...documentation.paths,
|
|
390
|
+
},
|
|
391
|
+
components: {
|
|
392
|
+
...documentation.components,
|
|
393
|
+
schemas: {
|
|
394
|
+
// @ts-ignore
|
|
395
|
+
...app.definitions?.type,
|
|
396
|
+
...documentation.components?.schemas,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
} satisfies OpenAPIV3.Document
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
return app
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
function getJsonSchema(schema: TypeSchema): JSONSchemaType<any> {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
406
|
+
if (!schema) return undefined as any
|
|
407
|
+
if (isZodSchema(schema)) {
|
|
408
|
+
let fn = zodToJsonSchema.default ?? zodToJsonSchema
|
|
409
|
+
let jsonSchema = fn(schema, {})
|
|
410
|
+
return jsonSchema as any
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return schema as any
|
|
414
414
|
}
|