@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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "es5",
4
+ "singleQuote": true,
5
+ "printWidth": 80,
6
+ "tabWidth": 2,
7
+ "useTabs": false
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
- // Environment-based baseURL (optional)
24
- env: 'sandbox',
25
- envBaseURLs: { sandbox: 'https://api-sandbox.example.com', production: 'https://api.example.com' },
26
- // Auth (generic API Key or Bearer header)
27
- accessToken: process.env.API_TOKEN,
28
- headerName: 'access_token', // or 'Authorization' (defaults to Authorization: Bearer <token>)
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
- {{~#if IR.securitySchemes~}}
2
- export type ClientOption = {
3
- baseURL?: string;
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 class FetchError<T = unknown> extends Error {
31
- constructor(
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
- }
41
-
42
- export class CoreClient {
43
- constructor(private cfg: ClientOption = {}) {
44
- // Set default base URL if not provided
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 (and (eq this.type "apiKey") (eq this.in "query"))~}}
78
- if (this.cfg.{{camel this.key}}) {
79
- url.searchParams.set("{{this.name}}", String(this.cfg.{{camel this.key}}));
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
- const headers = new Headers({
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 "bearer")~}}
100
- const {{camel this.key}}Key = "{{camel this.key}}";
101
- if (this.cfg[{{camel this.key}}Key])
102
- headers.set("Authorization", `Bearer ${this.cfg[{{camel this.key}}Key]}`);
103
- {{~else if (eq this.scheme "basic")~}}
104
- const {{camel this.key}}Key = "{{camel this.key}}";
105
- if (this.cfg[{{camel this.key}}Key]) {
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
- {{~else if (eq this.type "apiKey")~}}
64
+ {{~else if (eq this.type "apiKey")~}}
113
65
  {{~#if (eq this.in "header")~}}
114
- if (this.cfg?.{{camel this.key}})
115
- headers.set("{{this.name}}", String(this.cfg?.{{camel this.key}}));
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 (this.cfg?.{{camel this.key}})
118
- headers.set("Cookie", `${"{{this.name}}"}=${String(this.cfg?.{{camel this.key}})}`);
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
- // 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
- });
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
- const retries = this.cfg.retry?.retries ?? 0;
196
- const baseBackoff = this.cfg.retry?.backoffMs ?? 300;
197
- const retryOn = this.cfg.retry?.retryOn ?? [429, 500, 502, 503, 504];
122
+ super(fetchConfig);
123
+ }
198
124
 
199
- let lastError: unknown;
200
- for (let attempt = 0; attempt <= retries; attempt++) {
201
- try {
202
- return await doFetch(attempt);
203
- } catch (err: any) {
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
- throw lastError as any;
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
- let normalizedPath = init.path || "";
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 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",
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 Zod schemas from OpenAPI components.schemas
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 "zod";
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
- * Zod schema for {{this.name}}
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 = Schema.{{this.schema.ref}}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}}.and({{zodSchema this}}){{/if}}{{/each}};
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 Zod schemas
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
- * Zod schema for query params of {{../tag}}.{{pascal (methodName this)}}
124
+ * Schema for query params of {{../tag}}.{{pascal (methodName this)}}
126
125
  {{~#if this.description~}}
127
126
  * {{decodeHtml (replace this.description "*/" "*\\/")}}
128
127
  {{~/if~}}