@blimu/codegen 0.1.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 +142 -0
- package/dist/generator/typescript/templates/README.md.hbs +123 -0
- package/dist/generator/typescript/templates/client.ts.hbs +380 -0
- package/dist/generator/typescript/templates/index.ts.hbs +67 -0
- package/dist/generator/typescript/templates/package.json.hbs +48 -0
- package/dist/generator/typescript/templates/schema.ts.hbs +76 -0
- package/dist/generator/typescript/templates/schema.zod.ts.hbs +126 -0
- package/dist/generator/typescript/templates/service.ts.hbs +107 -0
- package/dist/generator/typescript/templates/tsconfig.json.hbs +28 -0
- package/dist/generator/typescript/templates/utils.ts.hbs +168 -0
- package/dist/index.d.mts +309 -0
- package/dist/index.d.ts +309 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +7 -0
- package/dist/index.mjs.map +1 -0
- package/dist/main.js +8 -0
- package/dist/main.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
import { CoreClient, FetchError, type ClientOption } from "./client";
|
|
3
|
+
{{~#each IR.services~}}
|
|
4
|
+
import { {{serviceName tag}} } from "./services/{{fileBase tag}}";
|
|
5
|
+
{{~/each~}}
|
|
6
|
+
|
|
7
|
+
{{~setVar "grouped" (groupByNamespace IR.services)~}}
|
|
8
|
+
|
|
9
|
+
{{~! Generate namespace classes for non-root services ~}}
|
|
10
|
+
{{~#each (groupByNamespace IR.services) as |services namespace|~}}
|
|
11
|
+
{{~#if (ne namespace "")~}}
|
|
12
|
+
class {{pascal namespace}}Namespace {
|
|
13
|
+
{{~#each services~}}
|
|
14
|
+
readonly {{serviceProp (getServiceName tag)}}: {{serviceName tag}};
|
|
15
|
+
{{~/each~}}
|
|
16
|
+
|
|
17
|
+
constructor(core: CoreClient) {
|
|
18
|
+
{{~#each services~}}
|
|
19
|
+
this.{{serviceProp (getServiceName tag)}} = new {{serviceName tag}}(core);
|
|
20
|
+
{{~/each~}}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
{{~/if~}}
|
|
25
|
+
{{~/each~}}
|
|
26
|
+
|
|
27
|
+
export class {{Client.name}} {
|
|
28
|
+
{{~! Root level services (no namespace) ~}}
|
|
29
|
+
{{~#each (getRootServices IR.services)~}}
|
|
30
|
+
readonly {{serviceProp tag}}: {{serviceName tag}};
|
|
31
|
+
{{~/each~}}
|
|
32
|
+
{{~! Namespace properties ~}}
|
|
33
|
+
{{~#each (groupByNamespace IR.services) as |services namespace|~}}
|
|
34
|
+
{{~#if (ne namespace "")~}}
|
|
35
|
+
readonly {{serviceProp namespace}}: {{pascal namespace}}Namespace;
|
|
36
|
+
{{~/if~}}
|
|
37
|
+
{{~/each~}}
|
|
38
|
+
|
|
39
|
+
constructor(options?: ClientOption) {
|
|
40
|
+
const core = new CoreClient(options);
|
|
41
|
+
|
|
42
|
+
{{~! Initialize root services ~}}
|
|
43
|
+
{{~#each (getRootServices IR.services)~}}
|
|
44
|
+
this.{{serviceProp tag}} = new {{serviceName tag}}(core);
|
|
45
|
+
{{~/each~}}
|
|
46
|
+
{{~! Initialize namespaces ~}}
|
|
47
|
+
{{~#each (groupByNamespace IR.services) as |services namespace|~}}
|
|
48
|
+
{{~#if (ne namespace "")~}}
|
|
49
|
+
this.{{serviceProp namespace}} = new {{pascal namespace}}Namespace(core);
|
|
50
|
+
{{~/if~}}
|
|
51
|
+
{{~/each~}}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type { ClientOption };
|
|
56
|
+
|
|
57
|
+
// Export FetchError for error handling
|
|
58
|
+
export { FetchError };
|
|
59
|
+
export const {{Client.name}}Error = FetchError;
|
|
60
|
+
|
|
61
|
+
// Re-exports for better ergonomics
|
|
62
|
+
export * from "./utils";
|
|
63
|
+
export * as Schema from "./schema";
|
|
64
|
+
export * as ZodSchema from "./schema.zod";
|
|
65
|
+
{{~#each IR.services~}}
|
|
66
|
+
export { {{serviceName tag}} } from "./services/{{fileBase tag}}";
|
|
67
|
+
{{~/each~}}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{Client.packageName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for {{Client.name}} API (auto-generated)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist/**"],
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./services/*": {
|
|
15
|
+
"types": "./dist/services/*.d.ts",
|
|
16
|
+
"import": "./dist/services/*.mjs",
|
|
17
|
+
"require": "./dist/services/*.js"
|
|
18
|
+
},
|
|
19
|
+
"./schema": {
|
|
20
|
+
"types": "./dist/schema.d.ts",
|
|
21
|
+
"import": "./dist/schema.mjs",
|
|
22
|
+
"require": "./dist/schema.js"
|
|
23
|
+
},
|
|
24
|
+
"./client": {
|
|
25
|
+
"types": "./dist/client.d.ts",
|
|
26
|
+
"import": "./dist/client.mjs",
|
|
27
|
+
"require": "./dist/client.js"
|
|
28
|
+
},
|
|
29
|
+
"./utils": {
|
|
30
|
+
"types": "./dist/utils.d.ts",
|
|
31
|
+
"import": "./dist/utils.mjs",
|
|
32
|
+
"require": "./dist/utils.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup src/index.ts src/services/*.ts src/schema.ts src/schema.zod.ts src/client.ts src/utils.ts --format cjs,esm --dts",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"lint": "eslint .",
|
|
39
|
+
"format": "eslint --fix . && prettier --write .",
|
|
40
|
+
"prepublishOnly": "npm run build && npm run typecheck || true"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^4.3.5"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"tsup": "^8.5.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Generated types from OpenAPI components.schemas
|
|
2
|
+
|
|
3
|
+
export type Enum<T> = T[keyof T];
|
|
4
|
+
|
|
5
|
+
{{~! Enums: const objects + union types ~}}
|
|
6
|
+
{{~#if (gt (len IR.modelDefs) 0)~}}
|
|
7
|
+
{{~#each IR.modelDefs~}}
|
|
8
|
+
{{~#if (eq this.schema.kind "enum")~}}
|
|
9
|
+
export const {{this.name}} = {
|
|
10
|
+
{{~#each this.schema.enumValues as |v|~}}
|
|
11
|
+
"{{v}}": {{#if (or (eq v "true") (eq v "false"))}}{{v}}{{else if (reMatch "^-?[0-9]+(\\.[0-9]+)?$" v)}}{{v}}{{else}}"{{v}}"{{/if}},
|
|
12
|
+
{{~/each~}}
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type {{this.name}} = Enum<typeof {{this.name}}>;
|
|
16
|
+
|
|
17
|
+
{{~/if~}}
|
|
18
|
+
{{~/each~}}
|
|
19
|
+
|
|
20
|
+
{{~! Objects and other named models: render interfaces/types from structured IR ~}}
|
|
21
|
+
{{~#each IR.modelDefs~}}
|
|
22
|
+
{{~#if this.annotations.description~}}
|
|
23
|
+
/**
|
|
24
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
25
|
+
*/
|
|
26
|
+
{{~/if~}}
|
|
27
|
+
{{~#if (eq this.schema.kind "object")~}}
|
|
28
|
+
export interface {{this.name}} {
|
|
29
|
+
{{~#each this.schema.properties~}}
|
|
30
|
+
{{~#if this.annotations.description~}}
|
|
31
|
+
/** {{decodeHtml (replace this.annotations.description "*/" "*\\/")}} */
|
|
32
|
+
{{~/if~}}
|
|
33
|
+
{{quotePropName this.name}}{{#unless this.required}}?{{/unless}}: {{tsTypeStripNs this.type}};
|
|
34
|
+
{{~/each~}}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
{{~else if (eq this.schema.kind "ref")~}}
|
|
38
|
+
export type {{this.name}} = {{tsTypeStripNs this.schema}};
|
|
39
|
+
|
|
40
|
+
{{~else if (or (eq this.schema.kind "oneOf") (eq this.schema.kind "anyOf") (eq this.schema.kind "allOf"))~}}
|
|
41
|
+
export type {{this.name}} = {{tsTypeStripNs this.schema}};
|
|
42
|
+
|
|
43
|
+
{{~else if (or (eq this.schema.kind "array") (eq this.schema.kind "string") (eq this.schema.kind "number") (eq this.schema.kind "integer") (eq this.schema.kind "boolean") (eq this.schema.kind "null") (eq this.schema.kind "enum"))~}}
|
|
44
|
+
export type {{this.name}} = {{tsTypeStripNs this.schema}};
|
|
45
|
+
|
|
46
|
+
{{~/if~}}
|
|
47
|
+
{{~/each~}}
|
|
48
|
+
{{~/if~}}
|
|
49
|
+
|
|
50
|
+
{{#if IR.services}}
|
|
51
|
+
|
|
52
|
+
// Operation query parameter interfaces
|
|
53
|
+
|
|
54
|
+
{{#each IR.services}}
|
|
55
|
+
{{#each this.operations}}
|
|
56
|
+
{{#if (gt (len this.queryParams) 0)}}
|
|
57
|
+
/**
|
|
58
|
+
* Query params for {{../tag}}.{{pascal (methodName this)}}
|
|
59
|
+
{{~#if this.description~}}
|
|
60
|
+
*
|
|
61
|
+
* {{decodeHtml (replace this.description "*/" "*\\/")}}
|
|
62
|
+
{{~/if~}}
|
|
63
|
+
*/
|
|
64
|
+
export interface {{pascal ../tag}}{{pascal (methodName this)}}Query {
|
|
65
|
+
{{~#each this.queryParams~}}
|
|
66
|
+
{{~#if this.description~}}
|
|
67
|
+
/** {{decodeHtml (replace this.description "*/" "*\\/")}} */
|
|
68
|
+
{{~/if~}}
|
|
69
|
+
{{quotePropName this.name}}{{#unless this.required}}?{{/unless}}: {{tsTypeStripNs this.schema}};
|
|
70
|
+
{{~/each~}}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
{{/if}}
|
|
74
|
+
{{/each}}
|
|
75
|
+
{{/each}}
|
|
76
|
+
{{/if}}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Generated Zod schemas from OpenAPI components.schemas
|
|
2
|
+
// Use these schemas for runtime validation in forms, API requests, etc.
|
|
3
|
+
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import * as Schema from "./schema";
|
|
6
|
+
|
|
7
|
+
{{#if (gt (len IR.modelDefs) 0)}}
|
|
8
|
+
{{~#each IR.modelDefs}}
|
|
9
|
+
{{~#if (eq this.schema.kind "enum")~}}
|
|
10
|
+
/**
|
|
11
|
+
* Zod schema for {{this.name}}
|
|
12
|
+
{{~#if this.annotations.description~}}
|
|
13
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
14
|
+
{{~/if~}}
|
|
15
|
+
*/
|
|
16
|
+
export const {{this.name}}Schema = z.enum([
|
|
17
|
+
{{~#each this.schema.enumValues as |v|~}}
|
|
18
|
+
{{#if (or (eq v "true") (eq v "false"))}}{{v}}{{else if (reMatch "^-?[0-9]+(\\.[0-9]+)?$" v)}}{{v}}{{else}}{{json v}}{{/if}}{{#unless @last}},{{/unless}}
|
|
19
|
+
{{~/each~}}
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
{{~else if (eq this.schema.kind "object")~}}
|
|
23
|
+
/**
|
|
24
|
+
* Zod schema for {{this.name}}
|
|
25
|
+
{{~#if this.annotations.description~}}
|
|
26
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
27
|
+
{{~/if~}}
|
|
28
|
+
*/
|
|
29
|
+
export const {{this.name}}Schema = z.object({
|
|
30
|
+
{{~#each this.schema.properties~}}
|
|
31
|
+
{{~#if this.annotations.description~}}
|
|
32
|
+
/** {{decodeHtml (replace this.annotations.description "*/" "*\\/")}} */
|
|
33
|
+
{{~/if~}}
|
|
34
|
+
{{quotePropName this.name}}: {{zodSchema this.type}}{{#unless this.required}}.optional(){{/unless}},
|
|
35
|
+
{{~/each~}}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
{{~else if (eq this.schema.kind "ref")~}}
|
|
39
|
+
/**
|
|
40
|
+
* Zod schema for {{this.name}}
|
|
41
|
+
*/
|
|
42
|
+
export const {{this.name}}Schema = Schema.{{this.schema.ref}}Schema;
|
|
43
|
+
|
|
44
|
+
{{~else if (eq this.schema.kind "oneOf")~}}
|
|
45
|
+
/**
|
|
46
|
+
* Zod schema for {{this.name}}
|
|
47
|
+
{{~#if this.annotations.description~}}
|
|
48
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
49
|
+
{{~/if~}}
|
|
50
|
+
*/
|
|
51
|
+
export const {{this.name}}Schema = z.union([
|
|
52
|
+
{{~#each this.schema.oneOf as |opt|~}}
|
|
53
|
+
{{zodSchema opt}}{{#unless @last}},{{/unless}}
|
|
54
|
+
{{~/each~}}
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
{{~else if (eq this.schema.kind "anyOf")~}}
|
|
58
|
+
/**
|
|
59
|
+
* Zod schema for {{this.name}}
|
|
60
|
+
{{~#if this.annotations.description~}}
|
|
61
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
62
|
+
{{~/if~}}
|
|
63
|
+
*/
|
|
64
|
+
export const {{this.name}}Schema = z.union([
|
|
65
|
+
{{~#each this.schema.anyOf as |opt|~}}
|
|
66
|
+
{{zodSchema opt}}{{#unless @last}},{{/unless}}
|
|
67
|
+
{{~/each~}}
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
{{~else if (eq this.schema.kind "allOf")~}}
|
|
71
|
+
/**
|
|
72
|
+
* Zod schema for {{this.name}}
|
|
73
|
+
{{~#if this.annotations.description~}}
|
|
74
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
75
|
+
{{~/if~}}
|
|
76
|
+
*/
|
|
77
|
+
export const {{this.name}}Schema = {{#each this.schema.allOf}}{{#if @first}}{{zodSchema this}}{{else}}.and({{zodSchema this}}){{/if}}{{/each}};
|
|
78
|
+
|
|
79
|
+
{{~else if (eq this.schema.kind "array")~}}
|
|
80
|
+
/**
|
|
81
|
+
* Zod schema for {{this.name}}
|
|
82
|
+
{{~#if this.annotations.description~}}
|
|
83
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
84
|
+
{{~/if~}}
|
|
85
|
+
*/
|
|
86
|
+
export const {{this.name}}Schema = z.array({{#if this.schema.items}}{{zodSchema this.schema.items}}{{else}}z.unknown(){{/if}});
|
|
87
|
+
|
|
88
|
+
{{~else~}}
|
|
89
|
+
/**
|
|
90
|
+
* Zod schema for {{this.name}}
|
|
91
|
+
{{~#if this.annotations.description~}}
|
|
92
|
+
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
93
|
+
{{~/if~}}
|
|
94
|
+
*/
|
|
95
|
+
export const {{this.name}}Schema = {{zodSchema this.schema}};
|
|
96
|
+
|
|
97
|
+
{{~/if~}}
|
|
98
|
+
{{~/each~}}
|
|
99
|
+
{{~/if~}}
|
|
100
|
+
|
|
101
|
+
{{#if IR.services}}
|
|
102
|
+
|
|
103
|
+
// Operation query parameter Zod schemas
|
|
104
|
+
|
|
105
|
+
{{#each IR.services}}
|
|
106
|
+
{{#each this.operations}}
|
|
107
|
+
{{#if (gt (len this.queryParams) 0)}}
|
|
108
|
+
/**
|
|
109
|
+
* Zod schema for query params of {{../tag}}.{{pascal (methodName this)}}
|
|
110
|
+
{{~#if this.description~}}
|
|
111
|
+
* {{decodeHtml (replace this.description "*/" "*\\/")}}
|
|
112
|
+
{{~/if~}}
|
|
113
|
+
*/
|
|
114
|
+
export const {{pascal ../tag}}{{pascal (methodName this)}}QuerySchema = z.object({
|
|
115
|
+
{{~#each this.queryParams~}}
|
|
116
|
+
{{~#if this.description~}}
|
|
117
|
+
/** {{decodeHtml (replace this.description "*/" "*\\/")}} */
|
|
118
|
+
{{~/if~}}
|
|
119
|
+
{{quotePropName this.name}}: {{zodSchema this.schema}}{{#unless this.required}}.optional(){{/unless}},
|
|
120
|
+
{{~/each~}}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
{{/if}}
|
|
124
|
+
{{/each}}
|
|
125
|
+
{{/each}}
|
|
126
|
+
{{/if}}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { CoreClient } from "../client";
|
|
2
|
+
import * as Schema from "../schema";
|
|
3
|
+
|
|
4
|
+
export class {{serviceName Service.tag}} {
|
|
5
|
+
constructor(private core: CoreClient) {}
|
|
6
|
+
{{#each Service.operations}}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* {{this.method}} {{this.path}}
|
|
10
|
+
{{~#if summary~}}
|
|
11
|
+
*
|
|
12
|
+
* @summary {{decodeHtml (replace summary "*/" "*\\/")}}
|
|
13
|
+
{{~else if response.description~}}
|
|
14
|
+
*
|
|
15
|
+
* @returns {{decodeHtml (replace response.description "*/" "*\\/")}}
|
|
16
|
+
{{~/if~}}
|
|
17
|
+
{{~#if description~}}
|
|
18
|
+
*
|
|
19
|
+
* @description {{decodeHtml (replace description "*/" "*\\/")}}
|
|
20
|
+
{{~/if~}}
|
|
21
|
+
*/
|
|
22
|
+
{{#if (isStreaming this)}}
|
|
23
|
+
async *{{methodName this}}(
|
|
24
|
+
{{#each (methodSignature this) as |param|}}
|
|
25
|
+
{{param}}{{#unless @last}},{{/unless}}
|
|
26
|
+
{{/each}}
|
|
27
|
+
): AsyncGenerator<{{{streamingItemType this}}}, void, unknown> {
|
|
28
|
+
yield* this.core.requestStream({
|
|
29
|
+
method: "{{this.method}}",
|
|
30
|
+
path: {{pathTemplate this}},
|
|
31
|
+
{{#if (gt (len queryParams) 0)}}
|
|
32
|
+
query,
|
|
33
|
+
{{/if}}
|
|
34
|
+
{{#if requestBody}}
|
|
35
|
+
{{#if (eq requestBody.contentType "application/json")}}
|
|
36
|
+
headers: {
|
|
37
|
+
...(init?.headers || {}),
|
|
38
|
+
"content-type": "application/json",
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
{{else if (eq requestBody.contentType "multipart/form-data")}}
|
|
42
|
+
body: (body as any),
|
|
43
|
+
{{else if (eq requestBody.contentType "application/x-www-form-urlencoded")}}
|
|
44
|
+
headers: {
|
|
45
|
+
...(init?.headers || {}),
|
|
46
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
47
|
+
},
|
|
48
|
+
body: (body as any),
|
|
49
|
+
{{else}}
|
|
50
|
+
body: (body as any),
|
|
51
|
+
{{/if}}
|
|
52
|
+
{{/if}}
|
|
53
|
+
contentType: "{{response.contentType}}",
|
|
54
|
+
streamingFormat: "{{response.streamingFormat}}",
|
|
55
|
+
...(init || {}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
{{else}}
|
|
59
|
+
{{methodName this}}(
|
|
60
|
+
{{#each (methodSignature this) as |param|}}
|
|
61
|
+
{{param}}{{#unless @last}},{{/unless}}
|
|
62
|
+
{{/each}}
|
|
63
|
+
): Promise<{{{tsType response.schema}}}> {
|
|
64
|
+
return this.core.request({
|
|
65
|
+
method: "{{this.method}}",
|
|
66
|
+
path: {{pathTemplate this}},
|
|
67
|
+
{{#if (gt (len queryParams) 0)}}
|
|
68
|
+
query,
|
|
69
|
+
{{/if}}
|
|
70
|
+
{{#if requestBody}}
|
|
71
|
+
{{#if (eq requestBody.contentType "application/json")}}
|
|
72
|
+
headers: {
|
|
73
|
+
...(init?.headers || {}),
|
|
74
|
+
"content-type": "application/json",
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify(body),
|
|
77
|
+
{{else if (eq requestBody.contentType "multipart/form-data")}}
|
|
78
|
+
body: (body as any),
|
|
79
|
+
{{else if (eq requestBody.contentType "application/x-www-form-urlencoded")}}
|
|
80
|
+
headers: {
|
|
81
|
+
...(init?.headers || {}),
|
|
82
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
83
|
+
},
|
|
84
|
+
body: (body as any),
|
|
85
|
+
{{else}}
|
|
86
|
+
body: (body as any),
|
|
87
|
+
{{/if}}
|
|
88
|
+
{{/if}}
|
|
89
|
+
...(init || {}),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
{{/if}}
|
|
93
|
+
{{#if ../Client.includeQueryKeys}}
|
|
94
|
+
/**
|
|
95
|
+
* @summary Get query keys for {{methodName this}}
|
|
96
|
+
* @returns [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}]
|
|
97
|
+
*/
|
|
98
|
+
{{methodName this}}__queryKeys(
|
|
99
|
+
{{~#each (methodSignatureNoInit this) as |param|~}}
|
|
100
|
+
{{param}}{{#unless @last}},{{/unless}}
|
|
101
|
+
{{~/each~}}
|
|
102
|
+
) {
|
|
103
|
+
return [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}] as const;
|
|
104
|
+
}
|
|
105
|
+
{{/if}}
|
|
106
|
+
{{/each}}
|
|
107
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rootDir": "./src",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"target": "ES2023",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"outDir": "./dist",
|
|
16
|
+
"baseUrl": "./src",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"strictNullChecks": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"noImplicitAny": false,
|
|
22
|
+
"strictBindCallApply": false,
|
|
23
|
+
"noFallthroughCasesInSwitch": false,
|
|
24
|
+
"useDefineForClassFields": false
|
|
25
|
+
},
|
|
26
|
+
"include": ["src/**/*"],
|
|
27
|
+
"exclude": ["node_modules", "dist"]
|
|
28
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export type PaginableQuery = { limit?: number; offset?: number } & Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export async function* paginate<T>(
|
|
4
|
+
fetchPage: (query?: any, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<{ data?: T[]; hasMore?: boolean; limit?: number; offset?: number }>,
|
|
5
|
+
initialQuery: PaginableQuery = {},
|
|
6
|
+
pageSize = 100,
|
|
7
|
+
): AsyncGenerator<T, void, unknown> {
|
|
8
|
+
let offset = Number(initialQuery.offset ?? 0);
|
|
9
|
+
const limit = Number(initialQuery.limit ?? pageSize);
|
|
10
|
+
// shallow copy to avoid mutating caller
|
|
11
|
+
const baseQuery: any = { ...initialQuery };
|
|
12
|
+
while (true) {
|
|
13
|
+
const page = await fetchPage({ ...baseQuery, limit, offset });
|
|
14
|
+
const items = page.data ?? [];
|
|
15
|
+
for (const item of items) {
|
|
16
|
+
yield item as T;
|
|
17
|
+
}
|
|
18
|
+
if (!page.hasMore || items.length < limit) break;
|
|
19
|
+
offset += limit;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function listAll<T>(
|
|
24
|
+
fetchPage: (query?: any, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<{ data?: T[]; hasMore?: boolean; limit?: number; offset?: number }>,
|
|
25
|
+
query: PaginableQuery = {},
|
|
26
|
+
pageSize = 100,
|
|
27
|
+
): Promise<T[]> {
|
|
28
|
+
const out: T[] = [];
|
|
29
|
+
for await (const item of paginate<T>(fetchPage, query, pageSize)) out.push(item);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse Server-Sent Events (SSE) stream
|
|
35
|
+
* Extracts data fields from text/event-stream format
|
|
36
|
+
*/
|
|
37
|
+
export async function* parseSSEStream(
|
|
38
|
+
response: Response
|
|
39
|
+
): AsyncGenerator<string, void, unknown> {
|
|
40
|
+
if (!response.body) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const reader = response.body.getReader();
|
|
45
|
+
const decoder = new TextDecoder();
|
|
46
|
+
let buffer = "";
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
while (true) {
|
|
50
|
+
const { done, value } = await reader.read();
|
|
51
|
+
if (done) break;
|
|
52
|
+
|
|
53
|
+
buffer += decoder.decode(value, { stream: true });
|
|
54
|
+
const lines = buffer.split("\n");
|
|
55
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
56
|
+
|
|
57
|
+
let currentEvent: { type?: string; data?: string; id?: string } = {};
|
|
58
|
+
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (line.trim() === "") {
|
|
61
|
+
// Empty line indicates end of event
|
|
62
|
+
if (currentEvent.data !== undefined) {
|
|
63
|
+
yield currentEvent.data;
|
|
64
|
+
}
|
|
65
|
+
currentEvent = {};
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const colonIndex = line.indexOf(":");
|
|
70
|
+
if (colonIndex === -1) continue;
|
|
71
|
+
|
|
72
|
+
const field = line.substring(0, colonIndex).trim();
|
|
73
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
74
|
+
|
|
75
|
+
if (field === "data") {
|
|
76
|
+
currentEvent.data = currentEvent.data
|
|
77
|
+
? currentEvent.data + "\n" + value
|
|
78
|
+
: value;
|
|
79
|
+
} else if (field === "event") {
|
|
80
|
+
currentEvent.type = value;
|
|
81
|
+
} else if (field === "id") {
|
|
82
|
+
currentEvent.id = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle any remaining event in buffer
|
|
88
|
+
if (buffer.trim()) {
|
|
89
|
+
const lines = buffer.split("\n");
|
|
90
|
+
let currentEvent: { data?: string } = {};
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
if (line.trim() === "" && currentEvent.data !== undefined) {
|
|
93
|
+
yield currentEvent.data;
|
|
94
|
+
currentEvent = {};
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const colonIndex = line.indexOf(":");
|
|
98
|
+
if (colonIndex !== -1) {
|
|
99
|
+
const field = line.substring(0, colonIndex).trim();
|
|
100
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
101
|
+
if (field === "data") {
|
|
102
|
+
currentEvent.data = currentEvent.data
|
|
103
|
+
? currentEvent.data + "\n" + value
|
|
104
|
+
: value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (currentEvent.data !== undefined) {
|
|
109
|
+
yield currentEvent.data;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} finally {
|
|
113
|
+
reader.releaseLock();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse NDJSON (Newline-Delimited JSON) stream
|
|
119
|
+
* Yields each JSON object as it arrives
|
|
120
|
+
*/
|
|
121
|
+
export async function* parseNDJSONStream<T = any>(
|
|
122
|
+
response: Response
|
|
123
|
+
): AsyncGenerator<T, void, unknown> {
|
|
124
|
+
if (!response.body) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const reader = response.body.getReader();
|
|
129
|
+
const decoder = new TextDecoder();
|
|
130
|
+
let buffer = "";
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
while (true) {
|
|
134
|
+
const { done, value } = await reader.read();
|
|
135
|
+
if (done) break;
|
|
136
|
+
|
|
137
|
+
buffer += decoder.decode(value, { stream: true });
|
|
138
|
+
const lines = buffer.split("\n");
|
|
139
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (trimmed === "") continue;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const parsed = JSON.parse(trimmed) as T;
|
|
147
|
+
yield parsed;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// Skip invalid JSON lines
|
|
150
|
+
console.warn("Skipping invalid JSON line:", trimmed);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle any remaining line in buffer
|
|
156
|
+
if (buffer.trim()) {
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(buffer.trim()) as T;
|
|
159
|
+
yield parsed;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// Skip invalid JSON
|
|
162
|
+
console.warn("Skipping invalid JSON in buffer:", buffer.trim());
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} finally {
|
|
166
|
+
reader.releaseLock();
|
|
167
|
+
}
|
|
168
|
+
}
|