@effect/openapi-generator 4.0.0-beta.9 → 4.0.0-beta.90

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 +88 -11
  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 +92 -15
  39. package/src/ParsedOperation.ts +235 -1
  40. package/src/Utils.ts +61 -0
  41. package/src/main.ts +58 -10
@@ -1,9 +1,204 @@
1
+ /**
2
+ * Normalized OpenAPI operation model shared by the generator pipeline.
3
+ *
4
+ * This module records the shape produced after an OpenAPI document is resolved
5
+ * into stable generator inputs: document metadata, tags, security schemes,
6
+ * per-operation parameters, request bodies, response media types, derived
7
+ * schema references, path templates, and streaming capabilities. Renderers
8
+ * consume this representation to emit HttpClient or HttpApi modules without
9
+ * reinterpreting raw OpenAPI path-item structures.
10
+ *
11
+ * @since 4.0.0
12
+ */
1
13
  import type * as Types from "effect/Types"
2
- import type { OpenAPISpecMethodName } from "effect/unstable/httpapi/OpenApi"
14
+ import type {
15
+ OpenAPISecurityRequirement,
16
+ OpenAPISpecExternalDocs,
17
+ OpenAPISpecLicense,
18
+ OpenAPISpecMethodName,
19
+ OpenAPISpecServer
20
+ } from "effect/unstable/httpapi/OpenApi"
3
21
 
