@effect/openapi-generator 4.0.0-beta.0
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/LICENSE +21 -0
- package/dist/JsonSchemaGenerator.d.ts +8 -0
- package/dist/JsonSchemaGenerator.d.ts.map +1 -0
- package/dist/JsonSchemaGenerator.js +75 -0
- package/dist/JsonSchemaGenerator.js.map +1 -0
- package/dist/OpenApiGenerator.d.ts +32 -0
- package/dist/OpenApiGenerator.d.ts.map +1 -0
- package/dist/OpenApiGenerator.js +206 -0
- package/dist/OpenApiGenerator.js.map +1 -0
- package/dist/OpenApiPatch.d.ts +296 -0
- package/dist/OpenApiPatch.d.ts.map +1 -0
- package/dist/OpenApiPatch.js +448 -0
- package/dist/OpenApiPatch.js.map +1 -0
- package/dist/OpenApiTransformer.d.ts +24 -0
- package/dist/OpenApiTransformer.d.ts.map +1 -0
- package/dist/OpenApiTransformer.js +740 -0
- package/dist/OpenApiTransformer.js.map +1 -0
- package/dist/ParsedOperation.d.ts +29 -0
- package/dist/ParsedOperation.d.ts.map +1 -0
- package/dist/ParsedOperation.js +13 -0
- package/dist/ParsedOperation.js.map +1 -0
- package/dist/Utils.d.ts +6 -0
- package/dist/Utils.d.ts.map +1 -0
- package/dist/Utils.js +42 -0
- package/dist/Utils.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +47 -0
- package/dist/main.js.map +1 -0
- package/package.json +67 -0
- package/src/JsonSchemaGenerator.ts +94 -0
- package/src/OpenApiGenerator.ts +309 -0
- package/src/OpenApiPatch.ts +514 -0
- package/src/OpenApiTransformer.ts +954 -0
- package/src/ParsedOperation.ts +43 -0
- package/src/Utils.ts +50 -0
- package/src/bin.ts +10 -0
- package/src/main.ts +68 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer"
|
|
2
|
+
import * as Predicate from "effect/Predicate"
|
|
3
|
+
import * as ServiceMap from "effect/ServiceMap"
|
|
4
|
+
import type { OpenAPISpecMethodName } from "effect/unstable/httpapi/OpenApi"
|
|
5
|
+
import type { ParsedOperation } from "./ParsedOperation.ts"
|
|
6
|
+
import * as Utils from "./Utils.ts"
|
|
7
|
+
|
|
8
|
+
export class OpenApiTransformer extends ServiceMap.Service<
|
|
9
|
+
OpenApiTransformer,
|
|
10
|
+
{
|
|
11
|
+
readonly imports: (importName: string, operations: ReadonlyArray<ParsedOperation>) => string
|
|
12
|
+
readonly toTypes: (importName: string, name: string, operations: ReadonlyArray<ParsedOperation>) => string
|
|
13
|
+
readonly toImplementation: (importName: string, name: string, operations: ReadonlyArray<ParsedOperation>) => string
|
|
14
|
+
}
|
|
15
|
+
>()("OpenApiTransformer") {}
|
|
16
|
+
|
|
17
|
+
interface ImportRequirements {
|
|
18
|
+
readonly eventStream: boolean
|
|
19
|
+
readonly octetStream: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const computeImportRequirements = (operations: ReadonlyArray<ParsedOperation>): ImportRequirements => {
|
|
23
|
+
let eventStream = false
|
|
24
|
+
let octetStream = false
|
|
25
|
+
for (const op of operations) {
|
|
26
|
+
if (op.sseSchema) {
|
|
27
|
+
eventStream = true
|
|
28
|
+
}
|
|
29
|
+
if (op.binaryResponse) {
|
|
30
|
+
octetStream = true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { eventStream, octetStream }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const requiresStreaming = (requirements: ImportRequirements): boolean =>
|
|
37
|
+
requirements.eventStream || requirements.octetStream
|
|
38
|
+
|
|
39
|
+
const httpClientMethodNames: Record<OpenAPISpecMethodName, string> = {
|
|
40
|
+
get: "get",
|
|
41
|
+
put: "put",
|
|
42
|
+
post: "post",
|
|
43
|
+
delete: "del",
|
|
44
|
+
options: "options",
|
|
45
|
+
head: "head",
|
|
46
|
+
patch: "patch",
|
|
47
|
+
trace: `make("TRACE")`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const makeTransformerSchema = () => {
|
|
51
|
+
const operationsToInterface = (
|
|
52
|
+
_importName: string,
|
|
53
|
+
name: string,
|
|
54
|
+
operations: ReadonlyArray<ParsedOperation>
|
|
55
|
+
) => {
|
|
56
|
+
const methods: Array<string> = []
|
|
57
|
+
for (const op of operations) {
|
|
58
|
+
methods.push(operationToMethod(name, op))
|
|
59
|
+
if (op.sseSchema) {
|
|
60
|
+
methods.push(operationToSseMethod(name, op))
|
|
61
|
+
}
|
|
62
|
+
if (op.binaryResponse) {
|
|
63
|
+
methods.push(operationToBinaryMethod(name, op))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return `export interface ${name} {
|
|
67
|
+
readonly httpClient: HttpClient.HttpClient
|
|
68
|
+
${methods.join("\n ")}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
${clientErrorSource(name)}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const operationToMethod = (name: string, operation: ParsedOperation) => {
|
|
75
|
+
const args: Array<string> = []
|
|
76
|
+
if (operation.pathIds.length > 0) {
|
|
77
|
+
Utils.spreadElementsInto(operation.pathIds.map((id) => `${id}: string`), args)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const options: Array<string> = []
|
|
81
|
+
if (operation.params) {
|
|
82
|
+
const key = `readonly params${operation.paramsOptional ? "?" : ""}`
|
|
83
|
+
const type = `typeof ${operation.params}.Encoded${operation.paramsOptional ? " | undefined" : ""}`
|
|
84
|
+
options.push(`${key}: ${type}`)
|
|
85
|
+
}
|
|
86
|
+
if (operation.payload) {
|
|
87
|
+
const key = `readonly payload`
|
|
88
|
+
const type = `typeof ${operation.payload}.Encoded`
|
|
89
|
+
options.push(`${key}: ${type}`)
|
|
90
|
+
}
|
|
91
|
+
options.push("readonly config?: Config | undefined")
|
|
92
|
+
|
|
93
|
+
// If all options are optional, the argument itself should be optional
|
|
94
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
95
|
+
if (hasOptions) {
|
|
96
|
+
args.push(`options: { ${options.join("; ")} }`)
|
|
97
|
+
} else {
|
|
98
|
+
args.push(`options: { ${options.join("; ")} } | undefined`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let success = "void"
|
|
102
|
+
if (operation.successSchemas.size > 0) {
|
|
103
|
+
success = Array.from(operation.successSchemas.values())
|
|
104
|
+
.map((schema) => `typeof ${schema}.Type`)
|
|
105
|
+
.join(" | ")
|
|
106
|
+
}
|
|
107
|
+
const errors = ["HttpClientError.HttpClientError", "SchemaError"]
|
|
108
|
+
if (operation.errorSchemas.size > 0) {
|
|
109
|
+
Utils.spreadElementsInto(
|
|
110
|
+
Array.from(operation.errorSchemas.values()).map(
|
|
111
|
+
(schema) => `${name}Error<"${schema}", typeof ${schema}.Type>`
|
|
112
|
+
),
|
|
113
|
+
errors
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const jsdoc = Utils.toComment(operation.description)
|
|
118
|
+
const methodKey = `readonly "${operation.id}"`
|
|
119
|
+
const generic = `<Config extends OperationConfig>`
|
|
120
|
+
const parameters = args.join(", ")
|
|
121
|
+
const returnType = `Effect.Effect<WithOptionalResponse<${success}, Config>, ${errors.join(" | ")}>`
|
|
122
|
+
return `${jsdoc}${methodKey}: ${generic}(${parameters}) => ${returnType}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const operationToSseMethod = (_name: string, operation: ParsedOperation) => {
|
|
126
|
+
const args: Array<string> = []
|
|
127
|
+
if (operation.pathIds.length > 0) {
|
|
128
|
+
Utils.spreadElementsInto(operation.pathIds.map((id) => `${id}: string`), args)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const options: Array<string> = []
|
|
132
|
+
if (operation.params) {
|
|
133
|
+
const key = `readonly params${operation.paramsOptional ? "?" : ""}`
|
|
134
|
+
const type = `typeof ${operation.params}.Encoded${operation.paramsOptional ? " | undefined" : ""}`
|
|
135
|
+
options.push(`${key}: ${type}`)
|
|
136
|
+
}
|
|
137
|
+
if (operation.payload) {
|
|
138
|
+
options.push(`readonly payload: typeof ${operation.payload}.Encoded`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
142
|
+
if (hasOptions) {
|
|
143
|
+
args.push(`options: { ${options.join("; ")} }`)
|
|
144
|
+
} else if (options.length > 0) {
|
|
145
|
+
args.push(`options: { ${options.join("; ")} } | undefined`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const jsdoc = Utils.toComment(operation.description)
|
|
149
|
+
const methodKey = `readonly "${operation.id}Sse"`
|
|
150
|
+
const parameters = args.join(", ")
|
|
151
|
+
const returnType =
|
|
152
|
+
`Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: typeof ${operation.sseSchema}.Type }, HttpClientError.HttpClientError | SchemaError | Sse.Retry, typeof ${operation.sseSchema}.DecodingServices>`
|
|
153
|
+
return `${jsdoc}${methodKey}: (${parameters}) => ${returnType}`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const operationToBinaryMethod = (_name: string, operation: ParsedOperation) => {
|
|
157
|
+
const args: Array<string> = []
|
|
158
|
+
if (operation.pathIds.length > 0) {
|
|
159
|
+
Utils.spreadElementsInto(operation.pathIds.map((id) => `${id}: string`), args)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const options: Array<string> = []
|
|
163
|
+
if (operation.params) {
|
|
164
|
+
const key = `readonly params${operation.paramsOptional ? "?" : ""}`
|
|
165
|
+
const type = `typeof ${operation.params}.Encoded${operation.paramsOptional ? " | undefined" : ""}`
|
|
166
|
+
options.push(`${key}: ${type}`)
|
|
167
|
+
}
|
|
168
|
+
if (operation.payload) {
|
|
169
|
+
options.push(`readonly payload: typeof ${operation.payload}.Encoded`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
173
|
+
if (hasOptions) {
|
|
174
|
+
args.push(`options: { ${options.join("; ")} }`)
|
|
175
|
+
} else if (options.length > 0) {
|
|
176
|
+
args.push(`options: { ${options.join("; ")} } | undefined`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const jsdoc = Utils.toComment(operation.description)
|
|
180
|
+
const methodKey = `readonly "${operation.id}Stream"`
|
|
181
|
+
const parameters = args.join(", ")
|
|
182
|
+
const returnType = `Stream.Stream<Uint8Array, HttpClientError.HttpClientError>`
|
|
183
|
+
return `${jsdoc}${methodKey}: (${parameters}) => ${returnType}`
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const operationsToImpl = (
|
|
187
|
+
importName: string,
|
|
188
|
+
name: string,
|
|
189
|
+
operations: ReadonlyArray<ParsedOperation>
|
|
190
|
+
) => {
|
|
191
|
+
const requirements = computeImportRequirements(operations)
|
|
192
|
+
const implMethods: Array<string> = []
|
|
193
|
+
for (const op of operations) {
|
|
194
|
+
implMethods.push(operationToImpl(op))
|
|
195
|
+
if (op.sseSchema) {
|
|
196
|
+
implMethods.push(operationToSseImpl(importName, op))
|
|
197
|
+
}
|
|
198
|
+
if (op.binaryResponse) {
|
|
199
|
+
implMethods.push(operationToBinaryImpl(op))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const helpers: Array<string> = [commonSource]
|
|
204
|
+
if (requirements.eventStream) {
|
|
205
|
+
helpers.push(sseRequestSource(importName))
|
|
206
|
+
}
|
|
207
|
+
if (requirements.octetStream) {
|
|
208
|
+
helpers.push(binaryRequestSource)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return `export interface OperationConfig {
|
|
212
|
+
/**
|
|
213
|
+
* Whether or not the response should be included in the value returned from
|
|
214
|
+
* an operation.
|
|
215
|
+
*
|
|
216
|
+
* If set to \`true\`, a tuple of \`[A, HttpClientResponse]\` will be returned,
|
|
217
|
+
* where \`A\` is the success type of the operation.
|
|
218
|
+
*
|
|
219
|
+
* If set to \`false\`, only the success type of the operation will be returned.
|
|
220
|
+
*/
|
|
221
|
+
readonly includeResponse?: boolean | undefined
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A utility type which optionally includes the response in the return result
|
|
226
|
+
* of an operation based upon the value of the \`includeResponse\` configuration
|
|
227
|
+
* option.
|
|
228
|
+
*/
|
|
229
|
+
export type WithOptionalResponse<A, Config extends OperationConfig> = Config extends {
|
|
230
|
+
readonly includeResponse: true
|
|
231
|
+
} ? [A, HttpClientResponse.HttpClientResponse] : A
|
|
232
|
+
|
|
233
|
+
export const make = (
|
|
234
|
+
httpClient: HttpClient.HttpClient,
|
|
235
|
+
options: {
|
|
236
|
+
readonly transformClient?: ((client: HttpClient.HttpClient) => Effect.Effect<HttpClient.HttpClient>) | undefined
|
|
237
|
+
} = {}
|
|
238
|
+
): ${name} => {
|
|
239
|
+
${helpers.join("\n ")}
|
|
240
|
+
const decodeSuccess =
|
|
241
|
+
<Schema extends ${importName}.Top>(schema: Schema) =>
|
|
242
|
+
(response: HttpClientResponse.HttpClientResponse) =>
|
|
243
|
+
HttpClientResponse.schemaBodyJson(schema)(response)
|
|
244
|
+
const decodeError =
|
|
245
|
+
<const Tag extends string, Schema extends ${importName}.Top>(tag: Tag, schema: Schema) =>
|
|
246
|
+
(response: HttpClientResponse.HttpClientResponse) =>
|
|
247
|
+
Effect.flatMap(
|
|
248
|
+
HttpClientResponse.schemaBodyJson(schema)(response),
|
|
249
|
+
(cause) => Effect.fail(${name}Error(tag, cause, response)),
|
|
250
|
+
)
|
|
251
|
+
return {
|
|
252
|
+
httpClient,
|
|
253
|
+
${implMethods.join(",\n ")}
|
|
254
|
+
}
|
|
255
|
+
}`
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const operationToImpl = (operation: ParsedOperation) => {
|
|
259
|
+
const args: Array<string> = [...operation.pathIds, "options"]
|
|
260
|
+
const params = `${args.join(", ")}`
|
|
261
|
+
|
|
262
|
+
const pipeline: Array<string> = []
|
|
263
|
+
|
|
264
|
+
if (operation.params) {
|
|
265
|
+
const paramsAccessor = resolveParamsAccessor(operation, "options", "params")
|
|
266
|
+
|
|
267
|
+
if (operation.urlParams.length > 0) {
|
|
268
|
+
const props = operation.urlParams.map(
|
|
269
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] as any`
|
|
270
|
+
)
|
|
271
|
+
pipeline.push(`HttpClientRequest.setUrlParams({ ${props.join(", ")} })`)
|
|
272
|
+
}
|
|
273
|
+
if (operation.headers.length > 0) {
|
|
274
|
+
const props = operation.headers.map(
|
|
275
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] ?? undefined`
|
|
276
|
+
)
|
|
277
|
+
pipeline.push(`HttpClientRequest.setHeaders({ ${props.join(", ")} })`)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const payloadVarName = "options.payload"
|
|
282
|
+
if (operation.payloadFormData) {
|
|
283
|
+
pipeline.push(`HttpClientRequest.bodyFormData(${payloadVarName} as any)`)
|
|
284
|
+
} else if (operation.payload) {
|
|
285
|
+
pipeline.push(`HttpClientRequest.bodyJsonUnsafe(${payloadVarName})`)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const decodes: Array<string> = []
|
|
289
|
+
const singleSuccessCode = operation.successSchemas.size === 1
|
|
290
|
+
operation.successSchemas.forEach((schema, status) => {
|
|
291
|
+
const statusCode = singleSuccessCode && status.startsWith("2") ? "2xx" : status
|
|
292
|
+
decodes.push(`"${statusCode}": decodeSuccess(${schema})`)
|
|
293
|
+
})
|
|
294
|
+
operation.errorSchemas.forEach((schema, status) => {
|
|
295
|
+
decodes.push(`"${status}": decodeError("${schema}", ${schema})`)
|
|
296
|
+
})
|
|
297
|
+
operation.voidSchemas.forEach((status) => {
|
|
298
|
+
decodes.push(`"${status}": () => Effect.void`)
|
|
299
|
+
})
|
|
300
|
+
decodes.push(`orElse: unexpectedStatus`)
|
|
301
|
+
|
|
302
|
+
const configAccessor = resolveConfigAccessor(operation, "options", "config")
|
|
303
|
+
pipeline.push(`withResponse(${configAccessor})(HttpClientResponse.matchStatus({
|
|
304
|
+
${decodes.join(",\n ")}
|
|
305
|
+
}))`)
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
`"${operation.id}": (${params}) => ` +
|
|
309
|
+
`HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
|
|
310
|
+
`.pipe(\n ${pipeline.join(",\n ")}\n )`
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const operationToSseImpl = (_importName: string, operation: ParsedOperation) => {
|
|
315
|
+
const args: Array<string> = [...operation.pathIds]
|
|
316
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
317
|
+
if (hasOptions || operation.params || operation.payload) {
|
|
318
|
+
args.push("options")
|
|
319
|
+
}
|
|
320
|
+
const params = args.join(", ")
|
|
321
|
+
|
|
322
|
+
const pipeline: Array<string> = []
|
|
323
|
+
|
|
324
|
+
if (operation.params) {
|
|
325
|
+
const paramsAccessor = resolveParamsAccessor(operation, "options", "params")
|
|
326
|
+
if (operation.urlParams.length > 0) {
|
|
327
|
+
const props = operation.urlParams.map(
|
|
328
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] as any`
|
|
329
|
+
)
|
|
330
|
+
pipeline.push(`HttpClientRequest.setUrlParams({ ${props.join(", ")} })`)
|
|
331
|
+
}
|
|
332
|
+
if (operation.headers.length > 0) {
|
|
333
|
+
const props = operation.headers.map(
|
|
334
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] ?? undefined`
|
|
335
|
+
)
|
|
336
|
+
pipeline.push(`HttpClientRequest.setHeaders({ ${props.join(", ")} })`)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (operation.payloadFormData) {
|
|
341
|
+
pipeline.push(`HttpClientRequest.bodyFormData(options.payload as any)`)
|
|
342
|
+
} else if (operation.payload) {
|
|
343
|
+
pipeline.push(`HttpClientRequest.bodyJsonUnsafe(options.payload)`)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
pipeline.push(`sseRequest(${operation.sseSchema})`)
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
`"${operation.id}Sse": (${params}) => ` +
|
|
350
|
+
`HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
|
|
351
|
+
`.pipe(\n ${pipeline.join(",\n ")}\n )`
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const operationToBinaryImpl = (operation: ParsedOperation) => {
|
|
356
|
+
const args: Array<string> = [...operation.pathIds]
|
|
357
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
358
|
+
if (hasOptions || operation.params || operation.payload) {
|
|
359
|
+
args.push("options")
|
|
360
|
+
}
|
|
361
|
+
const params = args.join(", ")
|
|
362
|
+
|
|
363
|
+
const pipeline: Array<string> = []
|
|
364
|
+
|
|
365
|
+
if (operation.params) {
|
|
366
|
+
const paramsAccessor = resolveParamsAccessor(operation, "options", "params")
|
|
367
|
+
if (operation.urlParams.length > 0) {
|
|
368
|
+
const props = operation.urlParams.map(
|
|
369
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] as any`
|
|
370
|
+
)
|
|
371
|
+
pipeline.push(`HttpClientRequest.setUrlParams({ ${props.join(", ")} })`)
|
|
372
|
+
}
|
|
373
|
+
if (operation.headers.length > 0) {
|
|
374
|
+
const props = operation.headers.map(
|
|
375
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] ?? undefined`
|
|
376
|
+
)
|
|
377
|
+
pipeline.push(`HttpClientRequest.setHeaders({ ${props.join(", ")} })`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (operation.payloadFormData) {
|
|
382
|
+
pipeline.push(`HttpClientRequest.bodyFormData(options.payload as any)`)
|
|
383
|
+
} else if (operation.payload) {
|
|
384
|
+
pipeline.push(`HttpClientRequest.bodyJsonUnsafe(options.payload)`)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
pipeline.push(`binaryRequest`)
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
`"${operation.id}Stream": (${params}) => ` +
|
|
391
|
+
`HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
|
|
392
|
+
`.pipe(\n ${pipeline.join(",\n ")}\n )`
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return OpenApiTransformer.of({
|
|
397
|
+
imports: (importName, operations) => {
|
|
398
|
+
const requirements = computeImportRequirements(operations)
|
|
399
|
+
const imports = [
|
|
400
|
+
`import * as Data from "effect/Data"`,
|
|
401
|
+
`import * as Effect from "effect/Effect"`,
|
|
402
|
+
`import type { SchemaError } from "effect/Schema"`,
|
|
403
|
+
`import * as ${importName} from "effect/Schema"`
|
|
404
|
+
]
|
|
405
|
+
if (requiresStreaming(requirements)) {
|
|
406
|
+
imports.push(`import * as Stream from "effect/Stream"`)
|
|
407
|
+
}
|
|
408
|
+
if (requirements.eventStream) {
|
|
409
|
+
imports.push(`import * as Sse from "effect/unstable/encoding/Sse"`)
|
|
410
|
+
}
|
|
411
|
+
// HttpClient needs to be a value import when streaming is used (for filterStatusOk)
|
|
412
|
+
if (requiresStreaming(requirements)) {
|
|
413
|
+
imports.push(`import * as HttpClient from "effect/unstable/http/HttpClient"`)
|
|
414
|
+
} else {
|
|
415
|
+
imports.push(`import type * as HttpClient from "effect/unstable/http/HttpClient"`)
|
|
416
|
+
}
|
|
417
|
+
imports.push(
|
|
418
|
+
`import * as HttpClientError from "effect/unstable/http/HttpClientError"`,
|
|
419
|
+
`import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"`,
|
|
420
|
+
`import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"`
|
|
421
|
+
)
|
|
422
|
+
return imports.join("\n")
|
|
423
|
+
},
|
|
424
|
+
toTypes: operationsToInterface,
|
|
425
|
+
toImplementation: operationsToImpl
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export const layerTransformerSchema = Layer.sync(
|
|
430
|
+
OpenApiTransformer,
|
|
431
|
+
makeTransformerSchema
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
export const makeTransformerTs = () => {
|
|
435
|
+
const operationsToInterface = (
|
|
436
|
+
_importName: string,
|
|
437
|
+
name: string,
|
|
438
|
+
operations: ReadonlyArray<ParsedOperation>
|
|
439
|
+
) => {
|
|
440
|
+
const methods: Array<string> = []
|
|
441
|
+
for (const op of operations) {
|
|
442
|
+
methods.push(operationToMethod(name, op))
|
|
443
|
+
if (op.sseSchema) {
|
|
444
|
+
methods.push(operationToSseMethod(op))
|
|
445
|
+
}
|
|
446
|
+
if (op.binaryResponse) {
|
|
447
|
+
methods.push(operationToBinaryMethod(op))
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return `export interface ${name} {
|
|
451
|
+
readonly httpClient: HttpClient.HttpClient
|
|
452
|
+
${methods.join("\n ")}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
${clientErrorSource(name)}`
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const operationToMethod = (name: string, operation: ParsedOperation) => {
|
|
459
|
+
const args: Array<string> = []
|
|
460
|
+
if (operation.pathIds.length > 0) {
|
|
461
|
+
Utils.spreadElementsInto(operation.pathIds.map((id) => `${id}: string`), args)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const options: Array<string> = []
|
|
465
|
+
if (operation.params) {
|
|
466
|
+
const key = `readonly params${operation.paramsOptional ? "?" : ""}`
|
|
467
|
+
const type = `${operation.params}${operation.paramsOptional ? " | undefined" : ""}`
|
|
468
|
+
options.push(`${key}: ${type}`)
|
|
469
|
+
}
|
|
470
|
+
if (operation.payload) {
|
|
471
|
+
options.push(`readonly payload: ${operation.payload}`)
|
|
472
|
+
}
|
|
473
|
+
options.push("readonly config?: Config | undefined")
|
|
474
|
+
|
|
475
|
+
// If all options are optional, the argument itself should be optional
|
|
476
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
477
|
+
if (hasOptions) {
|
|
478
|
+
args.push(`options: { ${options.join("; ")} }`)
|
|
479
|
+
} else {
|
|
480
|
+
args.push(`options: { ${options.join("; ")} } | undefined`)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let success = "void"
|
|
484
|
+
if (operation.successSchemas.size > 0) {
|
|
485
|
+
success = Array.from(operation.successSchemas.values()).join(" | ")
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const errors = ["HttpClientError.HttpClientError"]
|
|
489
|
+
if (operation.errorSchemas.size > 0) {
|
|
490
|
+
for (const schema of operation.errorSchemas.values()) {
|
|
491
|
+
errors.push(`${name}Error<"${schema}", ${schema}>`)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const jsdoc = Utils.toComment(operation.description)
|
|
496
|
+
const methodKey = `readonly "${operation.id}"`
|
|
497
|
+
const generic = `<Config extends OperationConfig>`
|
|
498
|
+
const parameters = args.join(", ")
|
|
499
|
+
const returnType = `Effect.Effect<WithOptionalResponse<${success}, Config>, ${errors.join(" | ")}>`
|
|
500
|
+
return `${jsdoc}${methodKey}: ${generic}(${parameters}) => ${returnType}`
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const operationToSseMethod = (operation: ParsedOperation) => {
|
|
504
|
+
const args: Array<string> = []
|
|
505
|
+
if (operation.pathIds.length > 0) {
|
|
506
|
+
Utils.spreadElementsInto(operation.pathIds.map((id) => `${id}: string`), args)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const options: Array<string> = []
|
|
510
|
+
if (operation.params) {
|
|
511
|
+
const key = `readonly params${operation.paramsOptional ? "?" : ""}`
|
|
512
|
+
const type = `${operation.params}${operation.paramsOptional ? " | undefined" : ""}`
|
|
513
|
+
options.push(`${key}: ${type}`)
|
|
514
|
+
}
|
|
515
|
+
if (operation.payload) {
|
|
516
|
+
options.push(`readonly payload: ${operation.payload}`)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
520
|
+
if (hasOptions) {
|
|
521
|
+
args.push(`options: { ${options.join("; ")} }`)
|
|
522
|
+
} else if (options.length > 0) {
|
|
523
|
+
args.push(`options: { ${options.join("; ")} } | undefined`)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const jsdoc = Utils.toComment(operation.description)
|
|
527
|
+
const methodKey = `readonly "${operation.id}Sse"`
|
|
528
|
+
const parameters = args.join(", ")
|
|
529
|
+
const returnType = `Stream.Stream<${operation.sseSchema}, HttpClientError.HttpClientError>`
|
|
530
|
+
return `${jsdoc}${methodKey}: (${parameters}) => ${returnType}`
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const operationToBinaryMethod = (operation: ParsedOperation) => {
|
|
534
|
+
const args: Array<string> = []
|
|
535
|
+
if (operation.pathIds.length > 0) {
|
|
536
|
+
Utils.spreadElementsInto(operation.pathIds.map((id) => `${id}: string`), args)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const options: Array<string> = []
|
|
540
|
+
if (operation.params) {
|
|
541
|
+
const key = `readonly params${operation.paramsOptional ? "?" : ""}`
|
|
542
|
+
const type = `${operation.params}${operation.paramsOptional ? " | undefined" : ""}`
|
|
543
|
+
options.push(`${key}: ${type}`)
|
|
544
|
+
}
|
|
545
|
+
if (operation.payload) {
|
|
546
|
+
options.push(`readonly payload: ${operation.payload}`)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
550
|
+
if (hasOptions) {
|
|
551
|
+
args.push(`options: { ${options.join("; ")} }`)
|
|
552
|
+
} else if (options.length > 0) {
|
|
553
|
+
args.push(`options: { ${options.join("; ")} } | undefined`)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const jsdoc = Utils.toComment(operation.description)
|
|
557
|
+
const methodKey = `readonly "${operation.id}Stream"`
|
|
558
|
+
const parameters = args.join(", ")
|
|
559
|
+
const returnType = `Stream.Stream<Uint8Array, HttpClientError.HttpClientError>`
|
|
560
|
+
return `${jsdoc}${methodKey}: (${parameters}) => ${returnType}`
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const operationsToImpl = (
|
|
564
|
+
_importName: string,
|
|
565
|
+
name: string,
|
|
566
|
+
operations: ReadonlyArray<ParsedOperation>
|
|
567
|
+
) => {
|
|
568
|
+
const requirements = computeImportRequirements(operations)
|
|
569
|
+
const implMethods: Array<string> = []
|
|
570
|
+
for (const op of operations) {
|
|
571
|
+
implMethods.push(operationToImpl(op))
|
|
572
|
+
if (op.sseSchema) {
|
|
573
|
+
implMethods.push(operationToSseImpl(op))
|
|
574
|
+
}
|
|
575
|
+
if (op.binaryResponse) {
|
|
576
|
+
implMethods.push(operationToBinaryImpl(op))
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const helpers: Array<string> = [commonSource]
|
|
581
|
+
if (requirements.eventStream) {
|
|
582
|
+
helpers.push(sseRequestSourceTs)
|
|
583
|
+
}
|
|
584
|
+
if (requirements.octetStream) {
|
|
585
|
+
helpers.push(binaryRequestSourceTs)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return `export interface OperationConfig {
|
|
589
|
+
/**
|
|
590
|
+
* Whether or not the response should be included in the value returned from
|
|
591
|
+
* an operation.
|
|
592
|
+
*
|
|
593
|
+
* If set to \`true\`, a tuple of \`[A, HttpClientResponse]\` will be returned,
|
|
594
|
+
* where \`A\` is the success type of the operation.
|
|
595
|
+
*
|
|
596
|
+
* If set to \`false\`, only the success type of the operation will be returned.
|
|
597
|
+
*/
|
|
598
|
+
readonly includeResponse?: boolean | undefined
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* A utility type which optionally includes the response in the return result
|
|
603
|
+
* of an operation based upon the value of the \`includeResponse\` configuration
|
|
604
|
+
* option.
|
|
605
|
+
*/
|
|
606
|
+
export type WithOptionalResponse<A, Config extends OperationConfig> = Config extends {
|
|
607
|
+
readonly includeResponse: true
|
|
608
|
+
} ? [A, HttpClientResponse.HttpClientResponse] : A
|
|
609
|
+
|
|
610
|
+
export const make = (
|
|
611
|
+
httpClient: HttpClient.HttpClient,
|
|
612
|
+
options: {
|
|
613
|
+
readonly transformClient?: ((client: HttpClient.HttpClient) => Effect.Effect<HttpClient.HttpClient>) | undefined
|
|
614
|
+
} = {}
|
|
615
|
+
): ${name} => {
|
|
616
|
+
${helpers.join("\n ")}
|
|
617
|
+
const decodeSuccess = <A>(response: HttpClientResponse.HttpClientResponse) =>
|
|
618
|
+
response.json as Effect.Effect<A, HttpClientError.HttpClientError>
|
|
619
|
+
const decodeVoid = (_response: HttpClientResponse.HttpClientResponse) =>
|
|
620
|
+
Effect.void
|
|
621
|
+
const decodeError =
|
|
622
|
+
<Tag extends string, E>(tag: Tag) =>
|
|
623
|
+
(
|
|
624
|
+
response: HttpClientResponse.HttpClientResponse,
|
|
625
|
+
): Effect.Effect<
|
|
626
|
+
never,
|
|
627
|
+
${name}Error<Tag, E> | HttpClientError.HttpClientError
|
|
628
|
+
> =>
|
|
629
|
+
Effect.flatMap(
|
|
630
|
+
response.json as Effect.Effect<E, HttpClientError.HttpClientError>,
|
|
631
|
+
(cause) => Effect.fail(${name}Error(tag, cause, response)),
|
|
632
|
+
)
|
|
633
|
+
const onRequest = <Config extends OperationConfig>(config: Config | undefined) => (
|
|
634
|
+
successCodes: ReadonlyArray<string>,
|
|
635
|
+
errorCodes?: Record<string, string>,
|
|
636
|
+
) => {
|
|
637
|
+
const cases: any = { orElse: unexpectedStatus }
|
|
638
|
+
for (const code of successCodes) {
|
|
639
|
+
cases[code] = decodeSuccess
|
|
640
|
+
}
|
|
641
|
+
if (errorCodes) {
|
|
642
|
+
for (const [code, tag] of Object.entries(errorCodes)) {
|
|
643
|
+
cases[code] = decodeError(tag)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (successCodes.length === 0) {
|
|
647
|
+
cases["2xx"] = decodeVoid
|
|
648
|
+
}
|
|
649
|
+
return withResponse(config)(HttpClientResponse.matchStatus(cases) as any)
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
httpClient,
|
|
653
|
+
${implMethods.join(",\n ")}
|
|
654
|
+
}
|
|
655
|
+
}`
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const operationToImpl = (operation: ParsedOperation) => {
|
|
659
|
+
const args: Array<string> = [...operation.pathIds, "options"]
|
|
660
|
+
const params = `${args.join(", ")}`
|
|
661
|
+
|
|
662
|
+
const pipeline: Array<string> = []
|
|
663
|
+
|
|
664
|
+
if (operation.params) {
|
|
665
|
+
const paramsAccessor = resolveParamsAccessor(operation, "options", "params")
|
|
666
|
+
|
|
667
|
+
if (operation.urlParams.length > 0) {
|
|
668
|
+
const props = operation.urlParams.map(
|
|
669
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] as any`
|
|
670
|
+
)
|
|
671
|
+
pipeline.push(`HttpClientRequest.setUrlParams({ ${props.join(", ")} })`)
|
|
672
|
+
}
|
|
673
|
+
if (operation.headers.length > 0) {
|
|
674
|
+
const props = operation.headers.map(
|
|
675
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] ?? undefined`
|
|
676
|
+
)
|
|
677
|
+
pipeline.push(`HttpClientRequest.setHeaders({ ${props.join(", ")} })`)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const payloadAccessor = "options.payload"
|
|
682
|
+
if (operation.payloadFormData) {
|
|
683
|
+
pipeline.push(`HttpClientRequest.bodyFormDataRecord(${payloadAccessor} as any)`)
|
|
684
|
+
} else if (operation.payload) {
|
|
685
|
+
pipeline.push(`HttpClientRequest.bodyJsonUnsafe(${payloadAccessor})`)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const successCodesRaw = Array.from(operation.successSchemas.keys())
|
|
689
|
+
const successCodes = successCodesRaw
|
|
690
|
+
.map((_) => JSON.stringify(_))
|
|
691
|
+
.join(", ")
|
|
692
|
+
const singleSuccessCode = successCodesRaw.length === 1 && successCodesRaw[0].startsWith("2")
|
|
693
|
+
const errorCodes = operation.errorSchemas.size > 0 &&
|
|
694
|
+
Object.fromEntries(operation.errorSchemas.entries())
|
|
695
|
+
const configAccessor = resolveConfigAccessor(operation, "options", "config")
|
|
696
|
+
pipeline.push(
|
|
697
|
+
`onRequest(${configAccessor})([${singleSuccessCode ? `"2xx"` : successCodes}]${
|
|
698
|
+
errorCodes ? `, ${JSON.stringify(errorCodes)}` : ""
|
|
699
|
+
})`
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
return (
|
|
703
|
+
`"${operation.id}": (${params}) => ` +
|
|
704
|
+
`HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
|
|
705
|
+
`.pipe(\n ${pipeline.join(",\n ")}\n )`
|
|
706
|
+
)
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const operationToSseImpl = (operation: ParsedOperation) => {
|
|
710
|
+
const args: Array<string> = [...operation.pathIds]
|
|
711
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
712
|
+
if (hasOptions || operation.params || operation.payload) {
|
|
713
|
+
args.push("options")
|
|
714
|
+
}
|
|
715
|
+
const params = args.join(", ")
|
|
716
|
+
|
|
717
|
+
const pipeline: Array<string> = []
|
|
718
|
+
|
|
719
|
+
if (operation.params) {
|
|
720
|
+
const paramsAccessor = resolveParamsAccessor(operation, "options", "params")
|
|
721
|
+
if (operation.urlParams.length > 0) {
|
|
722
|
+
const props = operation.urlParams.map(
|
|
723
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] as any`
|
|
724
|
+
)
|
|
725
|
+
pipeline.push(`HttpClientRequest.setUrlParams({ ${props.join(", ")} })`)
|
|
726
|
+
}
|
|
727
|
+
if (operation.headers.length > 0) {
|
|
728
|
+
const props = operation.headers.map(
|
|
729
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] ?? undefined`
|
|
730
|
+
)
|
|
731
|
+
pipeline.push(`HttpClientRequest.setHeaders({ ${props.join(", ")} })`)
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (operation.payloadFormData) {
|
|
736
|
+
pipeline.push(`HttpClientRequest.bodyFormDataRecord(options.payload as any)`)
|
|
737
|
+
} else if (operation.payload) {
|
|
738
|
+
pipeline.push(`HttpClientRequest.bodyJsonUnsafe(options.payload)`)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
pipeline.push(`sseRequest`)
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
`"${operation.id}Sse": (${params}) => ` +
|
|
745
|
+
`HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
|
|
746
|
+
`.pipe(\n ${pipeline.join(",\n ")}\n )`
|
|
747
|
+
)
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const operationToBinaryImpl = (operation: ParsedOperation) => {
|
|
751
|
+
const args: Array<string> = [...operation.pathIds]
|
|
752
|
+
const hasOptions = (operation.params && !operation.paramsOptional) || operation.payload
|
|
753
|
+
if (hasOptions || operation.params || operation.payload) {
|
|
754
|
+
args.push("options")
|
|
755
|
+
}
|
|
756
|
+
const params = args.join(", ")
|
|
757
|
+
|
|
758
|
+
const pipeline: Array<string> = []
|
|
759
|
+
|
|
760
|
+
if (operation.params) {
|
|
761
|
+
const paramsAccessor = resolveParamsAccessor(operation, "options", "params")
|
|
762
|
+
if (operation.urlParams.length > 0) {
|
|
763
|
+
const props = operation.urlParams.map(
|
|
764
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] as any`
|
|
765
|
+
)
|
|
766
|
+
pipeline.push(`HttpClientRequest.setUrlParams({ ${props.join(", ")} })`)
|
|
767
|
+
}
|
|
768
|
+
if (operation.headers.length > 0) {
|
|
769
|
+
const props = operation.headers.map(
|
|
770
|
+
(param) => `"${param}": ${paramsAccessor}["${param}"] ?? undefined`
|
|
771
|
+
)
|
|
772
|
+
pipeline.push(`HttpClientRequest.setHeaders({ ${props.join(", ")} })`)
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (operation.payloadFormData) {
|
|
777
|
+
pipeline.push(`HttpClientRequest.bodyFormDataRecord(options.payload as any)`)
|
|
778
|
+
} else if (operation.payload) {
|
|
779
|
+
pipeline.push(`HttpClientRequest.bodyJsonUnsafe(options.payload)`)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
pipeline.push(`binaryRequest`)
|
|
783
|
+
|
|
784
|
+
return (
|
|
785
|
+
`"${operation.id}Stream": (${params}) => ` +
|
|
786
|
+
`HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
|
|
787
|
+
`.pipe(\n ${pipeline.join(",\n ")}\n )`
|
|
788
|
+
)
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return OpenApiTransformer.of({
|
|
792
|
+
imports: (_importName, operations) => {
|
|
793
|
+
const requirements = computeImportRequirements(operations)
|
|
794
|
+
const imports = [
|
|
795
|
+
`import * as Data from "effect/Data"`,
|
|
796
|
+
`import * as Effect from "effect/Effect"`
|
|
797
|
+
]
|
|
798
|
+
if (requiresStreaming(requirements)) {
|
|
799
|
+
imports.push(`import * as Stream from "effect/Stream"`)
|
|
800
|
+
}
|
|
801
|
+
imports.push(
|
|
802
|
+
`import type * as HttpClient from "effect/unstable/http/HttpClient"`,
|
|
803
|
+
`import * as HttpClientError from "effect/unstable/http/HttpClientError"`,
|
|
804
|
+
`import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"`,
|
|
805
|
+
`import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"`
|
|
806
|
+
)
|
|
807
|
+
return imports.join("\n")
|
|
808
|
+
},
|
|
809
|
+
toTypes: operationsToInterface,
|
|
810
|
+
toImplementation: operationsToImpl
|
|
811
|
+
})
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export const layerTransformerTs = Layer.sync(
|
|
815
|
+
OpenApiTransformer,
|
|
816
|
+
makeTransformerTs
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
const commonSource = `const unexpectedStatus = (response: HttpClientResponse.HttpClientResponse) =>
|
|
820
|
+
Effect.flatMap(
|
|
821
|
+
Effect.orElseSucceed(response.json, () => "Unexpected status code"),
|
|
822
|
+
(description) =>
|
|
823
|
+
Effect.fail(
|
|
824
|
+
new HttpClientError.HttpClientError({
|
|
825
|
+
reason: new HttpClientError.StatusCodeError({
|
|
826
|
+
request: response.request,
|
|
827
|
+
response,
|
|
828
|
+
description: typeof description === "string" ? description : JSON.stringify(description),
|
|
829
|
+
}),
|
|
830
|
+
}),
|
|
831
|
+
),
|
|
832
|
+
)
|
|
833
|
+
const withResponse = <Config extends OperationConfig>(config: Config | undefined) => (
|
|
834
|
+
f: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<any, any>,
|
|
835
|
+
): (request: HttpClientRequest.HttpClientRequest) => Effect.Effect<any, any> => {
|
|
836
|
+
const withOptionalResponse = (
|
|
837
|
+
config?.includeResponse
|
|
838
|
+
? (response: HttpClientResponse.HttpClientResponse) => Effect.map(f(response), (a) => [a, response])
|
|
839
|
+
: (response: HttpClientResponse.HttpClientResponse) => f(response)
|
|
840
|
+
) as any
|
|
841
|
+
return options?.transformClient
|
|
842
|
+
? (request) =>
|
|
843
|
+
Effect.flatMap(
|
|
844
|
+
Effect.flatMap(options.transformClient!(httpClient), (client) => client.execute(request)),
|
|
845
|
+
withOptionalResponse
|
|
846
|
+
)
|
|
847
|
+
: (request) => Effect.flatMap(httpClient.execute(request), withOptionalResponse)
|
|
848
|
+
}`
|
|
849
|
+
|
|
850
|
+
const sseRequestSource = (_importName: string) =>
|
|
851
|
+
`const sseRequest = <
|
|
852
|
+
Type,
|
|
853
|
+
DecodingServices
|
|
854
|
+
>(
|
|
855
|
+
schema: Schema.Decoder<Type, DecodingServices>
|
|
856
|
+
) =>
|
|
857
|
+
(
|
|
858
|
+
request: HttpClientRequest.HttpClientRequest
|
|
859
|
+
): Stream.Stream<
|
|
860
|
+
{ readonly event: string; readonly id: string | undefined; readonly data: Type },
|
|
861
|
+
HttpClientError.HttpClientError | SchemaError | Sse.Retry,
|
|
862
|
+
DecodingServices
|
|
863
|
+
> =>
|
|
864
|
+
HttpClient.filterStatusOk(httpClient).execute(request).pipe(
|
|
865
|
+
Effect.map((response) => response.stream),
|
|
866
|
+
Stream.unwrap,
|
|
867
|
+
Stream.decodeText(),
|
|
868
|
+
Stream.pipeThroughChannel(Sse.decodeDataSchema(schema))
|
|
869
|
+
)`
|
|
870
|
+
|
|
871
|
+
const binaryRequestSource =
|
|
872
|
+
`const binaryRequest = (request: HttpClientRequest.HttpClientRequest): Stream.Stream<Uint8Array, HttpClientError.HttpClientError> =>
|
|
873
|
+
HttpClient.filterStatusOk(httpClient).execute(request).pipe(
|
|
874
|
+
Effect.map((response) => response.stream),
|
|
875
|
+
Stream.unwrap
|
|
876
|
+
)`
|
|
877
|
+
|
|
878
|
+
// Type-only mode helpers (no schema decoding)
|
|
879
|
+
const sseRequestSourceTs =
|
|
880
|
+
`const sseRequest = (request: HttpClientRequest.HttpClientRequest): Stream.Stream<unknown, HttpClientError.HttpClientError> =>
|
|
881
|
+
HttpClient.filterStatusOk(httpClient).execute(request).pipe(
|
|
882
|
+
Effect.map((response) => response.stream),
|
|
883
|
+
Stream.unwrap,
|
|
884
|
+
Stream.decodeText(),
|
|
885
|
+
Stream.splitLines,
|
|
886
|
+
Stream.filter((line) => line.startsWith("data: ")),
|
|
887
|
+
Stream.map((line) => JSON.parse(line.slice(6)))
|
|
888
|
+
)`
|
|
889
|
+
|
|
890
|
+
const binaryRequestSourceTs =
|
|
891
|
+
`const binaryRequest = (request: HttpClientRequest.HttpClientRequest): Stream.Stream<Uint8Array, HttpClientError.HttpClientError> =>
|
|
892
|
+
HttpClient.filterStatusOk(httpClient).execute(request).pipe(
|
|
893
|
+
Effect.map((response) => response.stream),
|
|
894
|
+
Stream.unwrap
|
|
895
|
+
)`
|
|
896
|
+
|
|
897
|
+
const clientErrorSource = (
|
|
898
|
+
name: string
|
|
899
|
+
) =>
|
|
900
|
+
`export interface ${name}Error<Tag extends string, E> {
|
|
901
|
+
readonly _tag: Tag
|
|
902
|
+
readonly request: HttpClientRequest.HttpClientRequest
|
|
903
|
+
readonly response: HttpClientResponse.HttpClientResponse
|
|
904
|
+
readonly cause: E
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
class ${name}ErrorImpl extends Data.Error<{
|
|
908
|
+
_tag: string
|
|
909
|
+
cause: any
|
|
910
|
+
request: HttpClientRequest.HttpClientRequest
|
|
911
|
+
response: HttpClientResponse.HttpClientResponse
|
|
912
|
+
}> {}
|
|
913
|
+
|
|
914
|
+
export const ${name}Error = <Tag extends string, E>(
|
|
915
|
+
tag: Tag,
|
|
916
|
+
cause: E,
|
|
917
|
+
response: HttpClientResponse.HttpClientResponse,
|
|
918
|
+
): ${name}Error<Tag, E> =>
|
|
919
|
+
new ${name}ErrorImpl({
|
|
920
|
+
_tag: tag,
|
|
921
|
+
cause,
|
|
922
|
+
response,
|
|
923
|
+
request: response.request,
|
|
924
|
+
}) as any`
|
|
925
|
+
|
|
926
|
+
const resolveConfigAccessor = (operation: ParsedOperation, rootKey: string, configKey: string): string => {
|
|
927
|
+
// If an operation payload is defined, then the root object must exist
|
|
928
|
+
if (Predicate.isNotUndefined(operation.payload)) {
|
|
929
|
+
return `${rootKey}.${configKey}`
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// If operation parameters are defined and non-optional, then the root object must exist
|
|
933
|
+
if (Predicate.isNotUndefined(operation.params) && !operation.paramsOptional) {
|
|
934
|
+
return `${rootKey}.${configKey}`
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// User-specified arguments are allowed but are not required, so the root object is optional
|
|
938
|
+
return `${rootKey}?.${configKey}`
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const resolveParamsAccessor = (operation: ParsedOperation, rootKey: string, paramsKey: string): string => {
|
|
942
|
+
// If an operation payload is not defined and parameters are optional, then the
|
|
943
|
+
// root object may or may not exist and parameters must be marked as optional
|
|
944
|
+
if (Predicate.isUndefined(operation.payload) && operation.paramsOptional) {
|
|
945
|
+
return `${rootKey}?.${paramsKey}?.`
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// If parameters are optional, they must be marked as optional
|
|
949
|
+
if (operation.paramsOptional) {
|
|
950
|
+
return `${rootKey}.${paramsKey}?.`
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return `${rootKey}.${paramsKey}`
|
|
954
|
+
}
|