@sdk-it/typescript 0.40.0 → 0.42.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/README.md CHANGED
@@ -50,7 +50,7 @@ await generate(spec, {
50
50
 
51
51
  ### Format Generated Code
52
52
 
53
- You can format the generated code using the `formatCode` option. This is especially useful if you include the generated code in source control.
53
+ You can format the generated code using the `formatCode` option. Useful when committing generated code to source control.
54
54
 
55
55
  ```typescript
56
56
  import { generate } from '@sdk-it/typescript';
@@ -86,7 +86,7 @@ bun ./openapi.ts
86
86
  ```typescript
87
87
  import { OpenStatus } from './client';
88
88
 
89
- const client = new Client({
89
+ const client = new OpenStatus({
90
90
  baseUrl: 'https://api.openstatus.dev/v1/',
91
91
  });
92
92
 
@@ -95,9 +95,9 @@ const [result, error] = await client.request('GET /status_report', {});
95
95
 
96
96
  ## Using with Your Favorite Frameworks
97
97
 
98
- The SDK works great on its own, but you might want to native integration with your frameworks:
98
+ The SDK works on its own, but you might want native integration with your frameworks:
99
99
 
100
100
  - [React Query](../../docs/react-query.md)
101
101
  - [Angular](../../docs/angular.md)
102
102
 
103
- Let us know what are you using, and we will help you integrate it.
103
+ Let us know what you're using, and we'll help you integrate it.
package/dist/index.js CHANGED
@@ -173,7 +173,7 @@ var ZodEmitter = class {
173
173
  normal(type, schema, required = false, nullable = false) {
174
174
  switch (type) {
175
175
  case "string": {
176
- const defaultVal = schema.format === "date" && schema.default ? `new Date(${JSON.stringify(schema.default)})` : JSON.stringify(schema.default);
176
+ const defaultVal = (schema["x-zod-type"] === "date" || schema["x-zod-type"] === "coerce-date") && schema.default ? `new Date(${JSON.stringify(schema.default)})` : JSON.stringify(schema.default);
177
177
  return `${this.string(schema)}${this.#suffixes(defaultVal, required, nullable)}`;
178
178
  }
179
179
  case "number":
@@ -182,7 +182,7 @@ var ZodEmitter = class {
182
182
  return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
183
183
  }
184
184
  case "boolean":
185
- return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
185
+ return `${schema["x-zod-type"] === "coerce-boolean" ? "z.coerce.boolean()" : "z.boolean()"}${this.#suffixes(schema.default, required, nullable)}`;
186
186
  case "object":
187
187
  return `${this.#object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
188
188
  // required always
@@ -253,7 +253,7 @@ var ZodEmitter = class {
253
253
  * Handle a `string` schema with possible format keywords (JSON Schema).
254
254
  */
255
255
  string(schema) {
256
- let base = "z.string()";
256
+ let base = schema["x-zod-type"] === "coerce-string" ? "z.coerce.string()" : "z.string()";
257
257
  if (schema.contentEncoding === "binary") {
258
258
  base = "z.instanceof(Blob)";
259
259
  return base;
@@ -261,39 +261,45 @@ var ZodEmitter = class {
261
261
  switch (schema.format) {
262
262
  case "date-time":
263
263
  case "datetime":
264
- base = "z.string().datetime()";
264
+ if (schema["x-zod-type"] === "coerce-date") {
265
+ base = "z.coerce.date()";
266
+ } else if (schema["x-zod-type"] === "date") {
267
+ base = "z.date()";
268
+ } else {
269
+ base += ".datetime()";
270
+ }
265
271
  break;
266
272
  case "date":
267
- base = "z.coerce.date()";
273
+ base += ".date()";
268
274
  break;
269
275
  case "time":
270
- base = "z.string() /* optionally add .regex(...) for HH:MM:SS format */";
276
+ base += " /* optionally add .regex(...) for HH:MM:SS format */";
271
277
  break;
272
278
  case "email":
273
- base = "z.string().email()";
279
+ base += ".email()";
274
280
  break;
275
281
  case "uuid":
276
- base = "z.string().uuid()";
282
+ base += ".uuid()";
277
283
  break;
278
284
  case "url":
279
285
  case "uri":
280
- base = "z.string().url()";
286
+ base += ".url()";
281
287
  break;
282
288
  case "ipv4":
283
- base = 'z.string().ip({version: "v4"})';
289
+ base += '.ip({version: "v4"})';
284
290
  break;
285
291
  case "ipv6":
286
- base = 'z.string().ip({version: "v6"})';
292
+ base += '.ip({version: "v6"})';
287
293
  break;
288
294
  case "phone":
289
- base = "z.string() /* or add .regex(...) for phone formats */";
295
+ base += " /* or add .regex(...) for phone formats */";
290
296
  break;
291
297
  case "byte":
292
298
  case "binary":
293
299
  base = "z.instanceof(Blob)";
294
300
  break;
295
301
  case "int64":
296
- base = "z.string() /* or z.bigint() if your app can handle it */";
302
+ base += " /* or z.bigint() if your app can handle it */";
297
303
  break;
298
304
  default:
299
305
  break;
@@ -307,14 +313,16 @@ var ZodEmitter = class {
307
313
  */
308
314
  #number(schema) {
309
315
  let defaultValue = schema.default;
310
- let base = "z.number()";
316
+ let base;
311
317
  if (schema.format === "int64") {
312
- base = "z.bigint()";
318
+ base = schema["x-zod-type"] === "coerce-bigint" ? "z.coerce.bigint()" : "z.bigint()";
313
319
  if (schema.default !== void 0) {
314
320
  defaultValue = `BigInt(${schema.default})`;
315
321
  }
322
+ } else {
323
+ base = schema["x-zod-type"] === "coerce-number" ? "z.coerce.number()" : "z.number()";
316
324
  }
317
- if (schema.format === "int32") {
325
+ if (schema.type === "integer" && schema.format !== "int64") {
318
326
  base += ".int()";
319
327
  }
320
328
  if (typeof schema.exclusiveMinimum === "number") {
@@ -694,7 +702,7 @@ var TypeScriptEmitter = class {
694
702
  case "date-time":
695
703
  case "datetime":
696
704
  case "date":
697
- type = "Date";
705
+ type = "string";
698
706
  break;
699
707
  case "binary":
700
708
  case "byte":
@@ -1302,7 +1310,7 @@ function operationSchema(ir, operation, type) {
1302
1310
  }
1303
1311
 
1304
1312
  // packages/typescript/src/lib/http/dispatcher.txt
1305
- var dispatcher_default = "export type Unionize<T> = T extends [infer Single extends OutputType]\n ? InstanceType<Single>\n : T extends readonly [...infer Tuple extends OutputType[]]\n ? { [I in keyof Tuple]: InstanceType<Tuple[I]> }[number]\n : never;\n\nexport type InstanceType<T> =\n T extends Type<infer U>\n ? U\n : T extends { type: Type<infer U> }\n ? U\n : T extends Array<unknown>\n ? Unionize<T>\n : never;\n\ntype ResponseData<T extends OutputType[]> =\n Extract<InstanceType<T>, SuccessfulResponse> extends SuccessfulResponse<\n infer P\n >\n ? P\n : unknown;\n\ntype ResponseMapper<T extends OutputType[], R> = (data: ResponseData<T>) => R;\n\nexport interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any> | SSEListener;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function parse<T extends OutputType[], R = ResponseData<T>>(\n outputs: T,\n response: Response,\n mapper: ResponseMapper<T, R>,\n) {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of outputs) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n response.headers,\n mapper((await parser(response)) as ResponseData<T>),\n );\n\n return apiresponse as RebindSuccessPayload<Extract<InstanceType<T>, SuccessfulResponse<unknown>>, R>;\n }\n\n throw (output || APIError).create(\n response.status,\n response.headers,\n await parser(response),\n );\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n\nexport class Dispatcher {\n #interceptors: Interceptor[] = [];\n #fetch: z.infer<typeof fetchType>;\n constructor(interceptors: Interceptor[], fetch?: z.infer<typeof fetchType>) {\n this.#interceptors = interceptors;\n this.#fetch = fetch;\n }\n\n async send<T extends OutputType[], R = ResponseData<T>>(\n config: RequestConfig,\n outputs: T,\n signal?: AbortSignal,\n mapper?: ResponseMapper<T, R>,\n ) {\n for (const interceptor of this.#interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (this.#fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: signal,\n },\n );\n\n for (let i = this.#interceptors.length - 1; i >= 0; i--) {\n const interceptor = this.#interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n return await parse(\n outputs,\n response,\n mapper ?? ((data: ResponseData<T>) => data as unknown as R),\n );\n }\n}\n";
1313
+ var dispatcher_default = "export type Unionize<T> = T extends [infer Single extends OutputType]\n ? InstanceType<Single>\n : T extends readonly [...infer Tuple extends OutputType[]]\n ? { [I in keyof Tuple]: InstanceType<Tuple[I]> }[number]\n : never;\n\nexport type InstanceType<T> =\n T extends Type<infer U>\n ? U\n : T extends { type: Type<infer U> }\n ? U\n : T extends Array<unknown>\n ? Unionize<T>\n : never;\n\ntype ResponseData<T extends OutputType[]> =\n Extract<InstanceType<T>, SuccessfulResponse> extends SuccessfulResponse<\n infer P\n >\n ? P\n : unknown;\n\ntype ResponseMapper<T extends OutputType[], R> = (data: ResponseData<T>) => R;\n\nexport interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any> | SSEListener;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function parse<T extends OutputType[]>(\n outputs: T,\n response: Response,\n): Promise<Extract<Unionize<T>, SuccessfulResponse<unknown>>>;\nexport async function parse<T extends OutputType[], R>(\n outputs: T,\n response: Response,\n mapper: ResponseMapper<T, R>,\n): Promise<RebindSuccessPayload<Extract<Unionize<T>, SuccessfulResponse<unknown>>, R>>;\nexport async function parse<T extends OutputType[], R = ResponseData<T>>(\n outputs: T,\n response: Response,\n mapper?: ResponseMapper<T, R>,\n) {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of outputs) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n if (response.ok) {\n const data = (await parser(response)) as ResponseData<T>;\n const mapped = mapper ? mapper(data) : data;\n const apiresponse = (output || APIResponse).create(\n response.status,\n response.headers,\n mapped,\n );\n\n return apiresponse as any;\n }\n\n throw (output || APIError).create(\n response.status,\n response.headers,\n await parser(response),\n );\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n\nexport class Dispatcher {\n #interceptors: Interceptor[] = [];\n #fetch: z.infer<typeof fetchType>;\n constructor(interceptors: Interceptor[], fetch?: z.infer<typeof fetchType>) {\n this.#interceptors = interceptors;\n this.#fetch = fetch;\n }\n\n async send<T extends OutputType[]>(\n config: RequestConfig,\n outputs: T,\n signal?: AbortSignal,\n ): Promise<Extract<Unionize<T>, SuccessfulResponse<unknown>>>;\n async send<T extends OutputType[], R>(\n config: RequestConfig,\n outputs: T,\n signal?: AbortSignal,\n mapper?: ResponseMapper<T, R>,\n ): Promise<RebindSuccessPayload<Extract<Unionize<T>, SuccessfulResponse<unknown>>, R>>;\n async send<T extends OutputType[], R = ResponseData<T>>(\n config: RequestConfig,\n outputs: T,\n signal?: AbortSignal,\n mapper?: ResponseMapper<T, R>,\n ) {\n for (const interceptor of this.#interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (this.#fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: signal,\n },\n );\n\n for (let i = this.#interceptors.length - 1; i >= 0; i--) {\n const interceptor = this.#interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n if (mapper) {\n return await parse(outputs, response, mapper);\n }\n return await parse(outputs, response);\n }\n}\n";
1306
1314
 
1307
1315
  // packages/typescript/src/lib/http/interceptors.txt
1308
1316
  var interceptors_default = "export interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig> | RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n headers: Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (baseUrl: string): Interceptor => {\n return {\n before({ init, url }) {\n if (url.protocol === 'local:') {\n return {\n init,\n url: new URL(url.href.replace('local://', baseUrl))\n };\n }\n return { init, url };\n },\n };\n};\n\nexport const logInterceptor: Interceptor = {\n before({ url, init }) {\n console.log('Request:', { url, init });\n return { url, init };\n },\n after(response) {\n console.log('Response:', response);\n return response;\n },\n};\n\n/**\n * Creates an interceptor that logs detailed information about requests and responses.\n * @param options Configuration options for the logger\n * @returns An interceptor object with before and after handlers\n */\nexport const createDetailedLogInterceptor = (options?: {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n includeRequestBody?: boolean;\n includeResponseBody?: boolean;\n}) => {\n const logLevel = options?.logLevel || 'info';\n const includeRequestBody = options?.includeRequestBody || false;\n const includeResponseBody = options?.includeResponseBody || false;\n\n return {\n async before(request: Request) {\n const logData = {\n url: request.url,\n method: request.method,\n contentType: request.headers.get('Content-Type'),\n headers: Object.fromEntries([...request.headers.entries()]),\n };\n\n console[logLevel]('\u{1F680} Outgoing Request:', logData);\n\n if (includeRequestBody) {\n try {\n // Clone the request to avoid consuming the body stream\n const clonedRequest = request.clone();\n if (clonedRequest.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedRequest.json().catch(() => null);\n console[logLevel]('Request Body:', body);\n } else {\n const body = await clonedRequest.text().catch(() => null);\n console[logLevel]('Request Body:', body);\n }\n } catch (error) {\n console.error('Could not log request body:', error);\n }\n }\n\n return request;\n },\n\n async after(response: Response) {\n const logData = {\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n headers: Object.fromEntries([...response.headers.entries()]),\n };\n\n console[logLevel]('\u{1F4E5} Incoming Response:', logData);\n\n if (includeResponseBody && response.body) {\n try {\n // Clone the response to avoid consuming the body stream\n const clonedResponse = response.clone();\n if (clonedResponse.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedResponse.json().catch(() => null);\n console[logLevel]('Response Body:', body);\n } else {\n const body = await clonedResponse.text().catch(() => null);\n if (body) {\n console[logLevel]('Response Body:', body.substring(0, 500) + (body.length > 500 ? '...' : ''));\n } else {\n console[logLevel]('No response body');\n }\n }\n } catch (error) {\n console.error('Could not log response body:', error);\n }\n }\n\n return response;\n },\n };\n};\n";