@blimu/codegen 0.2.1 → 0.3.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.
@@ -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,64 @@
1
+ import type { AuthStrategy } from '@blimu/fetch';
2
+ import type { ClientOption } from './client';
3
+
4
+ export function buildAuthStrategies(cfg: ClientOption): AuthStrategy[] {
5
+ const authStrategies: AuthStrategy[] = [...(cfg?.authStrategies || [])];
6
+
7
+ {{~#each IR.securitySchemes~}}
8
+ {{~#if (eq this.type "http")~}}
9
+ {{~#if (eq this.scheme "bearer")~}}
10
+ if (cfg.{{camel this.key}}) {
11
+ authStrategies.push({
12
+ type: 'bearer',
13
+ token: cfg.{{camel this.key}},
14
+ });
15
+ }
16
+ {{~/if~}}
17
+ {{~/if~}}
18
+ {{~/each~}}
19
+
20
+ {{~#each IR.securitySchemes~}}
21
+ {{~#if (eq this.type "http")~}}
22
+ {{~#if (eq this.scheme "basic")~}}
23
+ if (cfg.{{camel this.key}}) {
24
+ authStrategies.push({
25
+ type: 'basic',
26
+ username: cfg.{{camel this.key}}.username,
27
+ password: cfg.{{camel this.key}}.password,
28
+ });
29
+ }
30
+ {{~/if~}}
31
+ {{~else if (eq this.type "apiKey")~}}
32
+ {{~#if (eq this.in "header")~}}
33
+ if (cfg.{{camel this.key}}) {
34
+ authStrategies.push({
35
+ type: 'apiKey',
36
+ key: cfg.{{camel this.key}},
37
+ location: 'header',
38
+ name: '{{this.name}}',
39
+ });
40
+ }
41
+ {{~else if (eq this.in "query")~}}
42
+ if (cfg.{{camel this.key}}) {
43
+ authStrategies.push({
44
+ type: 'apiKey',
45
+ key: cfg.{{camel this.key}},
46
+ location: 'query',
47
+ name: '{{this.name}}',
48
+ });
49
+ }
50
+ {{~else if (eq this.in "cookie")~}}
51
+ if (cfg.{{camel this.key}}) {
52
+ authStrategies.push({
53
+ type: 'apiKey',
54
+ key: cfg.{{camel this.key}},
55
+ location: 'cookie',
56
+ name: '{{this.name}}',
57
+ });
58
+ }
59
+ {{~/if~}}
60
+ {{~/if~}}
61
+ {{~/each~}}
62
+
63
+ return authStrategies;
64
+ }
@@ -1,146 +1,89 @@
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~}}
11
+
2
12
 
3
13
  export type ClientOption = FetchClientConfig & {
4
14
  {{~#each IR.securitySchemes~}}
5
15
  {{~#if (eq this.type "http")~}}
6
16
  {{~#if (eq this.scheme "bearer")~}}
7
- {{camel this.key}}?: string | (() => string | undefined | Promise<string | undefined>);
17
+ {{camel this.key}}?: BearerAuthStrategy['token'];
8
18
  {{~else if (eq this.scheme "basic")~}}
9
19
  {{camel this.key}}?: { username: string; password: string };
10
20
  {{~/if~}}
11
- {{~else if (eq this.type "apiKey")~}}
12
- {{camel this.key}}?: string;
21
+ {{~else if (eq this.type "apiKey")~}}
22
+ {{camel this.key}}?: ApiKeyAuthStrategy['key'];
13
23
  {{~/if~}}
14
24
  {{~/each~}}
15
25
  };
16
26
 
17
- // Re-export FetchError for backward compatibility
18
- export { FetchError };
27
+ {{~setVar "grouped" (groupByNamespace IR.services)~}}
19
28
 
20
- export class CoreClient extends FetchClient {
21
- constructor(cfg: ClientOption = {}) {
22
- // Build auth strategies from OpenAPI security schemes
23
- const authStrategies: AuthStrategy[] = [];
29
+ {{~! Generate namespace classes for non-root services ~}}
30
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
31
+ {{~#if (ne namespace "")~}}
32
+ class {{pascal namespace}}Namespace {
33
+ {{~#each services~}}
34
+ readonly {{serviceProp (getServiceName tag)}}: {{serviceName tag}};
35
+ {{~/each~}}
24
36
 
25
- // Extract auth and security scheme properties to avoid passing them to FetchClient
37
+ constructor(core: FetchClient) {
38
+ {{~#each services~}}
39
+ this.{{serviceProp (getServiceName tag)}} = new {{serviceName tag}}(core);
40
+ {{~/each~}}
41
+ }
42
+ }
43
+
44
+ {{~/if~}}
45
+ {{~/each~}}
46
+
47
+ export class {{Client.name}} {
48
+ {{~! Root level services (no namespace) ~}}
49
+ {{~#each (getRootServices IR.services)~}}
50
+ readonly {{serviceProp tag}}: {{serviceName tag}};
51
+ {{~/each~}}
52
+ {{~! Namespace properties ~}}
53
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
54
+ {{~#if (ne namespace "")~}}
55
+ readonly {{serviceProp namespace}}: {{pascal namespace}}Namespace;
56
+ {{~/if~}}
57
+ {{~/each~}}
58
+
59
+ constructor(options?: ClientOption) {
26
60
  const {
27
- auth: _existingAuth{{~#each IR.securitySchemes~}},
28
- {{camel this.key}}{{~/each~}},
61
+ {{~#each IR.securitySchemes~}}
62
+ {{camel this.key}},{{~/each~}}
29
63
  ...restCfg
30
- } = cfg;
64
+ } = options || {};
65
+
66
+ const authStrategies = buildAuthStrategies(options || {});
31
67
 
32
- {{~#each IR.securitySchemes~}}
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
- }
48
- }
49
- {{~/if~}}
50
- {{~/if~}}
51
- {{~/each~}}
68
+ const core = new FetchClient({
69
+ ...restCfg,
70
+ baseURL: options?.baseURL ?? '{{Client.defaultBaseURL}}',
71
+ ...(authStrategies.length > 0 ? { authStrategies } : {}),
72
+ });
52
73
 
53
- {{~#each IR.securitySchemes~}}
54
- {{~#if (eq this.type "http")~}}
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
- });
62
- }
63
- {{~/if~}}
64
- {{~else if (eq this.type "apiKey")~}}
65
- {{~#if (eq this.in "header")~}}
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
- }
85
- {{~else if (eq this.in "cookie")~}}
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
- }
74
+ {{~! Initialize root services ~}}
75
+ {{~#each (getRootServices IR.services)~}}
76
+ this.{{serviceProp tag}} = new {{serviceName tag}}(core);
77
+ {{~/each~}}
78
+ {{~! Initialize namespaces ~}}
79
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
80
+ {{~#if (ne namespace "")~}}
81
+ this.{{serviceProp namespace}} = new {{pascal namespace}}Namespace(core);
95
82
  {{~/if~}}
96
- {{~/if~}}
97
83
  {{~/each~}}
98
-
99
-
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
- },
117
- }
118
- : {}),
119
- // Hooks are passed through directly from FetchClientConfig (no mapping needed)
120
- };
121
-
122
- super(fetchConfig);
123
- }
124
-
125
- async request(
126
- init: RequestInit & {
127
- path: string;
128
- method: string;
129
- query?: Record<string, any>;
130
- }
131
- ) {
132
- return await super.request(init);
133
- }
134
-
135
- async *requestStream<T = any>(
136
- init: RequestInit & {
137
- path: string;
138
- method: string;
139
- query?: Record<string, any>;
140
- contentType: string;
141
- streamingFormat?: "sse" | "ndjson" | "chunked";
142
- }
143
- ): AsyncGenerator<T, void, unknown> {
144
- yield* super.requestStream(init);
145
84
  }
146
85
  }
86
+
87
+ // Re-export FetchError for backward compatibility
88
+ export { FetchError };
89
+ 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~}}
@@ -1,5 +1,10 @@
1
- import { CoreClient } from "../client";
1
+ import { FetchClient } from '@blimu/fetch';
2
+ {{#if (serviceUsesSchema Service)}}
2
3
  import * as Schema from "../schema";
4
+ {{/if}}
5
+ {{#if Client.includeQueryKeys}}
6
+ import { isNotUndefined } from "../utils";
7
+ {{/if}}
3
8
  {{! Generate import statements for predefined types used in this specific service }}
4
9
  {{#if (getServicePredefinedTypes Service)}}
5
10
  {{#each (groupByPackage (getServicePredefinedTypes Service))}}
@@ -8,7 +13,7 @@ import type { {{joinTypes this.types}} } from '{{this.package}}';
8
13
  {{/if}}
9
14
 
10
15
  export class {{serviceName Service.tag}} {
11
- constructor(private core: CoreClient) {}
16
+ constructor(private core: FetchClient) {}
12
17
  {{#each Service.operations}}
13
18
 
14
19
  /**
@@ -38,7 +43,7 @@ export class {{serviceName Service.tag}} {
38
43
  query,
39
44
  {{/if}}
40
45
  {{#if requestBody}}
41
- body: body as any,
46
+ body,
42
47
  {{/if}}
43
48
  contentType: "{{response.contentType}}",
44
49
  streamingFormat: "{{response.streamingFormat}}",
@@ -58,7 +63,7 @@ export class {{serviceName Service.tag}} {
58
63
  query,
59
64
  {{/if}}
60
65
  {{#if requestBody}}
61
- body: body as any,
66
+ body,
62
67
  {{/if}}
63
68
  ...(init || {}),
64
69
  });
@@ -74,7 +79,8 @@ export class {{serviceName Service.tag}} {
74
79
  {{param}}{{#unless @last}},{{/unless}}
75
80
  {{~/each~}}
76
81
  ) {
77
- return [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}] as const;
82
+ const keys = [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}] as const;
83
+ return isNotUndefined(keys);
78
84
  }
79
85
  {{/if}}
80
86
  {{/each}}
@@ -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;
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;