@ioka-technologies/asyncapi-rust-client-template 0.0.22 → 0.0.24

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.
@@ -1,3 +1,4 @@
1
+ import "@asyncapi/generator-react-sdk";
1
2
  /******/ // The require scope
2
3
  /******/ var __webpack_require__ = {};
3
4
  /******/
@@ -1883,6 +1884,1469 @@ function generateOperationHandlerName(operation, suffix = 'Handler') {
1883
1884
  const pascalCaseName = toPascalCase(operationName);
1884
1885
  return `${pascalCaseName}${suffix}`;
1885
1886
  }
1887
+ ;// external "@asyncapi/generator-react-sdk"
1888
+
1889
+ ;// ../common/src/models-rust.js
1890
+ /* eslint-disable no-unused-vars */
1891
+
1892
+
1893
+ /**
1894
+ * Generate Rust models from AsyncAPI specification
1895
+ * This helper extracts the common schema processing logic used by both rust-server and rust-client templates
1896
+ *
1897
+ * @param {Object} asyncapi - AsyncAPI document
1898
+ * @param {Object} options - Generation options
1899
+ * @param {Function} options.toRustTypeName - Function to convert names to Rust type names
1900
+ * @param {Function} options.toRustFieldName - Function to convert names to Rust field names
1901
+ * @param {Function} options.toRustEnumVariantWithSerde - Function to convert enum variants with serde attributes
1902
+ * @param {boolean} options.includeAsyncApiTrait - Whether to include AsyncApiMessage trait implementations
1903
+ * @param {boolean} options.includeEnvelope - Whether to include MessageEnvelope (from envelope-rust.js)
1904
+ * @returns {Object} Generated models data and functions
1905
+ */
1906
+ function generateRustModels(asyncapi, options = {}) {
1907
+ const {
1908
+ toRustTypeName,
1909
+ toRustFieldName,
1910
+ toRustEnumVariantWithSerde,
1911
+ includeAsyncApiTrait = false,
1912
+ includeEnvelope = false
1913
+ } = options;
1914
+
1915
+ // Extract message schemas and build channel mapping
1916
+ const components = asyncapi.components();
1917
+ const messageSchemas = [];
1918
+ const componentSchemas = [];
1919
+ const messageToChannels = new Map();
1920
+ const generatedTypes = new Set();
1921
+ const nestedSchemas = new Map();
1922
+ const schemaRegistry = new Map();
1923
+
1924
+ // First, build channel to message mapping and extract inline message schemas
1925
+ if (asyncapi.channels) {
1926
+ const channels = asyncapi.channels();
1927
+ if (channels) {
1928
+ // Use proper iteration for AsyncAPI collection
1929
+ for (const channel of channels) {
1930
+ try {
1931
+ const channelName = channel.id();
1932
+
1933
+ // Handle AsyncAPI 3.x format - extract inline messages from channels
1934
+ if (channel.messages) {
1935
+ const messages = channel.messages();
1936
+ if (messages) {
1937
+ // Check if messages is an object with message names as keys
1938
+ if (typeof messages === 'object' && !Array.isArray(messages)) {
1939
+ // Iterate through message entries (messageName -> messageObject)
1940
+ Object.entries(messages).forEach(([messageName, message]) => {
1941
+ if (message && messageName) {
1942
+ let payload = null;
1943
+ let description = null;
1944
+
1945
+ // Get payload schema
1946
+ if (message.payload && typeof message.payload === 'function') {
1947
+ payload = message.payload();
1948
+ if (payload && payload.json && typeof payload.json === 'function') {
1949
+ payload = payload.json();
1950
+ }
1951
+ } else if (message.payload) {
1952
+ payload = message.payload;
1953
+ }
1954
+
1955
+ // Get description
1956
+ if (message.description && typeof message.description === 'function') {
1957
+ description = message.description();
1958
+ } else if (message.description) {
1959
+ description = message.description;
1960
+ }
1961
+
1962
+ // Add to channel mapping
1963
+ if (!messageToChannels.has(messageName)) {
1964
+ messageToChannels.set(messageName, []);
1965
+ }
1966
+ messageToChannels.get(messageName).push(channelName);
1967
+
1968
+ // Add to message schemas for inline messages
1969
+ messageSchemas.push({
1970
+ name: messageName,
1971
+ rustName: toRustTypeName(messageName),
1972
+ payload,
1973
+ rawPayload: payload,
1974
+ description,
1975
+ channels: [channelName]
1976
+ });
1977
+ }
1978
+ });
1979
+ } else {
1980
+ // Try iterating as a collection
1981
+ for (const message of messages) {
1982
+ if (message) {
1983
+ let messageName = null;
1984
+ let payload = null;
1985
+ let description = null;
1986
+
1987
+ // Get message name - try multiple approaches
1988
+ if (message._meta && message._meta.id) {
1989
+ messageName = message._meta.id;
1990
+ } else if (message._json && message._json['x-parser-message-name']) {
1991
+ messageName = message._json['x-parser-message-name'];
1992
+ } else if (message._json && message._json['x-parser-unique-object-id']) {
1993
+ messageName = message._json['x-parser-unique-object-id'];
1994
+ } else if (message.name && typeof message.name === 'function') {
1995
+ messageName = message.name();
1996
+ } else if (message.name) {
1997
+ messageName = message.name;
1998
+ } else if (message.$ref) {
1999
+ messageName = message.$ref.split('/').pop();
2000
+ }
2001
+
2002
+ // Get payload schema
2003
+ if (message.payload && typeof message.payload === 'function') {
2004
+ payload = message.payload();
2005
+ if (payload && payload.json && typeof payload.json === 'function') {
2006
+ payload = payload.json();
2007
+ }
2008
+ } else if (message.payload) {
2009
+ payload = message.payload;
2010
+ }
2011
+
2012
+ // Get description
2013
+ if (message.description && typeof message.description === 'function') {
2014
+ description = message.description();
2015
+ } else if (message.description) {
2016
+ description = message.description;
2017
+ }
2018
+ if (messageName) {
2019
+ // Add to channel mapping
2020
+ if (!messageToChannels.has(messageName)) {
2021
+ messageToChannels.set(messageName, []);
2022
+ }
2023
+ messageToChannels.get(messageName).push(channelName);
2024
+
2025
+ // Add to message schemas for inline messages
2026
+ messageSchemas.push({
2027
+ name: messageName,
2028
+ rustName: toRustTypeName(messageName),
2029
+ payload,
2030
+ rawPayload: payload,
2031
+ description,
2032
+ channels: [channelName]
2033
+ });
2034
+ }
2035
+ }
2036
+ }
2037
+ }
2038
+ }
2039
+ }
2040
+
2041
+ // Handle AsyncAPI 2.x format
2042
+ if (channel.subscribe && channel.subscribe()) {
2043
+ const message = channel.subscribe().message();
2044
+ if (message) {
2045
+ let messageName = null;
2046
+ if (message.$ref) {
2047
+ messageName = message.$ref.split('/').pop();
2048
+ } else if (message.name) {
2049
+ messageName = typeof message.name === 'function' ? message.name() : message.name;
2050
+ }
2051
+ if (messageName) {
2052
+ if (!messageToChannels.has(messageName)) {
2053
+ messageToChannels.set(messageName, []);
2054
+ }
2055
+ messageToChannels.get(messageName).push(channelName);
2056
+ }
2057
+ }
2058
+ }
2059
+ if (channel.publish && channel.publish()) {
2060
+ const message = channel.publish().message();
2061
+ if (message) {
2062
+ let messageName = null;
2063
+ if (message.$ref) {
2064
+ messageName = message.$ref.split('/').pop();
2065
+ } else if (message.name) {
2066
+ messageName = typeof message.name === 'function' ? message.name() : message.name;
2067
+ }
2068
+ if (messageName) {
2069
+ if (!messageToChannels.has(messageName)) {
2070
+ messageToChannels.set(messageName, []);
2071
+ }
2072
+ messageToChannels.get(messageName).push(channelName);
2073
+ }
2074
+ }
2075
+ }
2076
+ } catch (e) {
2077
+ // Ignore channel processing errors
2078
+ console.warn(`Error processing channel: ${e.message}`);
2079
+ }
2080
+ }
2081
+ }
2082
+ }
2083
+
2084
+ // Build schema registry from components.schemas
2085
+ // Try to access the raw AsyncAPI document
2086
+ let rawDoc = null;
2087
+ try {
2088
+ if (asyncapi.json && typeof asyncapi.json === 'function') {
2089
+ rawDoc = asyncapi.json();
2090
+ } else if (asyncapi._json) {
2091
+ rawDoc = asyncapi._json;
2092
+ }
2093
+ } catch (e) {
2094
+ // Ignore
2095
+ }
2096
+
2097
+ // Extract schemas from raw document if available
2098
+ if (rawDoc && rawDoc.components && rawDoc.components.schemas) {
2099
+ Object.entries(rawDoc.components.schemas).forEach(([name, schema]) => {
2100
+ if (name && typeof name === 'string' && schema && typeof schema === 'object') {
2101
+ schemaRegistry.set(name, schema);
2102
+ componentSchemas.push({
2103
+ name,
2104
+ rustName: toRustTypeName(name),
2105
+ schema: schema,
2106
+ description: schema.description
2107
+ });
2108
+ }
2109
+ });
2110
+ }
2111
+
2112
+ // Fallback: try the components.schemas() method
2113
+ if (componentSchemas.length === 0 && components && components.schemas) {
2114
+ try {
2115
+ const schemas = components.schemas();
2116
+ if (schemas) {
2117
+ // Try different ways to access schemas
2118
+ let schemaEntries = [];
2119
+ if (schemas instanceof Map) {
2120
+ schemaEntries = Array.from(schemas.entries());
2121
+ } else if (typeof schemas === 'object') {
2122
+ schemaEntries = Object.entries(schemas);
2123
+ } else if (schemas.all && typeof schemas.all === 'function') {
2124
+ // AsyncAPI parser might have an all() method
2125
+ const allSchemas = schemas.all();
2126
+ if (Array.isArray(allSchemas)) {
2127
+ schemaEntries = allSchemas.map(schema => {
2128
+ const name = schema.uid ? schema.uid() : schema.id ? schema.id() : null;
2129
+ return [name, schema];
2130
+ }).filter(([name]) => name);
2131
+ }
2132
+ }
2133
+ schemaEntries.forEach(([name, schema]) => {
2134
+ // Skip internal AsyncAPI parser objects and numeric keys
2135
+ if (!name || name === 'collections' || name === '_meta' || name.startsWith('_') || /^\d+$/.test(name)) {
2136
+ return;
2137
+ }
2138
+ let schemaData = null;
2139
+ let description = null;
2140
+ try {
2141
+ // Handle different schema object types
2142
+ if (schema && typeof schema.json === 'function') {
2143
+ schemaData = schema.json();
2144
+ } else if (schema && typeof schema === 'object') {
2145
+ schemaData = schema;
2146
+ }
2147
+ if (schema && typeof schema.description === 'function') {
2148
+ description = schema.description();
2149
+ } else if (schema && schema.description) {
2150
+ description = schema.description;
2151
+ }
2152
+ } catch (e) {
2153
+ // Ignore schema extraction errors
2154
+ console.warn(`Failed to extract schema for ${name}:`, e.message);
2155
+ }
2156
+ if (schemaData && typeof name === 'string' && name.length > 0) {
2157
+ schemaRegistry.set(name, schemaData);
2158
+ componentSchemas.push({
2159
+ name,
2160
+ rustName: toRustTypeName(name),
2161
+ schema: schemaData,
2162
+ description
2163
+ });
2164
+ }
2165
+ });
2166
+ }
2167
+ } catch (e) {
2168
+ console.warn('Failed to extract component schemas:', e.message);
2169
+ }
2170
+ }
2171
+
2172
+ // Extract messages from components
2173
+ if (components && components.messages) {
2174
+ const messages = components.messages();
2175
+ if (messages) {
2176
+ Object.entries(messages).forEach(([name, message]) => {
2177
+ let payload = null;
2178
+ let description = null;
2179
+ let title = null;
2180
+ let messageName = name;
2181
+ try {
2182
+ let rawPayload = null;
2183
+ if (message.payload && typeof message.payload === 'function') {
2184
+ const payloadSchema = message.payload();
2185
+ payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
2186
+ // Try to get the raw payload reference from the message
2187
+ if (message._json && message._json.payload) {
2188
+ rawPayload = message._json.payload;
2189
+ }
2190
+ }
2191
+ description = message.description && typeof message.description === 'function' ? message.description() : null;
2192
+ title = message.title && typeof message.title === 'function' ? message.title() : null;
2193
+
2194
+ // Try to get the actual message name
2195
+ if (message.name && typeof message.name === 'function') {
2196
+ messageName = message.name();
2197
+ } else if (message.name) {
2198
+ messageName = message.name;
2199
+ }
2200
+
2201
+ // If we have raw document access, try to get the payload reference from there
2202
+ if (!rawPayload && rawDoc && rawDoc.components && rawDoc.components.messages && rawDoc.components.messages[name]) {
2203
+ rawPayload = rawDoc.components.messages[name].payload;
2204
+ }
2205
+ const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
2206
+ messageSchemas.push({
2207
+ name: messageName,
2208
+ rustName: toRustTypeName(messageName),
2209
+ payload,
2210
+ rawPayload,
2211
+ description: description || title,
2212
+ channels
2213
+ });
2214
+ } catch (e) {
2215
+ // Ignore payload extraction errors
2216
+ const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
2217
+ messageSchemas.push({
2218
+ name: messageName,
2219
+ rustName: toRustTypeName(messageName),
2220
+ payload,
2221
+ rawPayload: null,
2222
+ description: description || title,
2223
+ channels
2224
+ });
2225
+ }
2226
+ });
2227
+ }
2228
+ }
2229
+
2230
+ // Helper function to convert JSON schema to Rust type
2231
+ function jsonSchemaToRustType(schema, typeName = null) {
2232
+ if (!schema) return 'serde_json::Value';
2233
+
2234
+ // Handle $ref
2235
+ if (schema.$ref) {
2236
+ const refName = schema.$ref.split('/').pop();
2237
+ const rustTypeName = toRustTypeName(refName);
2238
+
2239
+ // Generate the referenced schema if we have access to components
2240
+ if (components && components.schemas) {
2241
+ const schemas = components.schemas();
2242
+ if (schemas && schemas[refName] && !generatedTypes.has(rustTypeName)) {
2243
+ generatedTypes.add(rustTypeName);
2244
+ const referencedSchema = schemas[refName];
2245
+ const schemaJson = referencedSchema.json ? referencedSchema.json() : referencedSchema;
2246
+ nestedSchemas.set(rustTypeName, {
2247
+ type: 'struct',
2248
+ schema: schemaJson,
2249
+ description: referencedSchema.description && typeof referencedSchema.description === 'function' ? referencedSchema.description() : null
2250
+ });
2251
+ }
2252
+ }
2253
+ return rustTypeName;
2254
+ }
2255
+
2256
+ // Handle resolved $ref - check for x-parser-schema-id which indicates original schema name
2257
+ if (schema['x-parser-schema-id'] && typeof schema['x-parser-schema-id'] === 'string') {
2258
+ const schemaId = schema['x-parser-schema-id'];
2259
+ // Check if this matches a known component schema
2260
+ if (schemaRegistry.has(schemaId)) {
2261
+ const rustTypeName = toRustTypeName(schemaId);
2262
+ return rustTypeName;
2263
+ }
2264
+ }
2265
+ if (!schema.type) {
2266
+ // If no type specified, check for properties (object) or items (array)
2267
+ if (schema.properties) {
2268
+ schema.type = 'object';
2269
+ } else if (schema.items) {
2270
+ schema.type = 'array';
2271
+ } else {
2272
+ return 'serde_json::Value';
2273
+ }
2274
+ }
2275
+ switch (schema.type) {
2276
+ case 'string':
2277
+ if (schema.enum && schema.enum.length > 0) {
2278
+ // Generate enum type
2279
+ if (typeName) {
2280
+ const enumName = `${typeName}Enum`;
2281
+ if (!generatedTypes.has(enumName)) {
2282
+ generatedTypes.add(enumName);
2283
+ nestedSchemas.set(enumName, {
2284
+ type: 'enum',
2285
+ variants: schema.enum,
2286
+ description: schema.description
2287
+ });
2288
+ }
2289
+ return enumName;
2290
+ }
2291
+ return 'String'; // Fallback if no type name provided
2292
+ }
2293
+ if (schema.format === 'date-time') return 'chrono::DateTime<chrono::Utc>';
2294
+ if (schema.format === 'uuid') return 'uuid::Uuid';
2295
+ if (schema.format === 'email') return 'String';
2296
+ if (schema.format === 'uri') return 'String';
2297
+ return 'String';
2298
+ case 'integer':
2299
+ switch (schema.format) {
2300
+ case 'int32':
2301
+ return 'i32';
2302
+ case 'int64':
2303
+ return 'i64';
2304
+ case 'uint32':
2305
+ return 'u32';
2306
+ case 'uint64':
2307
+ return 'u64';
2308
+ default:
2309
+ // Default to i32 for unspecified format (maintains backward compatibility)
2310
+ return 'i32';
2311
+ }
2312
+ case 'number':
2313
+ return 'f64';
2314
+ case 'boolean':
2315
+ return 'bool';
2316
+ case 'array':
2317
+ {
2318
+ const itemType = jsonSchemaToRustType(schema.items);
2319
+ return `Vec<${itemType}>`;
2320
+ }
2321
+ case 'object':
2322
+ if (schema.properties && Object.keys(schema.properties).length > 0) {
2323
+ // Generate nested struct
2324
+ if (typeName) {
2325
+ const structName = toRustTypeName(typeName);
2326
+ if (!generatedTypes.has(structName)) {
2327
+ generatedTypes.add(structName);
2328
+ nestedSchemas.set(structName, {
2329
+ type: 'struct',
2330
+ schema: schema,
2331
+ description: schema.description
2332
+ });
2333
+ }
2334
+ return structName;
2335
+ }
2336
+ }
2337
+ return 'serde_json::Value';
2338
+ default:
2339
+ return 'serde_json::Value';
2340
+ }
2341
+ }
2342
+
2343
+ // Generate message structs
2344
+ function generateMessageStruct(schema, messageName) {
2345
+ if (!schema || !schema.properties) {
2346
+ return ' pub data: serde_json::Value,';
2347
+ }
2348
+ const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
2349
+ const rustFieldName = toRustFieldName(fieldName);
2350
+ const fieldTypeName = `${messageName}${toRustTypeName(fieldName)}`;
2351
+ const rustType = jsonSchemaToRustType(fieldSchema, fieldTypeName);
2352
+ const requiredFields = schema.required;
2353
+ const optional = !requiredFields || !Array.isArray(requiredFields) || requiredFields.indexOf(fieldName) === -1;
2354
+ const finalType = optional ? `Option<${rustType}>` : rustType;
2355
+ let fieldDoc = '';
2356
+ if (fieldSchema.description) {
2357
+ fieldDoc = ` /// ${fieldSchema.description}\n`;
2358
+ }
2359
+ let serdeRename = '';
2360
+ if (rustFieldName !== fieldName) {
2361
+ serdeRename = ` #[serde(rename = "${fieldName}")]\n`;
2362
+ }
2363
+ let skipSerializing = '';
2364
+ if (optional) {
2365
+ skipSerializing = ' #[serde(skip_serializing_if = "Option::is_none")]\n';
2366
+ }
2367
+ return `${fieldDoc}${serdeRename}${skipSerializing} pub ${rustFieldName}: ${finalType},`;
2368
+ }).join('\n');
2369
+ return fields;
2370
+ }
2371
+
2372
+ // Process all component schemas first to ensure they are available for references
2373
+ componentSchemas.forEach(schema => {
2374
+ jsonSchemaToRustType(schema.schema, schema.rustName);
2375
+ generatedTypes.add(schema.rustName);
2376
+ });
2377
+
2378
+ // Process all message schemas to ensure all referenced types are generated
2379
+ messageSchemas.forEach(schema => {
2380
+ if (schema.payload) {
2381
+ jsonSchemaToRustType(schema.payload, schema.rustName);
2382
+ }
2383
+ });
2384
+
2385
+ // Return the processed data and generation functions
2386
+ return {
2387
+ messageSchemas,
2388
+ componentSchemas,
2389
+ messageToChannels,
2390
+ generatedTypes,
2391
+ nestedSchemas,
2392
+ schemaRegistry,
2393
+ // Generation functions
2394
+ generateMessageStruct,
2395
+ jsonSchemaToRustType,
2396
+ // Generate component schema definitions
2397
+ generateComponentSchemas() {
2398
+ let result = '';
2399
+ componentSchemas.forEach(schema => {
2400
+ const doc = schema.description ? `/// ${schema.description}\n` : `/// ${schema.name}\n`;
2401
+
2402
+ // Check if this is a standalone enum schema
2403
+ if (schema.schema.type === 'string' && schema.schema.enum && Array.isArray(schema.schema.enum)) {
2404
+ // Generate enum definition with serde rename attributes for lowercase serialization
2405
+ const variants = schema.schema.enum.map(variant => {
2406
+ const {
2407
+ rustName,
2408
+ serializedName
2409
+ } = toRustEnumVariantWithSerde(variant);
2410
+ return ` #[serde(rename = "${serializedName}")]\n ${rustName}`;
2411
+ }).join(',\n');
2412
+ result += `
2413
+ ${doc}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2414
+ pub enum ${schema.rustName} {
2415
+ ${variants},
2416
+ }
2417
+ `;
2418
+ } else {
2419
+ // Generate struct definition
2420
+ const fields = generateMessageStruct(schema.schema, schema.rustName);
2421
+ result += `
2422
+ ${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
2423
+ pub struct ${schema.rustName} {
2424
+ ${fields}
2425
+ }
2426
+ `;
2427
+ }
2428
+ });
2429
+ return result;
2430
+ },
2431
+ // Generate nested type definitions
2432
+ generateNestedTypes() {
2433
+ let result = '';
2434
+ for (const [typeName, typeInfo] of nestedSchemas.entries()) {
2435
+ // Don't skip enums - they need to be generated even if the parent type exists
2436
+ const isEnum = typeInfo.type === 'enum';
2437
+ const isComponentSchema = componentSchemas.some(cs => cs.rustName === typeName);
2438
+
2439
+ // Skip if this type was already generated as a component schema (but not enums)
2440
+ if (!isEnum && isComponentSchema) {
2441
+ continue;
2442
+ }
2443
+ if (typeInfo.type === 'enum') {
2444
+ const variants = typeInfo.variants.map(variant => {
2445
+ const {
2446
+ rustName,
2447
+ serializedName
2448
+ } = toRustEnumVariantWithSerde(variant);
2449
+ return ` #[serde(rename = "${serializedName}")]\n ${rustName}`;
2450
+ }).join(',\n');
2451
+ const doc = typeInfo.description ? `/// ${typeInfo.description}\n` : '';
2452
+ result += `
2453
+ ${doc}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2454
+ pub enum ${typeName} {
2455
+ ${variants},
2456
+ }
2457
+ `;
2458
+ } else if (typeInfo.type === 'struct') {
2459
+ const fields = generateMessageStruct(typeInfo.schema, typeName);
2460
+ const doc = typeInfo.description ? `/// ${typeInfo.description}\n` : '';
2461
+ result += `
2462
+ ${doc}#[derive(Debug, Clone, Serialize, Deserialize)]
2463
+ pub struct ${typeName} {
2464
+ ${fields}
2465
+ }
2466
+ `;
2467
+ }
2468
+ }
2469
+ return result;
2470
+ },
2471
+ // Generate AsyncApiMessage trait implementations (optional)
2472
+ generateAsyncApiTrait() {
2473
+ if (!includeAsyncApiTrait) {
2474
+ return '';
2475
+ }
2476
+
2477
+ // Track which types have already had AsyncApiMessage implementations generated
2478
+ const implementedTypes = new Set();
2479
+ const implementations = [];
2480
+
2481
+ // First add the trait definition
2482
+ implementations.push(`
2483
+ /// Base trait for all AsyncAPI messages providing runtime type information
2484
+ ///
2485
+ /// This trait enables:
2486
+ /// - **Dynamic message routing**: Route messages based on their type at runtime
2487
+ /// - **Channel identification**: Determine which channel a message belongs to
2488
+ /// - **Logging and monitoring**: Track message types for observability
2489
+ /// - **Protocol abstraction**: Handle different message types uniformly
2490
+ pub trait AsyncApiMessage {
2491
+ /// Returns the message type identifier as defined in the AsyncAPI specification
2492
+ ///
2493
+ /// This is used for:
2494
+ /// - Message routing and dispatch
2495
+ /// - Logging and monitoring
2496
+ /// - Protocol-level message identification
2497
+ fn message_type(&self) -> &'static str;
2498
+
2499
+ /// Returns the primary channel this message is associated with
2500
+ ///
2501
+ /// Used for:
2502
+ /// - Default routing when channel is not explicitly specified
2503
+ /// - Message categorization and organization
2504
+ /// - Channel-based access control and filtering
2505
+ fn channel(&self) -> &'static str;
2506
+ }`);
2507
+ messageSchemas.forEach(schema => {
2508
+ const doc = schema.description ? `/// ${schema.description}` : `/// ${schema.name} message`;
2509
+ const primaryChannel = schema.channels.length > 0 ? schema.channels[0] : 'default';
2510
+
2511
+ // Check if the message payload references a component schema
2512
+ let payloadRustName = null;
2513
+ let isComponentMessage = false;
2514
+ if (schema.rawPayload && schema.rawPayload.$ref) {
2515
+ const refName = schema.rawPayload.$ref.split('/').pop();
2516
+ payloadRustName = toRustTypeName(refName);
2517
+ isComponentMessage = true;
2518
+ } else if (schema.payload && schema.payload.$ref) {
2519
+ const refName = schema.payload.$ref.split('/').pop();
2520
+ payloadRustName = toRustTypeName(refName);
2521
+ isComponentMessage = true;
2522
+ } else if (schema.payload && schema.payload['x-parser-schema-id']) {
2523
+ // Handle resolved $ref references
2524
+ const schemaId = schema.payload['x-parser-schema-id'];
2525
+ if (schemaRegistry.has(schemaId)) {
2526
+ payloadRustName = toRustTypeName(schemaId);
2527
+ isComponentMessage = true;
2528
+ }
2529
+ }
2530
+
2531
+ // For component messages, always generate the message wrapper type
2532
+ // even if the payload schema already exists
2533
+ if (isComponentMessage && payloadRustName && !implementedTypes.has(schema.rustName)) {
2534
+ implementedTypes.add(schema.rustName);
2535
+ implementations.push(`
2536
+ ${doc}
2537
+ #[derive(Debug, Clone, Serialize, Deserialize)]
2538
+ pub struct ${schema.rustName} {
2539
+ #[serde(flatten)]
2540
+ pub payload: ${payloadRustName},
2541
+ }
2542
+
2543
+ impl AsyncApiMessage for ${schema.rustName} {
2544
+ fn message_type(&self) -> &'static str {
2545
+ "${schema.name}"
2546
+ }
2547
+
2548
+ fn channel(&self) -> &'static str {
2549
+ "${primaryChannel}"
2550
+ }
2551
+ }`);
2552
+ } else if (!generatedTypes.has(schema.rustName) && !implementedTypes.has(schema.rustName)) {
2553
+ // Generate both struct and implementation for inline message schemas
2554
+ implementedTypes.add(schema.rustName);
2555
+ implementations.push(`
2556
+ ${doc}
2557
+ #[derive(Debug, Clone, Serialize, Deserialize)]
2558
+ pub struct ${schema.rustName} {
2559
+ ${generateMessageStruct(schema.payload, schema.rustName)}
2560
+ }
2561
+
2562
+ impl AsyncApiMessage for ${schema.rustName} {
2563
+ fn message_type(&self) -> &'static str {
2564
+ "${schema.name}"
2565
+ }
2566
+
2567
+ fn channel(&self) -> &'static str {
2568
+ "${primaryChannel}"
2569
+ }
2570
+ }`);
2571
+ } else if (payloadRustName && generatedTypes.has(payloadRustName) && !implementedTypes.has(payloadRustName)) {
2572
+ // Generate AsyncApiMessage implementation for existing component schema
2573
+ implementedTypes.add(payloadRustName);
2574
+ implementations.push(`
2575
+ impl AsyncApiMessage for ${payloadRustName} {
2576
+ fn message_type(&self) -> &'static str {
2577
+ "${schema.name}"
2578
+ }
2579
+
2580
+ fn channel(&self) -> &'static str {
2581
+ "${primaryChannel}"
2582
+ }
2583
+ }`);
2584
+ }
2585
+ });
2586
+ return implementations.join('');
2587
+ }
2588
+ };
2589
+ }
2590
+ ;// ../common/src/envelope-rust.js
2591
+ /* eslint-disable no-unused-vars */
2592
+
2593
+
2594
+ /**
2595
+ * Generate a unified MessageEnvelope for both rust-server and rust-client templates
2596
+ * This envelope includes all features needed by both templates:
2597
+ * - Basic message structure (id, operation, payload, timestamp)
2598
+ * - Request/response patterns (correlation_id, create_response)
2599
+ * - Channel routing (channel field)
2600
+ * - Error handling (error field, error methods)
2601
+ * - Authentication (auth header methods)
2602
+ * - Serialization (to_bytes, from_bytes)
2603
+ */
2604
+ function generateMessageEnvelope() {
2605
+ return `use serde::{de::DeserializeOwned, Deserialize, Serialize};
2606
+ use std::collections::HashMap;
2607
+ use uuid::Uuid;
2608
+
2609
+ /// Unified message envelope for consistent AsyncAPI message format
2610
+ ///
2611
+ /// This envelope provides a standardized structure for all messages sent through the system,
2612
+ /// enabling better correlation, error handling, authentication, and observability.
2613
+ ///
2614
+ /// ## Features
2615
+ ///
2616
+ /// - **Request/Response Patterns**: Correlation IDs for matching requests with responses
2617
+ /// - **Error Handling**: Built-in error information for failed operations
2618
+ /// - **Authentication**: Integrated auth header support
2619
+ /// - **Channel Routing**: Optional channel context for message routing
2620
+ /// - **Serialization**: Efficient byte conversion for transport layers
2621
+ /// - **Type Safety**: Strongly-typed payload extraction
2622
+ ///
2623
+ /// ## Usage
2624
+ ///
2625
+ /// \`\`\`no-run
2626
+ /// use crate::models::*;
2627
+ /// use uuid::Uuid;
2628
+ /// use std::collections::HashMap;
2629
+ ///
2630
+ /// // Create a basic message envelope
2631
+ /// let envelope = MessageEnvelope::new("sendChatMessage", chat_message)?;
2632
+ ///
2633
+ /// // Create with correlation ID for request/response
2634
+ /// let request = MessageEnvelope::new_with_correlation_id(
2635
+ /// "getUserProfile",
2636
+ /// user_request,
2637
+ /// Uuid::new_v4().to_string()
2638
+ /// )?;
2639
+ ///
2640
+ /// // Create response with same correlation ID
2641
+ /// let response = request.create_response("getUserProfile_response", user_profile)?;
2642
+ ///
2643
+ /// // Create error response
2644
+ /// let error = MessageEnvelope::error_response(
2645
+ /// "getUserProfile_response",
2646
+ /// "USER_NOT_FOUND",
2647
+ /// "User does not exist",
2648
+ /// request.correlation_id().map(|s| s.to_string())
2649
+ /// );
2650
+ ///
2651
+ /// // Add authentication headers
2652
+ /// let mut headers = HashMap::new();
2653
+ /// headers.insert("Authorization".to_string(), "Bearer token123".to_string());
2654
+ /// let auth_envelope = envelope.with_headers(headers);
2655
+ ///
2656
+ /// // Serialize for transport
2657
+ /// let bytes = envelope.to_bytes()?;
2658
+ /// let deserialized = MessageEnvelope::from_bytes(&bytes)?;
2659
+ /// \`\`\`
2660
+
2661
+ /// Standard message envelope for all AsyncAPI messages
2662
+ ///
2663
+ /// This envelope provides a consistent structure for all messages sent through the system,
2664
+ /// enabling better correlation, error handling, and observability.
2665
+ #[derive(Debug, Clone, Serialize, Deserialize)]
2666
+ pub struct MessageEnvelope {
2667
+ /// Unique message identifier
2668
+ pub id: String,
2669
+ /// AsyncAPI operation ID
2670
+ pub operation: String,
2671
+ /// Message payload (any serializable type)
2672
+ pub payload: serde_json::Value,
2673
+ /// ISO 8601 timestamp when message was created
2674
+ pub timestamp: String,
2675
+ /// Correlation ID for request/response patterns
2676
+ pub correlation_id: Option<String>,
2677
+ /// Optional channel context for routing
2678
+ pub channel: Option<String>,
2679
+ /// Transport-level headers (auth, routing, etc.)
2680
+ pub headers: Option<HashMap<String, String>>,
2681
+ /// Error information if applicable
2682
+ pub error: Option<MessageError>,
2683
+ }
2684
+
2685
+ /// Error information for failed operations
2686
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2687
+ pub struct MessageError {
2688
+ /// Error code (e.g., "VALIDATION_ERROR", "TIMEOUT", "UNAUTHORIZED")
2689
+ pub code: String,
2690
+ /// Human-readable error message
2691
+ pub message: String,
2692
+ }
2693
+
2694
+ impl MessageEnvelope {
2695
+ /// Create a new message envelope with the given operation and payload
2696
+ pub fn new<T: Serialize>(operation: &str, payload: T) -> Result<Self, serde_json::Error> {
2697
+ Ok(Self {
2698
+ id: Uuid::new_v4().to_string(),
2699
+ operation: operation.to_string(),
2700
+ payload: serde_json::to_value(payload)?,
2701
+ timestamp: chrono::Utc::now().to_rfc3339(),
2702
+ correlation_id: None,
2703
+ channel: None,
2704
+ headers: None,
2705
+ error: None,
2706
+ })
2707
+ }
2708
+
2709
+ /// Create a new envelope with automatic correlation ID generation
2710
+ pub fn new_with_id<T: Serialize>(
2711
+ operation: &str,
2712
+ payload: T,
2713
+ ) -> Result<Self, serde_json::Error> {
2714
+ Self::new(operation, payload)
2715
+ .map(|envelope| envelope.with_correlation_id(Uuid::new_v4().to_string()))
2716
+ }
2717
+
2718
+ /// Create a new message envelope with a specific correlation ID
2719
+ pub fn new_with_correlation_id<T: Serialize>(
2720
+ operation: &str,
2721
+ payload: T,
2722
+ correlation_id: String,
2723
+ ) -> Result<Self, serde_json::Error> {
2724
+ let mut envelope = Self::new(operation, payload)?;
2725
+ envelope.correlation_id = Some(correlation_id);
2726
+ Ok(envelope)
2727
+ }
2728
+
2729
+ /// Create an error response envelope
2730
+ pub fn error_response(
2731
+ operation: &str,
2732
+ error_code: &str,
2733
+ error_message: &str,
2734
+ correlation_id: Option<String>,
2735
+ ) -> Self {
2736
+ Self {
2737
+ id: Uuid::new_v4().to_string(),
2738
+ operation: operation.to_string(),
2739
+ payload: serde_json::Value::Null,
2740
+ timestamp: chrono::Utc::now().to_rfc3339(),
2741
+ correlation_id,
2742
+ channel: None,
2743
+ headers: None,
2744
+ error: Some(MessageError {
2745
+ code: error_code.to_string(),
2746
+ message: error_message.to_string(),
2747
+ }),
2748
+ }
2749
+ }
2750
+
2751
+ /// Set the correlation ID for this envelope
2752
+ pub fn with_correlation_id(mut self, id: String) -> Self {
2753
+ self.correlation_id = Some(id);
2754
+ self
2755
+ }
2756
+
2757
+ /// Set the channel for this envelope
2758
+ pub fn with_channel(mut self, channel: String) -> Self {
2759
+ self.channel = Some(channel);
2760
+ self
2761
+ }
2762
+
2763
+ /// Set headers for this envelope
2764
+ pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
2765
+ self.headers = Some(headers);
2766
+ self
2767
+ }
2768
+
2769
+ /// Add a single header to this envelope
2770
+ pub fn with_header(mut self, key: String, value: String) -> Self {
2771
+ if self.headers.is_none() {
2772
+ self.headers = Some(HashMap::new());
2773
+ }
2774
+ if let Some(ref mut headers) = self.headers {
2775
+ headers.insert(key, value);
2776
+ }
2777
+ self
2778
+ }
2779
+
2780
+ /// Add authentication headers to the envelope
2781
+ /// This method accepts any headers map, allowing templates to integrate their own auth systems
2782
+ pub fn with_auth_headers(mut self, auth_headers: HashMap<String, String>) -> Self {
2783
+ if !auth_headers.is_empty() {
2784
+ if let Some(ref mut headers) = self.headers {
2785
+ headers.extend(auth_headers);
2786
+ } else {
2787
+ self.headers = Some(auth_headers);
2788
+ }
2789
+ }
2790
+ self
2791
+ }
2792
+
2793
+ /// Set an error on this envelope
2794
+ pub fn with_error(mut self, code: &str, message: &str) -> Self {
2795
+ self.error = Some(MessageError {
2796
+ code: code.to_string(),
2797
+ message: message.to_string(),
2798
+ });
2799
+ self
2800
+ }
2801
+
2802
+ /// Extract the payload as a strongly-typed message
2803
+ pub fn extract_payload<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
2804
+ serde_json::from_value(self.payload.clone())
2805
+ }
2806
+
2807
+ /// Check if this envelope contains an error
2808
+ pub fn is_error(&self) -> bool {
2809
+ self.error.is_some()
2810
+ }
2811
+
2812
+ /// Get the correlation ID if present
2813
+ pub fn correlation_id(&self) -> Option<&str> {
2814
+ self.correlation_id.as_deref()
2815
+ }
2816
+
2817
+ /// Create a response envelope with the same correlation ID
2818
+ pub fn create_response<T: Serialize>(
2819
+ &self,
2820
+ response_operation: &str,
2821
+ payload: T,
2822
+ ) -> Result<Self, serde_json::Error> {
2823
+ let mut response = Self::new(response_operation, payload)?;
2824
+ response.correlation_id = self.correlation_id.clone();
2825
+ response.channel = self.channel.clone();
2826
+ Ok(response)
2827
+ }
2828
+
2829
+ /// Convert the envelope to bytes for transport
2830
+ pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
2831
+ serde_json::to_vec(self)
2832
+ }
2833
+
2834
+ /// Parse envelope from bytes received from transport
2835
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
2836
+ serde_json::from_slice(bytes)
2837
+ }
2838
+ }
2839
+
2840
+ #[cfg(test)]
2841
+ mod tests {
2842
+ use super::*;
2843
+ use serde::{Deserialize, Serialize};
2844
+
2845
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
2846
+ struct TestPayload {
2847
+ message: String,
2848
+ count: u32,
2849
+ }
2850
+
2851
+ #[test]
2852
+ fn test_envelope_creation() {
2853
+ let payload = TestPayload {
2854
+ message: "test".to_string(),
2855
+ count: 42,
2856
+ };
2857
+
2858
+ let envelope = MessageEnvelope::new("test_operation", &payload).unwrap();
2859
+
2860
+ assert_eq!(envelope.operation, "test_operation");
2861
+ assert!(!envelope.id.is_empty());
2862
+ assert!(!envelope.timestamp.is_empty());
2863
+ assert_eq!(envelope.correlation_id, None);
2864
+ assert_eq!(envelope.error, None);
2865
+
2866
+ let extracted: TestPayload = envelope.extract_payload().unwrap();
2867
+ assert_eq!(extracted, payload);
2868
+ }
2869
+
2870
+ #[test]
2871
+ fn test_envelope_with_correlation_id() {
2872
+ let payload = TestPayload {
2873
+ message: "test".to_string(),
2874
+ count: 42,
2875
+ };
2876
+
2877
+ let correlation_id = "test-correlation-id".to_string();
2878
+ let envelope = MessageEnvelope::new_with_correlation_id(
2879
+ "test_operation",
2880
+ &payload,
2881
+ correlation_id.clone(),
2882
+ ).unwrap();
2883
+
2884
+ assert_eq!(envelope.correlation_id, Some(correlation_id));
2885
+ }
2886
+
2887
+ #[test]
2888
+ fn test_error_response() {
2889
+ let error_envelope = MessageEnvelope::error_response(
2890
+ "test_operation_response",
2891
+ "TEST_ERROR",
2892
+ "Test error message",
2893
+ Some("correlation-123".to_string()),
2894
+ );
2895
+
2896
+ assert!(error_envelope.is_error());
2897
+ assert_eq!(error_envelope.correlation_id, Some("correlation-123".to_string()));
2898
+ if let Some(error) = &error_envelope.error {
2899
+ assert_eq!(error.code, "TEST_ERROR");
2900
+ assert_eq!(error.message, "Test error message");
2901
+ }
2902
+ }
2903
+
2904
+ #[test]
2905
+ fn test_envelope_serialization() {
2906
+ let payload = TestPayload {
2907
+ message: "test".to_string(),
2908
+ count: 42,
2909
+ };
2910
+
2911
+ let envelope = MessageEnvelope::new("test_operation", &payload).unwrap();
2912
+ let bytes = envelope.to_bytes().unwrap();
2913
+ let deserialized = MessageEnvelope::from_bytes(&bytes).unwrap();
2914
+
2915
+ assert_eq!(envelope.id, deserialized.id);
2916
+ assert_eq!(envelope.operation, deserialized.operation);
2917
+ assert_eq!(envelope.timestamp, deserialized.timestamp);
2918
+ }
2919
+
2920
+ #[test]
2921
+ fn test_response_creation() {
2922
+ let request_payload = TestPayload {
2923
+ message: "request".to_string(),
2924
+ count: 1,
2925
+ };
2926
+
2927
+ let response_payload = TestPayload {
2928
+ message: "response".to_string(),
2929
+ count: 2,
2930
+ };
2931
+
2932
+ let request = MessageEnvelope::new_with_correlation_id(
2933
+ "test_request",
2934
+ &request_payload,
2935
+ "test-correlation".to_string(),
2936
+ ).unwrap();
2937
+
2938
+ let response = request.create_response("test_response", &response_payload).unwrap();
2939
+
2940
+ assert_eq!(response.operation, "test_response");
2941
+ assert_eq!(response.correlation_id, request.correlation_id);
2942
+
2943
+ let extracted: TestPayload = response.extract_payload().unwrap();
2944
+ assert_eq!(extracted, response_payload);
2945
+ }
2946
+
2947
+ #[test]
2948
+ fn test_headers_and_auth() {
2949
+ let payload = TestPayload {
2950
+ message: "test".to_string(),
2951
+ count: 42,
2952
+ };
2953
+
2954
+ let mut auth_headers = HashMap::new();
2955
+ auth_headers.insert("Authorization".to_string(), "Bearer token123".to_string());
2956
+
2957
+ let envelope = MessageEnvelope::new("test_operation", &payload)
2958
+ .unwrap()
2959
+ .with_auth_headers(auth_headers)
2960
+ .with_header("Custom-Header".to_string(), "custom-value".to_string());
2961
+
2962
+ assert!(envelope.headers.is_some());
2963
+ let headers = envelope.headers.unwrap();
2964
+ assert_eq!(headers.get("Authorization"), Some(&"Bearer token123".to_string()));
2965
+ assert_eq!(headers.get("Custom-Header"), Some(&"custom-value".to_string()));
2966
+ }
2967
+ }
2968
+ `;
2969
+ }
2970
+ ;// ../common/src/models-ts.js
2971
+ /* eslint-disable no-unused-vars */
2972
+
2973
+
2974
+ /**
2975
+ * Generate TypeScript models from AsyncAPI specification
2976
+ * This helper extracts the common schema processing logic used by TypeScript templates
2977
+ *
2978
+ * @param {Object} asyncapi - AsyncAPI document
2979
+ * @param {Object} options - Generation options
2980
+ * @param {Function} options.toTypeScriptTypeName - Function to convert names to TypeScript type names
2981
+ * @param {Function} options.toTypeScriptIdentifier - Function to convert names to TypeScript identifiers
2982
+ * @param {boolean} options.includeMessageTypes - Whether to include message type constants
2983
+ * @returns {Object} Generated models data and functions
2984
+ */
2985
+ function generateTypeScriptModels(asyncapi, options = {}) {
2986
+ const {
2987
+ toTypeScriptTypeName,
2988
+ toTypeScriptIdentifier,
2989
+ includeMessageTypes = true
2990
+ } = options;
2991
+
2992
+ // Helper functions for TypeScript identifier generation
2993
+ function defaultToTypeScriptIdentifier(str) {
2994
+ if (!str) return 'unknown';
2995
+ let identifier = str.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]/, '_$&').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
2996
+ if (/^[0-9]/.test(identifier)) {
2997
+ identifier = 'item_' + identifier;
2998
+ }
2999
+ if (!identifier) {
3000
+ identifier = 'unknown';
3001
+ }
3002
+ return identifier;
3003
+ }
3004
+ function defaultToTypeScriptTypeName(str) {
3005
+ if (!str) return 'Unknown';
3006
+ // Handle camelCase and PascalCase properly
3007
+ const identifier = str.replace(/[^a-zA-Z0-9]/g, '_').replace(/^[0-9]/, '_$&').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
3008
+
3009
+ // Split on underscores and camelCase boundaries
3010
+ const parts = identifier.split(/[_\s]+|(?=[A-Z])/);
3011
+ return parts.filter(part => part.length > 0).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
3012
+ }
3013
+
3014
+ // Use provided functions or defaults
3015
+ const toTSTypeName = toTypeScriptTypeName || defaultToTypeScriptTypeName;
3016
+ const toTSIdentifier = toTypeScriptIdentifier || defaultToTypeScriptIdentifier;
3017
+
3018
+ // Extract message schemas and build channel mapping
3019
+ const components = asyncapi.components();
3020
+ const messageSchemas = [];
3021
+ const componentSchemas = [];
3022
+ const messageToChannels = new Map();
3023
+ const generatedTypes = new Set();
3024
+ const schemaRegistry = new Map();
3025
+
3026
+ // Build schema registry from components.schemas
3027
+ // Try to access the raw AsyncAPI document
3028
+ let rawDoc = null;
3029
+ try {
3030
+ if (asyncapi.json && typeof asyncapi.json === 'function') {
3031
+ rawDoc = asyncapi.json();
3032
+ } else if (asyncapi._json) {
3033
+ rawDoc = asyncapi._json;
3034
+ }
3035
+ } catch (e) {
3036
+ // Ignore
3037
+ }
3038
+
3039
+ // Extract schemas from raw document if available
3040
+ if (rawDoc && rawDoc.components && rawDoc.components.schemas) {
3041
+ Object.entries(rawDoc.components.schemas).forEach(([name, schema]) => {
3042
+ if (name && typeof name === 'string' && schema && typeof schema === 'object') {
3043
+ schemaRegistry.set(name, schema);
3044
+ componentSchemas.push({
3045
+ name,
3046
+ typeName: toTSTypeName(name),
3047
+ schema: schema,
3048
+ description: schema.description
3049
+ });
3050
+ }
3051
+ });
3052
+ }
3053
+
3054
+ // Fallback: try the components.schemas() method
3055
+ if (componentSchemas.length === 0 && components && components.schemas) {
3056
+ try {
3057
+ const schemas = components.schemas();
3058
+ if (schemas) {
3059
+ // Try different ways to access schemas
3060
+ let schemaEntries = [];
3061
+ if (schemas instanceof Map) {
3062
+ schemaEntries = Array.from(schemas.entries());
3063
+ } else if (typeof schemas === 'object') {
3064
+ schemaEntries = Object.entries(schemas);
3065
+ } else if (schemas.all && typeof schemas.all === 'function') {
3066
+ // AsyncAPI parser might have an all() method
3067
+ const allSchemas = schemas.all();
3068
+ if (Array.isArray(allSchemas)) {
3069
+ schemaEntries = allSchemas.map(schema => {
3070
+ const name = schema.uid ? schema.uid() : schema.id ? schema.id() : null;
3071
+ return [name, schema];
3072
+ }).filter(([name]) => name);
3073
+ }
3074
+ }
3075
+ schemaEntries.forEach(([name, schema]) => {
3076
+ // Skip internal AsyncAPI parser objects and numeric keys
3077
+ if (!name || name === 'collections' || name === '_meta' || name.startsWith('_') || /^\d+$/.test(name)) {
3078
+ return;
3079
+ }
3080
+ let schemaData = null;
3081
+ let description = null;
3082
+ try {
3083
+ // Handle different schema object types
3084
+ if (schema && typeof schema.json === 'function') {
3085
+ schemaData = schema.json();
3086
+ } else if (schema && typeof schema === 'object') {
3087
+ schemaData = schema;
3088
+ }
3089
+ if (schema && typeof schema.description === 'function') {
3090
+ description = schema.description();
3091
+ } else if (schema && schema.description) {
3092
+ description = schema.description;
3093
+ }
3094
+ } catch (e) {
3095
+ // Ignore schema extraction errors
3096
+ console.warn(`Failed to extract schema for ${name}:`, e.message);
3097
+ }
3098
+ if (schemaData && typeof name === 'string' && name.length > 0) {
3099
+ schemaRegistry.set(name, schemaData);
3100
+ componentSchemas.push({
3101
+ name,
3102
+ typeName: toTSTypeName(name),
3103
+ schema: schemaData,
3104
+ description
3105
+ });
3106
+ }
3107
+ });
3108
+ }
3109
+ } catch (e) {
3110
+ console.warn('Failed to extract component schemas:', e.message);
3111
+ }
3112
+ }
3113
+
3114
+ // First, build channel to message mapping
3115
+ if (asyncapi.channels) {
3116
+ const channels = asyncapi.channels();
3117
+ if (channels) {
3118
+ Object.entries(channels).forEach(([channelName, channel]) => {
3119
+ try {
3120
+ // Handle AsyncAPI 3.x format
3121
+ if (channel.messages) {
3122
+ const messages = channel.messages();
3123
+ if (messages) {
3124
+ Object.entries(messages).forEach(([msgKey, message]) => {
3125
+ if (message) {
3126
+ let messageName = null;
3127
+ if (message.$ref) {
3128
+ messageName = message.$ref.split('/').pop();
3129
+ } else if (message.name) {
3130
+ messageName = typeof message.name === 'function' ? message.name() : message.name;
3131
+ }
3132
+ if (messageName) {
3133
+ if (!messageToChannels.has(messageName)) {
3134
+ messageToChannels.set(messageName, []);
3135
+ }
3136
+ messageToChannels.get(messageName).push(channelName);
3137
+ }
3138
+ }
3139
+ });
3140
+ }
3141
+ }
3142
+ } catch (e) {
3143
+ // Ignore channel processing errors
3144
+ }
3145
+ });
3146
+ }
3147
+ }
3148
+
3149
+ // Extract messages from components
3150
+ if (components && components.messages) {
3151
+ const messages = components.messages();
3152
+ if (messages) {
3153
+ Object.entries(messages).forEach(([name, message]) => {
3154
+ // Skip internal AsyncAPI parser objects
3155
+ if (name === 'collections' || name === '_meta' || name.startsWith('_')) {
3156
+ return;
3157
+ }
3158
+ let payload = null;
3159
+ let description = null;
3160
+ let title = null;
3161
+ let messageName = name;
3162
+ try {
3163
+ if (message.payload && typeof message.payload === 'function') {
3164
+ const payloadSchema = message.payload();
3165
+ payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
3166
+ }
3167
+ description = message.description && typeof message.description === 'function' ? message.description() : message.description;
3168
+ title = message.title && typeof message.title === 'function' ? message.title() : message.title;
3169
+
3170
+ // Try to get the actual message name
3171
+ if (message.name && typeof message.name === 'function') {
3172
+ messageName = message.name();
3173
+ } else if (message.name) {
3174
+ messageName = message.name;
3175
+ }
3176
+ } catch (e) {
3177
+ // Ignore payload extraction errors
3178
+ }
3179
+ const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
3180
+ messageSchemas.push({
3181
+ name: messageName,
3182
+ typeName: toTSTypeName(messageName),
3183
+ payload,
3184
+ description: description || title,
3185
+ channels
3186
+ });
3187
+ });
3188
+ }
3189
+ }
3190
+
3191
+ // Helper function to convert JSON schema to TypeScript type
3192
+ function jsonSchemaToTypeScriptType(schema, fieldName = '') {
3193
+ if (!schema) return 'any';
3194
+
3195
+ // Handle $ref - resolve from schema registry
3196
+ if (schema.$ref) {
3197
+ const refName = schema.$ref.split('/').pop();
3198
+ // Always return the type name for $ref, since we generate all component schemas
3199
+ const typeName = toTSTypeName(refName);
3200
+ return typeName;
3201
+ }
3202
+
3203
+ // Handle resolved $ref - check for x-parser-schema-id which indicates original schema name
3204
+ if (schema['x-parser-schema-id'] && typeof schema['x-parser-schema-id'] === 'string') {
3205
+ const schemaId = schema['x-parser-schema-id'];
3206
+ // Check if this matches a known component schema
3207
+ if (schemaRegistry.has(schemaId)) {
3208
+ const typeName = toTSTypeName(schemaId);
3209
+ return typeName;
3210
+ }
3211
+ }
3212
+ if (!schema.type) {
3213
+ // If no type specified, check for properties (object) or items (array)
3214
+ if (schema.properties) {
3215
+ schema.type = 'object';
3216
+ } else if (schema.items) {
3217
+ schema.type = 'array';
3218
+ } else {
3219
+ return 'any';
3220
+ }
3221
+ }
3222
+ switch (schema.type) {
3223
+ case 'string':
3224
+ if (schema.enum && schema.enum.length > 0) {
3225
+ return schema.enum.map(val => `'${val}'`).join(' | ');
3226
+ }
3227
+ return 'string';
3228
+ case 'integer':
3229
+ case 'number':
3230
+ return 'number';
3231
+ case 'boolean':
3232
+ return 'boolean';
3233
+ case 'array':
3234
+ {
3235
+ if (schema.items) {
3236
+ const itemType = jsonSchemaToTypeScriptType(schema.items, fieldName);
3237
+ return `${itemType}[]`;
3238
+ }
3239
+ return 'any[]';
3240
+ }
3241
+ case 'object':
3242
+ // For objects with properties, we should generate inline types or check if it's a known schema
3243
+ if (schema.properties) {
3244
+ // This is a complex object - for now return Record<string, any>
3245
+ // In a more sophisticated implementation, we could generate inline types
3246
+ return 'Record<string, any>';
3247
+ }
3248
+ return 'Record<string, any>';
3249
+ default:
3250
+ return 'any';
3251
+ }
3252
+ }
3253
+
3254
+ // Generate message interfaces
3255
+ function generateMessageInterface(schema, messageName) {
3256
+ if (!schema || !schema.properties) {
3257
+ return ' [key: string]: any;';
3258
+ }
3259
+ const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
3260
+ const tsType = jsonSchemaToTypeScriptType(fieldSchema, fieldName);
3261
+ const optional = !schema.required || !schema.required.includes(fieldName);
3262
+ const optionalMarker = optional ? '?' : '';
3263
+ let fieldDoc = '';
3264
+ if (fieldSchema.description) {
3265
+ fieldDoc = ` /** ${fieldSchema.description} */\n`;
3266
+ }
3267
+ return `${fieldDoc} ${fieldName}${optionalMarker}: ${tsType};`;
3268
+ }).join('\n');
3269
+ return fields;
3270
+ }
3271
+
3272
+ // Return the processed data and generation functions
3273
+ return {
3274
+ messageSchemas,
3275
+ componentSchemas,
3276
+ messageToChannels,
3277
+ generatedTypes,
3278
+ schemaRegistry,
3279
+ // Generation functions
3280
+ generateMessageInterface,
3281
+ jsonSchemaToTypeScriptType,
3282
+ // Generate interfaces for component schemas
3283
+ generateComponentSchemas() {
3284
+ let content = '';
3285
+ componentSchemas.forEach(schema => {
3286
+ const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} */\n`;
3287
+
3288
+ // Check if this is a standalone enum schema
3289
+ if (schema.schema.type === 'string' && schema.schema.enum && Array.isArray(schema.schema.enum)) {
3290
+ // Generate union type for enum
3291
+ const enumValues = schema.schema.enum.map(val => `'${val}'`).join(' | ');
3292
+ content += `${doc}export type ${schema.typeName} = ${enumValues};\n\n`;
3293
+ } else {
3294
+ // Generate interface for object schema
3295
+ content += `${doc}export interface ${schema.typeName} {\n`;
3296
+ content += generateMessageInterface(schema.schema, schema.typeName);
3297
+ content += '\n}\n\n';
3298
+ }
3299
+
3300
+ // Track generated types to avoid duplicates
3301
+ generatedTypes.add(schema.typeName);
3302
+ });
3303
+ return content;
3304
+ },
3305
+ // Generate interfaces for each message
3306
+ generateMessageSchemas() {
3307
+ let content = '';
3308
+ messageSchemas.forEach(schema => {
3309
+ const interfaceName = `${schema.typeName}Payload`;
3310
+
3311
+ // Check if this is a duplicate of a component schema
3312
+ // For message payloads that match component schema names, skip the payload version
3313
+ if (generatedTypes.has(schema.typeName) || generatedTypes.has(interfaceName)) {
3314
+ // Skip generating the payload version if we already have the component schema
3315
+ return;
3316
+ }
3317
+ const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} message payload */\n`;
3318
+ content += `${doc}export interface ${interfaceName} {\n`;
3319
+ content += generateMessageInterface(schema.payload, schema.typeName);
3320
+ content += '\n}\n\n';
3321
+ generatedTypes.add(interfaceName);
3322
+ });
3323
+ return content;
3324
+ },
3325
+ // Generate message type constants and unions
3326
+ generateMessageTypes() {
3327
+ if (!includeMessageTypes || messageSchemas.length === 0) {
3328
+ return '';
3329
+ }
3330
+ let content = '';
3331
+
3332
+ // Generate a union type for all message payloads
3333
+ const payloadTypes = messageSchemas.map(schema => `${schema.typeName}Payload`).join(' | ');
3334
+ content += '/** Union type for all message payloads */\n';
3335
+ content += `export type MessagePayload = ${payloadTypes};\n\n`;
3336
+
3337
+ // Generate message type constants
3338
+ content += '/** Message type constants */\n';
3339
+ content += 'export const MessageTypes = {\n';
3340
+ messageSchemas.forEach(schema => {
3341
+ content += ` ${schema.typeName.toUpperCase()}: '${schema.name}',\n`;
3342
+ });
3343
+ content += '} as const;\n\n';
3344
+ content += '/** Message type union */\n';
3345
+ content += 'export type MessageType = typeof MessageTypes[keyof typeof MessageTypes];\n\n';
3346
+ return content;
3347
+ }
3348
+ };
3349
+ }
1886
3350
  ;// ../common/src/index.js
