@blimu/codegen 0.2.0 → 0.3.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.
@@ -21,14 +21,12 @@ const client = new {{pascal Client.name}}Client({
21
21
  timeoutMs: 10000,
22
22
  retry: { retries: 2, strategy: 'exponential', backoffMs: 300, retryOn: [429, 500, 502, 503, 504] },
23
23
  // Auth configuration
24
- auth: {
25
- strategies: [
26
- {
27
- type: 'bearer',
28
- token: process.env.API_TOKEN,
29
- },
30
- ],
31
- },
24
+ authStrategies: [
25
+ {
26
+ type: 'bearer',
27
+ token: process.env.API_TOKEN,
28
+ },
29
+ ],
32
30
  });
33
31
 
34
32
  {{#each IR.services}}
@@ -0,0 +1,68 @@
1
+ import type {
2
+ AuthStrategy{{~#if (hasBearerScheme IR.securitySchemes)}},
3
+ BearerAuthStrategy{{~/if~}}{{~#if (hasApiKeyScheme IR.securitySchemes)}},
4
+ ApiKeyAuthStrategy{{~/if~}}
5
+ } from '@blimu/fetch';
6
+ import type { ClientOption } from './client';
7
+
8
+ export function buildAuthStrategies(cfg: ClientOption): AuthStrategy[] {
9
+ const authStrategies: AuthStrategy[] = [...(cfg?.authStrategies || [])];
10
+
11
+ {{~#each IR.securitySchemes~}}
12
+ {{~#if (eq this.type "http")~}}
13
+ {{~#if (eq this.scheme "bearer")~}}
14
+ if (cfg.{{camel this.key}}) {
15
+ authStrategies.push({
16
+ type: 'bearer',
17
+ token: cfg.{{camel this.key}},
18
+ });
19
+ }
20
+ {{~/if~}}
21
+ {{~/if~}}
22
+ {{~/each~}}
23
+
24
+ {{~#each IR.securitySchemes~}}
25
+ {{~#if (eq this.type "http")~}}
26
+ {{~#if (eq this.scheme "basic")~}}
27
+ if (cfg.{{camel this.key}}) {
28
+ authStrategies.push({
29
+ type: 'basic',
30
+ username: cfg.{{camel this.key}}.username,
31
+ password: cfg.{{camel this.key}}.password,
32
+ });
33
+ }
34
+ {{~/if~}}
35
+ {{~else if (eq this.type "apiKey")~}}
36
+ {{~#if (eq this.in "header")~}}
37
+ if (cfg.{{camel this.key}}) {
38
+ authStrategies.push({
39
+ type: 'apiKey',
40
+ key: cfg.{{camel this.key}},
41
+ location: 'header',
42
+ name: '{{this.name}}',
43
+ });
44
+ }
45
+ {{~else if (eq this.in "query")~}}
46
+ if (cfg.{{camel this.key}}) {
47
+ authStrategies.push({
48
+ type: 'apiKey',
49
+ key: cfg.{{camel this.key}},
50
+ location: 'query',
51
+ name: '{{this.name}}',
52
+ });
53
+ }
54
+ {{~else if (eq this.in "cookie")~}}
55
+ if (cfg.{{camel this.key}}) {
56
+ authStrategies.push({
57
+ type: 'apiKey',
58
+ key: cfg.{{camel this.key}},
59
+ location: 'cookie',
60
+ name: '{{this.name}}',
61
+ });
62
+ }
63
+ {{~/if~}}
64
+ {{~/if~}}
65
+ {{~/each~}}
66
+
67
+ return authStrategies;
68
+ }
@@ -1,156 +1,88 @@
1
- import { FetchClient, FetchError, type FetchClientConfig, type AuthStrategy } from "@blimu/fetch";
1
+ import { FetchClient, FetchError } from '@blimu/fetch';
2
+ import {
3
+ type FetchClientConfig{{~#if (hasBearerScheme IR.securitySchemes)}},
4
+ type BearerAuthStrategy{{~/if~}}{{~#if (hasApiKeyScheme IR.securitySchemes)}},
5
+ type ApiKeyAuthStrategy{{~/if~}}
6
+ } from '@blimu/fetch';
7
+ import { buildAuthStrategies } from './auth-strategies';
8
+ {{~#each IR.services~}}
9
+ import { {{serviceName tag}} } from './services/{{fileBase tag}}';
10
+ {{~/each~}}
2
11
 
3
12
  export type ClientOption = FetchClientConfig & {
4
13
  {{~#each IR.securitySchemes~}}
5
14
  {{~#if (eq this.type "http")~}}
6
15
  {{~#if (eq this.scheme "bearer")~}}
7
- {{camel this.key}}?: string;
16
+ {{camel this.key}}?: BearerAuthStrategy['token'];
8
17
  {{~else if (eq this.scheme "basic")~}}
9
18
  {{camel this.key}}?: { username: string; password: string };
10
19
  {{~/if~}}
11
- {{~else if (eq this.type "apiKey")~}}
12
- {{camel this.key}}?: string;
20
+ {{~else if (eq this.type "apiKey")~}}
21
+ {{camel this.key}}?: ApiKeyAuthStrategy['key'];
13
22
  {{~/if~}}
14
23
  {{~/each~}}
15
- // Generic accessToken support (alias for bearer auth)
16
- accessToken?: string | (() => string | undefined | Promise<string | undefined>);
17
24
  };
18
25
 
19
- // Re-export FetchError for backward compatibility
20
- export { FetchError };
26
+ {{~setVar "grouped" (groupByNamespace IR.services)~}}
21
27
 
22
- export class CoreClient extends FetchClient {
23
- constructor(cfg: ClientOption = {}) {
24
- // Build auth strategies from OpenAPI security schemes
25
- const authStrategies: AuthStrategy[] = [];
26
-
27
- {{~#each IR.securitySchemes~}}
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
- });
36
- }
37
- {{~/if~}}
38
- {{~/if~}}
28
+ {{~! Generate namespace classes for non-root services ~}}
29
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
30
+ {{~#if (ne namespace "")~}}
31
+ class {{pascal namespace}}Namespace {
32
+ {{~#each services~}}
33
+ readonly {{serviceProp (getServiceName tag)}}: {{serviceName tag}};
39
34
  {{~/each~}}
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
- });
53
- }
54
- }
55
-
56
- {{~#each IR.securitySchemes~}}
57
- {{~#if (eq this.type "http")~}}
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
- });
65
- }
66
- {{~/if~}}
67
- {{~else if (eq this.type "apiKey")~}}
68
- {{~#if (eq this.in "header")~}}
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
- }
88
- {{~else if (eq this.in "cookie")~}}
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
- }
98
- {{~/if~}}
99
- {{~/if~}}
35
+
36
+ constructor(core: FetchClient) {
37
+ {{~#each services~}}
38
+ this.{{serviceProp (getServiceName tag)}} = new {{serviceName tag}}(core);
100
39
  {{~/each~}}
40
+ }
41
+ }
42
+
43
+ {{~/if~}}
44
+ {{~/each~}}
45
+
46
+ export class {{Client.name}} {
47
+ {{~! Root level services (no namespace) ~}}
48
+ {{~#each (getRootServices IR.services)~}}
49
+ readonly {{serviceProp tag}}: {{serviceName tag}};
50
+ {{~/each~}}
51
+ {{~! Namespace properties ~}}
52
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
53
+ {{~#if (ne namespace "")~}}
54
+ readonly {{serviceProp namespace}}: {{pascal namespace}}Namespace;
55
+ {{~/if~}}
56
+ {{~/each~}}
101
57
 
102
- // Extract accessToken, auth, and security scheme properties to avoid passing them to FetchClient
58
+ constructor(options?: ClientOption) {
103
59
  const {
104
- accessToken,
105
- auth: _existingAuth{{~#each IR.securitySchemes~}},
106
- {{camel this.key}}{{~/each~}},
60
+ {{~#each IR.securitySchemes~}}
61
+ {{camel this.key}},{{~/each~}}
107
62
  ...restCfg
108
- } = cfg;
63
+ } = options || {};
109
64
 
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 = {
65
+ const authStrategies = buildAuthStrategies(options || {});
66
+
67
+ const core = new FetchClient({
118
68
  ...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
- },
127
- }
128
- : {}),
129
- // Hooks are passed through directly from FetchClientConfig (no mapping needed)
130
- };
131
-
132
- super(fetchConfig);
133
- }
134
-
135
- async request(
136
- init: RequestInit & {
137
- path: string;
138
- method: string;
139
- query?: Record<string, any>;
140
- }
141
- ) {
142
- return await super.request(init);
143
- }
144
-
145
- async *requestStream<T = any>(
146
- init: RequestInit & {
147
- path: string;
148
- method: string;
149
- query?: Record<string, any>;
150
- contentType: string;
151
- streamingFormat?: "sse" | "ndjson" | "chunked";
152
- }
153
- ): AsyncGenerator<T, void, unknown> {
154
- yield* super.requestStream(init);
69
+ baseURL: options?.baseURL ?? '{{Client.defaultBaseURL}}',
70
+ ...(authStrategies.length > 0 ? { authStrategies } : {}),
71
+ });
72
+
73
+ {{~! Initialize root services ~}}
74
+ {{~#each (getRootServices IR.services)~}}
75
+ this.{{serviceProp tag}} = new {{serviceName tag}}(core);
76
+ {{~/each~}}
77
+ {{~! Initialize namespaces ~}}
78
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
79
+ {{~#if (ne namespace "")~}}
80
+ this.{{serviceProp namespace}} = new {{pascal namespace}}Namespace(core);
81
+ {{~/if~}}
82
+ {{~/each~}}
155
83
  }
156
84
  }
85
+
86
+ // Re-export FetchError for backward compatibility
87
+ export { FetchError };
88
+ export const {{Client.name}}Error = FetchError;
@@ -1,69 +1,19 @@
1
+ /**
2
+ * This file is generated only once. If it already exists, it will not be overwritten.
3
+ * You can safely add custom exports, re-exports, or any other code here as needed.
4
+ * Your customizations will be preserved across SDK regenerations.
5
+ */
1
6
 
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 };
7
+ // Re-export everything from client
8
+ export * from './client';
56
9
 
57
- // Export FetchError and CoreClient for error handling and advanced usage
58
- export { FetchError, CoreClient };
59
10
  // Re-export all error types from @blimu/fetch for instanceof checks
60
- export * from "@blimu/fetch";
61
- export const {{Client.name}}Error = FetchError;
11
+ export * from '@blimu/fetch';
62
12
 
63
13
  // Re-exports for better ergonomics
64
- export * from "./utils";
65
- export * as Schema from "./schema";
66
- export * as ZodSchema from "./schema.zod";
14
+ export * from './utils';
15
+ export * as Schema from './schema';
16
+ export * as ZodSchema from './schema.zod';
67
17
  {{~#each IR.services~}}
68
- export { {{serviceName tag}} } from "./services/{{fileBase tag}}";
18
+ export { {{serviceName tag}} } from './services/{{fileBase tag}}';
69
19
  {{~/each~}}
@@ -39,7 +39,7 @@
39
39
  }
40
40
  },
41
41
  "scripts": {
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",
42
+ "build": "tsup",
43
43
  "typecheck": "tsc -p tsconfig.json --noEmit",
44
44
  "lint": "eslint .",
45
45
  "format": "eslint --fix . && prettier --write .",
@@ -2,7 +2,6 @@
2
2
  // Use these schemas for runtime validation in forms, API requests, etc.
3
3
 
4
4
  import { z } from 'zod';
5
- import * as Schema from './schema';
6
5
 
7
6
  {{! Simple types: generate as regular Zod schema exports (not in namespace) }}
8
7
  {{#each IR.modelDefs}}
@@ -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
  /**
@@ -1,5 +1,8 @@
1
- import { CoreClient } from "../client";
1
+ import { FetchClient } from '@blimu/fetch';
2
2
  import * as Schema from "../schema";
3
+ {{#if Client.includeQueryKeys}}
4
+ import { isNotUndefined } from "../utils";
5
+ {{/if}}
3
6
  {{! Generate import statements for predefined types used in this specific service }}
4
7
  {{#if (getServicePredefinedTypes Service)}}
5
8
  {{#each (groupByPackage (getServicePredefinedTypes Service))}}
@@ -8,7 +11,7 @@ import type { {{joinTypes this.types}} } from '{{this.package}}';
8
11
  {{/if}}
9
12
 
10
13
  export class {{serviceName Service.tag}} {
11
- constructor(private core: CoreClient) {}
14
+ constructor(private core: FetchClient) {}
12
15
  {{#each Service.operations}}
13
16
 
14
17
  /**
@@ -38,7 +41,7 @@ export class {{serviceName Service.tag}} {
38
41
  query,
39
42
  {{/if}}
40
43
  {{#if requestBody}}
41
- body: body as any,
44
+ body,
42
45
  {{/if}}
43
46
  contentType: "{{response.contentType}}",
44
47
  streamingFormat: "{{response.streamingFormat}}",
@@ -58,7 +61,7 @@ export class {{serviceName Service.tag}} {
58
61
  query,
59
62
  {{/if}}
60
63
  {{#if requestBody}}
61
- body: body as any,
64
+ body,
62
65
  {{/if}}
63
66
  ...(init || {}),
64
67
  });
@@ -74,7 +77,8 @@ export class {{serviceName Service.tag}} {
74
77
  {{param}}{{#unless @last}},{{/unless}}
75
78
  {{~/each~}}
76
79
  ) {
77
- return [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}] as const;
80
+ const keys = [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}] as const;
81
+ return isNotUndefined(keys);
78
82
  }
79
83
  {{/if}}
80
84
  {{/each}}
@@ -1,26 +1,27 @@
1
1
  {
2
- "rootDir": "./src",
3
2
  "compilerOptions": {
4
- "module": "commonjs",
5
- "moduleResolution": "node",
3
+ "rootDir": "./src",
4
+ "module": "ES2022",
5
+ "moduleResolution": "Bundler",
6
+ "target": "ES2022",
6
7
  "esModuleInterop": true,
7
8
  "isolatedModules": true,
8
9
  "declaration": true,
10
+ "declarationMap": true,
9
11
  "removeComments": true,
10
12
  "allowSyntheticDefaultImports": true,
11
- "target": "ES2023",
12
13
  "sourceMap": true,
13
14
  "outDir": "./dist",
14
15
  "baseUrl": "./src",
15
16
  "incremental": true,
16
17
  "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
17
18
  "skipLibCheck": true,
18
- "strictNullChecks": true,
19
19
  "forceConsistentCasingInFileNames": true,
20
- "noImplicitAny": false,
21
- "strictBindCallApply": false,
22
- "noFallthroughCasesInSwitch": false,
23
- "useDefineForClassFields": false
20
+ "strict": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "useDefineForClassFields": true
24
25
  },
25
26
  "include": ["src/**/*"],
26
27
  "exclude": ["node_modules", "dist"]
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/**/*.ts"],
5
+ format: ["cjs", "esm"],
6
+ dts: true,
7
+ splitting: false,
8
+ sourcemap: true,
9
+ clean: true,
10
+ outDir: "dist",
11
+ tsconfig: "./tsconfig.json",
12
+ // External dependencies should not be bundled
13
+ // This ensures proper type resolution and smaller bundle sizes
14
+ external: [],
15
+ });
@@ -32,5 +32,13 @@ export async function listAll<T>(
32
32
  return out;
33
33
  }
34
34
 
35
+ /**
36
+ * Filters out undefined values from an array while preserving type safety
37
+ * Useful for query keys where optional parameters might be undefined
38
+ */
39
+ export function isNotUndefined<T>(arr: readonly (T | undefined)[]): T[] {
40
+ return arr.filter((item): item is T => item !== undefined);
41
+ }
42
+
35
43
  // Re-export streaming parsers from @blimu/fetch
36
44
  export { parseSSEStream, parseNDJSONStream };
package/dist/index.d.mts CHANGED
@@ -291,6 +291,7 @@ declare class TypeScriptGeneratorService implements Generator<TypeScriptClient>
291
291
  private registerHandlebarsHelpers;
292
292
  private renderTemplate;
293
293
  private generateClient;
294
+ private generateAuthStrategies;
294
295
  private generateIndex;
295
296
  private generateUtils;
296
297
  private generateServices;
@@ -298,6 +299,7 @@ declare class TypeScriptGeneratorService implements Generator<TypeScriptClient>
298
299
  private generateZodSchema;
299
300
  private generatePackageJson;
300
301
  private generateTsConfig;
302
+ private generateTsupConfig;
301
303
  private generateReadme;
302
304
  private generatePrettierConfig;
303
305
  }
package/dist/index.d.ts CHANGED
@@ -291,6 +291,7 @@ declare class TypeScriptGeneratorService implements Generator<TypeScriptClient>
291
291
  private registerHandlebarsHelpers;
292
292
  private renderTemplate;
293
293
  private generateClient;
294
+ private generateAuthStrategies;
294
295
  private generateIndex;
295
296
  private generateUtils;
296
297
  private generateServices;
@@ -298,6 +299,7 @@ declare class TypeScriptGeneratorService implements Generator<TypeScriptClient>
298
299
  private generateZodSchema;
299
300
  private generatePackageJson;
300
301
  private generateTsConfig;
302
+ private generateTsupConfig;
301
303
  private generateReadme;
302
304
  private generatePrettierConfig;
303
305
  }