@ioka-technologies/asyncapi-ts-client-template 0.0.8 → 0.0.11

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.
@@ -13,6 +13,8 @@ module.exports = function ({ asyncapi, params }) {
13
13
  {`import { v4 as uuidv4 } from 'uuid';
14
14
  import { Transport, TransportConfig, RequestOptions, ResponseHandler, MessageEnvelope, EnvelopeCallback } from '../types';
15
15
  import { TransportError, ConnectionError, TimeoutError } from '../errors';
16
+ import { generateAuthHeaders, generateAuthQueryParams, hasAuthCredentials, AuthError, UnauthorizedError, AuthCredentials } from '../auth';
17
+ import { createRetryManager, getRetryConfig } from '../retry';
16
18
 
17
19
  // Environment-aware WebSocket implementation
18
20
  const getWebSocketImpl = (): typeof WebSocket => {
@@ -122,13 +124,20 @@ export class WebSocketTransport implements Transport {
122
124
  throw new TransportError('WebSocket is not connected');
123
125
  }
124
126
 
127
+ // Generate auth headers if credentials are available
128
+ const authHeaders = this.config.auth ? generateAuthHeaders(this.config.auth) : {};
129
+
125
130
  // Ensure envelope has required fields
126
131
  const requestId = options?.correlationId || envelope.id || uuidv4();
127
132
  const messageEnvelope: MessageEnvelope = {
128
133
  ...envelope,
129
134
  id: requestId,
130
135
  channel: envelope.channel || channel,
131
- timestamp: envelope.timestamp || new Date().toISOString()
136
+ timestamp: envelope.timestamp || new Date().toISOString(),
137
+ headers: {
138
+ ...envelope.headers,
139
+ ...authHeaders
140
+ }
132
141
  };
133
142
 
134
143
  return new Promise((resolve, reject) => {
@@ -162,11 +171,15 @@ export class WebSocketTransport implements Transport {
162
171
 
163
172
  // Send subscription message to server using envelope format
164
173
  if (this.ws && this.ws.readyState === this.ws.OPEN) {
174
+ // Generate auth headers if credentials are available
175
+ const authHeaders = this.config.auth ? generateAuthHeaders(this.config.auth) : {};
176
+
165
177
  const subscribeEnvelope: MessageEnvelope = {
166
178
  operation: 'subscribe',
167
179
  channel,
168
180
  payload: { channel },
169
- timestamp: new Date().toISOString()
181
+ timestamp: new Date().toISOString(),
182
+ headers: authHeaders
170
183
  };
171
184
  this.ws.send(JSON.stringify(subscribeEnvelope));
172
185
  }
@@ -323,7 +336,19 @@ export class WebSocketTransport implements Transport {
323
336
  }
324
337
  }
325
338
  }
339
+
340
+ /**
341
+ * Update authentication configuration
342
+ * @param auth New authentication configuration
343
+ */
344
+ updateAuth(auth: AuthCredentials): void {
345
+ if (this.config) {
346
+ this.config.auth = auth;
347
+ }
348
+ // Note: For WebSocket, auth is typically handled during connection
349
+ // If you need to update auth for an active connection, you may need to reconnect
350
+ }
326
351
  }`}
327
352
  </File>
328
353
  );
