@ioka-technologies/asyncapi-ts-client-template 0.0.7

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,377 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+
4
+ function generateClient(asyncapi, clientName) {
5
+ // Method name sanitization function
6
+ function sanitizeMethodName(operationId) {
7
+ if (!operationId) return 'unknownOperation';
8
+
9
+ // Convert to camelCase and remove invalid characters
10
+ let sanitized = operationId
11
+ // Replace dots, hyphens, underscores, spaces, and forward slashes with camelCase
12
+ .replace(/[.\-_\s\/]+(.)/g, (_, char) => char.toUpperCase())
13
+ // Remove any remaining invalid characters
14
+ .replace(/[^a-zA-Z0-9]/g, '')
15
+ // Ensure it starts with a lowercase letter if it starts with a number
16
+ .replace(/^[^a-zA-Z]/, 'operation')
17
+ // Ensure first character is lowercase
18
+ .replace(/^[A-Z]/, char => char.toLowerCase());
19
+
20
+ // Handle JavaScript reserved words
21
+ const JS_RESERVED_WORDS = [
22
+ 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default',
23
+ 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for', 'function',
24
+ 'if', 'import', 'in', 'instanceof', 'new', 'return', 'super', 'switch',
25
+ 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield',
26
+ 'let', 'static', 'enum', 'implements', 'package', 'protected', 'interface',
27
+ 'private', 'public', 'async', 'await'
28
+ ];
29
+
30
+ if (JS_RESERVED_WORDS.includes(sanitized)) {
31
+ sanitized = 'operation' + sanitized.charAt(0).toUpperCase() + sanitized.slice(1);
32
+ }
33
+
34
+ // Handle conflicts with existing class methods
35
+ const CLASS_METHOD_NAMES = ['connect', 'disconnect', 'unsubscribe', 'constructor'];
36
+ if (CLASS_METHOD_NAMES.includes(sanitized)) {
37
+ sanitized = sanitized + 'Operation';
38
+ }
39
+
40
+ return sanitized;
41
+ }
42
+
43
+ let content = `import { TransportFactory } from './runtime/transports/factory';
44
+ import { Transport, TransportConfig, RequestOptions, MessageEnvelope } from './runtime/types';
45
+ import * as Models from './models';
46
+
47
+ export class ${clientName} {
48
+ private transport: Transport;
49
+ private config: TransportConfig;
50
+
51
+ constructor(config: TransportConfig) {
52
+ this.config = config;
53
+ this.transport = TransportFactory.create(config);
54
+ }
55
+
56
+ async connect(): Promise<void> {
57
+ await this.transport.connect();
58
+ }
59
+
60
+ async disconnect(): Promise<void> {
61
+ await this.transport.disconnect();
62
+ }
63
+
64
+ /**
65
+ * Unsubscribe from a specific channel
66
+ * @param channel Channel to unsubscribe from
67
+ * @param callback Optional specific callback to remove
68
+ */
69
+ unsubscribe(channel: string, callback?: (payload: any) => void): void {
70
+ this.transport.unsubscribe(channel, callback);
71
+ }
72
+
73
+ // Generated operation methods
74
+ `;
75
+
76
+ // Generate methods for each operation (AsyncAPI v3.0.0)
77
+ if (asyncapi.operations) {
78
+ const operations = asyncapi.operations();
79
+ if (operations) {
80
+ // Handle AsyncAPI parser collection - use .all() method to get array
81
+ const operationArray = operations.all ? operations.all() : Object.values(operations);
82
+ operationArray.forEach((operation) => {
83
+ // Get operation ID from the operation object
84
+ let operationId = null;
85
+ if (operation._meta && operation._meta.id) {
86
+ operationId = operation._meta.id;
87
+ } else if (operation.id && typeof operation.id === 'function') {
88
+ operationId = operation.id();
89
+ } else if (operation.id) {
90
+ operationId = operation.id;
91
+ }
92
+
93
+ if (!operationId) {
94
+ return;
95
+ }
96
+ try {
97
+ // Get the channel reference
98
+ let channelRef = null;
99
+ let channelAddress = null;
100
+
101
+ // Get channel information from embedded channel data (AsyncAPI v3.x approach)
102
+ const embeddedChannel = operation._json && operation._json.channel;
103
+
104
+ if (embeddedChannel) {
105
+ // Get the channel unique object ID
106
+ const embeddedChannelId = embeddedChannel['x-parser-unique-object-id'];
107
+
108
+ if (embeddedChannelId) {
109
+ // Find the channel by its ID
110
+ const channels = asyncapi.channels();
111
+
112
+ // Look for the channel with matching ID
113
+ for (const [channelKey, channel] of Object.entries(channels || {})) {
114
+ if (channel.id && channel.id() === embeddedChannelId) {
115
+ if (channel.address && typeof channel.address === 'function') {
116
+ channelAddress = channel.address();
117
+ } else if (channel.address) {
118
+ channelAddress = channel.address;
119
+ }
120
+ break;
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ // Fallback: try to get channel from $ref (AsyncAPI v2.x approach)
127
+ if (!channelAddress && operation._json && operation._json.channel && operation._json.channel.$ref) {
128
+ channelRef = operation._json.channel.$ref;
129
+
130
+ const channelName = channelRef.split('/').pop();
131
+
132
+ const channels = asyncapi.channels();
133
+ if (channels && channels[channelName]) {
134
+ const channel = channels[channelName];
135
+ if (channel.address && typeof channel.address === 'function') {
136
+ channelAddress = channel.address();
137
+ } else if (channel.address) {
138
+ channelAddress = channel.address;
139
+ }
140
+ }
141
+ }
142
+
143
+ // Get the action (send/receive)
144
+ let action = 'send';
145
+ if (operation.action && typeof operation.action === 'function') {
146
+ action = operation.action();
147
+ } else if (operation.action) {
148
+ action = operation.action;
149
+ }
150
+
151
+ // Get message types for this operation
152
+ let messageTypes = [];
153
+ try {
154
+ if (operation.messages && typeof operation.messages === 'function') {
155
+ const messages = operation.messages();
156
+ if (messages && typeof messages.all === 'function') {
157
+ // Handle AsyncAPI parser collection
158
+ const messageArray = messages.all();
159
+ messageTypes = messageArray.map(msg => {
160
+ // Try to get message name from various sources
161
+ if (msg.name && typeof msg.name === 'function') {
162
+ return msg.name();
163
+ } else if (msg.name) {
164
+ return msg.name;
165
+ } else if (msg._json && msg._json.name) {
166
+ return msg._json.name;
167
+ } else if (msg.$ref) {
168
+ return msg.$ref.split('/').pop();
169
+ }
170
+ return null;
171
+ }).filter(Boolean);
172
+ } else if (Array.isArray(messages)) {
173
+ messageTypes = messages.map(msg => {
174
+ // Try to get message name from various sources
175
+ if (msg.name && typeof msg.name === 'function') {
176
+ return msg.name();
177
+ } else if (msg.name) {
178
+ return msg.name;
179
+ } else if (msg._json && msg._json.name) {
180
+ return msg._json.name;
181
+ } else if (msg.$ref) {
182
+ return msg.$ref.split('/').pop();
183
+ }
184
+ return null;
185
+ }).filter(Boolean);
186
+ }
187
+ } else if (operation.messages && Array.isArray(operation.messages)) {
188
+ messageTypes = operation.messages.map(msg => {
189
+ // Try to get message name from various sources
190
+ if (msg.name && typeof msg.name === 'function') {
191
+ return msg.name();
192
+ } else if (msg.name) {
193
+ return msg.name;
194
+ } else if (msg._json && msg._json.name) {
195
+ return msg._json.name;
196
+ } else if (msg.$ref) {
197
+ return msg.$ref.split('/').pop();
198
+ }
199
+ return null;
200
+ }).filter(Boolean);
201
+ }
202
+
203
+ // Fallback: try to get from operation._json.messages
204
+ if (messageTypes.length === 0 && operation._json && operation._json.messages) {
205
+ messageTypes = operation._json.messages.map(msg => {
206
+ if (msg.name) {
207
+ return msg.name;
208
+ } else if (msg['x-parser-unique-object-id']) {
209
+ return msg['x-parser-unique-object-id'];
210
+ }
211
+ return null;
212
+ }).filter(Boolean);
213
+ }
214
+ } catch (msgError) {
215
+ // If we can't get messages, try to infer from channel
216
+ }
217
+
218
+ if (channelAddress) {
219
+ if (action === 'send') {
220
+ // Check if this is a request/response pattern
221
+ let hasReply = false;
222
+ let replyMessageTypes = [];
223
+
224
+ // Try multiple ways to access reply information
225
+ try {
226
+ // Method 1: Check operation.reply() function
227
+ if (operation.reply && typeof operation.reply === 'function') {
228
+ const reply = operation.reply();
229
+ if (reply) {
230
+ hasReply = true;
231
+
232
+ // Get reply messages
233
+ if (reply.messages && typeof reply.messages === 'function') {
234
+ const replyMessages = reply.messages();
235
+ if (replyMessages && typeof replyMessages.all === 'function') {
236
+ const messageArray = replyMessages.all();
237
+ replyMessageTypes = messageArray.map(msg => {
238
+ if (msg.name && typeof msg.name === 'function') {
239
+ return msg.name();
240
+ } else if (msg.name) {
241
+ return msg.name;
242
+ } else if (msg._json && msg._json.name) {
243
+ return msg._json.name;
244
+ } else if (msg.$ref) {
245
+ return msg.$ref.split('/').pop();
246
+ }
247
+ return null;
248
+ }).filter(Boolean);
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ // Method 2: Check operation._json.reply
255
+ if (!hasReply && operation._json && operation._json.reply) {
256
+ hasReply = true;
257
+ const reply = operation._json.reply;
258
+
259
+ if (reply.messages) {
260
+ replyMessageTypes = reply.messages.map(msg => {
261
+ if (msg.$ref) {
262
+ return msg.$ref.split('/').pop();
263
+ }
264
+ return null;
265
+ }).filter(Boolean);
266
+ }
267
+ }
268
+
269
+ } catch (replyError) {
270
+ // Could not get reply messages for operation
271
+ }
272
+
273
+ // Sanitize the method name
274
+ const methodName = sanitizeMethodName(operationId);
275
+
276
+ if (hasReply) {
277
+ // Request/Response pattern - send and wait for response
278
+ const requestPayloadType = messageTypes.length > 0 ? `Models.${messageTypes[0]}Payload` : 'any';
279
+ const responseType = replyMessageTypes.length > 0 ? `Models.${replyMessageTypes[0]}Payload` : 'any';
280
+
281
+ content += `
282
+ /**
283
+ * ${methodName} - Request/Response operation
284
+ * Original operation: ${operationId}
285
+ * Channel: ${channelAddress}
286
+ * @param payload Request payload
287
+ * @param options Request options
288
+ * @returns Promise that resolves with the response
289
+ */
290
+ async ${methodName}(payload: ${requestPayloadType}, options?: RequestOptions): Promise<${responseType}> {
291
+ const envelope: MessageEnvelope = {
292
+ operation: '${operationId}',
293
+ payload,
294
+ channel: '${channelAddress}'
295
+ };
296
+ return this.transport.send('${channelAddress}', envelope, options);
297
+ }
298
+ `;
299
+ } else {
300
+ // Regular send operation (fire and forget)
301
+ const payloadType = messageTypes.length > 0 ? `Models.${messageTypes[0]}Payload` : 'any';
302
+ content += `
303
+ /**
304
+ * ${methodName} - Send operation (fire and forget)
305
+ * Original operation: ${operationId}
306
+ * Channel: ${channelAddress}
307
+ */
308
+ async ${methodName}(payload: ${payloadType}, options?: RequestOptions): Promise<void> {
309
+ const envelope: MessageEnvelope = {
310
+ operation: '${operationId}',
311
+ payload,
312
+ channel: '${channelAddress}'
313
+ };
314
+ await this.transport.send('${channelAddress}', envelope, options);
315
+ }
316
+ `;
317
+ }
318
+ } else if (action === 'receive') {
319
+ // Sanitize the method name
320
+ const methodName = sanitizeMethodName(operationId);
321
+
322
+ // Generate receive method (event listener setup)
323
+ const payloadType = messageTypes.length > 0 ? `Models.${messageTypes[0]}Payload` : 'any';
324
+ content += `
325
+ /**
326
+ * ${methodName} - Receive operation
327
+ * Original operation: ${operationId}
328
+ * Channel: ${channelAddress}
329
+ * @param callback Function to call when a message is received
330
+ * @returns Unsubscribe function to stop listening for messages
331
+ */
332
+ ${methodName}(callback: (payload: ${payloadType}) => void): () => void {
333
+ return this.transport.subscribe('${channelAddress}', (envelope: MessageEnvelope) => {
334
+ // Filter by operation to ensure we only handle messages for this specific operation
335
+ if (envelope.operation === '${operationId}') {
336
+ callback(envelope.payload);
337
+ }
338
+ });
339
+ }
340
+ `;
341
+ }
342
+ }
343
+ } catch (error) {
344
+ // Skip operations that can't be processed
345
+ }
346
+ });
347
+ }
348
+ }
349
+
350
+ content += `
351
+ }
352
+
353
+ export default ${clientName};
354
+ `;
355
+
356
+ return content;
357
+ }
358
+
359
+ module.exports = function ({ asyncapi, params }) {
360
+ const title = asyncapi.info().title();
361
+
362
+ // Always use the processed title, ignore params.clientName if it contains template variables
363
+ let clientName = `${title.replace(/[^a-zA-Z0-9]/g, '')}Client`;
364
+
365
+ // Only use params.clientName if it doesn't contain template variables
366
+ if (params.clientName && !params.clientName.includes('{{')) {
367
+ clientName = params.clientName;
368
+ }
369
+
370
+ const generatedContent = generateClient(asyncapi, clientName);
371
+
372
+ return (
373
+ <File name="client.ts">
374
+ {generatedContent}
375
+ </File>
376
+ );
377
+ }
@@ -0,0 +1,13 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+
4
+ module.exports = function ({ asyncapi, params }) {
5
+ return (
6
+ <File name="index.ts">
7
+ {`export * from './client';
8
+ export * from './models';
9
+ export * from './runtime/types';
10
+ export * from './runtime/errors';`}
11
+ </File>
12
+ );
13
+ }
@@ -0,0 +1,229 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+
4
+ function generateModels(asyncapi) {
5
+ let content = `// Generated TypeScript models from AsyncAPI specification\n\n`;
6
+
7
+ // Helper functions for TypeScript identifier generation
8
+ function toTypeScriptIdentifier(str) {
9
+ if (!str) return 'unknown';
10
+ let identifier = str
11
+ .replace(/[^a-zA-Z0-9_]/g, '_')
12
+ .replace(/^[0-9]/, '_$&')
13
+ .replace(/_+/g, '_')
14
+ .replace(/^_+|_+$/g, '');
15
+ if (/^[0-9]/.test(identifier)) {
16
+ identifier = 'Item' + identifier;
17
+ }
18
+ if (!identifier) {
19
+ identifier = 'unknown';
20
+ }
21
+ return identifier;
22
+ }
23
+
24
+ function toTypeScriptTypeName(str) {
25
+ if (!str) return 'Unknown';
26
+ // Handle camelCase and PascalCase properly
27
+ const identifier = str
28
+ .replace(/[^a-zA-Z0-9]/g, '_')
29
+ .replace(/^[0-9]/, '_$&')
30
+ .replace(/_+/g, '_')
31
+ .replace(/^_+|_+$/g, '');
32
+
33
+ // Split on underscores and camelCase boundaries
34
+ const parts = identifier.split(/[_\s]+|(?=[A-Z])/);
35
+
36
+ return parts
37
+ .filter(part => part.length > 0)
38
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
39
+ .join('');
40
+ }
41
+
42
+ // Extract message schemas and build channel mapping
43
+ const components = asyncapi.components();
44
+ const messageSchemas = [];
45
+ const messageToChannels = new Map();
46
+ const generatedTypes = new Set();
47
+
48
+ // First, build channel to message mapping
49
+ if (asyncapi.channels) {
50
+ const channels = asyncapi.channels();
51
+ if (channels) {
52
+ Object.entries(channels).forEach(([channelName, channel]) => {
53
+ try {
54
+ // Handle AsyncAPI 3.x format
55
+ if (channel.messages) {
56
+ const messages = channel.messages();
57
+ if (messages) {
58
+ Object.entries(messages).forEach(([msgKey, message]) => {
59
+ if (message) {
60
+ let messageName = null;
61
+ if (message.$ref) {
62
+ messageName = message.$ref.split('/').pop();
63
+ } else if (message.name) {
64
+ messageName = typeof message.name === 'function' ? message.name() : message.name;
65
+ }
66
+
67
+ if (messageName) {
68
+ if (!messageToChannels.has(messageName)) {
69
+ messageToChannels.set(messageName, []);
70
+ }
71
+ messageToChannels.get(messageName).push(channelName);
72
+ }
73
+ }
74
+ });
75
+ }
76
+ }
77
+ } catch (e) {
78
+ // Ignore channel processing errors
79
+ }
80
+ });
81
+ }
82
+ }
83
+
84
+ // Extract messages from components
85
+ if (components && components.messages) {
86
+ const messages = components.messages();
87
+ if (messages) {
88
+ Object.entries(messages).forEach(([name, message]) => {
89
+ // Skip internal AsyncAPI parser objects
90
+ if (name === 'collections' || name === '_meta' || name.startsWith('_')) {
91
+ return;
92
+ }
93
+ let payload = null;
94
+ let description = null;
95
+ let title = null;
96
+ let messageName = name;
97
+
98
+ try {
99
+ if (message.payload && typeof message.payload === 'function') {
100
+ const payloadSchema = message.payload();
101
+ payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
102
+ }
103
+ description = message.description && typeof message.description === 'function' ? message.description() : message.description;
104
+ title = message.title && typeof message.title === 'function' ? message.title() : message.title;
105
+
106
+ // Try to get the actual message name
107
+ if (message.name && typeof message.name === 'function') {
108
+ messageName = message.name();
109
+ } else if (message.name) {
110
+ messageName = message.name;
111
+ }
112
+ } catch (e) {
113
+ // Ignore payload extraction errors
114
+ }
115
+
116
+ const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
117
+ messageSchemas.push({
118
+ name: messageName,
119
+ typeName: toTypeScriptTypeName(messageName),
120
+ payload,
121
+ description: description || title,
122
+ channels
123
+ });
124
+ });
125
+ }
126
+ }
127
+
128
+ // Helper function to convert JSON schema to TypeScript type
129
+ function jsonSchemaToTypeScriptType(schema) {
130
+ if (!schema) return 'any';
131
+
132
+ // Handle $ref
133
+ if (schema.$ref) {
134
+ const refName = schema.$ref.split('/').pop();
135
+ return toTypeScriptTypeName(refName);
136
+ }
137
+
138
+ if (!schema.type) {
139
+ // If no type specified, check for properties (object) or items (array)
140
+ if (schema.properties) {
141
+ schema.type = 'object';
142
+ } else if (schema.items) {
143
+ schema.type = 'array';
144
+ } else {
145
+ return 'any';
146
+ }
147
+ }
148
+
149
+ switch (schema.type) {
150
+ case 'string':
151
+ if (schema.enum && schema.enum.length > 0) {
152
+ return schema.enum.map(val => `'${val}'`).join(' | ');
153
+ }
154
+ return 'string';
155
+ case 'integer':
156
+ case 'number':
157
+ return 'number';
158
+ case 'boolean':
159
+ return 'boolean';
160
+ case 'array': {
161
+ const itemType = jsonSchemaToTypeScriptType(schema.items);
162
+ return `${itemType}[]`;
163
+ }
164
+ case 'object':
165
+ return 'Record<string, any>';
166
+ default:
167
+ return 'any';
168
+ }
169
+ }
170
+
171
+ // Generate message interfaces
172
+ function generateMessageInterface(schema, messageName) {
173
+ if (!schema || !schema.properties) {
174
+ return ' [key: string]: any;';
175
+ }
176
+
177
+ const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
178
+ const tsType = jsonSchemaToTypeScriptType(fieldSchema);
179
+ const optional = !schema.required || !schema.required.includes(fieldName);
180
+ const optionalMarker = optional ? '?' : '';
181
+
182
+ let fieldDoc = '';
183
+ if (fieldSchema.description) {
184
+ fieldDoc = ` /** ${fieldSchema.description} */\n`;
185
+ }
186
+
187
+ return `${fieldDoc} ${fieldName}${optionalMarker}: ${tsType};`;
188
+ }).join('\n');
189
+
190
+ return fields;
191
+ }
192
+
193
+ // Generate interfaces for each message
194
+ messageSchemas.forEach(schema => {
195
+ const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} message payload */\n`;
196
+
197
+ content += `${doc}export interface ${schema.typeName}Payload {\n`;
198
+ content += generateMessageInterface(schema.payload, schema.typeName);
199
+ content += `\n}\n\n`;
200
+ });
201
+
202
+ // Generate a union type for all message payloads
203
+ if (messageSchemas.length > 0) {
204
+ const payloadTypes = messageSchemas.map(schema => `${schema.typeName}Payload`).join(' | ');
205
+ content += `/** Union type for all message payloads */\n`;
206
+ content += `export type MessagePayload = ${payloadTypes};\n\n`;
207
+
208
+ // Generate message type constants
209
+ content += `/** Message type constants */\n`;
210
+ content += `export const MessageTypes = {\n`;
211
+ messageSchemas.forEach(schema => {
212
+ content += ` ${schema.typeName.toUpperCase()}: '${schema.name}',\n`;
213
+ });
214
+ content += `} as const;\n\n`;
215
+
216
+ content += `/** Message type union */\n`;
217
+ content += `export type MessageType = typeof MessageTypes[keyof typeof MessageTypes];\n\n`;
218
+ }
219
+
220
+ return content;
221
+ }
222
+
223
+ module.exports = function ({ asyncapi, params }) {
224
+ return (
225
+ <File name="models.ts">
226
+ {generateModels(asyncapi)}
227
+ </File>
228
+ );
229
+ }
@@ -0,0 +1,29 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+
4
+ module.exports = function ({ asyncapi, params }) {
5
+ return (
6
+ <File name="errors.ts">
7
+ {`export class TransportError extends Error {
8
+ constructor(message: string) {
9
+ super(message);
10
+ this.name = 'TransportError';
11
+ }
12
+ }
13
+
14
+ export class ConnectionError extends TransportError {
15
+ constructor(message: string) {
16
+ super(message);
17
+ this.name = 'ConnectionError';
18
+ }
19
+ }
20
+
21
+ export class TimeoutError extends TransportError {
22
+ constructor(message: string) {
23
+ super(message);
24
+ this.name = 'TimeoutError';
25
+ }
26
+ }`}
27
+ </File>
28
+ );
29
+ }
@@ -0,0 +1,33 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { File } from '@asyncapi/generator-react-sdk';
3
+
4
+ module.exports = function ({ asyncapi, params }) {
5
+ const transports = (params.transports || 'websocket,http').split(',').map(t => t.trim());
6
+
7
+ let imports = `import { Transport, TransportConfig } from '../types';\n`;
8
+ let cases = '';
9
+
10
+ if (transports.includes('websocket')) {
11
+ imports += `import { WebSocketTransport } from './websocket';\n`;
12
+ cases += ` case 'websocket': return new WebSocketTransport(config);\n`;
13
+ }
14
+
15
+ if (transports.includes('http')) {
16
+ imports += `import { HttpTransport } from './http';\n`;
17
+ cases += ` case 'http': return new HttpTransport(config);\n`;
18
+ }
19
+
20
+ return (
21
+ <File name="factory.ts">
22
+ {`${imports}
23
+ export class TransportFactory {
24
+ static create(config: TransportConfig): Transport {
25
+ switch (config.type) {
26
+ ${cases} default:
27
+ throw new Error(\`Unsupported transport type: \${config.type}\`);
28
+ }
29
+ }
30
+ }`}
31
+ </File>
32
+ );
33
+ }