@blimu/codegen 0.1.0 → 0.2.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 +0 -1
- package/dist/generator/typescript/templates/.prettierrc.hbs +8 -0
- package/dist/generator/typescript/templates/README.md.hbs +10 -7
- package/dist/generator/typescript/templates/client.ts.hbs +108 -332
- package/dist/generator/typescript/templates/index.ts.hbs +4 -2
- package/dist/generator/typescript/templates/package.json.hbs +18 -9
- package/dist/generator/typescript/templates/schema.ts.hbs +51 -27
- package/dist/generator/typescript/templates/schema.zod.ts.hbs +22 -6
- package/dist/generator/typescript/templates/service.ts.hbs +8 -34
- package/dist/generator/typescript/templates/tsconfig.json.hbs +1 -2
- package/dist/generator/typescript/templates/utils.ts.hbs +4 -136
- package/dist/index.d.mts +37 -25
- package/dist/index.d.ts +37 -25
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/index.mjs.map +1 -1
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -135,7 +135,6 @@ const config = await loadConfig("./chunkflow-codegen.config.mjs");
|
|
|
135
135
|
- `postCommand` (optional): Commands to run after SDK generation
|
|
136
136
|
- `defaultBaseURL` (optional): Default base URL for the client
|
|
137
137
|
- `exclude` (optional): Array of file paths to exclude from generation
|
|
138
|
-
- `typeAugmentation` (optional): Options for type augmentation generators
|
|
139
138
|
|
|
140
139
|
## License
|
|
141
140
|
|
|
@@ -19,13 +19,16 @@ import { {{pascal Client.name}}Client } from '{{Client.packageName}}';
|
|
|
19
19
|
const client = new {{pascal Client.name}}Client({
|
|
20
20
|
baseURL: '{{Client.defaultBaseURL}}',
|
|
21
21
|
timeoutMs: 10000,
|
|
22
|
-
retry: { retries: 2, backoffMs: 300, retryOn: [429, 500, 502, 503, 504] },
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
retry: { retries: 2, strategy: 'exponential', backoffMs: 300, retryOn: [429, 500, 502, 503, 504] },
|
|
23
|
+
// Auth configuration
|
|
24
|
+
auth: {
|
|
25
|
+
strategies: [
|
|
26
|
+
{
|
|
27
|
+
type: 'bearer',
|
|
28
|
+
token: process.env.API_TOKEN,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
{{#each IR.services}}
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
headers?: Record<string, string>;
|
|
5
|
-
timeoutMs?: number;
|
|
6
|
-
retry?: { retries: number; backoffMs: number; retryOn?: number[] };
|
|
7
|
-
onRequest?: (ctx: { url: string; init: RequestInit & { path: string; method: string; query?: Record<string, any>; headers: Headers }; attempt: number }) => void | Promise<void>;
|
|
8
|
-
onResponse?: (ctx: { url: string; init: RequestInit & { path: string; method: string; query?: Record<string, any>; headers: Headers }; attempt: number; response: Response }) => void | Promise<void>;
|
|
9
|
-
onError?: (err: unknown, ctx: { url: string; init: RequestInit & { path: string; method: string; query?: Record<string, any> }; attempt: number }) => void | Promise<void>;
|
|
10
|
-
// Environment & Auth
|
|
11
|
-
env?: 'sandbox' | 'production';
|
|
12
|
-
envBaseURLs?: { sandbox: string; production: string };
|
|
13
|
-
accessToken?: string | undefined | (() => string | undefined | Promise<string | undefined>);
|
|
14
|
-
headerName?: string;
|
|
1
|
+
import { FetchClient, FetchError, type FetchClientConfig, type AuthStrategy } from "@blimu/fetch";
|
|
2
|
+
|
|
3
|
+
export type ClientOption = FetchClientConfig & {
|
|
15
4
|
{{~#each IR.securitySchemes~}}
|
|
16
5
|
{{~#if (eq this.type "http")~}}
|
|
17
6
|
{{~#if (eq this.scheme "bearer")~}}
|
|
@@ -23,200 +12,134 @@ export type ClientOption = {
|
|
|
23
12
|
{{camel this.key}}?: string;
|
|
24
13
|
{{~/if~}}
|
|
25
14
|
{{~/each~}}
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
// Generic accessToken support (alias for bearer auth)
|
|
16
|
+
accessToken?: string | (() => string | undefined | Promise<string | undefined>);
|
|
28
17
|
};
|
|
29
18
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
message: string,
|
|
33
|
-
readonly status: number,
|
|
34
|
-
readonly data?: T,
|
|
35
|
-
readonly headers?: Headers,
|
|
36
|
-
) {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = "FetchError";
|
|
39
|
-
}
|
|
40
|
-
}
|
|
19
|
+
// Re-export FetchError for backward compatibility
|
|
20
|
+
export { FetchError };
|
|
41
21
|
|
|
42
|
-
export class CoreClient {
|
|
43
|
-
constructor(
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.cfg.baseURL = this.cfg.env === 'production' ? this.cfg.envBaseURLs.production : this.cfg.envBaseURLs.sandbox;
|
|
48
|
-
} else {
|
|
49
|
-
this.cfg.baseURL = "{{Client.defaultBaseURL}}";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
setAccessToken(token: string | undefined | (() => string | undefined | Promise<string | undefined>)) {
|
|
54
|
-
this.cfg.accessToken = token;
|
|
55
|
-
}
|
|
56
|
-
async request(
|
|
57
|
-
init: RequestInit & {
|
|
58
|
-
path: string;
|
|
59
|
-
method: string;
|
|
60
|
-
query?: Record<string, any>;
|
|
61
|
-
}
|
|
62
|
-
) {
|
|
63
|
-
let normalizedPath = init.path || "";
|
|
64
|
-
if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
|
|
65
|
-
normalizedPath = normalizedPath.slice(0, -1);
|
|
66
|
-
}
|
|
67
|
-
const url = new URL((this.cfg.baseURL || "") + normalizedPath);
|
|
68
|
-
if (init.query) {
|
|
69
|
-
Object.entries(init.query).forEach(([k, v]) => {
|
|
70
|
-
if (v === undefined || v === null) return;
|
|
71
|
-
if (Array.isArray(v))
|
|
72
|
-
v.forEach((vv) => url.searchParams.append(k, String(vv)));
|
|
73
|
-
else url.searchParams.set(k, String(v));
|
|
74
|
-
});
|
|
75
|
-
}
|
|
22
|
+
export class CoreClient extends FetchClient {
|
|
23
|
+
constructor(cfg: ClientOption = {}) {
|
|
24
|
+
// Build auth strategies from OpenAPI security schemes
|
|
25
|
+
const authStrategies: AuthStrategy[] = [];
|
|
26
|
+
|
|
76
27
|
{{~#each IR.securitySchemes~}}
|
|
77
|
-
{{~#if (
|
|
78
|
-
|
|
79
|
-
|
|
28
|
+
{{~#if (eq this.type "http")~}}
|
|
29
|
+
{{~#if (eq this.scheme "bearer")~}}
|
|
30
|
+
if (cfg.{{camel this.key}}) {
|
|
31
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
32
|
+
authStrategies.push({
|
|
33
|
+
type: "bearer",
|
|
34
|
+
token: () => {{camel this.key}}Value,
|
|
35
|
+
});
|
|
80
36
|
}
|
|
37
|
+
{{~/if~}}
|
|
81
38
|
{{~/if~}}
|
|
82
39
|
{{~/each~}}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
40
|
+
// Support accessToken as an alias for bearer auth (only if no bearer auth was already added)
|
|
41
|
+
if (!authStrategies.some((s: any) => s.type === "bearer") && cfg.accessToken) {
|
|
42
|
+
const accessTokenValue = cfg.accessToken;
|
|
43
|
+
if (typeof accessTokenValue === "string") {
|
|
44
|
+
authStrategies.push({
|
|
45
|
+
type: "bearer",
|
|
46
|
+
token: () => accessTokenValue,
|
|
47
|
+
});
|
|
48
|
+
} else if (typeof accessTokenValue === "function") {
|
|
49
|
+
authStrategies.push({
|
|
50
|
+
type: "bearer",
|
|
51
|
+
token: accessTokenValue as () => string | undefined | Promise<string | undefined>,
|
|
52
|
+
});
|
|
95
53
|
}
|
|
96
54
|
}
|
|
55
|
+
|
|
97
56
|
{{~#each IR.securitySchemes~}}
|
|
98
57
|
{{~#if (eq this.type "http")~}}
|
|
99
|
-
{{~#if (eq this.scheme "
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const u = this.cfg[{{camel this.key}}Key].username;
|
|
107
|
-
const p = this.cfg[{{camel this.key}}Key].password;
|
|
108
|
-
const encoded = typeof btoa !== 'undefined' ? btoa(`${u}:${p}`) : (typeof Buffer !== 'undefined' ? Buffer.from(`${u}:${p}`).toString('base64') : '' );
|
|
109
|
-
if (encoded) headers.set("Authorization", `Basic ${encoded}`);
|
|
58
|
+
{{~#if (eq this.scheme "basic")~}}
|
|
59
|
+
if (cfg.{{camel this.key}}) {
|
|
60
|
+
authStrategies.push({
|
|
61
|
+
type: "basic",
|
|
62
|
+
username: cfg.{{camel this.key}}.username,
|
|
63
|
+
password: cfg.{{camel this.key}}.password,
|
|
64
|
+
});
|
|
110
65
|
}
|
|
111
66
|
{{~/if~}}
|
|
112
|
-
|
|
67
|
+
{{~else if (eq this.type "apiKey")~}}
|
|
113
68
|
{{~#if (eq this.in "header")~}}
|
|
114
|
-
if (
|
|
115
|
-
|
|
69
|
+
if (cfg?.{{camel this.key}}) {
|
|
70
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
71
|
+
authStrategies.push({
|
|
72
|
+
type: "apiKey",
|
|
73
|
+
key: () => {{camel this.key}}Value,
|
|
74
|
+
location: "header",
|
|
75
|
+
name: "{{this.name}}",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
{{~else if (eq this.in "query")~}}
|
|
79
|
+
if (cfg?.{{camel this.key}}) {
|
|
80
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
81
|
+
authStrategies.push({
|
|
82
|
+
type: "apiKey",
|
|
83
|
+
key: () => {{camel this.key}}Value,
|
|
84
|
+
location: "query",
|
|
85
|
+
name: "{{this.name}}",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
116
88
|
{{~else if (eq this.in "cookie")~}}
|
|
117
|
-
if (
|
|
118
|
-
|
|
89
|
+
if (cfg?.{{camel this.key}}) {
|
|
90
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
91
|
+
authStrategies.push({
|
|
92
|
+
type: "apiKey",
|
|
93
|
+
key: () => {{camel this.key}}Value,
|
|
94
|
+
location: "cookie",
|
|
95
|
+
name: "{{this.name}}",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
119
98
|
{{~/if~}}
|
|
120
99
|
{{~/if~}}
|
|
121
100
|
{{~/each~}}
|
|
122
101
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// If there's an existing signal, combine it with the timeout signal
|
|
149
|
-
// The combined controller will abort when either signal aborts
|
|
150
|
-
if (existingSignal) {
|
|
151
|
-
// If existing signal is already aborted, abort the new controller immediately
|
|
152
|
-
if (existingSignal.aborted) {
|
|
153
|
-
controller.abort();
|
|
154
|
-
} else {
|
|
155
|
-
// Listen to the existing signal and abort the combined controller when it aborts
|
|
156
|
-
existingSignal.addEventListener('abort', () => {
|
|
157
|
-
controller?.abort();
|
|
158
|
-
});
|
|
102
|
+
// Extract accessToken, auth, and security scheme properties to avoid passing them to FetchClient
|
|
103
|
+
const {
|
|
104
|
+
accessToken,
|
|
105
|
+
auth: _existingAuth{{~#each IR.securitySchemes~}},
|
|
106
|
+
{{camel this.key}}{{~/each~}},
|
|
107
|
+
...restCfg
|
|
108
|
+
} = cfg;
|
|
109
|
+
|
|
110
|
+
// Build final auth config (merge existing with new strategies)
|
|
111
|
+
const finalAuthStrategies = [
|
|
112
|
+
...(_existingAuth?.strategies || []),
|
|
113
|
+
...authStrategies,
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// Build fetchConfig, ensuring auth comes after restCfg spread to override any existing auth
|
|
117
|
+
const fetchConfig: FetchClientConfig = {
|
|
118
|
+
...restCfg,
|
|
119
|
+
baseURL: cfg.baseURL ?? "{{Client.defaultBaseURL}}",
|
|
120
|
+
// Explicitly set auth after restCfg to ensure it's not overwritten
|
|
121
|
+
// (restCfg might have an auth property that we want to replace)
|
|
122
|
+
...(finalAuthStrategies.length > 0
|
|
123
|
+
? {
|
|
124
|
+
auth: {
|
|
125
|
+
strategies: finalAuthStrategies,
|
|
126
|
+
},
|
|
159
127
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
fetchInit.signal = controller.signal;
|
|
163
|
-
timeoutId = setTimeout(() => controller?.abort(), this.cfg.timeoutMs);
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
const res = await (this.cfg.fetch || fetch)(url.toString(), fetchInit);
|
|
167
|
-
if (this.cfg.onResponse) await this.cfg.onResponse({ url: url.toString(), init: fetchInit, attempt, response: res });
|
|
168
|
-
const ct = res.headers.get("content-type") || "";
|
|
169
|
-
let parsed: any;
|
|
170
|
-
if (ct.includes("application/json")) {
|
|
171
|
-
parsed = await res.json();
|
|
172
|
-
} else if (ct.startsWith("text/")) {
|
|
173
|
-
parsed = await res.text();
|
|
174
|
-
} else {
|
|
175
|
-
// binary or unknown -> ArrayBuffer
|
|
176
|
-
parsed = await res.arrayBuffer();
|
|
177
|
-
}
|
|
178
|
-
if (!res.ok) {
|
|
179
|
-
throw new FetchError(
|
|
180
|
-
parsed?.message || `HTTP ${res.status}`,
|
|
181
|
-
res.status,
|
|
182
|
-
parsed,
|
|
183
|
-
res.headers,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return parsed as any;
|
|
187
|
-
} catch (err) {
|
|
188
|
-
if (this.cfg.onError) await this.cfg.onError(err, { url: url.toString(), init, attempt });
|
|
189
|
-
throw err;
|
|
190
|
-
} finally {
|
|
191
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
192
|
-
}
|
|
128
|
+
: {}),
|
|
129
|
+
// Hooks are passed through directly from FetchClientConfig (no mapping needed)
|
|
193
130
|
};
|
|
194
131
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const retryOn = this.cfg.retry?.retryOn ?? [429, 500, 502, 503, 504];
|
|
132
|
+
super(fetchConfig);
|
|
133
|
+
}
|
|
198
134
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Retry on network errors or configured status errors
|
|
205
|
-
const status = err?.status as number | undefined;
|
|
206
|
-
const shouldRetry = status ? retryOn.includes(status) : true;
|
|
207
|
-
if (attempt < retries && shouldRetry) {
|
|
208
|
-
const delay = baseBackoff * Math.pow(2, attempt);
|
|
209
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
210
|
-
lastError = err;
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
if (err instanceof DOMException) throw err;
|
|
214
|
-
if (err instanceof FetchError) throw err;
|
|
215
|
-
if (typeof err === 'string') throw new FetchError(err, status ?? 0);
|
|
216
|
-
throw new FetchError((err as Error)?.message || 'Network error', status ?? 0);
|
|
217
|
-
}
|
|
135
|
+
async request(
|
|
136
|
+
init: RequestInit & {
|
|
137
|
+
path: string;
|
|
138
|
+
method: string;
|
|
139
|
+
query?: Record<string, any>;
|
|
218
140
|
}
|
|
219
|
-
|
|
141
|
+
) {
|
|
142
|
+
return await super.request(init);
|
|
220
143
|
}
|
|
221
144
|
|
|
222
145
|
async *requestStream<T = any>(
|
|
@@ -228,153 +151,6 @@ export class CoreClient {
|
|
|
228
151
|
streamingFormat?: "sse" | "ndjson" | "chunked";
|
|
229
152
|
}
|
|
230
153
|
): AsyncGenerator<T, void, unknown> {
|
|
231
|
-
|
|
232
|
-
if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
|
|
233
|
-
normalizedPath = normalizedPath.slice(0, -1);
|
|
234
|
-
}
|
|
235
|
-
const url = new URL((this.cfg.baseURL || "") + normalizedPath);
|
|
236
|
-
if (init.query) {
|
|
237
|
-
Object.entries(init.query).forEach(([k, v]) => {
|
|
238
|
-
if (v === undefined || v === null) return;
|
|
239
|
-
if (Array.isArray(v))
|
|
240
|
-
v.forEach((vv) => url.searchParams.append(k, String(vv)));
|
|
241
|
-
else url.searchParams.set(k, String(v));
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
{{~#each IR.securitySchemes~}}
|
|
245
|
-
{{~#if (and (eq this.type "apiKey") (eq this.in "query"))~}}
|
|
246
|
-
if (this.cfg.{{camel this.key}}) {
|
|
247
|
-
url.searchParams.set("{{this.name}}", String(this.cfg.{{camel this.key}}));
|
|
248
|
-
}
|
|
249
|
-
{{~/if~}}
|
|
250
|
-
{{~/each~}}
|
|
251
|
-
const headers = new Headers({
|
|
252
|
-
...(this.cfg.headers || {}),
|
|
253
|
-
...(init.headers as any),
|
|
254
|
-
});
|
|
255
|
-
// Generic access token support (optional)
|
|
256
|
-
if (this.cfg.accessToken) {
|
|
257
|
-
const token = typeof this.cfg.accessToken === 'function' ? await this.cfg.accessToken() : this.cfg.accessToken;
|
|
258
|
-
// Only set header if token is not nullish
|
|
259
|
-
if (token != null) {
|
|
260
|
-
const name = this.cfg.headerName || 'Authorization';
|
|
261
|
-
if (name.toLowerCase() === 'authorization') headers.set(name, `Bearer ${String(token)}`);
|
|
262
|
-
else headers.set(name, String(token));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
{{~#each IR.securitySchemes~}}
|
|
266
|
-
{{~#if (eq this.type "http")~}}
|
|
267
|
-
{{~#if (eq this.scheme "bearer")~}}
|
|
268
|
-
const {{camel this.key}}Key = "{{camel this.key}}";
|
|
269
|
-
if (this.cfg[{{camel this.key}}Key])
|
|
270
|
-
headers.set("Authorization", `Bearer ${this.cfg[{{camel this.key}}Key]}`);
|
|
271
|
-
{{~else if (eq this.scheme "basic")~}}
|
|
272
|
-
const {{camel this.key}}Key = "{{camel this.key}}";
|
|
273
|
-
if (this.cfg[{{camel this.key}}Key]) {
|
|
274
|
-
const u = this.cfg[{{camel this.key}}Key].username;
|
|
275
|
-
const p = this.cfg[{{camel this.key}}Key].password;
|
|
276
|
-
const encoded = typeof btoa !== 'undefined' ? btoa(`${u}:${p}`) : (typeof Buffer !== 'undefined' ? Buffer.from(`${u}:${p}`).toString('base64') : '' );
|
|
277
|
-
if (encoded) headers.set("Authorization", `Basic ${encoded}`);
|
|
278
|
-
}
|
|
279
|
-
{{~/if~}}
|
|
280
|
-
{{~else if (eq this.type "apiKey")~}}
|
|
281
|
-
{{~#if (eq this.in "header")~}}
|
|
282
|
-
if (this.cfg?.{{camel this.key}})
|
|
283
|
-
headers.set("{{this.name}}", String(this.cfg?.{{camel this.key}}));
|
|
284
|
-
{{~else if (eq this.in "cookie")~}}
|
|
285
|
-
if (this.cfg?.{{camel this.key}})
|
|
286
|
-
headers.set("Cookie", `${"{{this.name}}"}=${String(this.cfg?.{{camel this.key}})}`);
|
|
287
|
-
{{~/if~}}
|
|
288
|
-
{{~/if~}}
|
|
289
|
-
{{~/each~}}
|
|
290
|
-
|
|
291
|
-
const fetchInit: RequestInit & {
|
|
292
|
-
path: string;
|
|
293
|
-
method: string;
|
|
294
|
-
query?: Record<string, any>;
|
|
295
|
-
headers: Headers;
|
|
296
|
-
} = {
|
|
297
|
-
...init,
|
|
298
|
-
headers,
|
|
299
|
-
};
|
|
300
|
-
// Set credentials from config if provided
|
|
301
|
-
if (this.cfg.credentials !== undefined) {
|
|
302
|
-
fetchInit.credentials = this.cfg.credentials;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (this.cfg.onRequest) await this.cfg.onRequest({ url: url.toString(), init: fetchInit, attempt: 0 });
|
|
306
|
-
|
|
307
|
-
let controller: AbortController | undefined;
|
|
308
|
-
let timeoutId: any;
|
|
309
|
-
const existingSignal = fetchInit.signal;
|
|
310
|
-
|
|
311
|
-
if (this.cfg.timeoutMs && typeof AbortController !== 'undefined') {
|
|
312
|
-
controller = new AbortController();
|
|
313
|
-
if (existingSignal) {
|
|
314
|
-
if (existingSignal.aborted) {
|
|
315
|
-
controller.abort();
|
|
316
|
-
} else {
|
|
317
|
-
existingSignal.addEventListener('abort', () => {
|
|
318
|
-
controller?.abort();
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
fetchInit.signal = controller.signal;
|
|
323
|
-
timeoutId = setTimeout(() => controller?.abort(), this.cfg.timeoutMs);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
const res = await (this.cfg.fetch || fetch)(url.toString(), fetchInit);
|
|
328
|
-
if (this.cfg.onResponse) await this.cfg.onResponse({ url: url.toString(), init: fetchInit, attempt: 0, response: res });
|
|
329
|
-
|
|
330
|
-
if (!res.ok) {
|
|
331
|
-
const ct = res.headers.get("content-type") || "";
|
|
332
|
-
let parsed: any;
|
|
333
|
-
if (ct.includes("application/json")) {
|
|
334
|
-
parsed = await res.json();
|
|
335
|
-
} else if (ct.startsWith("text/")) {
|
|
336
|
-
parsed = await res.text();
|
|
337
|
-
} else {
|
|
338
|
-
parsed = await res.arrayBuffer();
|
|
339
|
-
}
|
|
340
|
-
throw new FetchError(
|
|
341
|
-
parsed?.message || `HTTP ${res.status}`,
|
|
342
|
-
res.status,
|
|
343
|
-
parsed,
|
|
344
|
-
res.headers,
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Import streaming parsers
|
|
349
|
-
const { parseSSEStream, parseNDJSONStream } = await import("./utils");
|
|
350
|
-
|
|
351
|
-
// Route to appropriate parser based on streaming format
|
|
352
|
-
if (init.streamingFormat === "sse") {
|
|
353
|
-
yield* parseSSEStream(res) as AsyncGenerator<T, void, unknown>;
|
|
354
|
-
} else if (init.streamingFormat === "ndjson") {
|
|
355
|
-
yield* parseNDJSONStream<T>(res);
|
|
356
|
-
} else {
|
|
357
|
-
// Generic chunked streaming - yield raw chunks as strings
|
|
358
|
-
if (!res.body) return;
|
|
359
|
-
const reader = res.body.getReader();
|
|
360
|
-
const decoder = new TextDecoder();
|
|
361
|
-
try {
|
|
362
|
-
while (true) {
|
|
363
|
-
const { done, value } = await reader.read();
|
|
364
|
-
if (done) break;
|
|
365
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
366
|
-
yield chunk as T;
|
|
367
|
-
}
|
|
368
|
-
} finally {
|
|
369
|
-
reader.releaseLock();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
} catch (err) {
|
|
373
|
-
if (this.cfg.onError) await this.cfg.onError(err, { url: url.toString(), init, attempt: 0 });
|
|
374
|
-
throw err;
|
|
375
|
-
} finally {
|
|
376
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
377
|
-
}
|
|
154
|
+
yield* super.requestStream(init);
|
|
378
155
|
}
|
|
379
156
|
}
|
|
380
|
-
{{~/if~}}
|
|
@@ -54,8 +54,10 @@ export class {{Client.name}} {
|
|
|
54
54
|
|
|
55
55
|
export type { ClientOption };
|
|
56
56
|
|
|
57
|
-
// Export FetchError for error handling
|
|
58
|
-
export { FetchError };
|
|
57
|
+
// Export FetchError and CoreClient for error handling and advanced usage
|
|
58
|
+
export { FetchError, CoreClient };
|
|
59
|
+
// Re-export all error types from @blimu/fetch for instanceof checks
|
|
60
|
+
export * from "@blimu/fetch";
|
|
59
61
|
export const {{Client.name}}Error = FetchError;
|
|
60
62
|
|
|
61
63
|
// Re-exports for better ergonomics
|
|
@@ -3,46 +3,55 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"description": "TypeScript SDK for {{Client.name}} API (auto-generated)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
|
-
"files": ["dist/**"],
|
|
8
|
+
"files": ["dist/**", "src/**"],
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/index.d.ts",
|
|
11
12
|
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
13
15
|
},
|
|
14
16
|
"./services/*": {
|
|
15
17
|
"types": "./dist/services/*.d.ts",
|
|
16
18
|
"import": "./dist/services/*.mjs",
|
|
17
|
-
"require": "./dist/services/*.js"
|
|
19
|
+
"require": "./dist/services/*.js",
|
|
20
|
+
"default": "./dist/services/*.js"
|
|
18
21
|
},
|
|
19
22
|
"./schema": {
|
|
20
23
|
"types": "./dist/schema.d.ts",
|
|
21
24
|
"import": "./dist/schema.mjs",
|
|
22
|
-
"require": "./dist/schema.js"
|
|
25
|
+
"require": "./dist/schema.js",
|
|
26
|
+
"default": "./dist/schema.js"
|
|
23
27
|
},
|
|
24
28
|
"./client": {
|
|
25
29
|
"types": "./dist/client.d.ts",
|
|
26
30
|
"import": "./dist/client.mjs",
|
|
27
|
-
"require": "./dist/client.js"
|
|
31
|
+
"require": "./dist/client.js",
|
|
32
|
+
"default": "./dist/client.js"
|
|
28
33
|
},
|
|
29
34
|
"./utils": {
|
|
30
35
|
"types": "./dist/utils.d.ts",
|
|
31
36
|
"import": "./dist/utils.mjs",
|
|
32
|
-
"require": "./dist/utils.js"
|
|
37
|
+
"require": "./dist/utils.js",
|
|
38
|
+
"default": "./dist/utils.js"
|
|
33
39
|
}
|
|
34
40
|
},
|
|
35
41
|
"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",
|
|
42
|
+
"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 --no-splitting",
|
|
37
43
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
44
|
"lint": "eslint .",
|
|
39
45
|
"format": "eslint --fix . && prettier --write .",
|
|
40
46
|
"prepublishOnly": "npm run build && npm run typecheck || true"
|
|
41
47
|
},
|
|
42
48
|
"dependencies": {
|
|
43
|
-
|
|
49
|
+
{{#each (getAllDependencies Client)}}
|
|
50
|
+
"{{@key}}": "{{this}}"{{#unless @last}},{{/unless}}
|
|
51
|
+
{{/each}}
|
|
44
52
|
},
|
|
45
53
|
"devDependencies": {
|
|
46
|
-
"tsup": "^8.5.1"
|
|
54
|
+
"tsup": "^8.5.1"{{#if Client.devDependencies}}{{#each Client.devDependencies}},
|
|
55
|
+
"{{@key}}": "{{this}}"{{/each}}{{/if}}
|
|
47
56
|
}
|
|
48
57
|
}
|