@sdk-it/typescript 0.41.0 → 0.42.1
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 +4 -4
- package/dist/index.js +58 -42
- package/dist/index.js.map +2 -2
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/typescript-snippet.d.ts.map +1 -1
- package/package.json +4 -4
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.
|
|
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
|
|
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
|
|
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
|
|
103
|
+
Let us know what you're using, and we'll help you integrate it.
|
package/dist/index.js
CHANGED
|
@@ -182,7 +182,7 @@ var ZodEmitter = class {
|
|
|
182
182
|
return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
|
|
183
183
|
}
|
|
184
184
|
case "boolean":
|
|
185
|
-
return
|
|
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,9 +253,9 @@ 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
|
-
base = "z.
|
|
258
|
+
base = "z.custom<Blob>()";
|
|
259
259
|
return base;
|
|
260
260
|
}
|
|
261
261
|
switch (schema.format) {
|
|
@@ -266,40 +266,40 @@ var ZodEmitter = class {
|
|
|
266
266
|
} else if (schema["x-zod-type"] === "date") {
|
|
267
267
|
base = "z.date()";
|
|
268
268
|
} else {
|
|
269
|
-
base
|
|
269
|
+
base += ".datetime()";
|
|
270
270
|
}
|
|
271
271
|
break;
|
|
272
272
|
case "date":
|
|
273
|
-
base
|
|
273
|
+
base += ".date()";
|
|
274
274
|
break;
|
|
275
275
|
case "time":
|
|
276
|
-
base
|
|
276
|
+
base += " /* optionally add .regex(...) for HH:MM:SS format */";
|
|
277
277
|
break;
|
|
278
278
|
case "email":
|
|
279
|
-
base
|
|
279
|
+
base += ".email()";
|
|
280
280
|
break;
|
|
281
281
|
case "uuid":
|
|
282
|
-
base
|
|
282
|
+
base += ".uuid()";
|
|
283
283
|
break;
|
|
284
284
|
case "url":
|
|
285
285
|
case "uri":
|
|
286
|
-
base
|
|
286
|
+
base += ".url()";
|
|
287
287
|
break;
|
|
288
288
|
case "ipv4":
|
|
289
|
-
base
|
|
289
|
+
base += '.ip({version: "v4"})';
|
|
290
290
|
break;
|
|
291
291
|
case "ipv6":
|
|
292
|
-
base
|
|
292
|
+
base += '.ip({version: "v6"})';
|
|
293
293
|
break;
|
|
294
294
|
case "phone":
|
|
295
|
-
base
|
|
295
|
+
base += " /* or add .regex(...) for phone formats */";
|
|
296
296
|
break;
|
|
297
297
|
case "byte":
|
|
298
298
|
case "binary":
|
|
299
|
-
base = "z.
|
|
299
|
+
base = "z.custom<Blob>()";
|
|
300
300
|
break;
|
|
301
301
|
case "int64":
|
|
302
|
-
base
|
|
302
|
+
base += " /* or z.bigint() if your app can handle it */";
|
|
303
303
|
break;
|
|
304
304
|
default:
|
|
305
305
|
break;
|
|
@@ -402,6 +402,7 @@ function toZod(schema, required) {
|
|
|
402
402
|
|
|
403
403
|
// packages/typescript/src/lib/client.ts
|
|
404
404
|
var client_default = (spec) => {
|
|
405
|
+
const baseUrlSchema = `z.union([z.string(),z.function().returns(z.union([z.string(), z.promise(z.string())])),])${spec.servers.length ? ".default(servers[0])" : ""}`;
|
|
405
406
|
const defaultHeaders = `{${spec.options.filter((value) => value.in === "header").map(
|
|
406
407
|
(value) => `'${value.name}': options['${value["x-optionName"] ?? value.name}']`
|
|
407
408
|
).join(",\n")}}`;
|
|
@@ -432,7 +433,12 @@ var client_default = (spec) => {
|
|
|
432
433
|
schema: `fetchType.describe('Custom fetch implementation. Defaults to globalThis.fetch.')`
|
|
433
434
|
},
|
|
434
435
|
baseUrl: {
|
|
435
|
-
schema:
|
|
436
|
+
schema: `${baseUrlSchema}.transform(async (baseUrl) => {
|
|
437
|
+
if (typeof baseUrl === 'function') {
|
|
438
|
+
return Promise.resolve(baseUrl());
|
|
439
|
+
}
|
|
440
|
+
return baseUrl;
|
|
441
|
+
}).describe('Base URL of the API server. Can be a string or a function that returns a string.')`
|
|
436
442
|
},
|
|
437
443
|
headers: {
|
|
438
444
|
schema: `z.record(z.string()).optional().describe('Default headers to include in all requests.')`
|
|
@@ -484,7 +490,7 @@ export class ${spec.name} {
|
|
|
484
490
|
async prepare<const E extends keyof typeof schemas>(
|
|
485
491
|
endpoint: E,
|
|
486
492
|
input: z.input<(typeof schemas)[E]['schema']>,
|
|
487
|
-
options?: { headers?: HeadersInit },
|
|
493
|
+
options?: { signal?: AbortSignal; headers?: HeadersInit },
|
|
488
494
|
) {
|
|
489
495
|
return prepare(this, endpoint, input, options);
|
|
490
496
|
}
|
|
@@ -565,7 +571,7 @@ export async function prepare<const E extends keyof typeof schemas>(
|
|
|
565
571
|
client: ${spec.name},
|
|
566
572
|
endpoint: E,
|
|
567
573
|
input: z.input<(typeof schemas)[E]['schema']>,
|
|
568
|
-
requestOptions?: { headers?: HeadersInit },
|
|
574
|
+
requestOptions?: { signal?: AbortSignal; headers?: HeadersInit },
|
|
569
575
|
): Promise<RequestConfig & {
|
|
570
576
|
parse: (response: Response) => ReturnType<typeof parse>;
|
|
571
577
|
}> {
|
|
@@ -586,6 +592,15 @@ export async function prepare<const E extends keyof typeof schemas>(
|
|
|
586
592
|
];
|
|
587
593
|
|
|
588
594
|
let config = route.toRequest(parsedInput as never);
|
|
595
|
+
if (requestOptions?.signal) {
|
|
596
|
+
config = {
|
|
597
|
+
...config,
|
|
598
|
+
init: {
|
|
599
|
+
...config.init,
|
|
600
|
+
signal: requestOptions.signal,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
589
604
|
for (const interceptor of interceptors) {
|
|
590
605
|
if (interceptor.before) {
|
|
591
606
|
config = await interceptor.before(config);
|
|
@@ -873,12 +888,12 @@ function paginationOperation(operation) {
|
|
|
873
888
|
const sameInputNames = pagination.limitParamName === "limit" && pagination.offsetParamName === "offset";
|
|
874
889
|
const initialParams = sameInputNames ? "input" : `{...input, limit: input.${pagination.limitParamName}, offset: input.${pagination.offsetParamName}}`;
|
|
875
890
|
const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.offsetParamName}: nextPageParams.offset, ${pagination.limitParamName}: nextPageParams.limit`;
|
|
876
|
-
const logic = `const pagination = new OffsetPagination(${initialParams}, async (nextPageParams) => {
|
|
891
|
+
const logic = `const pagination = new OffsetPagination(${initialParams}, async (nextPageParams, requestOptions) => {
|
|
877
892
|
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
878
893
|
const result = await dispatcher.send(
|
|
879
894
|
this.toRequest({...input, ${nextPageParams}}),
|
|
880
895
|
this.output,
|
|
881
|
-
options.signal,
|
|
896
|
+
requestOptions?.signal ?? options.signal,
|
|
882
897
|
);
|
|
883
898
|
return {
|
|
884
899
|
data: ${data}.${pagination.items},
|
|
@@ -886,7 +901,7 @@ function paginationOperation(operation) {
|
|
|
886
901
|
hasMore: Boolean(${data}.${pagination.hasMore}),
|
|
887
902
|
},
|
|
888
903
|
};
|
|
889
|
-
});
|
|
904
|
+
}, { signal: options.signal });
|
|
890
905
|
await pagination.getNextPage();
|
|
891
906
|
return ${returnValue}
|
|
892
907
|
`;
|
|
@@ -897,12 +912,12 @@ function paginationOperation(operation) {
|
|
|
897
912
|
const initialParams = sameInputNames ? "input" : `{...input, cursor: input.${pagination.cursorParamName}}`;
|
|
898
913
|
const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.cursorParamName}: nextPageParams.cursor`;
|
|
899
914
|
const logic = `
|
|
900
|
-
const pagination = new CursorPagination(${initialParams}, async (nextPageParams) => {
|
|
915
|
+
const pagination = new CursorPagination(${initialParams}, async (nextPageParams, requestOptions) => {
|
|
901
916
|
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
902
917
|
const result = await dispatcher.send(
|
|
903
918
|
this.toRequest({...input, ${nextPageParams}}),
|
|
904
919
|
this.output,
|
|
905
|
-
options.signal,
|
|
920
|
+
requestOptions?.signal ?? options.signal,
|
|
906
921
|
);
|
|
907
922
|
return {
|
|
908
923
|
data: ${data}.${pagination.items},
|
|
@@ -910,7 +925,7 @@ function paginationOperation(operation) {
|
|
|
910
925
|
hasMore: Boolean(${data}.${pagination.hasMore}),
|
|
911
926
|
},
|
|
912
927
|
};
|
|
913
|
-
});
|
|
928
|
+
}, { signal: options.signal });
|
|
914
929
|
await pagination.getNextPage();
|
|
915
930
|
return ${returnValue}
|
|
916
931
|
`;
|
|
@@ -921,12 +936,12 @@ function paginationOperation(operation) {
|
|
|
921
936
|
const initialParams = sameInputNames ? "input" : `{...input, page: input.${pagination.pageNumberParamName}, pageSize: input.${pagination.pageSizeParamName}}`;
|
|
922
937
|
const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.pageNumberParamName}: nextPageParams.page, ${pagination.pageSizeParamName}: nextPageParams.pageSize`;
|
|
923
938
|
const logic = `
|
|
924
|
-
const pagination = new Pagination(${initialParams}, async (nextPageParams) => {
|
|
939
|
+
const pagination = new Pagination(${initialParams}, async (nextPageParams, requestOptions) => {
|
|
925
940
|
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
926
941
|
const result = await dispatcher.send(
|
|
927
942
|
this.toRequest({...input, ${nextPageParams}}),
|
|
928
943
|
this.output,
|
|
929
|
-
options.signal,
|
|
944
|
+
requestOptions?.signal ?? options.signal,
|
|
930
945
|
);
|
|
931
946
|
return {
|
|
932
947
|
data: ${data}.${pagination.items},
|
|
@@ -934,7 +949,8 @@ function paginationOperation(operation) {
|
|
|
934
949
|
hasMore: Boolean(${data}.${pagination.hasMore}),
|
|
935
950
|
},
|
|
936
951
|
};
|
|
937
|
-
});
|
|
952
|
+
}, { signal: options.signal });
|
|
953
|
+
await pagination.getNextPage();
|
|
938
954
|
return ${returnValue}
|
|
939
955
|
`;
|
|
940
956
|
return `{${logic}}}`;
|
|
@@ -1310,7 +1326,7 @@ function operationSchema(ir, operation, type) {
|
|
|
1310
1326
|
}
|
|
1311
1327
|
|
|
1312
1328
|
// packages/typescript/src/lib/http/dispatcher.txt
|
|
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.
|
|
1329
|
+
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.custom<Request>())\n .returns(z.promise(z.custom<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 const init = signal === undefined ? config.init : { ...config.init, signal };\n\n let response = await (this.#fetch ?? fetch)(new Request(config.url, init));\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";
|
|
1314
1330
|
|
|
1315
1331
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1316
1332
|
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";
|
|
@@ -1331,13 +1347,13 @@ var response_default = "export class APIResponse<Body = unknown, Status extends
|
|
|
1331
1347
|
var sse_default = 'export type SSEListener = (eventType: string) => AsyncIterable<string>;\n\nexport function sse(response: Response): SSEListener {\n const subscribers = new Map<\n string,\n { queue: string[]; resolve: (() => void) | null }\n >();\n let started = false;\n let streamDone = false;\n\n function dispatch(eventType: string, data: string) {\n const sub = subscribers.get(eventType);\n if (sub) {\n sub.queue.push(data);\n sub.resolve?.();\n sub.resolve = null;\n }\n }\n\n function endAll() {\n streamDone = true;\n for (const sub of subscribers.values()) {\n sub.resolve?.();\n }\n }\n\n function startReading() {\n if (started) return;\n started = true;\n const decoder = new TextDecoder();\n let buffer = "";\n\n (async () => {\n try {\n for await (const value of response.body!) {\n buffer += decoder.decode(value, { stream: true });\n const parts = buffer.split("\\n\\n");\n buffer = parts.pop()!;\n\n for (const part of parts) {\n if (!part.trim()) continue;\n let eventType = "message";\n let data = "";\n for (const line of part.split("\\n")) {\n if (line.startsWith("event:")) {\n eventType = line.slice(6).trim();\n } else if (line.startsWith("data:")) {\n data += (data ? "\\n" : "") + line.slice(5).trim();\n }\n }\n if (data) dispatch(eventType, data);\n }\n }\n } catch {}\n endAll();\n })();\n }\n\n return function listen(eventType: string): AsyncIterable<string> {\n if (!subscribers.has(eventType)) {\n subscribers.set(eventType, { queue: [], resolve: null });\n }\n const sub = subscribers.get(eventType)!;\n startReading();\n\n return {\n [Symbol.asyncIterator]() {\n return {\n async next(): Promise<IteratorResult<string>> {\n while (sub.queue.length === 0) {\n if (streamDone) return { value: undefined as any, done: true };\n const { promise, resolve } = Promise.withResolvers<void>();\n sub.resolve = resolve;\n await promise;\n }\n return { value: sub.queue.shift()!, done: false };\n },\n async return(): Promise<IteratorResult<string>> {\n return { value: undefined as any, done: true };\n },\n };\n },\n };\n };\n}\n';
|
|
1332
1348
|
|
|
1333
1349
|
// packages/typescript/src/lib/paginations/cursor-pagination.txt
|
|
1334
|
-
var cursor_pagination_default = "type CursorPaginationParams = {\n cursor?: string;\n};\n\ninterface CursorMetadata extends Metadata {\n nextCursor?: string;\n}\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends CursorMetadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends CursorMetadata> = (\n input: CursorPaginationParams,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class CursorPagination<T, M extends CursorMetadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: CursorPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n\n constructor(\n initialParams: PartialNullable<CursorPaginationParams>,\n fetchFn: FetchFn<T, M>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = {\n cursor: initialParams.cursor ?? undefined,\n };\n }\n\n async getNextPage() {\n const result = await this.#fetchFn(this.#params);\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n cursor: result.meta.nextCursor,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter() {\n if (!this.#currentPage) {\n yield await this.getNextPage();\n }\n\n while (this.hasMore) {\n yield await this.getNextPage();\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n\ntype PartialNullable<T> = {\n [K in keyof T]?: T[K] | null;\n};\n";
|
|
1350
|
+
var cursor_pagination_default = "type CursorPaginationParams = {\n cursor?: string;\n};\n\ninterface CursorMetadata extends Metadata {\n nextCursor?: string;\n}\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationRequestOptions = {\n signal?: AbortSignal;\n};\n\ntype PaginationResult<T, M extends CursorMetadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends CursorMetadata> = (\n input: CursorPaginationParams,\n requestOptions?: PaginationRequestOptions,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class CursorPagination<T, M extends CursorMetadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: CursorPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n readonly #requestOptions: PaginationRequestOptions;\n\n constructor(\n initialParams: PartialNullable<CursorPaginationParams>,\n fetchFn: FetchFn<T, M>,\n requestOptions: PaginationRequestOptions = {},\n ) {\n this.#fetchFn = fetchFn;\n this.#requestOptions = requestOptions;\n this.#params = {\n cursor: initialParams.cursor ?? undefined,\n };\n }\n\n async getNextPage(requestOptions?: PaginationRequestOptions) {\n const result = await this.#fetchFn(this.#params, {\n ...this.#requestOptions,\n ...requestOptions,\n });\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n cursor: result.meta.nextCursor,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter(requestOptions?: PaginationRequestOptions) {\n if (!this.#currentPage) {\n yield await this.getNextPage(requestOptions);\n }\n\n while (this.hasMore) {\n yield await this.getNextPage(requestOptions);\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n\ntype PartialNullable<T> = {\n [K in keyof T]?: T[K] | null;\n};\n";
|
|
1335
1351
|
|
|
1336
1352
|
// packages/typescript/src/lib/paginations/offset-pagination.txt
|
|
1337
|
-
var offset_pagination_default = "type OffsetPaginationParams = {\n offset: number;\n limit: number;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends Metadata> = (\n input: OffsetPaginationParams,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class OffsetPagination<T, M extends Metadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: OffsetPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n\n constructor(\n initialParams: Partial<OffsetPaginationParams>,\n fetchFn: FetchFn<T, M>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = {\n limit: initialParams.limit ?? 0,\n offset: initialParams.offset ?? 0,\n };\n }\n\n async getNextPage() {\n const result = await this.#fetchFn(this.#params);\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n offset: this.#params.offset + this.#params.limit,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter() {\n if (!this.#currentPage) {\n yield await this.getNextPage();\n }\n\n while (this.hasMore) {\n yield await this.getNextPage();\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n\n reset(params?: Partial<OffsetPaginationParams>) {\n this.#meta = null;\n this.#currentPage = null;\n if (params) {\n this.#params = { ...this.#params, ...params };\n } else {\n this.#params.offset = 0;\n }\n return this;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
|
|
1353
|
+
var offset_pagination_default = "type OffsetPaginationParams = {\n offset: number;\n limit: number;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationRequestOptions = {\n signal?: AbortSignal;\n};\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends Metadata> = (\n input: OffsetPaginationParams,\n requestOptions?: PaginationRequestOptions,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class OffsetPagination<T, M extends Metadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: OffsetPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n readonly #requestOptions: PaginationRequestOptions;\n\n constructor(\n initialParams: Partial<OffsetPaginationParams>,\n fetchFn: FetchFn<T, M>,\n requestOptions: PaginationRequestOptions = {},\n ) {\n this.#fetchFn = fetchFn;\n this.#requestOptions = requestOptions;\n this.#params = {\n limit: initialParams.limit ?? 0,\n offset: initialParams.offset ?? 0,\n };\n }\n\n async getNextPage(requestOptions?: PaginationRequestOptions) {\n const result = await this.#fetchFn(this.#params, {\n ...this.#requestOptions,\n ...requestOptions,\n });\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n offset: this.#params.offset + this.#params.limit,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter(requestOptions?: PaginationRequestOptions) {\n if (!this.#currentPage) {\n yield await this.getNextPage(requestOptions);\n }\n\n while (this.hasMore) {\n yield await this.getNextPage(requestOptions);\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n\n reset(params?: Partial<OffsetPaginationParams>) {\n this.#meta = null;\n this.#currentPage = null;\n if (params) {\n this.#params = { ...this.#params, ...params };\n } else {\n this.#params.offset = 0;\n }\n return this;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
|
|
1338
1354
|
|
|
1339
1355
|
// packages/typescript/src/lib/paginations/page-pagination.txt
|
|
1340
|
-
var page_pagination_default = "type InferPage<T> = T extends Page<infer U> ? U : never;\ntype PaginationParams<P extends number | bigint, S extends number | bigint> = {\n page?: P;\n pageSize?: S;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> = (input: Partial<PaginationParams<P, S
|
|
1356
|
+
var page_pagination_default = "type InferPage<T> = T extends Page<infer U> ? U : never;\ntype PaginationParams<P extends number | bigint, S extends number | bigint> = {\n page?: P;\n pageSize?: S;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationRequestOptions = {\n signal?: AbortSignal;\n};\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> = (\n input: Partial<PaginationParams<P, S>>,\n requestOptions?: PaginationRequestOptions,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class Pagination<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: PaginationParams<P, S>;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M, P, S>;\n readonly #requestOptions: PaginationRequestOptions;\n\n constructor(\n initialParams: Partial<PaginationParams<P, S>>,\n fetchFn: FetchFn<T, M, P, S>,\n requestOptions: PaginationRequestOptions = {},\n ) {\n this.#fetchFn = fetchFn;\n this.#requestOptions = requestOptions;\n this.#params = { ...initialParams, page: initialParams.page };\n }\n\n async getNextPage(requestOptions?: PaginationRequestOptions) {\n const result = await this.#fetchFn(this.#params, {\n ...this.#requestOptions,\n ...requestOptions,\n });\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n page: (((this.#params.page as number) || 0) + 1) as never,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter(requestOptions?: PaginationRequestOptions) {\n if (!this.#currentPage) {\n yield await this.getNextPage(requestOptions);\n }\n\n while (this.hasMore) {\n yield await this.getNextPage(requestOptions);\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
|
|
1341
1357
|
|
|
1342
1358
|
// packages/typescript/src/lib/readme/readme.ts
|
|
1343
1359
|
import { isEmpty as isEmpty3 } from "@sdk-it/core";
|
|
@@ -2035,22 +2051,25 @@ var TypeScriptSnippet = class {
|
|
|
2035
2051
|
switch (pagination.type) {
|
|
2036
2052
|
case "page":
|
|
2037
2053
|
return {
|
|
2038
|
-
content: `const
|
|
2039
|
-
|
|
2054
|
+
content: `const controller = new AbortController();
|
|
2055
|
+
const result = await ${camelcase5(this.#clientName)}.request('${entry.method.toUpperCase()} ${entry.path}', ${payload}, { signal: controller.signal });`,
|
|
2056
|
+
footer: `for await (const page of result.iter({ signal: controller.signal })) {
|
|
2040
2057
|
console.log(page);
|
|
2041
2058
|
}`
|
|
2042
2059
|
};
|
|
2043
2060
|
case "offset":
|
|
2044
2061
|
return {
|
|
2045
|
-
content: `const
|
|
2046
|
-
|
|
2062
|
+
content: `const controller = new AbortController();
|
|
2063
|
+
const result = await ${camelcase5(this.#clientName)}.request('${entry.method.toUpperCase()} ${entry.path}', ${payload}, { signal: controller.signal });`,
|
|
2064
|
+
footer: `for await (const page of result.iter({ signal: controller.signal })) {
|
|
2047
2065
|
console.log(page);
|
|
2048
2066
|
}`
|
|
2049
2067
|
};
|
|
2050
2068
|
case "cursor":
|
|
2051
2069
|
return {
|
|
2052
|
-
content: `const
|
|
2053
|
-
|
|
2070
|
+
content: `const controller = new AbortController();
|
|
2071
|
+
const result = await ${camelcase5(this.#clientName)}.request('${entry.method.toUpperCase()} ${entry.path}', ${payload}, { signal: controller.signal });`,
|
|
2072
|
+
footer: `for await (const page of result.iter({ signal: controller.signal })) {
|
|
2054
2073
|
console.log(page);
|
|
2055
2074
|
}`
|
|
2056
2075
|
};
|
|
@@ -2156,7 +2175,7 @@ ${client.use}`;
|
|
|
2156
2175
|
);
|
|
2157
2176
|
if (hasServers) {
|
|
2158
2177
|
sections.push(
|
|
2159
|
-
"| `baseUrl` | `string` | No | API base URL (default: `" + baseUrl + "`) |"
|
|
2178
|
+
"| `baseUrl` | `string | (() => string | Promise<string>)` | No | API base URL (default: `" + baseUrl + "`) |"
|
|
2160
2179
|
);
|
|
2161
2180
|
}
|
|
2162
2181
|
for (const authOption of authOptions) {
|
|
@@ -2230,10 +2249,10 @@ ${client.use}`;
|
|
|
2230
2249
|
"",
|
|
2231
2250
|
"// Check if more pages exist",
|
|
2232
2251
|
"if (result.hasMore) {",
|
|
2233
|
-
" await result.getNextPage();",
|
|
2252
|
+
" await result.getNextPage({ signal: controller.signal });",
|
|
2234
2253
|
"}",
|
|
2235
2254
|
"",
|
|
2236
|
-
"// Or iterate through all pages
|
|
2255
|
+
"// Or iterate through all pages with cancellable page fetches",
|
|
2237
2256
|
paginationExample.footer
|
|
2238
2257
|
])
|
|
2239
2258
|
);
|
|
@@ -2897,12 +2916,9 @@ ${utils_default}`
|
|
|
2897
2916
|
skipLibCheck: true,
|
|
2898
2917
|
skipDefaultLibCheck: true,
|
|
2899
2918
|
target: "ESNext",
|
|
2900
|
-
module: "ESNext",
|
|
2901
2919
|
noEmit: true,
|
|
2902
|
-
strict: true,
|
|
2903
2920
|
allowImportingTsExtensions: true,
|
|
2904
2921
|
verbatimModuleSyntax: true,
|
|
2905
|
-
baseUrl: ".",
|
|
2906
2922
|
moduleResolution: "bundler"
|
|
2907
2923
|
},
|
|
2908
2924
|
include: ["**/*.ts"]
|