@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.
Files changed (41) hide show
  1. package/dist/HttpApiTransformer.d.ts +41 -0
  2. package/dist/HttpApiTransformer.d.ts.map +1 -0
  3. package/dist/HttpApiTransformer.js +393 -0
  4. package/dist/HttpApiTransformer.js.map +1 -0
  5. package/dist/JsonSchemaGenerator.d.ts +25 -3
  6. package/dist/JsonSchemaGenerator.d.ts.map +1 -1
  7. package/dist/JsonSchemaGenerator.js +178 -47
  8. package/dist/JsonSchemaGenerator.js.map +1 -1
  9. package/dist/OpenApiGenerator.d.ts +81 -7
  10. package/dist/OpenApiGenerator.d.ts.map +1 -1
  11. package/dist/OpenApiGenerator.js +763 -119
  12. package/dist/OpenApiGenerator.js.map +1 -1
  13. package/dist/OpenApiPatch.d.ts +47 -24
  14. package/dist/OpenApiPatch.d.ts.map +1 -1
  15. package/dist/OpenApiPatch.js +42 -19
  16. package/dist/OpenApiPatch.js.map +1 -1
  17. package/dist/OpenApiTransformer.d.ts +85 -12
  18. package/dist/OpenApiTransformer.d.ts.map +1 -1
  19. package/dist/OpenApiTransformer.js +85 -8
  20. package/dist/OpenApiTransformer.js.map +1 -1
  21. package/dist/ParsedOperation.d.ts +184 -1
  22. package/dist/ParsedOperation.d.ts.map +1 -1
  23. package/dist/ParsedOperation.js +32 -0
  24. package/dist/ParsedOperation.js.map +1 -1
  25. package/dist/Utils.d.ts +51 -0
  26. package/dist/Utils.d.ts.map +1 -1
  27. package/dist/Utils.js +61 -0
  28. package/dist/Utils.js.map +1 -1
  29. package/dist/main.d.ts +12 -0
  30. package/dist/main.d.ts.map +1 -1
  31. package/dist/main.js +41 -8
  32. package/dist/main.js.map +1 -1
  33. package/package.json +10 -8
  34. package/src/HttpApiTransformer.ts +552 -0
  35. package/src/JsonSchemaGenerator.ts +272 -47
  36. package/src/OpenApiGenerator.ts +1057 -165
  37. package/src/OpenApiPatch.ts +56 -31
  38. package/src/OpenApiTransformer.ts +89 -12
  39. package/src/ParsedOperation.ts +235 -1
  40. package/src/Utils.ts +61 -0
  41. 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: "openapi-3.0" | "openapi-3.1",
63
+ source: Source,
19
64
  components: JsonSchema.Definitions,
20
65
  typeOnly: boolean,
21
- options?: {
22
- readonly onEnter?: ((js: JsonSchema.JsonSchema) => JsonSchema.JsonSchema) | undefined
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
- if (Arr.isArrayNonEmpty(schemas)) {
39
- const multiDocument: SchemaRepresentation.MultiDocument = SchemaRepresentation.fromJsonSchemaMultiDocument({
40
- dialect: "draft-2020-12",
41
- schemas,
42
- definitions
43
- }, {
44
- onEnter(js) {
45
- const out = { ...js }
46
- if (out.type === "object" && out.additionalProperties === undefined) {
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
- const codeDocument = SchemaRepresentation.toCodeDocument(multiDocument)
213
+ return {
214
+ nameMap,
215
+ codeDocument: SchemaRepresentation.toCodeDocument(multiDocument)
216
+ }
217
+ }
54
218
 
55
- const nonRecursives = codeDocument.references.nonRecursives.map(({ $ref, code }) => renderSchema($ref, code))
56
- const recursives = Object.entries(codeDocument.references.recursives).map(([$ref, code]) =>
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
- const s = render("non-recursive definitions", nonRecursives) +
62
- render("recursive definitions", recursives) +
63
- render("schemas", codes)
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
- return s
66
- } else {
67
- return ""
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
- function fromSchemaOpenApi(jsonSchema: JsonSchema.JsonSchema) {
71
- switch (source) {
72
- case "openapi-3.1":
73
- return JsonSchema.fromSchemaOpenApi3_1(jsonSchema)
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
- function renderSchema($ref: string, code: SchemaRepresentation.Code) {
80
- const strings = [`export type ${$ref} = ${code.Type}`]
81
- if (!typeOnly) {
82
- strings.push(`export const ${$ref} = ${code.runtime}`)
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
- function render(title: string, as: ReadonlyArray<string>) {
88
- if (as.length === 0) return ""
89
- return "// " + title + "\n" + as.join("\n") + "\n"
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 { addSchema, generate } as const
318
+ return internalNames
94
319
  }