@effect/openapi-generator 4.0.0-beta.6 → 4.0.0-beta.62
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/dist/HttpApiTransformer.d.ts +6 -0
- package/dist/HttpApiTransformer.d.ts.map +1 -0
- package/dist/HttpApiTransformer.js +354 -0
- package/dist/HttpApiTransformer.js.map +1 -0
- package/dist/JsonSchemaGenerator.d.ts +13 -3
- package/dist/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/JsonSchemaGenerator.js +146 -47
- package/dist/JsonSchemaGenerator.js.map +1 -1
- package/dist/OpenApiGenerator.d.ts +19 -7
- package/dist/OpenApiGenerator.d.ts.map +1 -1
- package/dist/OpenApiGenerator.js +657 -119
- package/dist/OpenApiGenerator.js.map +1 -1
- package/dist/OpenApiPatch.d.ts +4 -4
- package/dist/OpenApiPatch.d.ts.map +1 -1
- package/dist/OpenApiPatch.js.map +1 -1
- package/dist/OpenApiTransformer.d.ts +12 -12
- package/dist/OpenApiTransformer.d.ts.map +1 -1
- package/dist/OpenApiTransformer.js +18 -24
- package/dist/OpenApiTransformer.js.map +1 -1
- package/dist/ParsedOperation.d.ts +81 -1
- package/dist/ParsedOperation.d.ts.map +1 -1
- package/dist/ParsedOperation.js +26 -0
- package/dist/ParsedOperation.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +19 -8
- package/dist/main.js.map +1 -1
- package/package.json +7 -7
- package/src/HttpApiTransformer.ts +497 -0
- package/src/JsonSchemaGenerator.ts +240 -47
- package/src/OpenApiGenerator.ts +919 -173
- package/src/OpenApiPatch.ts +13 -11
- package/src/OpenApiTransformer.ts +22 -30
- package/src/ParsedOperation.ts +129 -1
- package/src/main.ts +36 -10
|
@@ -3,6 +3,19 @@ import * as JsonSchema from "effect/JsonSchema"
|
|
|
3
3
|
import * as Rec from "effect/Record"
|
|
4
4
|
import * as SchemaRepresentation from "effect/SchemaRepresentation"
|
|
5
5
|
|
|
6
|
+
type Source = "openapi-3.0" | "openapi-3.1"
|
|
7
|
+
|
|
8
|
+
interface GenerateOptions {
|
|
9
|
+
readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface GenerateHttpApiOptions extends GenerateOptions {
|
|
13
|
+
readonly multipartSchemaRefs?: {
|
|
14
|
+
readonly singleFile: string
|
|
15
|
+
readonly files: string
|
|
16
|
+
} | undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
export function make() {
|
|
7
20
|
const store: Record<string, JsonSchema.JsonSchema> = {}
|
|
8
21
|
|
|
@@ -15,80 +28,260 @@ export function make() {
|
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
function generate(
|
|
18
|
-
source:
|
|
31
|
+
source: Source,
|
|
19
32
|
components: JsonSchema.Definitions,
|
|
20
33
|
typeOnly: boolean,
|
|
21
|
-
options?:
|
|
22
|
-
|
|
34
|
+
options?: GenerateOptions
|
|
35
|
+
) {
|
|
36
|
+
const generated = makeCodeDocument(source, components, options)
|
|
37
|
+
if (generated === undefined) {
|
|
38
|
+
return ""
|
|
23
39
|
}
|
|
40
|
+
|
|
41
|
+
const nonRecursiveReferences = generated.codeDocument.references.nonRecursives
|
|
42
|
+
const recursiveReferences = Object.entries(generated.codeDocument.references.recursives)
|
|
43
|
+
|
|
44
|
+
const nonRecursives = nonRecursiveReferences.map(({ $ref, code }) =>
|
|
45
|
+
renderSchemaTypeAndRuntime($ref, code, typeOnly)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const recursiveDeclarations: Array<string> = []
|
|
49
|
+
const recursives: Array<string> = []
|
|
50
|
+
|
|
51
|
+
if (typeOnly) {
|
|
52
|
+
for (const [$ref, code] of recursiveReferences) {
|
|
53
|
+
recursives.push(renderSchemaTypeAndRuntime($ref, code, true))
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
const recursivelyForwardReferenced = collectForwardReferencedRecursives(
|
|
57
|
+
nonRecursiveReferences,
|
|
58
|
+
recursiveReferences
|
|
59
|
+
)
|
|
60
|
+
const recursiveInternalNames = makeRecursiveInternalNameMap(
|
|
61
|
+
recursivelyForwardReferenced,
|
|
62
|
+
[
|
|
63
|
+
...nonRecursiveReferences.map(({ $ref }) => $ref),
|
|
64
|
+
...recursiveReferences.map(([$ref]) => $ref),
|
|
65
|
+
...generated.nameMap
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for (const [$ref, code] of recursiveReferences) {
|
|
70
|
+
if (recursivelyForwardReferenced.has($ref)) {
|
|
71
|
+
const internalName = recursiveInternalNames.get($ref)!
|
|
72
|
+
recursiveDeclarations.push(renderRecursiveReferenceDeclaration($ref, code, internalName))
|
|
73
|
+
recursives.push(`const ${internalName} = ${code.runtime}`)
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
recursives.push(renderSchemaTypeAndRuntime($ref, code, false))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const codes = generated.codeDocument.codes.map((code, i) =>
|
|
82
|
+
renderSchemaTypeAndRuntime(generated.nameMap[i], code, typeOnly)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return render("recursive declarations", recursiveDeclarations) +
|
|
86
|
+
render("non-recursive definitions", nonRecursives) +
|
|
87
|
+
render("recursive definitions", recursives) +
|
|
88
|
+
render("schemas", codes)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function generateHttpApi(
|
|
92
|
+
source: Source,
|
|
93
|
+
components: JsonSchema.Definitions,
|
|
94
|
+
options?: GenerateHttpApiOptions
|
|
24
95
|
) {
|
|
96
|
+
const generated = makeCodeDocument(source, components, options)
|
|
97
|
+
if (generated === undefined) {
|
|
98
|
+
return ""
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const nonRecursiveReferences = generated.codeDocument.references.nonRecursives
|
|
102
|
+
const recursiveReferences = Object.entries(generated.codeDocument.references.recursives)
|
|
103
|
+
|
|
104
|
+
const nonRecursives = nonRecursiveReferences.map(({ $ref, code }) =>
|
|
105
|
+
renderSchemaTypeAndRuntime($ref, code, false, options?.multipartSchemaRefs)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const recursivelyForwardReferenced = collectForwardReferencedRecursives(nonRecursiveReferences, recursiveReferences)
|
|
109
|
+
const recursiveInternalNames = makeRecursiveInternalNameMap(
|
|
110
|
+
recursivelyForwardReferenced,
|
|
111
|
+
[
|
|
112
|
+
...nonRecursiveReferences.map(({ $ref }) => $ref),
|
|
113
|
+
...recursiveReferences.map(([$ref]) => $ref),
|
|
114
|
+
...generated.nameMap
|
|
115
|
+
]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const recursiveDeclarations: Array<string> = []
|
|
119
|
+
const recursives: Array<string> = []
|
|
120
|
+
|
|
121
|
+
for (const [$ref, code] of recursiveReferences) {
|
|
122
|
+
if (recursivelyForwardReferenced.has($ref)) {
|
|
123
|
+
const internalName = recursiveInternalNames.get($ref)!
|
|
124
|
+
recursiveDeclarations.push(renderRecursiveReferenceDeclaration($ref, code, internalName))
|
|
125
|
+
recursives.push(`const ${internalName} = ${code.runtime}`)
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
recursives.push(renderSchemaTypeAndRuntime($ref, code, false, options?.multipartSchemaRefs))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const codes = generated.codeDocument.codes.map((code, i) =>
|
|
133
|
+
renderSchemaTypeAndRuntime(generated.nameMap[i], code, false, options?.multipartSchemaRefs)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return render("recursive declarations", recursiveDeclarations) +
|
|
137
|
+
render("non-recursive definitions", nonRecursives) +
|
|
138
|
+
render("recursive definitions", recursives) +
|
|
139
|
+
render("schemas", codes)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function makeCodeDocument(
|
|
143
|
+
source: Source,
|
|
144
|
+
components: JsonSchema.Definitions,
|
|
145
|
+
options?: GenerateOptions
|
|
146
|
+
): {
|
|
147
|
+
readonly nameMap: Array<string>
|
|
148
|
+
readonly codeDocument: SchemaRepresentation.CodeDocument
|
|
149
|
+
} | undefined {
|
|
25
150
|
const nameMap: Array<string> = []
|
|
26
151
|
const schemas: Array<JsonSchema.JsonSchema> = []
|
|
27
152
|
|
|
28
153
|
const definitions: JsonSchema.Definitions = Rec.map(
|
|
29
154
|
components,
|
|
30
|
-
(js) => fromSchemaOpenApi(js).schema
|
|
155
|
+
(js) => fromSchemaOpenApi(source, js).schema
|
|
31
156
|
)
|
|
32
157
|
|
|
33
158
|
for (const [name, js] of Object.entries(store)) {
|
|
34
159
|
nameMap.push(name)
|
|
35
|
-
schemas.push(fromSchemaOpenApi(js).schema)
|
|
160
|
+
schemas.push(fromSchemaOpenApi(source, js).schema)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!Arr.isArrayNonEmpty(schemas)) {
|
|
164
|
+
return
|
|
36
165
|
}
|
|
37
166
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
out.additionalProperties = false
|
|
48
|
-
}
|
|
49
|
-
return options?.onEnter?.(out) ?? out
|
|
167
|
+
const multiDocument: SchemaRepresentation.MultiDocument = SchemaRepresentation.fromJsonSchemaMultiDocument({
|
|
168
|
+
dialect: "draft-2020-12",
|
|
169
|
+
schemas,
|
|
170
|
+
definitions
|
|
171
|
+
}, {
|
|
172
|
+
onEnter(js) {
|
|
173
|
+
const out = { ...js }
|
|
174
|
+
if (out.type === "object" && out.additionalProperties === undefined) {
|
|
175
|
+
out.additionalProperties = false
|
|
50
176
|
}
|
|
51
|
-
|
|
177
|
+
return options?.onEnter?.(out) ?? out
|
|
178
|
+
}
|
|
179
|
+
})
|
|
52
180
|
|
|
53
|
-
|
|
181
|
+
return {
|
|
182
|
+
nameMap,
|
|
183
|
+
codeDocument: SchemaRepresentation.toCodeDocument(multiDocument)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
54
186
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
renderSchema($ref, code)
|
|
58
|
-
)
|
|
59
|
-
const codes = codeDocument.codes.map((code, i) => renderSchema(nameMap[i], code))
|
|
187
|
+
return { addSchema, generate, generateHttpApi } as const
|
|
188
|
+
}
|
|
60
189
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
190
|
+
function fromSchemaOpenApi(source: Source, jsonSchema: JsonSchema.JsonSchema) {
|
|
191
|
+
switch (source) {
|
|
192
|
+
case "openapi-3.1":
|
|
193
|
+
return JsonSchema.fromSchemaOpenApi3_1(jsonSchema)
|
|
194
|
+
case "openapi-3.0":
|
|
195
|
+
return JsonSchema.fromSchemaOpenApi3_0(jsonSchema)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
64
198
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
199
|
+
function renderSchemaTypeAndRuntime(
|
|
200
|
+
$ref: string,
|
|
201
|
+
code: SchemaRepresentation.Code,
|
|
202
|
+
typeOnly: boolean,
|
|
203
|
+
multipartSchemaRefs?: {
|
|
204
|
+
readonly singleFile: string
|
|
205
|
+
readonly files: string
|
|
206
|
+
}
|
|
207
|
+
) {
|
|
208
|
+
if (!typeOnly && multipartSchemaRefs !== undefined) {
|
|
209
|
+
if ($ref === multipartSchemaRefs.singleFile) {
|
|
210
|
+
return [
|
|
211
|
+
`export type ${$ref} = Multipart.PersistedFile`,
|
|
212
|
+
`export const ${$ref} = Multipart.SingleFileSchema`
|
|
213
|
+
].join("\n")
|
|
68
214
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case "openapi-3.0":
|
|
75
|
-
return JsonSchema.fromSchemaOpenApi3_0(jsonSchema)
|
|
76
|
-
}
|
|
215
|
+
if ($ref === multipartSchemaRefs.files) {
|
|
216
|
+
return [
|
|
217
|
+
`export type ${$ref} = ReadonlyArray<Multipart.PersistedFile>`,
|
|
218
|
+
`export const ${$ref} = Multipart.FilesSchema`
|
|
219
|
+
].join("\n")
|
|
77
220
|
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const strings = [`export type ${$ref} = ${code.Type}`]
|
|
224
|
+
if (!typeOnly) {
|
|
225
|
+
strings.push(`export const ${$ref} = ${code.runtime}`)
|
|
226
|
+
}
|
|
227
|
+
return strings.join("\n")
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderRecursiveReferenceDeclaration(
|
|
231
|
+
$ref: string,
|
|
232
|
+
code: SchemaRepresentation.Code,
|
|
233
|
+
internalName: string
|
|
234
|
+
): string {
|
|
235
|
+
return [
|
|
236
|
+
`export type ${$ref} = ${code.Type}`,
|
|
237
|
+
`export const ${$ref} = Schema.suspend((): Schema.Codec<${$ref}> => ${internalName})`
|
|
238
|
+
].join("\n")
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function render(title: string, as: ReadonlyArray<string>) {
|
|
242
|
+
if (as.length === 0) return ""
|
|
243
|
+
return "// " + title + "\n" + as.join("\n") + "\n"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const tokenPattern = /[A-Za-z_$][A-Za-z0-9_$]*/g
|
|
247
|
+
|
|
248
|
+
function collectForwardReferencedRecursives(
|
|
249
|
+
nonRecursives: ReadonlyArray<{
|
|
250
|
+
readonly $ref: string
|
|
251
|
+
readonly code: SchemaRepresentation.Code
|
|
252
|
+
}>,
|
|
253
|
+
recursives: ReadonlyArray<readonly [string, SchemaRepresentation.Code]>
|
|
254
|
+
): Set<string> {
|
|
255
|
+
const recursiveNames = new Set(recursives.map(([name]) => name))
|
|
256
|
+
const referenced = new Set<string>()
|
|
78
257
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
258
|
+
for (const { code } of nonRecursives) {
|
|
259
|
+
for (const token of code.runtime.matchAll(tokenPattern)) {
|
|
260
|
+
const identifier = token[0]
|
|
261
|
+
if (recursiveNames.has(identifier)) {
|
|
262
|
+
referenced.add(identifier)
|
|
83
263
|
}
|
|
84
|
-
return strings.join("\n")
|
|
85
264
|
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return referenced
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function makeRecursiveInternalNameMap(
|
|
271
|
+
recursiveNames: ReadonlySet<string>,
|
|
272
|
+
existingNames: ReadonlyArray<string>
|
|
273
|
+
): Map<string, string> {
|
|
274
|
+
const usedNames = new Set(existingNames)
|
|
275
|
+
const internalNames = new Map<string, string>()
|
|
86
276
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
277
|
+
for (const name of recursiveNames) {
|
|
278
|
+
let candidate = `__recursive_${name}`
|
|
279
|
+
while (usedNames.has(candidate)) {
|
|
280
|
+
candidate = `_${candidate}`
|
|
90
281
|
}
|
|
282
|
+
usedNames.add(candidate)
|
|
283
|
+
internalNames.set(name, candidate)
|
|
91
284
|
}
|
|
92
285
|
|
|
93
|
-
return
|
|
286
|
+
return internalNames
|
|
94
287
|
}
|