@effect/openapi-generator 4.0.0-beta.8 → 4.0.0-beta.81
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 +41 -0
- package/dist/HttpApiTransformer.d.ts.map +1 -0
- package/dist/HttpApiTransformer.js +393 -0
- package/dist/HttpApiTransformer.js.map +1 -0
- package/dist/JsonSchemaGenerator.d.ts +25 -3
- package/dist/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/JsonSchemaGenerator.js +178 -47
- package/dist/JsonSchemaGenerator.js.map +1 -1
- package/dist/OpenApiGenerator.d.ts +81 -7
- package/dist/OpenApiGenerator.d.ts.map +1 -1
- package/dist/OpenApiGenerator.js +763 -119
- package/dist/OpenApiGenerator.js.map +1 -1
- package/dist/OpenApiPatch.d.ts +47 -24
- package/dist/OpenApiPatch.d.ts.map +1 -1
- package/dist/OpenApiPatch.js +42 -19
- package/dist/OpenApiPatch.js.map +1 -1
- package/dist/OpenApiTransformer.d.ts +85 -12
- package/dist/OpenApiTransformer.d.ts.map +1 -1
- package/dist/OpenApiTransformer.js +85 -8
- package/dist/OpenApiTransformer.js.map +1 -1
- package/dist/ParsedOperation.d.ts +184 -1
- package/dist/ParsedOperation.d.ts.map +1 -1
- package/dist/ParsedOperation.js +32 -0
- package/dist/ParsedOperation.js.map +1 -1
- package/dist/Utils.d.ts +51 -0
- package/dist/Utils.d.ts.map +1 -1
- package/dist/Utils.js +61 -0
- package/dist/Utils.js.map +1 -1
- package/dist/main.d.ts +12 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +41 -8
- package/dist/main.js.map +1 -1
- package/package.json +10 -8
- package/src/HttpApiTransformer.ts +552 -0
- package/src/JsonSchemaGenerator.ts +272 -47
- package/src/OpenApiGenerator.ts +1057 -165
- package/src/OpenApiPatch.ts +56 -31
- package/src/OpenApiTransformer.ts +89 -12
- package/src/ParsedOperation.ts +235 -1
- package/src/Utils.ts +61 -0
- package/src/main.ts +58 -10
|
@@ -1,8 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate TypeScript source for JSON Schema declarations extracted from
|
|
3
|
+
* OpenAPI documents.
|
|
4
|
+
*
|
|
5
|
+
* This module is the schema-rendering stage of the OpenAPI generator. Callers
|
|
6
|
+
* register named OpenAPI 3.0 or 3.1 schemas, provide the reusable component
|
|
7
|
+
* definitions for the document, and receive source text containing exported
|
|
8
|
+
* TypeScript aliases plus Effect Schema runtime values. The generator first
|
|
9
|
+
* normalizes OpenAPI-specific schema shapes into Effect's JSON Schema model,
|
|
10
|
+
* then delegates recursive analysis and runtime expression construction to
|
|
11
|
+
* `SchemaRepresentation`.
|
|
12
|
+
*
|
|
13
|
+
* The renderer keeps the emitted module usable for both human maintainers and
|
|
14
|
+
* automated code-generation consumers by grouping recursive declarations,
|
|
15
|
+
* reusable references, and locally registered schemas. It also contains the
|
|
16
|
+
* HttpApi-specific multipart file substitutions that map generated schemas to
|
|
17
|
+
* Effect's multipart runtime types.
|
|
18
|
+
*
|
|
19
|
+
* @since 4.0.0
|
|
20
|
+
*/
|
|
1
21
|
import * as Arr from "effect/Array"
|
|
2
22
|
import * as JsonSchema from "effect/JsonSchema"
|
|
3
23
|
import * as Rec from "effect/Record"
|
|
4
24
|
import * as SchemaRepresentation from "effect/SchemaRepresentation"
|
|
5
25
|
|
|
26
|
+
type Source = "openapi-3.0" | "openapi-3.1"
|
|
27
|
+
|
|
28
|
+
interface GenerateOptions {
|
|
29
|
+
readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface GenerateHttpApiOptions extends GenerateOptions {
|
|
33
|
+
readonly multipartSchemaRefs?: {
|
|
34
|
+
readonly singleFile: string
|
|
35
|
+
readonly files: string
|
|
36
|
+
} | undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a stateful JSON Schema code generator for OpenAPI-derived schemas.
|
|
41
|
+
*
|
|
42
|
+
* **Details**
|
|
43
|
+
*
|
|
44
|
+
* Schemas registered with the returned generator are converted into TypeScript
|
|
45
|
+
* type aliases and Effect Schema runtime declarations, with reusable OpenAPI
|
|
46
|
+
* component definitions supplied at generation time.
|
|
47
|
+
*
|
|
48
|
+
* @category code generation
|
|
49
|
+
* @since 4.0.0
|
|
50
|
+
*/
|
|
6
51
|
export function make() {
|
|
7
52
|
const store: Record<string, JsonSchema.JsonSchema> = {}
|
|
8
53
|
|
|
@@ -15,80 +60,260 @@ export function make() {
|
|
|
15
60
|
}
|
|
16
61
|
|
|
17
62
|
function generate(
|
|
18
|
-
source:
|
|
63
|
+
source: Source,
|
|
19
64
|
components: JsonSchema.Definitions,
|
|
20
65
|
typeOnly: boolean,
|
|
21
|
-
options?:
|
|
22
|
-
|
|
66
|
+
options?: GenerateOptions
|
|
67
|
+
) {
|
|
68
|
+
const generated = makeCodeDocument(source, components, options)
|
|
69
|
+
if (generated === undefined) {
|
|
70
|
+
return ""
|
|
23
71
|
}
|
|
72
|
+
|
|
73
|
+
const nonRecursiveReferences = generated.codeDocument.references.nonRecursives
|
|
74
|
+
const recursiveReferences = Object.entries(generated.codeDocument.references.recursives)
|
|
75
|
+
|
|
76
|
+
const nonRecursives = nonRecursiveReferences.map(({ $ref, code }) =>
|
|
77
|
+
renderSchemaTypeAndRuntime($ref, code, typeOnly)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const recursiveDeclarations: Array<string> = []
|
|
81
|
+
const recursives: Array<string> = []
|
|
82
|
+
|
|
83
|
+
if (typeOnly) {
|
|
84
|
+
for (const [$ref, code] of recursiveReferences) {
|
|
85
|
+
recursives.push(renderSchemaTypeAndRuntime($ref, code, true))
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
const recursivelyForwardReferenced = collectForwardReferencedRecursives(
|
|
89
|
+
nonRecursiveReferences,
|
|
90
|
+
recursiveReferences
|
|
91
|
+
)
|
|
92
|
+
const recursiveInternalNames = makeRecursiveInternalNameMap(
|
|
93
|
+
recursivelyForwardReferenced,
|
|
94
|
+
[
|
|
95
|
+
...nonRecursiveReferences.map(({ $ref }) => $ref),
|
|
96
|
+
...recursiveReferences.map(([$ref]) => $ref),
|
|
97
|
+
...generated.nameMap
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
for (const [$ref, code] of recursiveReferences) {
|
|
102
|
+
if (recursivelyForwardReferenced.has($ref)) {
|
|
103
|
+
const internalName = recursiveInternalNames.get($ref)!
|
|
104
|
+
recursiveDeclarations.push(renderRecursiveReferenceDeclaration($ref, code, internalName))
|
|
105
|
+
recursives.push(`const ${internalName} = ${code.runtime}`)
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
recursives.push(renderSchemaTypeAndRuntime($ref, code, false))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const codes = generated.codeDocument.codes.map((code, i) =>
|
|
114
|
+
renderSchemaTypeAndRuntime(generated.nameMap[i], code, typeOnly)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return render("recursive declarations", recursiveDeclarations) +
|
|
118
|
+
render("non-recursive definitions", nonRecursives) +
|
|
119
|
+
render("recursive definitions", recursives) +
|
|
120
|
+
render("schemas", codes)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function generateHttpApi(
|
|
124
|
+
source: Source,
|
|
125
|
+
components: JsonSchema.Definitions,
|
|
126
|
+
options?: GenerateHttpApiOptions
|
|
24
127
|
) {
|
|
128
|
+
const generated = makeCodeDocument(source, components, options)
|
|
129
|
+
if (generated === undefined) {
|
|
130
|
+
return ""
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const nonRecursiveReferences = generated.codeDocument.references.nonRecursives
|
|
134
|
+
const recursiveReferences = Object.entries(generated.codeDocument.references.recursives)
|
|
135
|
+
|
|
136
|
+
const nonRecursives = nonRecursiveReferences.map(({ $ref, code }) =>
|
|
137
|
+
renderSchemaTypeAndRuntime($ref, code, false, options?.multipartSchemaRefs)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const recursivelyForwardReferenced = collectForwardReferencedRecursives(nonRecursiveReferences, recursiveReferences)
|
|
141
|
+
const recursiveInternalNames = makeRecursiveInternalNameMap(
|
|
142
|
+
recursivelyForwardReferenced,
|
|
143
|
+
[
|
|
144
|
+
...nonRecursiveReferences.map(({ $ref }) => $ref),
|
|
145
|
+
...recursiveReferences.map(([$ref]) => $ref),
|
|
146
|
+
...generated.nameMap
|
|
147
|
+
]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
const recursiveDeclarations: Array<string> = []
|
|
151
|
+
const recursives: Array<string> = []
|
|
152
|
+
|
|
153
|
+
for (const [$ref, code] of recursiveReferences) {
|
|
154
|
+
if (recursivelyForwardReferenced.has($ref)) {
|
|
155
|
+
const internalName = recursiveInternalNames.get($ref)!
|
|
156
|
+
recursiveDeclarations.push(renderRecursiveReferenceDeclaration($ref, code, internalName))
|
|
157
|
+
recursives.push(`const ${internalName} = ${code.runtime}`)
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
recursives.push(renderSchemaTypeAndRuntime($ref, code, false, options?.multipartSchemaRefs))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const codes = generated.codeDocument.codes.map((code, i) =>
|
|
165
|
+
renderSchemaTypeAndRuntime(generated.nameMap[i], code, false, options?.multipartSchemaRefs)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return render("recursive declarations", recursiveDeclarations) +
|
|
169
|
+
render("non-recursive definitions", nonRecursives) +
|
|
170
|
+
render("recursive definitions", recursives) +
|
|
171
|
+
render("schemas", codes)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function makeCodeDocument(
|
|
175
|
+
source: Source,
|
|
176
|
+
components: JsonSchema.Definitions,
|
|
177
|
+
options?: GenerateOptions
|
|
178
|
+
): {
|
|
179
|
+
readonly nameMap: Array<string>
|
|
180
|
+
readonly codeDocument: SchemaRepresentation.CodeDocument
|
|
181
|
+
} | undefined {
|
|
25
182
|
const nameMap: Array<string> = []
|
|
26
183
|
const schemas: Array<JsonSchema.JsonSchema> = []
|
|
27
184
|
|
|
28
185
|
const definitions: JsonSchema.Definitions = Rec.map(
|
|
29
186
|
components,
|
|
30
|
-
(js) => fromSchemaOpenApi(js).schema
|
|
187
|
+
(js) => fromSchemaOpenApi(source, js).schema
|
|
31
188
|
)
|
|
32
189
|
|
|
33
190
|
for (const [name, js] of Object.entries(store)) {
|
|
34
191
|
nameMap.push(name)
|
|
35
|
-
schemas.push(fromSchemaOpenApi(js).schema)
|
|
192
|
+
schemas.push(fromSchemaOpenApi(source, js).schema)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!Arr.isArrayNonEmpty(schemas)) {
|
|
196
|
+
return
|
|
36
197
|
}
|
|
37
198
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
out.additionalProperties = false
|
|
48
|
-
}
|
|
49
|
-
return options?.onEnter?.(out) ?? out
|
|
199
|
+
const multiDocument: SchemaRepresentation.MultiDocument = SchemaRepresentation.fromJsonSchemaMultiDocument({
|
|
200
|
+
dialect: "draft-2020-12",
|
|
201
|
+
schemas,
|
|
202
|
+
definitions
|
|
203
|
+
}, {
|
|
204
|
+
onEnter(js) {
|
|
205
|
+
const out = { ...js }
|
|
206
|
+
if (out.type === "object" && out.additionalProperties === undefined) {
|
|
207
|
+
out.additionalProperties = false
|
|
50
208
|
}
|
|
51
|
-
|
|
209
|
+
return options?.onEnter?.(out) ?? out
|
|
210
|
+
}
|
|
211
|
+
})
|
|
52
212
|
|
|
53
|
-
|
|
213
|
+
return {
|
|
214
|
+
nameMap,
|
|
215
|
+
codeDocument: SchemaRepresentation.toCodeDocument(multiDocument)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
54
218
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
renderSchema($ref, code)
|
|
58
|
-
)
|
|
59
|
-
const codes = codeDocument.codes.map((code, i) => renderSchema(nameMap[i], code))
|
|
219
|
+
return { addSchema, generate, generateHttpApi } as const
|
|
220
|
+
}
|
|
60
221
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
222
|
+
function fromSchemaOpenApi(source: Source, jsonSchema: JsonSchema.JsonSchema) {
|
|
223
|
+
switch (source) {
|
|
224
|
+
case "openapi-3.1":
|
|
225
|
+
return JsonSchema.fromSchemaOpenApi3_1(jsonSchema)
|
|
226
|
+
case "openapi-3.0":
|
|
227
|
+
return JsonSchema.fromSchemaOpenApi3_0(jsonSchema)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
64
230
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
231
|
+
function renderSchemaTypeAndRuntime(
|
|
232
|
+
$ref: string,
|
|
233
|
+
code: SchemaRepresentation.Code,
|
|
234
|
+
typeOnly: boolean,
|
|
235
|
+
multipartSchemaRefs?: {
|
|
236
|
+
readonly singleFile: string
|
|
237
|
+
readonly files: string
|
|
238
|
+
}
|
|
239
|
+
) {
|
|
240
|
+
if (!typeOnly && multipartSchemaRefs !== undefined) {
|
|
241
|
+
if ($ref === multipartSchemaRefs.singleFile) {
|
|
242
|
+
return [
|
|
243
|
+
`export type ${$ref} = Multipart.PersistedFile`,
|
|
244
|
+
`export const ${$ref} = Multipart.SingleFileSchema`
|
|
245
|
+
].join("\n")
|
|
68
246
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
case "openapi-3.0":
|
|
75
|
-
return JsonSchema.fromSchemaOpenApi3_0(jsonSchema)
|
|
76
|
-
}
|
|
247
|
+
if ($ref === multipartSchemaRefs.files) {
|
|
248
|
+
return [
|
|
249
|
+
`export type ${$ref} = ReadonlyArray<Multipart.PersistedFile>`,
|
|
250
|
+
`export const ${$ref} = Multipart.FilesSchema`
|
|
251
|
+
].join("\n")
|
|
77
252
|
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const strings = [`export type ${$ref} = ${code.Type}`]
|
|
256
|
+
if (!typeOnly) {
|
|
257
|
+
strings.push(`export const ${$ref} = ${code.runtime}`)
|
|
258
|
+
}
|
|
259
|
+
return strings.join("\n")
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function renderRecursiveReferenceDeclaration(
|
|
263
|
+
$ref: string,
|
|
264
|
+
code: SchemaRepresentation.Code,
|
|
265
|
+
internalName: string
|
|
266
|
+
): string {
|
|
267
|
+
return [
|
|
268
|
+
`export type ${$ref} = ${code.Type}`,
|
|
269
|
+
`export const ${$ref} = Schema.suspend((): Schema.Codec<${$ref}> => ${internalName})`
|
|
270
|
+
].join("\n")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function render(title: string, as: ReadonlyArray<string>) {
|
|
274
|
+
if (as.length === 0) return ""
|
|
275
|
+
return "// " + title + "\n" + as.join("\n") + "\n"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const tokenPattern = /[A-Za-z_$][A-Za-z0-9_$]*/g
|
|
279
|
+
|
|
280
|
+
function collectForwardReferencedRecursives(
|
|
281
|
+
nonRecursives: ReadonlyArray<{
|
|
282
|
+
readonly $ref: string
|
|
283
|
+
readonly code: SchemaRepresentation.Code
|
|
284
|
+
}>,
|
|
285
|
+
recursives: ReadonlyArray<readonly [string, SchemaRepresentation.Code]>
|
|
286
|
+
): Set<string> {
|
|
287
|
+
const recursiveNames = new Set(recursives.map(([name]) => name))
|
|
288
|
+
const referenced = new Set<string>()
|
|
78
289
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
290
|
+
for (const { code } of nonRecursives) {
|
|
291
|
+
for (const token of code.runtime.matchAll(tokenPattern)) {
|
|
292
|
+
const identifier = token[0]
|
|
293
|
+
if (recursiveNames.has(identifier)) {
|
|
294
|
+
referenced.add(identifier)
|
|
83
295
|
}
|
|
84
|
-
return strings.join("\n")
|
|
85
296
|
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return referenced
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function makeRecursiveInternalNameMap(
|
|
303
|
+
recursiveNames: ReadonlySet<string>,
|
|
304
|
+
existingNames: ReadonlyArray<string>
|
|
305
|
+
): Map<string, string> {
|
|
306
|
+
const usedNames = new Set(existingNames)
|
|
307
|
+
const internalNames = new Map<string, string>()
|
|
86
308
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
309
|
+
for (const name of recursiveNames) {
|
|
310
|
+
let candidate = `__recursive_${name}`
|
|
311
|
+
while (usedNames.has(candidate)) {
|
|
312
|
+
candidate = `_${candidate}`
|
|
90
313
|
}
|
|
314
|
+
usedNames.add(candidate)
|
|
315
|
+
internalNames.set(name, candidate)
|
|
91
316
|
}
|
|
92
317
|
|
|
93
|
-
return
|
|
318
|
+
return internalNames
|
|
94
319
|
}
|