1887
3351
  /**
1888
3352
  * AsyncAPI Common Template Utilities
@@ -1909,6 +3373,11 @@ function generateOperationHandlerName(operation, suffix = 'Handler') {
1909
3373
  // Operation utilities
1910
3374
 
1911
3375
 
3376
+ // Model generation utilities
3377
+
3378
+
3379
+
3380
+
1912
3381
  // Re-export all utilities
1913
3382
 
1914
3383
 
@@ -1998,7 +3467,12 @@ function getAllUtilities() {
1998
3467
  operationHasTraits: operationHasTraits,
1999
3468
  getOperationTags: getOperationTags,
2000
3469
  generateOperationHandlerName: generateOperationHandlerName
3470
+ },
3471
+ models: {
3472
+ generateRustModels: generateRustModels,
3473
+ generateMessageEnvelope: generateMessageEnvelope,
3474
+ generateTypeScriptModels: generateTypeScriptModels
2001
3475
  }
2002
3476
  };
2003
3477
  }
2004
- export { VERSION, analyzeChannelServerMappings, analyzeOperationSecurity, channelHasParameters, extractAllOperations, extractAsyncApiInfo, extractChannelParameters, extractChannelVariables, extractOperationSecurityMap, extractServerNameFromRef, formatGenerationDate, generateChannelFormatting, generateChannelParameterArgs, generateEslintConfig, generateFileHeader, generateOperationHandlerName, generatePackageJson, generateReadmeContent, generateTypeScriptChannelFormatting, generateTypeScriptChannelParameterArgs, generateTypeScriptConfig, getAllSecuritySchemes, getAllUtilities, getChannelAddress, getChannelParameters, getDefaultPort, getMessageContentType, getMessageRustTypeName, getMessageTypeName, getMessageTypeScriptTypeName, getNatsSubject, getOperationAction, getOperationChannel, getOperationDescription, getOperationMessages, getOperationName, getOperationRustFunctionName, getOperationSummary, getOperationTags, getOperationTraits, getOperationTypeScriptMethodName, getPayloadRustTypeName, getPayloadTypeScriptTypeName, getSecuritySchemeLocation, getSecuritySchemeName, getSecuritySchemeType, groupOperationsByAction, hasSecuritySchemes, isChannelAllowedOnServer, isDynamicChannel, isOperationReceive, isOperationSend, isSecuritySchemeType, isTemplateVariable, messageHasPayload, operationHasSecurity, operationHasTraits, operationRequiresAuth, resolveChannelAddress, resolveTemplateParameters, toCamelCase, toKebabCase, toPascalCase, toRustEnumVariant, toRustEnumVariantWithSerde, toRustFieldName, toRustIdentifier, toRustTypeName, toSnakeCase, validateChannelServerReferences, validateTemplateParameters };
3478
+ export { VERSION, analyzeChannelServerMappings, analyzeOperationSecurity, channelHasParameters, extractAllOperations, extractAsyncApiInfo, extractChannelParameters, extractChannelVariables, extractOperationSecurityMap, extractServerNameFromRef, formatGenerationDate, generateChannelFormatting, generateChannelParameterArgs, generateEslintConfig, generateFileHeader, generateMessageEnvelope, generateOperationHandlerName, generatePackageJson, generateReadmeContent, generateRustModels, generateTypeScriptChannelFormatting, generateTypeScriptChannelParameterArgs, generateTypeScriptConfig, generateTypeScriptModels, getAllSecuritySchemes, getAllUtilities, getChannelAddress, getChannelParameters, getDefaultPort, getMessageContentType, getMessageRustTypeName, getMessageTypeName, getMessageTypeScriptTypeName, getNatsSubject, getOperationAction, getOperationChannel, getOperationDescription, getOperationMessages, getOperationName, getOperationRustFunctionName, getOperationSummary, getOperationTags, getOperationTraits, getOperationTypeScriptMethodName, getPayloadRustTypeName, getPayloadTypeScriptTypeName, getSecuritySchemeLocation, getSecuritySchemeName, getSecuritySchemeType, groupOperationsByAction, hasSecuritySchemes, isChannelAllowedOnServer, isDynamicChannel, isOperationReceive, isOperationSend, isSecuritySchemeType, isTemplateVariable, messageHasPayload, operationHasSecurity, operationHasTraits, operationRequiresAuth, resolveChannelAddress, resolveTemplateParameters, toCamelCase, toKebabCase, toPascalCase, toRustEnumVariant, toRustEnumVariantWithSerde, toRustFieldName, toRustIdentifier, toRustTypeName, toSnakeCase, validateChannelServerReferences, validateTemplateParameters };