22
+ /**
23
+ * Root OpenAPI metadata preserved for generated client and HttpApi output.
24
+ *
25
+ * @category models
26
+ * @since 4.0.0
27
+ */
28
+ export interface ParsedOpenApiMetadata {
29
+ readonly title: string
30
+ readonly version: string
31
+ readonly summary: string | undefined
32
+ readonly description: string | undefined
33
+ readonly license: OpenAPISpecLicense | undefined
34
+ readonly servers: ReadonlyArray<OpenAPISpecServer> | undefined
35
+ }
36
+
37
+ /**
38
+ * Tag metadata used to group and annotate generated operations.
39
+ *
40
+ * @category models
41
+ * @since 4.0.0
42
+ */
43
+ export interface ParsedOpenApiTag {
44
+ readonly name: string
45
+ readonly description: string | undefined
46
+ readonly externalDocs: OpenAPISpecExternalDocs | undefined
47
+ }
48
+
49
+ /**
50
+ * Supported security scheme extracted from an OpenAPI components section.
51
+ *
52
+ * @category models
53
+ * @since 4.0.0
54
+ */
55
+ export interface ParsedOpenApiSecurityScheme {
56
+ readonly name: string
57
+ readonly type: "basic" | "bearer" | "apiKey" | "http"
58
+ readonly description: string | undefined
59
+ readonly bearerFormat: string | undefined
60
+ readonly scheme: string | undefined
61
+ readonly key: string | undefined
62
+ readonly in: "header" | "query" | "cookie" | undefined
63
+ }
64
+
65
+ /**
66
+ * Normalized OpenAPI document consumed by the generator renderers.
67
+ *
68
+ * @category models
69
+ * @since 4.0.0
70
+ */
71
+ export interface ParsedOpenApi {
72
+ readonly metadata: ParsedOpenApiMetadata
73
+ readonly tags: ReadonlyArray<ParsedOpenApiTag>
74
+ readonly securitySchemes: ReadonlyArray<ParsedOpenApiSecurityScheme>
75
+ readonly operations: ReadonlyArray<ParsedOperation>
76
+ }
77
+
78
+ /**
79
+ * Documentation and lifecycle metadata associated with an operation.
80
+ *
81
+ * @category models
82
+ * @since 4.0.0
83
+ */
84
+ export interface ParsedOperationMetadata {
85
+ readonly summary: string | undefined
86
+ readonly description: string | undefined
87
+ readonly deprecated: boolean
88
+ readonly externalDocs: OpenAPISpecExternalDocs | undefined
89
+ }
90
+
91
+ /**
92
+ * Resolved OpenAPI parameter grouped by where it appears in the request.
93
+ *
94
+ * @category models
95
+ * @since 4.0.0
96
+ */
97
+ export interface ParsedOperationParameter {
98
+ readonly name: string
99
+ readonly in: "path" | "query" | "header" | "cookie"
100
+ readonly required: boolean
101
+ readonly description: string | undefined
102
+ readonly schema: {}
103
+ }
104
+
105
+ /**
106
+ * Summary of the request body declaration before per-media schemas are rendered.
107
+ *
108
+ * @category models
109
+ * @since 4.0.0
110
+ */
111
+ export interface ParsedOperationRequestBody {
112
+ readonly required: boolean
113
+ readonly contentTypes: Array<string>
114
+ }
115
+
116
+ /**
117
+ * Encoding strategy the generator can use for a request or response media type.
118
+ *
119
+ * @category models
120
+ * @since 4.0.0
121
+ */
122
+ export type ParsedOperationMediaTypeEncoding =
123
+ | "json"
124
+ | "multipart"
125
+ | "form-url-encoded"
126
+ | "text"
127
+ | "binary"
128
+
129
+ /**
130
+ * Media type whose schema can be represented in generated Effect code.
131
+ *
132
+ * @category models
133
+ * @since 4.0.0
134
+ */
135
+ export type ParsedOperationMediaTypeSchema =
136
+ | {
137
+ readonly contentType: string
138
+ readonly encoding: ParsedOperationMediaTypeEncoding
139
+ readonly schema: string
140
+ readonly effectStream?: undefined
141
+ }
142
+ | {
143
+ readonly contentType: string
144
+ readonly encoding: "text"
145
+ readonly schema: string
146
+ readonly effectStream: "sse"
147
+ readonly errorSchema: string
148
+ }
149
+ | {
150
+ readonly contentType: string
151
+ readonly encoding: "binary"
152
+ readonly schema?: undefined
153
+ readonly effectStream: "uint8array"
154
+ }
155
+
156
+ /**
157
+ * Parsed response metadata together with generated schema references.
158
+ *
159
+ * @category models
160
+ * @since 4.0.0
161
+ */
162
+ export interface ParsedOperationResponse {
163
+ readonly status: string
164
+ readonly description: string | undefined
165
+ readonly contentTypes: Array<string>
166
+ readonly hasHeaders: boolean
167
+ readonly isEmpty: boolean
168
+ readonly representable: ReadonlyArray<ParsedOperationMediaTypeSchema>
169
+ }
170
+
171
+ /**
172
+ * Resolved security requirement applied to a parsed operation.
173
+ *
174
+ * @category models
175
+ * @since 4.0.0
176
+ */
177
+ export type ParsedOperationSecurityRequirement = Readonly<OpenAPISecurityRequirement>
178
+
179
+ /**
180
+ * Normalized operation model shared by all OpenAPI generator backends.
181
+ *
182
+ * @category models
183
+ * @since 4.0.0
184
+ */
4
185
  export interface ParsedOperation {
5
186
  readonly id: string
187
+ readonly operationId: string | undefined
188
+ readonly path: string
6
189
  readonly method: OpenAPISpecMethodName
190
+ readonly tags: ReadonlyArray<string>
191
+ readonly metadata: ParsedOperationMetadata
192
+ readonly parameters: {
193
+ readonly path: ReadonlyArray<ParsedOperationParameter>
194
+ readonly query: ReadonlyArray<ParsedOperationParameter>
195
+ readonly header: ReadonlyArray<ParsedOperationParameter>
196
+ readonly cookie: ReadonlyArray<ParsedOperationParameter>
197
+ }
198
+ readonly requestBody: ParsedOperationRequestBody | undefined
199
+ readonly responses: ReadonlyArray<ParsedOperationResponse>
200
+ readonly defaultResponse: ParsedOperationResponse | undefined
201
+ readonly effectiveSecurity: ReadonlyArray<ParsedOperationSecurityRequirement>
7
202
  readonly description: string | undefined
8
203
  readonly params?: string
9
204
  readonly paramsOptional: boolean
@@ -12,6 +207,13 @@ export interface ParsedOperation {
12
207
  readonly cookies: ReadonlyArray<string>
13
208
  readonly payload?: string
14
209
  readonly payloadFormData: boolean
210
+ readonly payloadFormUrlEncoded: boolean
211
+ readonly pathSchema: string | undefined
212
+ readonly querySchema: string | undefined
213
+ readonly querySchemaOptional: boolean
214
+ readonly headersSchema: string | undefined
215
+ readonly headersSchemaOptional: boolean
216
+ readonly requestBodyRepresentable: ReadonlyArray<ParsedOperationMediaTypeSchema>
15
217
  readonly pathIds: ReadonlyArray<string>
16
218
  readonly pathTemplate: string
17
219
  readonly successSchemas: ReadonlyMap<string, string>
@@ -23,6 +225,12 @@ export interface ParsedOperation {
23
225
  readonly binaryResponse: boolean
24
226
  }
25
227
 
228
+ /**
229
+ * Creates a mutable operation accumulator populated with parser defaults.
230
+ *
231
+ * @category constructors
232
+ * @since 4.0.0
233
+ */
26
234
  export const makeDeepMutable = (options: {
27
235
  readonly id: string
28
236
  readonly method: OpenAPISpecMethodName
@@ -31,10 +239,36 @@ export const makeDeepMutable = (options: {
31
239
  readonly description: string | undefined
32
240
  }): Types.DeepMutable<ParsedOperation> => ({
33
241
  ...options,
242
+ operationId: undefined,
243
+ path: "",
244
+ tags: [],
245
+ metadata: {
246
+ summary: undefined,
247
+ description: options.description,
248
+ deprecated: false,
249
+ externalDocs: undefined
250
+ },
251
+ parameters: {
252
+ path: [],
253
+ query: [],
254
+ header: [],
255
+ cookie: []
256
+ },
257
+ requestBody: undefined,
258
+ responses: [],
259
+ defaultResponse: undefined,
260
+ effectiveSecurity: [],
34
261
  urlParams: [],
35
262
  headers: [],
36
263
  cookies: [],
37
264
  payloadFormData: false,
265
+ payloadFormUrlEncoded: false,
266
+ pathSchema: undefined,
267
+ querySchema: undefined,
268
+ querySchemaOptional: true,
269
+ headersSchema: undefined,
270
+ headersSchemaOptional: true,
271
+ requestBodyRepresentable: [],
38
272
  successSchemas: new Map(),
39
273
  errorSchemas: new Map(),
40
274
  voidSchemas: new Set(),
package/src/Utils.ts CHANGED
@@ -1,6 +1,27 @@
1
+ /**
2
+ * Shared utility helpers for the OpenAPI generator.
3
+ *
4
+ * This module centralizes the small transformations used while rendering
5
+ * generated TypeScript, including operation-name normalization, optional
6
+ * description handling, safe JSDoc comment emission, and direct array merging
7
+ * for code-generation accumulators.
8
+ *
9
+ * @since 4.0.0
10
+ */
1
11
  import * as String from "effect/String"
2
12
  import * as UndefinedOr from "effect/UndefinedOr"
3
13
 
14
+ /**
15
+ * Converts an OpenAPI name into the generator's camel-case form.
16
+ *
17
+ * **Details**
18
+ *
19
+ * Separators are removed, leading digits are ignored, and letters following a
20
+ * separator or digit are upper-cased without otherwise changing letter casing.
21
+ *
22
+ * @category converting
23
+ * @since 4.0.0
24
+ */
4
25
  export const camelize = (self: string): string => {
5
26
  let str = ""
6
27
  let hadSymbol = false
@@ -24,8 +45,26 @@ export const camelize = (self: string): string => {
24
45
  return str
25
46
  }
26
47
 
48
+ /**
49
+ * Converts an OpenAPI operation id into the exported operation identifier used
50
+ * by generated TypeScript modules.
51
+ *
52
+ * @category converting
53
+ * @since 4.0.0
54
+ */
27
55
  export const identifier = (operationId: string) => String.capitalize(camelize(operationId))
28
56
 
57
+ /**
58
+ * Extracts a trimmed, non-empty string from an unknown value.
59
+ *
60
+ * **Details**
61
+ *
62
+ * Returns `undefined` for non-string values and for strings containing only
63
+ * whitespace.
64
+ *
65
+ * @category filtering
66
+ * @since 4.0.0
67
+ */
29
68
  export const nonEmptyString = (a: unknown): string | undefined => {
30
69
  if (typeof a === "string") {
31
70
  const trimmed = String.trim(a)
@@ -35,6 +74,17 @@ export const nonEmptyString = (a: unknown): string | undefined => {
35
74
  }
36
75
  }
37
76
 
77
+ /**
78
+ * Renders an optional description as a JSDoc block for generated TypeScript.
79
+ *
80
+ * **Details**
81
+ *
82
+ * Returns an empty string when the description is absent and escapes any
83
+ * closing comment marker so generated source remains syntactically valid.
84
+ *
85
+ * @category converting
86
+ * @since 4.0.0
87
+ */
38
88
  export const toComment = UndefinedOr.match({
39
89
  onUndefined: () => "",
40
90
  onDefined: (description: string) =>
@@ -43,6 +93,17 @@ export const toComment = UndefinedOr.match({
43
93
  */\n`
44
94
  })
45
95
 
96
+ /**
97
+ * Appends every element from `source` into `destination` in order.
98
+ *
99
+ * **Details**
100
+ *
101
+ * This mutates `destination` directly, which avoids allocating an intermediate
102
+ * array when generator code needs to merge collections.
103
+ *
104
+ * @category concatenating
105
+ * @since 4.0.0
106
+ */
46
107
  export const spreadElementsInto = <A>(source: Array<A>, destination: Array<A>): void => {
47
108
  for (let i = 0; i < source.length; i++) {
48
109
  destination.push(source[i])
package/src/main.ts CHANGED
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Command-line entry point for generating Effect HTTP clients or HttpApi
3
+ * definitions from an OpenAPI specification.
4
+ *
5
+ * The CLI reads a spec file, optionally applies JSON patches in order, selects
6
+ * the generator layer for the requested output format, reports generation
7
+ * warnings to stderr, and writes the generated source to stdout.
8
+ *
9
+ * @since 4.0.0
10
+ */
1
11
  import * as Console from "effect/Console"
2
12
  import * as Effect from "effect/Effect"
3
13
  import type * as Schema from "effect/Schema"
@@ -10,18 +20,21 @@ import * as OpenApiPatch from "./OpenApiPatch.ts"
10
20
 
11
21
  const spec = Flag.fileParse("spec").pipe(
12
22
  Flag.withAlias("s"),
13
- Flag.withDescription("The OpenAPI spec file to generate the client from")
23
+ Flag.withDescription("The OpenAPI spec file to generate output from")
14
24
  )
15
25
 
16
26
  const name = Flag.string("name").pipe(
17
27
  Flag.withAlias("n"),
18
- Flag.withDescription("The name of the generated client"),
28
+ Flag.withDescription("The name of the generated output"),
19
29
  Flag.withDefault("Client")
20
30
  )
21
31
 
22
- const typeOnly = Flag.boolean("type-only").pipe(
23
- Flag.withAlias("t"),
24
- Flag.withDescription("Generate a type-only client without schemas")
32
+ const format = Flag.choice("format", ["httpclient", "httpclient-type-only", "httpapi"] as const).pipe(
33
+ Flag.withAlias("f"),
34
+ Flag.withDescription(
35
+ "Output format to generate: httpclient | httpclient-type-only | httpapi (default: httpclient)"
36
+ ),
37
+ Flag.withDefault("httpclient")
25
38
  )
26
39
 
27
40
  const patch = Flag.string("patch").pipe(
@@ -34,8 +47,8 @@ const patch = Flag.string("patch").pipe(
34
47
  Flag.between(0, Infinity)
35
48
  )
36
49
 
37
- const root = Command.make("openapigen", { spec, typeOnly, name, patch }).pipe(
38
- Command.withHandler(Effect.fnUntraced(function*({ name, spec, typeOnly, patch }) {
50
+ const root = Command.make("openapigen", { spec, format, name, patch }).pipe(
51
+ Command.withHandler(Effect.fnUntraced(function*({ name, spec, format, patch }) {
39
52
  let patchedSpec: Schema.Json = spec as Schema.Json
40
53
 
41
54
  if (patch.length > 0) {
@@ -53,16 +66,51 @@ const root = Command.make("openapigen", { spec, typeOnly, name, patch }).pipe(
53
66
  }
54
67
 
55
68
  const generator = yield* OpenApiGenerator.OpenApiGenerator
56
- const source = yield* generator.generate(patchedSpec as unknown as OpenAPISpec, { name, typeOnly })
69
+ const warnings: Array<OpenApiGenerator.OpenApiGeneratorWarning> = []
70
+ const source = yield* generator.generate(patchedSpec as unknown as OpenAPISpec, {
71
+ name,
72
+ format,
73
+ onWarning: (warning) => {
74
+ warnings.push(warning)
75
+ }
76
+ })
77
+ yield* Effect.forEach(
78
+ warnings,
79
+ (warning) => Console.error(formatWarning(warning)),
80
+ { discard: true }
81
+ )
57
82
  return yield* Console.log(source)
58
83
  })),
59
- Command.provide(({ typeOnly }) =>
60
- typeOnly
84
+ Command.provide(({ format }) =>
85
+ format === "httpclient-type-only"
61
86
  ? OpenApiGenerator.layerTransformerTs
62
87
  : OpenApiGenerator.layerTransformerSchema
63
88
  )
64
89
  )
65
90
 
91
+ /**
92
+ * Runs the OpenAPI generator command-line program.
93
+ *
94
+ * **Details**
95
+ *
96
+ * The command reads an OpenAPI specification, optionally applies JSON patches,
97
+ * generates source code in the selected format, writes any generation warnings
98
+ * to stderr, and prints the generated source to stdout.
99
+ *
100
+ * @category running
101
+ * @since 4.0.0
102
+ */
66
103
  export const run: Effect.Effect<void, CliError.CliError, Command.Environment> = Command.run(root, {
67
104
  version: "0.0.0"
68
105
  })
106
+
107
+ const formatWarning = (warning: OpenApiGenerator.OpenApiGeneratorWarning): string => {
108
+ const context = [
109
+ warning.method?.toUpperCase(),
110
+ warning.path,
111
+ warning.operationId ? `(${warning.operationId})` : undefined
112
+ ].filter((value): value is string => value !== undefined)
113
+ return context.length > 0
114
+ ? `WARNING [${warning.code}] ${context.join(" ")}: ${warning.message}`
115
+ : `WARNING [${warning.code}] ${warning.message}`
116
+ }