329
- }
354
+ };
@@ -4,7 +4,10 @@ import { File } from '@asyncapi/generator-react-sdk';
4
4
  module.exports = function ({ asyncapi, params }) {
5
5
  return (
6
6
  <File name="types.ts">
7
- {`/**
7
+ {`import { AuthCredentials, AuthEventCallbacks } from './auth';
8
+ import { RetryConfig, RetryPreset, RetryEventCallbacks } from './retry';
9
+
10
+ /**
8
11
  * Standard message envelope for all AsyncAPI communications
9
12
  */
10
13
  export interface MessageEnvelope {
@@ -13,6 +16,7 @@ export interface MessageEnvelope {
13
16
  channel?: string; // Optional channel context
14
17
  payload: any; // Message payload
15
18
  timestamp?: string; // Optional ISO 8601 timestamp
19
+ headers?: Record<string, string>; // Transport-level headers (auth, routing, etc.)
16
20
  error?: { // Error information
17
21
  code: string;
18
22
  message: string;
@@ -38,6 +42,10 @@ export interface TransportConfig {
38
42
  url: string;
39
43
  headers?: Record<string, string>;
40
44
  timeout?: number;
45
+ auth?: AuthCredentials; // Authentication credentials
46
+ retry?: RetryConfig | RetryPreset; // Retry configuration
47
+ authCallbacks?: AuthEventCallbacks; // Auth event callbacks
48
+ retryCallbacks?: RetryEventCallbacks; // Retry event callbacks
41
49
  }
42
50
 
43
51
  /**
@@ -45,7 +53,8 @@ export interface TransportConfig {
45
53
  */
46
54
  export interface RequestOptions {
47
55
  timeout?: number;
48
- correlationId?: string; // Override correlation ID
56
+ correlationId?: string; // Override correlation ID
57
+ retry?: RetryConfig | RetryPreset; // Override retry config for this request
49
58
  }
50
59
 
51
60
  /**
@@ -72,4 +81,4 @@ export type UnsubscribeFunction = () => void;
72
81
  export type EnvelopeCallback = (envelope: MessageEnvelope) => void;`}
73
82
  </File>
74
83
  );
75
- }
84
+ };
package/USAGE.md DELETED
@@ -1,586 +0,0 @@
1
- # TypeScript AsyncAPI Client Generator - Usage Guide
2
-
3
- This guide demonstrates how to use the TypeScript AsyncAPI client generator template to create fully-typed, production-ready clients that are compatible with rust-asyncapi servers.
4
-
5
- ## 🚀 Quick Start
6
-
7
- ### 1. Install AsyncAPI CLI
8
-
9
- ```bash
10
- npm install -g @asyncapi/cli
11
- ```
12
-
13
- ### 2. Generate Your Client
14
-
15
- ```bash
16
- # Generate from your AsyncAPI specification
17
- asyncapi generate fromTemplate your-api.yaml ./template -o ./my-client
18
-
19
- # Or use the published template (when available)
20
- # asyncapi generate fromTemplate your-api.yaml @asyncapi/typescript-template -o ./my-client
21
- ```
22
-
23
- ### 3. Install and Build
24
-
25
- ```bash
26
- cd my-client
27
- npm install
28
- npm run build
29
- ```
30
-
31
- ## 📋 Example AsyncAPI Specification
32
-
33
- Here's an example AsyncAPI specification that demonstrates the features supported by this template:
34
-
35
- ```yaml
36
- asyncapi: 3.0.0
37
- info:
38
- title: Example API
39
- version: 1.0.0
40
- description: An example AsyncAPI for demonstrating the TypeScript client generator
41
-
42
- servers:
43
- websocket:
44
- host: localhost:8080
45
- protocol: ws
46
- description: WebSocket server
47
- http:
48
- host: localhost:8080
49
- protocol: http
50
- description: HTTP server
51
-
52
- channels:
53
- user/profile:
54
- address: user/profile
55
- messages:
56
- getUserProfile:
57
- $ref: '#/components/messages/GetUserProfile'
58
- userProfile:
59
- $ref: '#/components/messages/UserProfile'
60
-
61
- user/create:
62
- address: user/create
63
- messages:
64
- createUser:
65
- $ref: '#/components/messages/CreateUser'
66
- userCreated:
67
- $ref: '#/components/messages/UserCreated'
68
-
69
- operations:
70
- getUserProfile:
71
- action: send
72
- channel:
73
- $ref: '#/channels/user~1profile'
74
- messages:
75
- - $ref: '#/channels/user~1profile/messages/getUserProfile'
76
- reply:
77
- channel:
78
- $ref: '#/channels/user~1profile'
79
- messages:
80
- - $ref: '#/channels/user~1profile/messages/userProfile'
81
-
82
- createUser:
83
- action: send
84
- channel:
85
- $ref: '#/channels/user~1create'
86
- messages:
87
- - $ref: '#/channels/user~1create/messages/createUser'
88
- reply:
89
- channel:
90
- $ref: '#/channels/user~1create'
91
- messages:
92
- - $ref: '#/channels/user~1create/messages/userCreated'
93
-
94
- components:
95
- messages:
96
- GetUserProfile:
97
- payload:
98
- type: object
99
- properties:
100
- userId:
101
- type: string
102
- description: The user ID to fetch
103
- required:
104
- - userId
105
-
106
- UserProfile:
107
- payload:
108
- type: object
109
- properties:
110
- id:
111
- type: string
112
- name:
113
- type: string
114
- email:
115
- type: string
116
- createdAt:
117
- type: string
118
- format: date-time
119
- required:
120
- - id
121
- - name
122
- - email
123
- - createdAt
124
-
125
- CreateUser:
126
- payload:
127
- type: object
128
- properties:
129
- name:
130
- type: string
131
- email:
132
- type: string
133
- required:
134
- - name
135
- - email
136
-
137
- UserCreated:
138
- payload:
139
- type: object
140
- properties:
141
- id:
142
- type: string
143
- name:
144
- type: string
145
- email:
146
- type: string
147
- createdAt:
148
- type: string
149
- format: date-time
150
- required:
151
- - id
152
- - name
153
- - email
154
- - createdAt
155
- ```
156
-
157
- ## 🔧 Generated Client Usage
158
-
159
- ### WebSocket Client Example
160
-
161
- ```typescript
162
- import { ExampleApiClient } from './my-client';
163
-
164
- async function websocketExample() {
165
- // Create client with WebSocket transport
166
- const client = new ExampleApiClient({
167
- transport: 'websocket',
168
- websocket: {
169
- url: 'ws://localhost:8080',
170
- reconnect: true,
171
- reconnectInterval: 5000,
172
- maxReconnectAttempts: 5,
173
- timeout: 10000,
174
- auth: {
175
- token: 'your-jwt-token'
176
- // or apiKey: 'your-api-key'
177
- // or authorization: 'Bearer custom-token'
178
- }
179
- }
180
- });
181
-
182
- try {
183
- // Connect to the server
184
- await client.connect();
185
- console.log('✅ Connected to WebSocket server');
186
-
187
- // Send a request and get a typed response
188
- const userProfile = await client.getUserProfile({
189
- userId: '123'
190
- });
191
-
192
- // TypeScript provides full type safety
193
- console.log(`User: ${userProfile.name} (${userProfile.email})`);
194
- console.log(`Created: ${userProfile.createdAt}`);
195
-
196
- // Create a new user
197
- const newUser = await client.createUser({
198
- name: 'John Doe',
199
- email: 'john@example.com'
200
- });
201
-
202
- console.log(`Created user with ID: ${newUser.id}`);
203
-
204
- } catch (error) {
205
- console.error('Error:', error);
206
- } finally {
207
- // Always disconnect when done
208
- await client.disconnect();
209
- }
210
- }
211
-
212
- websocketExample();
213
- ```
214
-
215
- ### HTTP Client Example
216
-
217
- ```typescript
218
- import { ExampleApiClient } from './my-client';
219
-
220
- async function httpExample() {
221
- // Create client with HTTP transport
222
- const client = new ExampleApiClient({
223
- transport: 'http',
224
- http: {
225
- baseUrl: 'http://localhost:8080',
226
- timeout: 30000,
227
- auth: {
228
- token: 'your-jwt-token'
229
- },
230
- retry: {
231
- attempts: 3,
232
- delay: 1000,
233
- backoff: 'exponential',
234
- maxDelay: 10000
235
- }
236
- }
237
- });
238
-
239
- try {
240
- // Connect (for HTTP this just validates config)
241
- await client.connect();
242
- console.log('✅ HTTP client ready');
243
-
244
- // Send requests with automatic retry
245
- const userProfile = await client.getUserProfile({
246
- userId: '456'
247
- });
248
-
249
- console.log(`User: ${userProfile.name} (${userProfile.email})`);
250
-
251
- // Create a new user
252
- const newUser = await client.createUser({
253
- name: 'Jane Smith',
254
- email: 'jane@example.com'
255
- });
256
-
257
- console.log(`Created user with ID: ${newUser.id}`);
258
-
259
- } catch (error) {
260
- console.error('Error:', error);
261
- }
262
- }
263
-
264
- httpExample();
265
- ```
266
-
267
- ### Event Handling
268
-
269
- ```typescript
270
- import { ExampleApiClient } from './my-client';
271
-
272
- async function eventHandlingExample() {
273
- const client = new ExampleApiClient({
274
- transport: 'websocket',
275
- websocket: {
276
- url: 'ws://localhost:8080',
277
- reconnect: true
278
- }
279
- });
280
-
281
- // Connection events
282
- client.on('connected', () => {
283
- console.log('🔌 Connected to server');
284
- });
285
-
286
- client.on('disconnected', (reason) => {
287
- console.log('🔌 Disconnected:', reason);
288
- });
289
-
290
- client.on('reconnecting', (attempt) => {
291
- console.log(`🔄 Reconnecting... attempt ${attempt}`);
292
- });
293
-
294
- client.on('error', (error) => {
295
- console.error('❌ Client error:', error);
296
- });
297
-
298
- // Raw message events (for debugging)
299
- client.on('message', (envelope) => {
300
- console.log('📨 Raw message:', envelope);
301
- });
302
-
303
- await client.connect();
304
-
305
- // Your application logic here...
306
-
307
- await client.disconnect();
308
- }
309
-
310
- eventHandlingExample();
311
- ```
312
-
313
- ### Error Handling
314
-
315
- ```typescript
316
- import {
317
- ExampleApiClient,
318
- ConnectionError,
319
- MessageTimeoutError,
320
- HttpError,
321
- ConfigurationError
322
- } from './my-client';
323
-
324
- async function errorHandlingExample() {
325
- const client = new ExampleApiClient({
326
- transport: 'websocket',
327
- websocket: {
328
- url: 'ws://localhost:8080'
329
- }
330
- });
331
-
332
- try {
333
- await client.connect();
334
-
335
- const result = await client.getUserProfile({
336
- userId: 'invalid-id'
337
- });
338
-
339
- } catch (error) {
340
- if (error instanceof ConnectionError) {
341
- console.error('Connection failed:', error.message);
342
- // Handle connection issues
343
-
344
- } else if (error instanceof MessageTimeoutError) {
345
- console.error('Request timed out:', error.message);
346
- // Handle timeout
347
-
348
- } else if (error instanceof HttpError) {
349
- console.error(`HTTP error ${error.status}: ${error.message}`);
350
- // Handle HTTP errors
351
-
352
- } else if (error instanceof ConfigurationError) {
353
- console.error('Configuration error:', error.message);
354
- // Handle config issues
355
-
356
- } else {
357
- console.error('Unknown error:', error);
358
- // Handle unexpected errors
359
- }
360
- }
361
- }
362
-
363
- errorHandlingExample();
364
- ```
365
-
366
- ### Advanced Configuration
367
-
368
- ```typescript
369
- import { ExampleApiClient } from './my-client';
370
-
371
- // Advanced WebSocket configuration
372
- const wsClient = new ExampleApiClient({
373
- transport: 'websocket',
374
- websocket: {
375
- url: 'wss://api.example.com',
376
- reconnect: true,
377
- reconnectInterval: 5000,
378
- maxReconnectAttempts: 10,
379
- timeout: 15000,
380
- auth: {
381
- headers: {
382
- 'X-API-Key': 'your-api-key',
383
- 'X-Client-Version': '1.0.0'
384
- }
385
- }
386
- }
387
- });
388
-
389
- // Advanced HTTP configuration
390
- const httpClient = new ExampleApiClient({
391
- transport: 'http',
392
- http: {
393
- baseUrl: 'https://api.example.com',
394
- timeout: 60000,
395
- auth: {
396
- token: 'jwt-token'
397
- },
398
- retry: {
399
- attempts: 5,
400
- delay: 2000,
401
- backoff: 'exponential',
402
- maxDelay: 30000,
403
- retryCondition: (error) => {
404
- // Custom retry logic
405
- return error.status >= 500 || error.status === 429;
406
- }
407
- },
408
- headers: {
409
- 'User-Agent': 'MyApp/1.0.0',
410
- 'Accept': 'application/json'
411
- }
412
- }
413
- });
414
- ```
415
-
416
- ## 🧪 Testing Your Client
417
-
418
- ```typescript
419
- import { ExampleApiClient } from './my-client';
420
-
421
- // Mock transport for testing
422
- const mockTransport = {
423
- connect: jest.fn().mockResolvedValue(undefined),
424
- disconnect: jest.fn().mockResolvedValue(undefined),
425
- request: jest.fn(),
426
- on: jest.fn(),
427
- off: jest.fn(),
428
- getConnectionState: jest.fn().mockReturnValue({ status: 'connected' })
429
- };
430
-
431
- describe('ExampleApiClient', () => {
432
- let client: ExampleApiClient;
433
-
434
- beforeEach(() => {
435
- client = new ExampleApiClient({
436
- transport: 'websocket',
437
- websocket: { url: 'ws://test' }
438
- });
439
-
440
- // Inject mock transport
441
- (client as any).transport = mockTransport;
442
- });
443
-
444
- test('should get user profile', async () => {
445
- const mockResponse = {
446
- id: '123',
447
- name: 'John Doe',
448
- email: 'john@example.com',
449
- createdAt: '2023-01-01T00:00:00Z'
450
- };
451
-
452
- mockTransport.request.mockResolvedValue({
453
- payload: mockResponse
454
- });
455
-
456
- const result = await client.getUserProfile({ userId: '123' });
457
-
458
- expect(result).toEqual(mockResponse);
459
- expect(mockTransport.request).toHaveBeenCalledWith({
460
- operation: 'getUserProfile',
461
- payload: { userId: '123' }
462
- });
463
- });
464
- });
465
- ```
466
-
467
- ## 🔄 Rust-AsyncAPI Compatibility
468
-
469
- This template generates clients that are fully compatible with servers generated by the rust-asyncapi template:
470
-
471
- ### Message Envelope Structure
472
-
473
- Both clients and servers use the same message envelope format:
474
-
475
- ```typescript
476
- interface MessageEnvelope {
477
- correlationId: string;
478
- operation: string;
479
- payload: any;
480
- timestamp?: string;
481
- error?: {
482
- code: string;
483
- message: string;
484
- };
485
- }
486
- ```
487
-
488
- ### Operation Naming
489
-
490
- Operation names are derived consistently from the AsyncAPI specification:
491
- - Channel addresses become method names (e.g., `user/profile` → `getUserProfile`)
492
- - CamelCase conversion follows the same rules as rust-asyncapi
493
- - Request/response patterns are automatically detected
494
-
495
- ### Type Compatibility
496
-
497
- - TypeScript interfaces match Rust struct definitions
498
- - Optional fields are handled consistently
499
- - Enum types are converted to TypeScript union types
500
- - Date/time fields use ISO 8601 strings
501
-
502
- ## 📚 API Reference
503
-
504
- ### Client Configuration
505
-
506
- ```typescript
507
- interface ClientConfig {
508
- transport: 'websocket' | 'http';
509
- websocket?: WebSocketConfig;
510
- http?: HttpConfig;
511
- }
512
-
513
- interface WebSocketConfig {
514
- url: string;
515
- reconnect?: boolean;
516
- reconnectInterval?: number;
517
- maxReconnectAttempts?: number;
518
- timeout?: number;
519
- auth?: AuthConfig;
520
- }
521
-
522
- interface HttpConfig {
523
- baseUrl: string;
524
- timeout?: number;
525
- auth?: AuthConfig;
526
- retry?: RetryConfig;
527
- headers?: Record<string, string>;
528
- }
529
-
530
- interface AuthConfig {
531
- token?: string;
532
- apiKey?: string;
533
- authorization?: string;
534
- headers?: Record<string, string>;
535
- }
536
-
537
- interface RetryConfig {
538
- attempts: number;
539
- delay: number;
540
- backoff?: 'linear' | 'exponential';
541
- maxDelay?: number;
542
- retryCondition?: (error: any) => boolean;
543
- }
544
- ```
545
-
546
- ### Client Methods
547
-
548
- ```typescript
549
- class ExampleApiClient {
550
- constructor(config: ClientConfig);
551
-
552
- // Connection management
553
- connect(): Promise<void>;
554
- disconnect(): Promise<void>;
555
- getConnectionState(): ConnectionState;
556
-
557
- // Event handling
558
- on(event: string, handler: Function): void;
559
- off(event: string, handler: Function): void;
560
-
561
- // Generated operation methods (based on your AsyncAPI spec)
562
- getUserProfile(request: GetUserProfileRequest): Promise<UserProfile>;
563
- createUser(request: CreateUserRequest): Promise<UserCreated>;
564
- // ... other operations
565
- }
566
- ```
567
-
568
- ## 🎯 Best Practices
569
-
570
- 1. **Always handle errors**: Use try-catch blocks and specific error types
571
- 2. **Clean up connections**: Always call `disconnect()` when done
572
- 3. **Use TypeScript**: Take advantage of the generated types for better development experience
573
- 4. **Configure retries**: Set appropriate retry policies for production use
574
- 5. **Monitor connections**: Listen to connection events for better observability
575
- 6. **Test thoroughly**: Use the provided testing patterns to ensure reliability
576
-
577
- ## 🔗 Related Resources
578
-
579
- - [AsyncAPI Specification](https://www.asyncapi.com/docs/reference/specification/v3.0.0)
580
- - [AsyncAPI Generator](https://github.com/asyncapi/generator)
581
- - [Rust AsyncAPI Template](https://github.com/asyncapi/rust-template)
582
- - [TypeScript Documentation](https://www.typescriptlang.org/docs/)
583
-
584
- ---
585
-
586
- Generated with ❤️ by [AsyncAPI Generator](https://github.com/asyncapi/generator)