@@ -70,29 +70,29 @@ export const generateFile = (options: GeneratorOptions) => {
70
70
ctx . runtime === "none"
71
71
? ( file : string ) => file
72
72
: ( file : string ) => {
73
- const model = Codegen . TypeScriptToModel . Generate ( file ) ;
74
- const transformer = runtimeValidationGenerator [ ctx . runtime as Exclude < typeof ctx . runtime , "none" > ] ;
75
- // tmp fix for typebox, there's currently a "// todo" only with Codegen.ModelToTypeBox.Generate
76
- // https://github.com/sinclairzx81/typebox-codegen/blob/44d44d55932371b69f349331b1c8a60f5d760d9e/src/model/model-to-typebox.ts#L31
77
- const generated = ctx . runtime === "typebox" ? Codegen . TypeScriptToTypeBox . Generate ( file ) : transformer ( model ) ;
78
-
79
- let converted = "" ;
80
- const match = generated . match ( / ( c o n s t _ _ E N D P O I N T S _ S T A R T _ _ = ) ( [ \s \S ] * ?) ( e x p o r t t y p e _ _ E N D P O I N T S _ E N D _ _ ) / ) ;
81
- const content = match ?. [ 2 ] ;
82
-
83
- if ( content && ctx . runtime in replacerByRuntime ) {
84
- const before = generated . slice ( 0 , generated . indexOf ( "export type __ENDPOINTS_START" ) ) ;
85
- converted =
86
- before +
87
- replacerByRuntime [ ctx . runtime as keyof typeof replacerByRuntime ] (
88
- content . slice ( content . indexOf ( "export" ) ) ,
89
- ) ;
90
- } else {
91
- converted = generated ;
92
- }
73
+ const model = Codegen . TypeScriptToModel . Generate ( file ) ;
74
+ const transformer = runtimeValidationGenerator [ ctx . runtime as Exclude < typeof ctx . runtime , "none" > ] ;
75
+ // tmp fix for typebox, there's currently a "// todo" only with Codegen.ModelToTypeBox.Generate
76
+ // https://github.com/sinclairzx81/typebox-codegen/blob/44d44d55932371b69f349331b1c8a60f5d760d9e/src/model/model-to-typebox.ts#L31
77
+ const generated = ctx . runtime === "typebox" ? Codegen . TypeScriptToTypeBox . Generate ( file ) : transformer ( model ) ;
78
+
79
+ let converted = "" ;
80
+ const match = generated . match ( / ( c o n s t _ _ E N D P O I N T S _ S T A R T _ _ = ) ( [ \s \S ] * ?) ( e x p o r t t y p e _ _ E N D P O I N T S _ E N D _ _ ) / ) ;
81
+ const content = match ?. [ 2 ] ;
82
+
83
+ if ( content && ctx . runtime in replacerByRuntime ) {
84
+ const before = generated . slice ( 0 , generated . indexOf ( "export type __ENDPOINTS_START" ) ) ;
85
+ converted =
86
+ before +
87
+ replacerByRuntime [ ctx . runtime as keyof typeof replacerByRuntime ] (
88
+ content . slice ( content . indexOf ( "export" ) ) ,
89
+ ) ;
90
+ } else {
91
+ converted = generated ;
92
+ }
93
93
94
- return converted ;
95
- } ;
94
+ return converted ;
95
+ } ;
96
96
97
97
const file = `
98
98
${ transform ( schemaList + endpointSchemaList ) }
@@ -132,6 +132,24 @@ const parameterObjectToString = (parameters: Box<AnyBoxDef> | Record<string, Any
132
132
}
133
133
return str + "}" ;
134
134
} ;
135
+
136
+ const responseHeadersObjectToString = ( responseHeaders : Record < string , AnyBox > , ctx : GeneratorContext ) => {
137
+ let str = "{" ;
138
+ for ( const [ key , responseHeader ] of Object . entries ( responseHeaders ) ) {
139
+ const value = ctx . runtime === "none"
140
+ ? responseHeader . recompute ( ( box ) => {
141
+ if ( Box . isReference ( box ) && ! box . params . generics && box . value !== "null" ) {
142
+ box . value = `Schemas.${ box . value } ` ;
143
+ }
144
+
145
+ return box ;
146
+ } ) . value
147
+ : responseHeader . value
148
+ str += `${ wrapWithQuotesIfNeeded ( key . toLowerCase ( ) ) } : ${ value } ,\n` ;
149
+ }
150
+ return str + "}" ;
151
+ }
152
+
135
153
const generateEndpointSchemaList = ( ctx : GeneratorContext ) => {
136
154
let file = `
137
155
${ ctx . runtime === "none" ? "export namespace Endpoints {" : "" }
@@ -145,39 +163,42 @@ const generateEndpointSchemaList = (ctx: GeneratorContext) => {
145
163
path: "${ endpoint . path } ",
146
164
requestFormat: "${ endpoint . requestFormat } ",
147
165
${
148
- endpoint . meta . hasParameters
149
- ? `parameters: {
166
+ endpoint . meta . hasParameters
167
+ ? `parameters: {
150
168
${ parameters . query ? `query: ${ parameterObjectToString ( parameters . query ) } ,` : "" }
151
169
${ parameters . path ? `path: ${ parameterObjectToString ( parameters . path ) } ,` : "" }
152
170
${ parameters . header ? `header: ${ parameterObjectToString ( parameters . header ) } ,` : "" }
153
171
${
154
172
parameters . body
155
173
? `body: ${ parameterObjectToString (
156
- ctx . runtime === "none"
157
- ? parameters . body . recompute ( ( box ) => {
158
- if ( Box . isReference ( box ) && ! box . params . generics ) {
159
- box . value = `Schemas.${ box . value } ` ;
160
- }
161
- return box ;
162
- } )
163
- : parameters . body ,
164
- ) } ,`
174
+ ctx . runtime === "none"
175
+ ? parameters . body . recompute ( ( box ) => {
176
+ if ( Box . isReference ( box ) && ! box . params . generics ) {
177
+ box . value = `Schemas.${ box . value } ` ;
178
+ }
179
+ return box ;
180
+ } )
181
+ : parameters . body ,
182
+ ) } ,`
165
183
: ""
166
184
}
167
185
}`
168
- : "parameters: never,"
169
- }
186
+ : "parameters: never,"
187
+ }
170
188
response: ${
171
- ctx . runtime === "none"
172
- ? endpoint . response . recompute ( ( box ) => {
173
- if ( Box . isReference ( box ) && ! box . params . generics && box . value !== "null" ) {
174
- box . value = `Schemas.${ box . value } ` ;
175
- }
176
-
177
- return box ;
178
- } ) . value
179
- : endpoint . response . value
180
- } ,
189
+ ctx . runtime === "none"
190
+ ? endpoint . response . recompute ( ( box ) => {
191
+ if ( Box . isReference ( box ) && ! box . params . generics && box . value !== "null" ) {
192
+ box . value = `Schemas.${ box . value } ` ;
193
+ }
194
+
195
+ return box ;
196
+ } ) . value
197
+ : endpoint . response . value
198
+ } ,
199
+ ${
200
+ endpoint . responseHeaders ? `responseHeaders: ${ responseHeadersObjectToString ( endpoint . responseHeaders , ctx ) } ,` : ""
201
+ }
181
202
}\n` ;
182
203
} ) ;
183
204
@@ -199,14 +220,14 @@ const generateEndpointByMethod = (ctx: GeneratorContext) => {
199
220
// <EndpointByMethod>
200
221
export ${ ctx . runtime === "none" ? "type" : "const" } EndpointByMethod = {
201
222
${ Object . entries ( byMethods )
202
- . map ( ( [ method , list ] ) => {
203
- return `${ method } : {
223
+ . map ( ( [ method , list ] ) => {
224
+ return `${ method } : {
204
225
${ list . map (
205
- ( endpoint ) => `"${ endpoint . path } ": ${ ctx . runtime === "none" ? "Endpoints." : "" } ${ endpoint . meta . alias } ` ,
206
- ) }
226
+ ( endpoint ) => `"${ endpoint . path } ": ${ ctx . runtime === "none" ? "Endpoints." : "" } ${ endpoint . meta . alias } ` ,
227
+ ) }
207
228
}` ;
208
- } )
209
- . join ( ",\n" ) }
229
+ } )
230
+ . join ( ",\n" ) }
210
231
}
211
232
${ ctx . runtime === "none" ? "" : "export type EndpointByMethod = typeof EndpointByMethod;" }
212
233
// </EndpointByMethod>
@@ -216,8 +237,8 @@ const generateEndpointByMethod = (ctx: GeneratorContext) => {
216
237
217
238
// <EndpointByMethod.Shorthands>
218
239
${ Object . keys ( byMethods )
219
- . map ( ( method ) => `export type ${ capitalize ( method ) } Endpoints = EndpointByMethod["${ method } "]` )
220
- . join ( "\n" ) }
240
+ . map ( ( method ) => `export type ${ capitalize ( method ) } Endpoints = EndpointByMethod["${ method } "]` )
241
+ . join ( "\n" ) }
221
242
// </EndpointByMethod.Shorthands>
222
243
` ;
223
244
@@ -246,6 +267,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
246
267
export type DefaultEndpoint = {
247
268
parameters?: EndpointParameters | undefined;
248
269
response: unknown;
270
+ responseHeaders?: Record<string, unknown>;
249
271
};
250
272
251
273
export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
@@ -260,6 +282,7 @@ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
260
282
areParametersRequired: boolean;
261
283
};
262
284
response: TConfig["response"];
285
+ responseHeaders?: TConfig["responseHeaders"]
263
286
};
264
287
265
288
export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
@@ -303,18 +326,18 @@ export class ApiClient {
303
326
${ method } <Path extends keyof ${ capitalizedMethod } Endpoints, TEndpoint extends ${ capitalizedMethod } Endpoints[Path]>(
304
327
path: Path,
305
328
...params: MaybeOptionalArg<${ match ( ctx . runtime )
306
- . with ( "zod" , "yup" , ( ) => infer ( `TEndpoint["parameters"]` ) )
307
- . with ( "arktype" , "io-ts" , "typebox" , "valibot" , ( ) => infer ( `TEndpoint` ) + `["parameters"]` )
308
- . otherwise ( ( ) => `TEndpoint["parameters"]` ) } >
329
+ . with ( "zod" , "yup" , ( ) => infer ( `TEndpoint["parameters"]` ) )
330
+ . with ( "arktype" , "io-ts" , "typebox" , "valibot" , ( ) => infer ( `TEndpoint` ) + `["parameters"]` )
331
+ . otherwise ( ( ) => `TEndpoint["parameters"]` ) } >
309
332
): Promise<${ match ( ctx . runtime )
310
- . with ( "zod" , "yup" , ( ) => infer ( `TEndpoint["response"]` ) )
311
- . with ( "arktype" , "io-ts" , "typebox" , "valibot" , ( ) => infer ( `TEndpoint` ) + `["response"]` )
312
- . otherwise ( ( ) => `TEndpoint["response"]` ) } > {
333
+ . with ( "zod" , "yup" , ( ) => infer ( `TEndpoint["response"]` ) )
334
+ . with ( "arktype" , "io-ts" , "typebox" , "valibot" , ( ) => infer ( `TEndpoint` ) + `["response"]` )
335
+ . otherwise ( ( ) => `TEndpoint["response"]` ) } > {
313
336
return this.fetcher("${ method } ", this.baseUrl + path, params[0])
314
337
.then(response => this.parseResponse(response))${ match ( ctx . runtime )
315
- . with ( "zod" , "yup" , ( ) => `as Promise<${ infer ( `TEndpoint["response"]` ) } >` )
316
- . with ( "arktype" , "io-ts" , "typebox" , "valibot" , ( ) => `as Promise<${ infer ( `TEndpoint` ) + `["response"]` } >` )
317
- . otherwise ( ( ) => `as Promise<TEndpoint["response"]>` ) } ;
338
+ . with ( "zod" , "yup" , ( ) => `as Promise<${ infer ( `TEndpoint["response"]` ) } >` )
339
+ . with ( "arktype" , "io-ts" , "typebox" , "valibot" , ( ) => `as Promise<${ infer ( `TEndpoint` ) + `["response"]` } >` )
340
+ . otherwise ( ( ) => `as Promise<TEndpoint["response"]>` ) } ;
318
341
}
319
342
// </ApiClient.${ method } >
320
343
`
@@ -334,17 +357,17 @@ export class ApiClient {
334
357
method: TMethod,
335
358
path: TPath,
336
359
...params: MaybeOptionalArg<${ match ( ctx . runtime )
337
- . with ( "zod" , "yup" , ( ) =>
338
- inferByRuntime [ ctx . runtime ] ( `TEndpoint extends { parameters: infer Params } ? Params : never` ) ,
339
- )
340
- . with (
341
- "arktype" ,
342
- "io-ts" ,
343
- "typebox" ,
344
- "valibot" ,
345
- ( ) => inferByRuntime [ ctx . runtime ] ( `TEndpoint` ) + `["parameters"]` ,
346
- )
347
- . otherwise ( ( ) => `TEndpoint extends { parameters: infer Params } ? Params : never` ) } >)
360
+ . with ( "zod" , "yup" , ( ) =>
361
+ inferByRuntime [ ctx . runtime ] ( `TEndpoint extends { parameters: infer Params } ? Params : never` ) ,
362
+ )
363
+ . with (
364
+ "arktype" ,
365
+ "io-ts" ,
366
+ "typebox" ,
367
+ "valibot" ,
368
+ ( ) => inferByRuntime [ ctx . runtime ] ( `TEndpoint` ) + `["parameters"]` ,
369
+ )
370
+ . otherwise ( ( ) => `TEndpoint extends { parameters: infer Params } ? Params : never` ) } >)
348
371
: Promise<Omit<Response, "json"> & {
349
372
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */
350
373
json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
0 commit comments