@openchoreo/backstage-plugin-thunder-idp-client-node 1.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # @openchoreo/backstage-plugin-thunder-idp-client-node
2
+
3
+ ## 1.1.0
4
+
5
+ - Initial public release on GitHub Packages, aligned with the OpenChoreo platform release line (`1.1.0`).
package/README.md ADDED
@@ -0,0 +1,411 @@
1
+ # @openchoreo/backstage-plugin-thunder-idp-client-node
2
+
3
+ Auto-generated TypeScript API clients for [Thunder Identity Provider](https://github.com/asgardeo/thunder) User and Group Management APIs.
4
+
5
+ This library provides type-safe, fully typed API clients for interacting with Thunder IdP, built using `openapi-typescript` and `openapi-fetch` for maximum type safety and developer experience.
6
+
7
+ ## Features
8
+
9
+ - ✨ **Fully Type-Safe**: Generated from OpenAPI specs with complete TypeScript types
10
+ - 🔄 **Auto-Regeneration**: Automatically regenerates clients on build
11
+ - 📦 **Zero Runtime Dependencies**: Uses native `fetch` API (Node.js 18+)
12
+ - 🎯 **Version-Controlled**: Thunder version tracked in `package.json`
13
+ - 🔧 **Backstage Integration**: Factory functions for easy Backstage backend integration
14
+ - 🚀 **Modern Stack**: Built with `openapi-typescript` and `openapi-fetch`
15
+
16
+ ## Installation
17
+
18
+ This package is part of the OpenChoreo Backstage plugins monorepo and is installed automatically when you install the workspace dependencies.
19
+
20
+ ```bash
21
+ yarn install
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### Basic Usage
27
+
28
+ ```typescript
29
+ import {
30
+ createThunderUserClient,
31
+ createThunderGroupClient,
32
+ } from '@openchoreo/backstage-plugin-thunder-idp-client-node';
33
+
34
+ // Create API clients
35
+ const userClient = createThunderUserClient({
36
+ baseUrl: 'https://thunder.example.com:8090',
37
+ token: 'your-bearer-token',
38
+ });
39
+
40
+ const groupClient = createThunderGroupClient({
41
+ baseUrl: 'https://thunder.example.com:8090',
42
+ token: 'your-bearer-token',
43
+ });
44
+
45
+ // List users with type-safe parameters
46
+ const { data: users, error: userError } = await userClient.GET('/users', {
47
+ params: {
48
+ query: {
49
+ limit: 10,
50
+ offset: 0,
51
+ filter: 'username eq "john.doe"',
52
+ },
53
+ },
54
+ });
55
+
56
+ if (userError) {
57
+ console.error('Error fetching users:', userError);
58
+ } else {
59
+ console.log('Users:', users);
60
+ }
61
+
62
+ // List groups
63
+ const { data: groups, error: groupError } = await groupClient.GET('/groups', {
64
+ params: {
65
+ query: { limit: 10 },
66
+ },
67
+ });
68
+ ```
69
+
70
+ ### Backstage Integration
71
+
72
+ For Backstage backend modules, use the config-based factory:
73
+
74
+ ```typescript
75
+ import { createThunderClientsFromConfig } from '@openchoreo/backstage-plugin-thunder-idp-client-node';
76
+ import { LoggerService } from '@backstage/backend-plugin-api';
77
+ import { Config } from '@backstage/config';
78
+
79
+ export function createMyService(config: Config, logger: LoggerService) {
80
+ const { userClient, groupClient } = createThunderClientsFromConfig(
81
+ config,
82
+ logger,
83
+ );
84
+
85
+ // Use the clients
86
+ const { data: users } = await userClient.GET('/users');
87
+ const { data: groups } = await groupClient.GET('/groups');
88
+
89
+ return { users, groups };
90
+ }
91
+ ```
92
+
93
+ **app-config.yaml**:
94
+
95
+ ```yaml
96
+ thunder:
97
+ baseUrl: https://thunder.example.com:8090
98
+ token: ${THUNDER_TOKEN} # From environment variable
99
+ ```
100
+
101
+ ## API Clients
102
+
103
+ This library provides two main API clients:
104
+
105
+ ### User Management API
106
+
107
+ Interact with Thunder's User Management API:
108
+
109
+ ```typescript
110
+ // List users
111
+ await userClient.GET('/users', { params: { query: { limit: 10 } } });
112
+
113
+ // Get user by ID
114
+ await userClient.GET('/users/{id}', { params: { path: { id: 'user-uuid' } } });
115
+
116
+ // Create user
117
+ await userClient.POST('/users', {
118
+ body: {
119
+ organizationUnit: 'org-uuid',
120
+ type: 'customer',
121
+ attributes: {
122
+ email: 'user@example.com',
123
+ username: 'john.doe',
124
+ },
125
+ },
126
+ });
127
+
128
+ // Update user
129
+ await userClient.PUT('/users/{id}', {
130
+ params: { path: { id: 'user-uuid' } },
131
+ body: {
132
+ /* updated attributes */
133
+ },
134
+ });
135
+
136
+ // Delete user
137
+ await userClient.DELETE('/users/{id}', {
138
+ params: { path: { id: 'user-uuid' } },
139
+ });
140
+
141
+ // Get user's groups
142
+ await userClient.GET('/users/{id}/groups', {
143
+ params: { path: { id: 'user-uuid' } },
144
+ });
145
+ ```
146
+
147
+ ### Group Management API
148
+
149
+ Interact with Thunder's Group Management API:
150
+
151
+ ```typescript
152
+ // List groups
153
+ await groupClient.GET('/groups', { params: { query: { limit: 10 } } });
154
+
155
+ // Get group by ID
156
+ await groupClient.GET('/groups/{id}', {
157
+ params: { path: { id: 'group-uuid' } },
158
+ });
159
+
160
+ // Create group
161
+ await groupClient.POST('/groups', {
162
+ body: {
163
+ name: 'Engineering',
164
+ description: 'Engineering team',
165
+ organizationUnitId: 'org-uuid',
166
+ members: [
167
+ { id: 'user-uuid-1', type: 'user' },
168
+ { id: 'user-uuid-2', type: 'user' },
169
+ ],
170
+ },
171
+ });
172
+
173
+ // Update group
174
+ await groupClient.PUT('/groups/{id}', {
175
+ params: { path: { id: 'group-uuid' } },
176
+ body: {
177
+ /* updated fields */
178
+ },
179
+ });
180
+
181
+ // Delete group
182
+ await groupClient.DELETE('/groups/{id}', {
183
+ params: { path: { id: 'group-uuid' } },
184
+ });
185
+
186
+ // Get group members
187
+ await groupClient.GET('/groups/{id}/members', {
188
+ params: { path: { id: 'group-uuid' } },
189
+ });
190
+ ```
191
+
192
+ ## Generating API Clients
193
+
194
+ ### Automatic Generation (Recommended)
195
+
196
+ Clients are automatically generated before build:
197
+
198
+ ```bash
199
+ yarn build
200
+ ```
201
+
202
+ This will:
203
+
204
+ 1. Download OpenAPI specs from Thunder repository (using version from `package.json`)
205
+ 2. Generate TypeScript types
206
+ 3. Build the package
207
+
208
+ ### Manual Generation
209
+
210
+ Generate clients manually:
211
+
212
+ ```bash
213
+ # Generate using version from package.json
214
+ yarn generate:clients
215
+
216
+ # Clean generated files
217
+ yarn clean:generated
218
+
219
+ # Clean and regenerate
220
+ yarn clean:generated && yarn generate:clients
221
+ ```
222
+
223
+ ### Testing Against Different Versions
224
+
225
+ Test against a specific Thunder version without modifying `package.json`:
226
+
227
+ ```bash
228
+ bash scripts/generate-clients.sh --thunder-version v0.11.0
229
+ ```
230
+
231
+ ## Upgrading Thunder Version
232
+
233
+ To upgrade to a new Thunder version:
234
+
235
+ 1. **Update `package.json`**:
236
+
237
+ ```json
238
+ {
239
+ "thunderVersion": "v0.11.0"
240
+ }
241
+ ```
242
+
243
+ 2. **Regenerate clients**:
244
+
245
+ ```bash
246
+ yarn clean:generated
247
+ yarn generate:clients
248
+ ```
249
+
250
+ 3. **Test the changes**:
251
+
252
+ ```bash
253
+ yarn build
254
+ yarn test
255
+ ```
256
+
257
+ 4. **Commit**:
258
+ ```bash
259
+ git add plugins/thunder-idp-client-node/package.json
260
+ git commit -m "chore: upgrade Thunder IdP client to v0.11.0"
261
+ ```
262
+
263
+ ## Configuration Options
264
+
265
+ ### ThunderClientConfig
266
+
267
+ ```typescript
268
+ interface ThunderClientConfig {
269
+ baseUrl: string; // Thunder API base URL
270
+ token?: string; // Bearer token for authentication
271
+ fetchApi?: typeof fetch; // Custom fetch implementation (optional)
272
+ logger?: LoggerService; // Backstage logger (optional)
273
+ }
274
+ ```
275
+
276
+ ## Type Safety
277
+
278
+ All API endpoints, parameters, request bodies, and response types are fully typed:
279
+
280
+ ```typescript
281
+ // ✅ TypeScript will validate paths, parameters, and responses
282
+ const { data } = await userClient.GET('/users', {
283
+ params: {
284
+ query: {
285
+ limit: 10,
286
+ offset: 0,
287
+ filter: 'username eq "john.doe"',
288
+ },
289
+ },
290
+ });
291
+
292
+ // ❌ TypeScript will error on invalid paths
293
+ const { data } = await userClient.GET('/invalid-path'); // Type error!
294
+
295
+ // ❌ TypeScript will error on invalid parameters
296
+ const { data } = await userClient.GET('/users', {
297
+ params: {
298
+ query: {
299
+ invalidParam: true, // Type error!
300
+ },
301
+ },
302
+ });
303
+ ```
304
+
305
+ ## Error Handling
306
+
307
+ `openapi-fetch` returns both `data` and `error`, never throws:
308
+
309
+ ```typescript
310
+ const { data, error } = await userClient.GET('/users');
311
+
312
+ if (error) {
313
+ // Handle error (error is typed based on OpenAPI spec)
314
+ console.error('API Error:', error);
315
+ return;
316
+ }
317
+
318
+ // TypeScript knows data is defined here
319
+ console.log('Users:', data.users);
320
+ ```
321
+
322
+ ## Development
323
+
324
+ ### Project Structure
325
+
326
+ ```
327
+ plugins/thunder-idp-client-node/
328
+ ├── src/
329
+ │ ├── generated/ # Auto-generated (gitignored)
330
+ │ │ ├── user/ # User API types
331
+ │ │ │ ├── types.ts
332
+ │ │ │ └── index.ts
333
+ │ │ └── group/ # Group API types
334
+ │ │ ├── types.ts
335
+ │ │ └── index.ts
336
+ │ ├── factory.ts # Client factory functions
337
+ │ ├── index.ts # Public API exports
338
+ │ └── version.ts # Thunder version (auto-generated)
339
+ ├── openapi/ # Downloaded specs (gitignored)
340
+ │ ├── user.yaml
341
+ │ └── group.yaml
342
+ ├── scripts/
343
+ │ └── generate-clients.sh # Generation script
344
+ ├── package.json # Contains thunderVersion field
345
+ └── README.md
346
+ ```
347
+
348
+ ### Scripts
349
+
350
+ - `yarn generate:clients` - Generate API clients from OpenAPI specs
351
+ - `yarn clean:generated` - Remove generated files
352
+ - `yarn build` - Build the package (auto-generates clients first)
353
+ - `yarn lint` - Lint the code
354
+ - `yarn test` - Run tests
355
+
356
+ ## Thunder Version Information
357
+
358
+ Current Thunder version: Check `thunderVersion` in `package.json`
359
+
360
+ Generated clients are version-specific to the Thunder release. The version constant is exported:
361
+
362
+ ```typescript
363
+ import { THUNDER_VERSION } from '@openchoreo/backstage-plugin-thunder-idp-client-node';
364
+
365
+ console.log('Using Thunder version:', THUNDER_VERSION); // e.g., "v0.10.0"
366
+ ```
367
+
368
+ ## Troubleshooting
369
+
370
+ ### "Cannot find module './generated/user'"
371
+
372
+ Run the generation script:
373
+
374
+ ```bash
375
+ yarn generate:clients
376
+ ```
377
+
378
+ ### "Failed to download user.yaml"
379
+
380
+ Check that the Thunder version exists:
381
+
382
+ ```bash
383
+ # Check available tags at:
384
+ # https://github.com/asgardeo/thunder/tags
385
+ ```
386
+
387
+ ### Type errors after upgrading Thunder version
388
+
389
+ Clean and regenerate:
390
+
391
+ ```bash
392
+ yarn clean:generated
393
+ yarn generate:clients
394
+ yarn build
395
+ ```
396
+
397
+ ## Contributing
398
+
399
+ This package is part of the OpenChoreo Backstage plugins monorepo. See the main repository README for contribution guidelines.
400
+
401
+ ## License
402
+
403
+ Apache-2.0
404
+
405
+ ## Links
406
+
407
+ - [Thunder IdP Repository](https://github.com/asgardeo/thunder)
408
+ - [OpenAPI Spec - User API](https://github.com/asgardeo/thunder/blob/main/docs/apis/user.yaml)
409
+ - [OpenAPI Spec - Group API](https://github.com/asgardeo/thunder/blob/main/docs/apis/group.yaml)
410
+ - [openapi-typescript](https://github.com/drwpow/openapi-typescript)
411
+ - [openapi-fetch](https://github.com/drwpow/openapi-typescript/tree/main/packages/openapi-fetch)
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ var createClient = require('openapi-fetch');
4
+
5
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
6
+
7
+ var createClient__default = /*#__PURE__*/_interopDefaultCompat(createClient);
8
+
9
+ function createThunderUserClient(config) {
10
+ const { baseUrl, token, fetchApi, logger } = config;
11
+ logger?.debug(`Creating Thunder User API client with baseUrl: ${baseUrl}`);
12
+ const clientOptions = {
13
+ baseUrl,
14
+ fetch: fetchApi,
15
+ headers: token ? {
16
+ Authorization: `Bearer ${token}`
17
+ } : void 0
18
+ };
19
+ return createClient__default.default(clientOptions);
20
+ }
21
+ function createThunderGroupClient(config) {
22
+ const { baseUrl, token, fetchApi, logger } = config;
23
+ logger?.debug(`Creating Thunder Group API client with baseUrl: ${baseUrl}`);
24
+ const clientOptions = {
25
+ baseUrl,
26
+ fetch: fetchApi,
27
+ headers: token ? {
28
+ Authorization: `Bearer ${token}`
29
+ } : void 0
30
+ };
31
+ return createClient__default.default(clientOptions);
32
+ }
33
+ function createThunderClientsFromConfig(config, logger) {
34
+ const baseUrl = config.getString("thunder.baseUrl");
35
+ const token = config.getOptionalString("thunder.token");
36
+ logger?.info("Initializing Thunder IdP API clients");
37
+ const clientConfig = {
38
+ baseUrl,
39
+ token,
40
+ logger
41
+ };
42
+ return {
43
+ userClient: createThunderUserClient(clientConfig),
44
+ groupClient: createThunderGroupClient(clientConfig)
45
+ };
46
+ }
47
+
48
+ exports.createThunderClientsFromConfig = createThunderClientsFromConfig;
49
+ exports.createThunderGroupClient = createThunderGroupClient;
50
+ exports.createThunderUserClient = createThunderUserClient;
51
+ //# sourceMappingURL=factory.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.cjs.js","sources":["../src/factory.ts"],"sourcesContent":["/**\n * Factory functions for creating Thunder IdP API clients\n *\n * @packageDocumentation\n */\n\nimport { Config } from '@backstage/config';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport createClient, { type ClientOptions } from 'openapi-fetch';\nimport type { paths as UserPaths } from './generated/user/types';\nimport type { paths as GroupPaths } from './generated/group/types';\n\n/**\n * Configuration options for Thunder IdP API clients\n */\nexport interface ThunderClientConfig {\n /**\n * Base URL for the Thunder IdP API\n * @example 'https://thunder.example.com:8090'\n */\n baseUrl: string;\n\n /**\n * Authentication token (Bearer token)\n * If not provided, requests will be made without authentication\n */\n token?: string;\n\n /**\n * Custom fetch implementation\n * Useful for testing or using a specific fetch polyfill\n */\n fetchApi?: typeof fetch;\n\n /**\n * Optional logger for debugging\n */\n logger?: LoggerService;\n}\n\n/**\n * Creates a Thunder User API client\n *\n * @param config - Configuration options for the client\n * @returns Configured User API client instance\n *\n * @example\n * ```typescript\n * const userClient = createThunderUserClient({\n * baseUrl: 'https://thunder.example.com:8090',\n * token: 'your-auth-token'\n * });\n *\n * const { data, error } = await userClient.GET('/users', {\n * params: { query: { limit: 10 } }\n * });\n * ```\n */\nexport function createThunderUserClient(config: ThunderClientConfig) {\n const { baseUrl, token, fetchApi, logger } = config;\n\n logger?.debug(`Creating Thunder User API client with baseUrl: ${baseUrl}`);\n\n const clientOptions: ClientOptions = {\n baseUrl: baseUrl,\n fetch: fetchApi,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : undefined,\n };\n\n return createClient<UserPaths>(clientOptions);\n}\n\n/**\n * Creates a Thunder Group API client\n *\n * @param config - Configuration options for the client\n * @returns Configured Group API client instance\n *\n * @example\n * ```typescript\n * const groupClient = createThunderGroupClient({\n * baseUrl: 'https://thunder.example.com:8090',\n * token: 'your-auth-token'\n * });\n *\n * const { data, error } = await groupClient.GET('/groups', {\n * params: { query: { limit: 10 } }\n * });\n * ```\n */\nexport function createThunderGroupClient(config: ThunderClientConfig) {\n const { baseUrl, token, fetchApi, logger } = config;\n\n logger?.debug(`Creating Thunder Group API client with baseUrl: ${baseUrl}`);\n\n const clientOptions: ClientOptions = {\n baseUrl: baseUrl,\n fetch: fetchApi,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : undefined,\n };\n\n return createClient<GroupPaths>(clientOptions);\n}\n\n/**\n * Creates Thunder API clients from Backstage configuration\n *\n * @param config - Backstage Config object\n * @param logger - Optional logger service\n * @returns Object containing both user and group API clients\n *\n * @example\n * ```typescript\n * // In your Backstage backend module\n * const clients = createThunderClientsFromConfig(config, logger);\n * const { data: users } = await clients.userClient.GET('/users', {\n * params: { query: { limit: 10 } }\n * });\n * const { data: groups } = await clients.groupClient.GET('/groups', {\n * params: { query: { limit: 10 } }\n * });\n * ```\n *\n * @remarks\n * Expects the following configuration in app-config.yaml:\n * ```yaml\n * thunder:\n * baseUrl: https://thunder.example.com:8090\n * token: ${THUNDER_TOKEN}\n * ```\n */\nexport function createThunderClientsFromConfig(\n config: Config,\n logger?: LoggerService,\n) {\n const baseUrl = config.getString('thunder.baseUrl');\n const token = config.getOptionalString('thunder.token');\n\n logger?.info('Initializing Thunder IdP API clients');\n\n const clientConfig: ThunderClientConfig = {\n baseUrl,\n token,\n logger,\n };\n\n return {\n userClient: createThunderUserClient(clientConfig),\n groupClient: createThunderGroupClient(clientConfig),\n };\n}\n"],"names":["createClient"],"mappings":";;;;;;;;AA0DO,SAAS,wBAAwB,MAAA,EAA6B;AACnE,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAO,QAAA,EAAU,QAAO,GAAI,MAAA;AAE7C,EAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,+CAAA,EAAkD,OAAO,CAAA,CAAE,CAAA;AAEzE,EAAA,MAAM,aAAA,GAA+B;AAAA,IACnC,OAAA;AAAA,IACA,KAAA,EAAO,QAAA;AAAA,IACP,SAAS,KAAA,GACL;AAAA,MACE,aAAA,EAAe,UAAU,KAAK,CAAA;AAAA,KAChC,GACA;AAAA,GACN;AAEA,EAAA,OAAOA,8BAAwB,aAAa,CAAA;AAC9C;AAoBO,SAAS,yBAAyB,MAAA,EAA6B;AACpE,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAO,QAAA,EAAU,QAAO,GAAI,MAAA;AAE7C,EAAA,MAAA,EAAQ,KAAA,CAAM,CAAA,gDAAA,EAAmD,OAAO,CAAA,CAAE,CAAA;AAE1E,EAAA,MAAM,aAAA,GAA+B;AAAA,IACnC,OAAA;AAAA,IACA,KAAA,EAAO,QAAA;AAAA,IACP,SAAS,KAAA,GACL;AAAA,MACE,aAAA,EAAe,UAAU,KAAK,CAAA;AAAA,KAChC,GACA;AAAA,GACN;AAEA,EAAA,OAAOA,8BAAyB,aAAa,CAAA;AAC/C;AA6BO,SAAS,8BAAA,CACd,QACA,MAAA,EACA;AACA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,SAAA,CAAU,iBAAiB,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,iBAAA,CAAkB,eAAe,CAAA;AAEtD,EAAA,MAAA,EAAQ,KAAK,sCAAsC,CAAA;AAEnD,EAAA,MAAM,YAAA,GAAoC;AAAA,IACxC,OAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,wBAAwB,YAAY,CAAA;AAAA,IAChD,WAAA,EAAa,yBAAyB,YAAY;AAAA,GACpD;AACF;;;;;;"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var factory = require('./factory.cjs.js');
4
+ var version = require('./version.cjs.js');
5
+
6
+
7
+
8
+ exports.createThunderClientsFromConfig = factory.createThunderClientsFromConfig;
9
+ exports.createThunderGroupClient = factory.createThunderGroupClient;
10
+ exports.createThunderUserClient = factory.createThunderUserClient;
11
+ exports.THUNDER_VERSION = version.THUNDER_VERSION;
12
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}