@blimu/codegen 0.1.1 → 0.2.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/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 +101 -335
- package/dist/generator/typescript/templates/index.ts.hbs +4 -2
- package/dist/generator/typescript/templates/package.json.hbs +13 -7
- package/dist/generator/typescript/templates/schema.zod.ts.hbs +7 -8
- package/dist/generator/typescript/templates/service.ts.hbs +2 -34
- package/dist/generator/typescript/templates/tsconfig.json.hbs +10 -9
- package/dist/generator/typescript/templates/tsup.config.ts.hbs +15 -0
- package/dist/generator/typescript/templates/utils.ts.hbs +4 -136
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +19 -4
- package/dist/index.mjs.map +1 -1
- package/dist/main.js +18 -3
- package/dist/main.js.map +1 -1
- package/package.json +10 -8
|
@@ -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,21 +1,10 @@
|
|
|
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")~}}
|
|
18
|
-
{{camel this.key}}?: string;
|
|
7
|
+
{{camel this.key}}?: string | (() => string | undefined | Promise<string | undefined>);
|
|
19
8
|
{{~else if (eq this.scheme "basic")~}}
|
|
20
9
|
{{camel this.key}}?: { username: string; password: string };
|
|
21
10
|
{{~/if~}}
|
|
@@ -23,200 +12,124 @@ export type ClientOption = {
|
|
|
23
12
|
{{camel this.key}}?: string;
|
|
24
13
|
{{~/if~}}
|
|
25
14
|
{{~/each~}}
|
|
26
|
-
fetch?: typeof fetch;
|
|
27
|
-
credentials?: RequestCredentials;
|
|
28
15
|
};
|
|
29
16
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!this.cfg.baseURL) {
|
|
46
|
-
if (this.cfg.env && this.cfg.envBaseURLs) {
|
|
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
|
-
}
|
|
17
|
+
// Re-export FetchError for backward compatibility
|
|
18
|
+
export { FetchError };
|
|
19
|
+
|
|
20
|
+
export class CoreClient extends FetchClient {
|
|
21
|
+
constructor(cfg: ClientOption = {}) {
|
|
22
|
+
// Build auth strategies from OpenAPI security schemes
|
|
23
|
+
const authStrategies: AuthStrategy[] = [];
|
|
24
|
+
|
|
25
|
+
// Extract auth and security scheme properties to avoid passing them to FetchClient
|
|
26
|
+
const {
|
|
27
|
+
auth: _existingAuth{{~#each IR.securitySchemes~}},
|
|
28
|
+
{{camel this.key}}{{~/each~}},
|
|
29
|
+
...restCfg
|
|
30
|
+
} = cfg;
|
|
31
|
+
|
|
76
32
|
{{~#each IR.securitySchemes~}}
|
|
77
|
-
{{~#if (
|
|
78
|
-
|
|
79
|
-
|
|
33
|
+
{{~#if (eq this.type "http")~}}
|
|
34
|
+
{{~#if (eq this.scheme "bearer")~}}
|
|
35
|
+
if (cfg.{{camel this.key}}) {
|
|
36
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
37
|
+
if (typeof {{camel this.key}}Value === "string") {
|
|
38
|
+
authStrategies.push({
|
|
39
|
+
type: "bearer",
|
|
40
|
+
token: () => {{camel this.key}}Value,
|
|
41
|
+
});
|
|
42
|
+
} else if (typeof {{camel this.key}}Value === "function") {
|
|
43
|
+
authStrategies.push({
|
|
44
|
+
type: "bearer",
|
|
45
|
+
token: {{camel this.key}}Value as () => string | undefined | Promise<string | undefined>,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
80
48
|
}
|
|
49
|
+
{{~/if~}}
|
|
81
50
|
{{~/if~}}
|
|
82
51
|
{{~/each~}}
|
|
83
|
-
|
|
84
|
-
...(this.cfg.headers || {}),
|
|
85
|
-
...(init.headers as any),
|
|
86
|
-
});
|
|
87
|
-
// Generic access token support (optional)
|
|
88
|
-
if (this.cfg.accessToken) {
|
|
89
|
-
const token = typeof this.cfg.accessToken === 'function' ? await this.cfg.accessToken() : this.cfg.accessToken;
|
|
90
|
-
// Only set header if token is not nullish
|
|
91
|
-
if (token != null) {
|
|
92
|
-
const name = this.cfg.headerName || 'Authorization';
|
|
93
|
-
if (name.toLowerCase() === 'authorization') headers.set(name, `Bearer ${String(token)}`);
|
|
94
|
-
else headers.set(name, String(token));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
52
|
+
|
|
97
53
|
{{~#each IR.securitySchemes~}}
|
|
98
54
|
{{~#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}`);
|
|
55
|
+
{{~#if (eq this.scheme "basic")~}}
|
|
56
|
+
if (cfg.{{camel this.key}}) {
|
|
57
|
+
authStrategies.push({
|
|
58
|
+
type: "basic",
|
|
59
|
+
username: cfg.{{camel this.key}}.username,
|
|
60
|
+
password: cfg.{{camel this.key}}.password,
|
|
61
|
+
});
|
|
110
62
|
}
|
|
111
63
|
{{~/if~}}
|
|
112
|
-
|
|
64
|
+
{{~else if (eq this.type "apiKey")~}}
|
|
113
65
|
{{~#if (eq this.in "header")~}}
|
|
114
|
-
if (
|
|
115
|
-
|
|
66
|
+
if (cfg?.{{camel this.key}}) {
|
|
67
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
68
|
+
authStrategies.push({
|
|
69
|
+
type: "apiKey",
|
|
70
|
+
key: () => {{camel this.key}}Value,
|
|
71
|
+
location: "header",
|
|
72
|
+
name: "{{this.name}}",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
{{~else if (eq this.in "query")~}}
|
|
76
|
+
if (cfg?.{{camel this.key}}) {
|
|
77
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
78
|
+
authStrategies.push({
|
|
79
|
+
type: "apiKey",
|
|
80
|
+
key: () => {{camel this.key}}Value,
|
|
81
|
+
location: "query",
|
|
82
|
+
name: "{{this.name}}",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
116
85
|
{{~else if (eq this.in "cookie")~}}
|
|
117
|
-
if (
|
|
118
|
-
|
|
86
|
+
if (cfg?.{{camel this.key}}) {
|
|
87
|
+
const {{camel this.key}}Value = cfg.{{camel this.key}};
|
|
88
|
+
authStrategies.push({
|
|
89
|
+
type: "apiKey",
|
|
90
|
+
key: () => {{camel this.key}}Value,
|
|
91
|
+
location: "cookie",
|
|
92
|
+
name: "{{this.name}}",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
119
95
|
{{~/if~}}
|
|
120
96
|
{{~/if~}}
|
|
121
97
|
{{~/each~}}
|
|
122
98
|
|
|
123
|
-
const doFetch = async (attempt: number) => {
|
|
124
|
-
// Clone init to prevent mutations from affecting concurrent requests
|
|
125
|
-
// Create a new Headers object for each request to avoid sharing references
|
|
126
|
-
const requestHeaders = new Headers(headers);
|
|
127
|
-
const fetchInit: RequestInit & {
|
|
128
|
-
path: string;
|
|
129
|
-
method: string;
|
|
130
|
-
query?: Record<string, any>;
|
|
131
|
-
headers: Headers;
|
|
132
|
-
} = {
|
|
133
|
-
...init,
|
|
134
|
-
headers: requestHeaders,
|
|
135
|
-
};
|
|
136
|
-
// Set credentials from config if provided (can be overridden by onRequest)
|
|
137
|
-
if (this.cfg.credentials !== undefined) {
|
|
138
|
-
fetchInit.credentials = this.cfg.credentials;
|
|
139
|
-
}
|
|
140
|
-
if (this.cfg.onRequest) await this.cfg.onRequest({ url: url.toString(), init: fetchInit, attempt });
|
|
141
|
-
let controller: AbortController | undefined;
|
|
142
|
-
let timeoutId: any;
|
|
143
|
-
const existingSignal = fetchInit.signal;
|
|
144
|
-
|
|
145
|
-
if (this.cfg.timeoutMs && typeof AbortController !== 'undefined') {
|
|
146
|
-
controller = new AbortController();
|
|
147
99
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
100
|
+
// Build final auth config (merge existing with new strategies)
|
|
101
|
+
const finalAuthStrategies = [
|
|
102
|
+
...(_existingAuth?.strategies || []),
|
|
103
|
+
...authStrategies,
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Build fetchConfig, ensuring auth comes after restCfg spread to override any existing auth
|
|
107
|
+
const fetchConfig: FetchClientConfig = {
|
|
108
|
+
...restCfg,
|
|
109
|
+
baseURL: cfg.baseURL ?? "{{Client.defaultBaseURL}}",
|
|
110
|
+
// Explicitly set auth after restCfg to ensure it's not overwritten
|
|
111
|
+
// (restCfg might have an auth property that we want to replace)
|
|
112
|
+
...(finalAuthStrategies.length > 0
|
|
113
|
+
? {
|
|
114
|
+
auth: {
|
|
115
|
+
strategies: finalAuthStrategies,
|
|
116
|
+
},
|
|
159
117
|
}
|
|
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
|
-
}
|
|
118
|
+
: {}),
|
|
119
|
+
// Hooks are passed through directly from FetchClientConfig (no mapping needed)
|
|
193
120
|
};
|
|
194
121
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const retryOn = this.cfg.retry?.retryOn ?? [429, 500, 502, 503, 504];
|
|
122
|
+
super(fetchConfig);
|
|
123
|
+
}
|
|
198
124
|
|
|
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
|
-
}
|
|
125
|
+
async request(
|
|
126
|
+
init: RequestInit & {
|
|
127
|
+
path: string;
|
|
128
|
+
method: string;
|
|
129
|
+
query?: Record<string, any>;
|
|
218
130
|
}
|
|
219
|
-
|
|
131
|
+
) {
|
|
132
|
+
return await super.request(init);
|
|
220
133
|
}
|
|
221
134
|
|
|
222
135
|
async *requestStream<T = any>(
|
|
@@ -228,153 +141,6 @@ export class CoreClient {
|
|
|
228
141
|
streamingFormat?: "sse" | "ndjson" | "chunked";
|
|
229
142
|
}
|
|
230
143
|
): 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
|
-
}
|
|
144
|
+
yield* super.requestStream(init);
|
|
378
145
|
}
|
|
379
146
|
}
|
|
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,37 +3,43 @@
|
|
|
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
|
|
42
|
+
"build": "tsup",
|
|
37
43
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
44
|
"lint": "eslint .",
|
|
39
45
|
"format": "eslint --fix . && prettier --write .",
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
// Generated
|
|
1
|
+
// Generated zod schemas from OpenAPI components.schemas
|
|
2
2
|
// Use these schemas for runtime validation in forms, API requests, etc.
|
|
3
3
|
|
|
4
|
-
import { z } from
|
|
5
|
-
import * as Schema from "./schema";
|
|
4
|
+
import { z } from 'zod';
|
|
6
5
|
|
|
7
6
|
{{! Simple types: generate as regular Zod schema exports (not in namespace) }}
|
|
8
7
|
{{#each IR.modelDefs}}
|
|
9
8
|
{{#if (or (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"))}}
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
10
|
+
* Schema for {{this.name}}
|
|
12
11
|
{{#if this.annotations.description}}
|
|
13
12
|
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
14
13
|
{{/if}}
|
|
@@ -54,7 +53,7 @@ export const {{this.name}}Schema = z.object({
|
|
|
54
53
|
/**
|
|
55
54
|
* Zod schema for {{this.name}}
|
|
56
55
|
*/
|
|
57
|
-
export const {{this.name}}Schema =
|
|
56
|
+
export const {{this.name}}Schema = {{zodSchema this.schema}};
|
|
58
57
|
|
|
59
58
|
{{~else if (eq this.schema.kind "oneOf")~}}
|
|
60
59
|
/**
|
|
@@ -89,7 +88,7 @@ export const {{this.name}}Schema = z.union([
|
|
|
89
88
|
* {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
|
|
90
89
|
{{~/if~}}
|
|
91
90
|
*/
|
|
92
|
-
export const {{this.name}}Schema = {{#each this.schema.allOf}}{{#if @first}}{{zodSchema this}}{{else}}.
|
|
91
|
+
export const {{this.name}}Schema = {{#each this.schema.allOf}}{{#if @first}}{{zodSchema this}}{{else}}.extend({{zodSchema this}}.shape){{/if}}{{/each}};
|
|
93
92
|
|
|
94
93
|
{{~else if (eq this.schema.kind "array")~}}
|
|
95
94
|
/**
|
|
@@ -116,13 +115,13 @@ export const {{this.name}}Schema = {{zodSchema this.schema}};
|
|
|
116
115
|
|
|
117
116
|
{{#if IR.services}}
|
|
118
117
|
|
|
119
|
-
// Operation query parameter
|
|
118
|
+
// Operation query parameter schemas
|
|
120
119
|
|
|
121
120
|
{{#each IR.services}}
|
|
122
121
|
{{#each this.operations}}
|
|
123
122
|
{{#if (gt (len this.queryParams) 0)}}
|
|
124
123
|
/**
|
|
125
|
-
*
|
|
124
|
+
* Schema for query params of {{../tag}}.{{pascal (methodName this)}}
|
|
126
125
|
{{~#if this.description~}}
|
|
127
126
|
* {{decodeHtml (replace this.description "*/" "*\\/")}}
|
|
128
127
|
{{~/if~}}
|