@sdk-it/typescript 0.36.2 → 0.37.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 +1 -1
- package/dist/index.js +52 -32
- package/dist/index.js.map +2 -2
- package/dist/lib/client.d.ts.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -393,18 +393,31 @@ function toZod(schema, required) {
|
|
|
393
393
|
// packages/typescript/src/lib/client.ts
|
|
394
394
|
var client_default = (spec, style) => {
|
|
395
395
|
const defaultHeaders = `{${spec.options.filter((value) => value.in === "header").map(
|
|
396
|
-
(value) => `'${value.name}':
|
|
396
|
+
(value) => `'${value.name}': options['${value["x-optionName"] ?? value.name}']`
|
|
397
397
|
).join(",\n")}}`;
|
|
398
398
|
const defaultInputs = `{${spec.options.filter((value) => value.in === "input").map(
|
|
399
|
-
(value) => `'${value.name}':
|
|
399
|
+
(value) => `'${value.name}': options['${value["x-optionName"] ?? value.name}']`
|
|
400
400
|
).join(",\n")}}`;
|
|
401
|
+
const globalOptions = Object.fromEntries(
|
|
402
|
+
spec.options.map((value) => [
|
|
403
|
+
`'${value["x-optionName"] ?? value.name}'`,
|
|
404
|
+
{ schema: toZod(value.schema, value.required) }
|
|
405
|
+
])
|
|
406
|
+
);
|
|
401
407
|
const specOptions = {
|
|
402
|
-
...
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
+
...globalOptions,
|
|
409
|
+
...globalOptions["'token'"] ? {
|
|
410
|
+
"'token'": {
|
|
411
|
+
schema: `z.union([z.string(),z.function().returns(z.union([z.string(), z.promise(z.string())])),]).optional()
|
|
412
|
+
.transform(async (token) => {
|
|
413
|
+
if (!token) return undefined;
|
|
414
|
+
if (typeof token === 'function') {
|
|
415
|
+
token = await Promise.resolve(token());
|
|
416
|
+
}
|
|
417
|
+
return \`Bearer \${token}\`;
|
|
418
|
+
})`
|
|
419
|
+
}
|
|
420
|
+
} : {},
|
|
408
421
|
fetch: {
|
|
409
422
|
schema: "fetchType"
|
|
410
423
|
},
|
|
@@ -430,30 +443,34 @@ ${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, n
|
|
|
430
443
|
const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
|
|
431
444
|
${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
|
|
432
445
|
|
|
433
|
-
type ${spec.name}Options = z.
|
|
446
|
+
type ${spec.name}Options = z.input<typeof optionsSchema>;
|
|
434
447
|
|
|
435
448
|
export class ${spec.name} {
|
|
436
|
-
public options: ${spec.name}Options
|
|
449
|
+
public options: ${spec.name}Options;
|
|
437
450
|
constructor(options: ${spec.name}Options) {
|
|
438
|
-
this.options =
|
|
451
|
+
this.options = options;
|
|
439
452
|
}
|
|
440
453
|
|
|
441
454
|
async request<const E extends keyof typeof schemas>(
|
|
442
455
|
endpoint: E,
|
|
443
|
-
input: z.
|
|
456
|
+
input: z.input<(typeof schemas)[E]['schema']>,
|
|
444
457
|
options?: { signal?: AbortSignal, headers?: HeadersInit },
|
|
445
458
|
) ${style.errorAsValue ? `: Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>| [never, ParseError<(typeof schemas)[E]['schema']>]>` : `: Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>>`} {
|
|
446
459
|
const route = schemas[endpoint];
|
|
447
|
-
const withDefaultInputs = Object.assign({}, this.#defaultInputs, input);
|
|
460
|
+
const withDefaultInputs = Object.assign({}, await this.#defaultInputs(), input);
|
|
448
461
|
const [parsedInput, parseError] = parseInput(route.schema, withDefaultInputs);
|
|
449
462
|
if (parseError) {
|
|
450
463
|
${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
|
|
451
464
|
}
|
|
465
|
+
const clientOptions = await optionsSchema.parseAsync(this.options);
|
|
452
466
|
const result = await route.dispatch(parsedInput as never, {
|
|
453
|
-
fetch:
|
|
467
|
+
fetch: clientOptions.fetch,
|
|
454
468
|
interceptors: [
|
|
455
|
-
createHeadersInterceptor(
|
|
456
|
-
|
|
469
|
+
createHeadersInterceptor(
|
|
470
|
+
await this.#defaultHeaders(),
|
|
471
|
+
options?.headers ?? {},
|
|
472
|
+
),
|
|
473
|
+
createBaseUrlInterceptor(clientOptions.baseUrl),
|
|
457
474
|
],
|
|
458
475
|
signal: options?.signal,
|
|
459
476
|
});
|
|
@@ -462,7 +479,7 @@ export class ${spec.name} {
|
|
|
462
479
|
|
|
463
480
|
async prepare<const E extends keyof typeof schemas>(
|
|
464
481
|
endpoint: E,
|
|
465
|
-
input: z.
|
|
482
|
+
input: z.input<(typeof schemas)[E]['schema']>,
|
|
466
483
|
options?: { headers?: HeadersInit },
|
|
467
484
|
): ${style.errorAsValue ? `Promise<
|
|
468
485
|
readonly [
|
|
@@ -474,14 +491,14 @@ export class ${spec.name} {
|
|
|
474
491
|
>` : `Promise<RequestConfig & {
|
|
475
492
|
parse: (response: Response) => ReturnType<typeof parse>;
|
|
476
493
|
}>`} {
|
|
494
|
+
const clientOptions = await optionsSchema.parseAsync(this.options);
|
|
477
495
|
const route = schemas[endpoint];
|
|
478
|
-
|
|
479
496
|
const interceptors = [
|
|
480
497
|
createHeadersInterceptor(
|
|
481
|
-
|
|
498
|
+
await this.#defaultHeaders(),
|
|
482
499
|
options?.headers ?? {},
|
|
483
500
|
),
|
|
484
|
-
createBaseUrlInterceptor(
|
|
501
|
+
createBaseUrlInterceptor(clientOptions.baseUrl),
|
|
485
502
|
];
|
|
486
503
|
const [parsedInput, parseError] = parseInput(route.schema, input);
|
|
487
504
|
if (parseError) {
|
|
@@ -498,23 +515,26 @@ export class ${spec.name} {
|
|
|
498
515
|
return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared as any"}
|
|
499
516
|
}
|
|
500
517
|
|
|
501
|
-
|
|
502
|
-
|
|
518
|
+
async #defaultHeaders() {
|
|
519
|
+
const options = await optionsSchema.parseAsync(this.options);
|
|
520
|
+
return {
|
|
521
|
+
...${defaultHeaders},
|
|
522
|
+
...options.headers,
|
|
523
|
+
};
|
|
503
524
|
}
|
|
504
525
|
|
|
505
|
-
|
|
526
|
+
async #defaultInputs() {
|
|
527
|
+
const options = await optionsSchema.parseAsync(this.options);
|
|
506
528
|
return ${defaultInputs}
|
|
507
529
|
}
|
|
508
530
|
|
|
509
531
|
setOptions(options: Partial<${spec.name}Options>) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
(this.options[key] as typeof validated[typeof key]) = validated[key]!;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
532
|
+
this.options = {
|
|
533
|
+
...this.options,
|
|
534
|
+
...options,
|
|
535
|
+
};
|
|
517
536
|
}
|
|
537
|
+
|
|
518
538
|
}`;
|
|
519
539
|
};
|
|
520
540
|
|
|
@@ -955,7 +975,7 @@ function inputToPath(operation, inputs) {
|
|
|
955
975
|
}
|
|
956
976
|
|
|
957
977
|
// packages/typescript/src/lib/styles/github/endpoints.txt
|
|
958
|
-
var endpoints_default = "type EndpointOutput<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n SuccessfulResponse\n>;\n\ntype EndpointError<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n ProblematicResponse\n>;\n\nexport type Endpoints = {\n [K in keyof typeof schemas]: {\n input: z.
|
|
978
|
+
var endpoints_default = "type EndpointOutput<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n SuccessfulResponse\n>;\n\ntype EndpointError<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n ProblematicResponse\n>;\n\nexport type Endpoints = {\n [K in keyof typeof schemas]: {\n input: z.input<(typeof schemas)[K]['schema']>;\n output: <% if (outputType === 'default') { %>EndpointOutput<K>['data']<% } else { %>EndpointOutput<K><% } %>;\n error: EndpointError<K> | ParseError<(typeof schemas)[K]['schema']>;\n };\n};";
|
|
959
979
|
|
|
960
980
|
// packages/typescript/src/lib/generator.ts
|
|
961
981
|
function coearceRequestInput(spec, operation, type) {
|
|
@@ -1215,7 +1235,7 @@ function operationSchema(ir, operation, type) {
|
|
|
1215
1235
|
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\nexport interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\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) <% if(!throwError) { %>\n: Promise<\n [\n Extract<InstanceType<T>, SuccessfulResponse>['data'],\n Extract<InstanceType<T>, ProblematicResponse>['data'],\n ]\n>\n <% } %>\n\n\n\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\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n <% if(throwError) { %>\n return <% if (outputType === 'default') { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse><% } else { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse>;<% } %>;\n <% } else { %>\n return [<% if (outputType === 'default') { %>apiresponse.data as Extract<InstanceType<T>, SuccessfulResponse>['data']<% } else { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse><% } %>, null] as const;\n <% } %>\n }\n<% if(throwError) { %>\n throw (output || APIError).create(\n response.status,\n await parser(response),\n );\n<% } else { %>\n const data = (output || APIError).create(\n response.status,\n await parser(response),\n );\n return [null, data] as const;\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 ) {\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(outputs, response);\n }\n}\n";
|
|
1216
1236
|
|
|
1217
1237
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1218
|
-
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
|
|
1238
|
+
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";
|
|
1219
1239
|
|
|
1220
1240
|
// packages/typescript/src/lib/http/parse-response.txt
|
|
1221
1241
|
var parse_response_default = 'import { parse } from "fast-content-type-parse";\n\nasync function handleChunkedResponse(response: Response, contentType: string) {\n const { type } = parse(contentType);\n\n switch (type) {\n case "application/json": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return JSON.parse(buffer);\n }\n case "text/html":\n case "text/plain": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return buffer;\n }\n default:\n return response.body;\n }\n}\n\nexport function chunked(response: Response) {\n return response.body!;\n}\n\nexport async function buffered(response: Response) {\n const contentType = response.headers.get("Content-Type");\n if (!contentType) {\n throw new Error("Content-Type header is missing");\n }\n\n if (response.status === 204) {\n return null;\n }\n\n const { type } = parse(contentType);\n switch (type) {\n case "application/json":\n return response.json();\n case "text/plain":\n return response.text();\n case "text/html":\n return response.text();\n case "text/xml":\n case "application/xml":\n return response.text();\n case "application/x-www-form-urlencoded": {\n const text = await response.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n case "multipart/form-data":\n return response.formData();\n default:\n throw new Error(`Unsupported content type: ${contentType}`);\n }\n}\n';
|