@ioka-technologies/asyncapi-ts-client-template 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/index.js +3478 -0
- package/package.json +12 -5
- package/template/helpers/security.js +20 -73
- package/template/helpers/security.js.dev +84 -0
- package/template/index.js +4 -4
- package/template/src/client.ts.js +2 -2
- package/template/src/index.ts.js +1 -1
- package/template/src/models.ts.js +12 -369
- package/template/src/runtime/auth/headers.ts.js +2 -2
- package/template/src/runtime/auth/index.ts.js +2 -2
- package/template/src/runtime/auth/types.ts.js +2 -2
- package/template/src/runtime/errors.ts.js +1 -1
- package/template/src/runtime/retry/index.ts.js +2 -2
- package/template/src/runtime/retry/manager.ts.js +2 -2
- package/template/src/runtime/retry/presets.ts.js +2 -2
- package/template/src/runtime/retry/types.ts.js +2 -2
- package/template/src/runtime/transports/factory.ts.js +1 -1
- package/template/src/runtime/transports/http.ts.js +2 -2
- package/template/src/runtime/transports/websocket.ts.js +2 -2
- package/template/src/runtime/types.ts.js +2 -2
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ioka-technologies/asyncapi-ts-client-template",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "TypeScript AsyncAPI client generator template compatible with rust-asyncapi patterns",
|
|
5
5
|
"main": "template/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"build": "webpack --config ../webpack.config.js --env template=ts-client",
|
|
8
|
+
"build:dev": "webpack --config ../webpack.config.js --env template=ts-client --mode development",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
7
10
|
"test": "npm run test:generate",
|
|
8
|
-
"test:generate": "asyncapi generate fromTemplate examples/auth-retry/asyncapi.yaml
|
|
9
|
-
"test:realworld": "asyncapi generate fromTemplate ../examples/realworld/asyncapi.yaml
|
|
10
|
-
"test:simple": "asyncapi generate fromTemplate ../examples/simple/asyncapi.yaml
|
|
11
|
-
"clean": "rm -rf test-output-*"
|
|
11
|
+
"test:generate": "asyncapi generate fromTemplate examples/auth-retry/asyncapi.yaml ./ -o test-output-auth-retry --force-write && cd test-output-auth-retry && npm install && npm run build",
|
|
12
|
+
"test:realworld": "asyncapi generate fromTemplate ../examples/realworld/asyncapi.yaml ./ -o test-output-realworld --force-write",
|
|
13
|
+
"test:simple": "asyncapi generate fromTemplate ../examples/simple/asyncapi.yaml ./ -o test-output-simple --force-write",
|
|
14
|
+
"clean": "rm -rf test-output-* dist"
|
|
12
15
|
},
|
|
13
16
|
"keywords": [
|
|
14
17
|
"asyncapi",
|
|
@@ -22,6 +25,9 @@
|
|
|
22
25
|
],
|
|
23
26
|
"author": "AsyncAPI TypeScript Generator",
|
|
24
27
|
"license": "Apache-2.0",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@asyncapi/generator-react-sdk": "^1.0.20"
|
|
30
|
+
},
|
|
25
31
|
"devDependencies": {
|
|
26
32
|
"@asyncapi/cli": "^1.18.0"
|
|
27
33
|
},
|
|
@@ -86,6 +92,7 @@
|
|
|
86
92
|
},
|
|
87
93
|
"files": [
|
|
88
94
|
"template/**/*",
|
|
95
|
+
"dist/common/**/*",
|
|
89
96
|
"examples/**/*",
|
|
90
97
|
"README.md",
|
|
91
98
|
"USAGE.md",
|
|
@@ -1,78 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Security analysis helper functions for TypeScript client template
|
|
3
|
-
*
|
|
3
|
+
* Most common security utilities have been moved to the shared @common package.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
export function analyzeOperationSecurity(operation) {
|
|
13
|
-
try {
|
|
14
|
-
// Check AsyncAPI security field
|
|
15
|
-
const security = operation.security && operation.security();
|
|
16
|
-
if (security && Array.isArray(security) && security.length > 0) {
|
|
17
|
-
return {
|
|
18
|
-
hasSecurityRequirements: true,
|
|
19
|
-
securitySchemes: security,
|
|
20
|
-
requiresAuthentication: true
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Check if operation has security defined in AsyncAPI spec
|
|
25
|
-
const operationJson = operation._json || operation;
|
|
26
|
-
if (operationJson.security && Array.isArray(operationJson.security) && operationJson.security.length > 0) {
|
|
27
|
-
return {
|
|
28
|
-
hasSecurityRequirements: true,
|
|
29
|
-
securitySchemes: operationJson.security,
|
|
30
|
-
requiresAuthentication: true
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
hasSecurityRequirements: false,
|
|
36
|
-
securitySchemes: [],
|
|
37
|
-
requiresAuthentication: false
|
|
38
|
-
};
|
|
39
|
-
} catch (e) {
|
|
40
|
-
return {
|
|
41
|
-
hasSecurityRequirements: false,
|
|
42
|
-
securitySchemes: [],
|
|
43
|
-
requiresAuthentication: false
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Checks if an operation has security requirements
|
|
50
|
-
*
|
|
51
|
-
* @param {object} operation - AsyncAPI operation object
|
|
52
|
-
* @returns {boolean} True if operation has security requirements
|
|
53
|
-
*/
|
|
54
|
-
export function operationRequiresAuth(operation) {
|
|
55
|
-
const analysis = analyzeOperationSecurity(operation);
|
|
56
|
-
return analysis.hasSecurityRequirements;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Checks if the AsyncAPI specification has security schemes defined
|
|
61
|
-
*
|
|
62
|
-
* @param {object} asyncapi - AsyncAPI specification object
|
|
63
|
-
* @returns {boolean} True if security schemes are present
|
|
64
|
-
*/
|
|
65
|
-
export function hasSecuritySchemes(asyncapi) {
|
|
66
|
-
try {
|
|
67
|
-
const components = asyncapi.components();
|
|
68
|
-
if (!components) return false;
|
|
69
|
-
|
|
70
|
-
const securitySchemes = components.securitySchemes();
|
|
71
|
-
return securitySchemes && Object.keys(securitySchemes).length > 0;
|
|
72
|
-
} catch (e) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
6
|
+
import {
|
|
7
|
+
analyzeOperationSecurity,
|
|
8
|
+
operationHasSecurity,
|
|
9
|
+
hasSecuritySchemes
|
|
10
|
+
} from "../dist/common/index.js";
|
|
76
11
|
|
|
77
12
|
/**
|
|
78
13
|
* Get security scheme type from AsyncAPI security scheme definition
|
|
@@ -80,7 +15,7 @@ export function hasSecuritySchemes(asyncapi) {
|
|
|
80
15
|
* @param {object} securityScheme - AsyncAPI security scheme object
|
|
81
16
|
* @returns {string} Security scheme type ('jwt', 'basic', 'apikey', etc.)
|
|
82
17
|
*/
|
|
83
|
-
|
|
18
|
+
function getSecuritySchemeType(securityScheme) {
|
|
84
19
|
try {
|
|
85
20
|
if (securityScheme.type && typeof securityScheme.type === 'function') {
|
|
86
21
|
return securityScheme.type();
|
|
@@ -103,7 +38,7 @@ export function getSecuritySchemeType(securityScheme) {
|
|
|
103
38
|
* @param {object} asyncapi - AsyncAPI specification object
|
|
104
39
|
* @returns {object} Map of operation names to their security requirements
|
|
105
40
|
*/
|
|
106
|
-
|
|
41
|
+
function extractOperationSecurityMap(asyncapi) {
|
|
107
42
|
const securityMap = {};
|
|
108
43
|
|
|
109
44
|
try {
|
|
@@ -135,3 +70,15 @@ export function extractOperationSecurityMap(asyncapi) {
|
|
|
135
70
|
|
|
136
71
|
return securityMap;
|
|
137
72
|
}
|
|
73
|
+
|
|
74
|
+
// Export all functions
|
|
75
|
+
export {
|
|
76
|
+
// Re-export common security utilities for backward compatibility
|
|
77
|
+
analyzeOperationSecurity,
|
|
78
|
+
operationHasSecurity as operationRequiresAuth,
|
|
79
|
+
hasSecuritySchemes,
|
|
80
|
+
|
|
81
|
+
// TypeScript client specific functions
|
|
82
|
+
getSecuritySchemeType,
|
|
83
|
+
extractOperationSecurityMap
|
|
84
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security analysis helper functions for TypeScript client template
|
|
3
|
+
* Most common security utilities have been moved to the shared @common package.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
analyzeOperationSecurity,
|
|
8
|
+
operationHasSecurity,
|
|
9
|
+
hasSecuritySchemes
|
|
10
|
+
} from '../../../common/src/index.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get security scheme type from AsyncAPI security scheme definition
|
|
14
|
+
*
|
|
15
|
+
* @param {object} securityScheme - AsyncAPI security scheme object
|
|
16
|
+
* @returns {string} Security scheme type ('jwt', 'basic', 'apikey', etc.)
|
|
17
|
+
*/
|
|
18
|
+
function getSecuritySchemeType(securityScheme) {
|
|
19
|
+
try {
|
|
20
|
+
if (securityScheme.type && typeof securityScheme.type === 'function') {
|
|
21
|
+
return securityScheme.type();
|
|
22
|
+
}
|
|
23
|
+
if (securityScheme.type) {
|
|
24
|
+
return securityScheme.type;
|
|
25
|
+
}
|
|
26
|
+
if (securityScheme._json && securityScheme._json.type) {
|
|
27
|
+
return securityScheme._json.type;
|
|
28
|
+
}
|
|
29
|
+
return 'unknown';
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return 'unknown';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract security requirements for all operations in the AsyncAPI spec
|
|
37
|
+
*
|
|
38
|
+
* @param {object} asyncapi - AsyncAPI specification object
|
|
39
|
+
* @returns {object} Map of operation names to their security requirements
|
|
40
|
+
*/
|
|
41
|
+
function extractOperationSecurityMap(asyncapi) {
|
|
42
|
+
const securityMap = {};
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const operations = asyncapi.operations && asyncapi.operations();
|
|
46
|
+
if (operations) {
|
|
47
|
+
// Handle AsyncAPI parser collection - use .all() method to get array
|
|
48
|
+
const operationArray = operations.all ? operations.all() : Object.values(operations);
|
|
49
|
+
|
|
50
|
+
operationArray.forEach((operation) => {
|
|
51
|
+
// Get operation ID
|
|
52
|
+
let operationId = null;
|
|
53
|
+
if (operation._meta && operation._meta.id) {
|
|
54
|
+
operationId = operation._meta.id;
|
|
55
|
+
} else if (operation.id && typeof operation.id === 'function') {
|
|
56
|
+
operationId = operation.id();
|
|
57
|
+
} else if (operation.id) {
|
|
58
|
+
operationId = operation.id;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (operationId) {
|
|
62
|
+
const securityAnalysis = analyzeOperationSecurity(operation);
|
|
63
|
+
securityMap[operationId] = securityAnalysis;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.warn('Error extracting operation security map:', e.message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return securityMap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Export all functions
|
|
75
|
+
export {
|
|
76
|
+
// Re-export common security utilities for backward compatibility
|
|
77
|
+
analyzeOperationSecurity,
|
|
78
|
+
operationHasSecurity as operationRequiresAuth,
|
|
79
|
+
hasSecuritySchemes,
|
|
80
|
+
|
|
81
|
+
// TypeScript client specific functions
|
|
82
|
+
getSecuritySchemeType,
|
|
83
|
+
extractOperationSecurityMap
|
|
84
|
+
};
|
package/template/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { File } from '@asyncapi/generator-react-sdk';
|
|
2
|
+
import React from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
export default function ({ asyncapi, params }) {
|
|
5
5
|
// Extract info from AsyncAPI spec
|
|
6
6
|
let title, version, description;
|
|
7
7
|
try {
|
|
@@ -272,4 +272,4 @@ ${params.license || 'Apache-2.0'}
|
|
|
272
272
|
`
|
|
273
273
|
)
|
|
274
274
|
];
|
|
275
|
-
}
|
|
275
|
+
}
|
|
@@ -806,7 +806,7 @@ export default ${clientName};
|
|
|
806
806
|
return content;
|
|
807
807
|
}
|
|
808
808
|
|
|
809
|
-
|
|
809
|
+
export default function ({ asyncapi, params }) {
|
|
810
810
|
const title = asyncapi.info().title();
|
|
811
811
|
|
|
812
812
|
// Always use the processed title, ignore params.clientName if it contains template variables
|
|
@@ -824,4 +824,4 @@ module.exports = function ({ asyncapi, params }) {
|
|
|
824
824
|
{generatedContent}
|
|
825
825
|
</File>
|
|
826
826
|
);
|
|
827
|
-
}
|
|
827
|
+
}
|
package/template/src/index.ts.js
CHANGED
|
@@ -1,378 +1,21 @@
|
|
|
1
1
|
/* eslint-disable no-unused-vars */
|
|
2
2
|
import { File } from '@asyncapi/generator-react-sdk';
|
|
3
|
+
import { generateTypeScriptModels } from '../../../common/src/index.js';
|
|
3
4
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
25
|
-
function toTypeScriptTypeName(str) {
|
|
26
|
-
if (!str) return 'Unknown';
|
|
27
|
-
// Handle camelCase and PascalCase properly
|
|
28
|
-
const identifier = str
|
|
29
|
-
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
30
|
-
.replace(/^[0-9]/, '_$&')
|
|
31
|
-
.replace(/_+/g, '_')
|
|
32
|
-
.replace(/^_+|_+$/g, '');
|
|
33
|
-
|
|
34
|
-
// Split on underscores and camelCase boundaries
|
|
35
|
-
const parts = identifier.split(/[_\s]+|(?=[A-Z])/);
|
|
36
|
-
|
|
37
|
-
return parts
|
|
38
|
-
.filter(part => part.length > 0)
|
|
39
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
40
|
-
.join('');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Extract message schemas and build channel mapping
|
|
44
|
-
const components = asyncapi.components();
|
|
45
|
-
const messageSchemas = [];
|
|
46
|
-
const componentSchemas = [];
|
|
47
|
-
const messageToChannels = new Map();
|
|
48
|
-
const generatedTypes = new Set();
|
|
49
|
-
const schemaRegistry = new Map();
|
|
50
|
-
|
|
51
|
-
// Build schema registry from components.schemas
|
|
52
|
-
// Try to access the raw AsyncAPI document
|
|
53
|
-
let rawDoc = null;
|
|
54
|
-
try {
|
|
55
|
-
if (asyncapi.json && typeof asyncapi.json === 'function') {
|
|
56
|
-
rawDoc = asyncapi.json();
|
|
57
|
-
} else if (asyncapi._json) {
|
|
58
|
-
rawDoc = asyncapi._json;
|
|
59
|
-
}
|
|
60
|
-
} catch (e) {
|
|
61
|
-
// Ignore
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Extract schemas from raw document if available
|
|
65
|
-
if (rawDoc && rawDoc.components && rawDoc.components.schemas) {
|
|
66
|
-
Object.entries(rawDoc.components.schemas).forEach(([name, schema]) => {
|
|
67
|
-
if (name && typeof name === 'string' && schema && typeof schema === 'object') {
|
|
68
|
-
schemaRegistry.set(name, schema);
|
|
69
|
-
componentSchemas.push({
|
|
70
|
-
name,
|
|
71
|
-
typeName: toTypeScriptTypeName(name),
|
|
72
|
-
schema: schema,
|
|
73
|
-
description: schema.description
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Fallback: try the components.schemas() method
|
|
80
|
-
if (componentSchemas.length === 0 && components && components.schemas) {
|
|
81
|
-
try {
|
|
82
|
-
const schemas = components.schemas();
|
|
83
|
-
if (schemas) {
|
|
84
|
-
// Try different ways to access schemas
|
|
85
|
-
let schemaEntries = [];
|
|
86
|
-
|
|
87
|
-
if (schemas instanceof Map) {
|
|
88
|
-
schemaEntries = Array.from(schemas.entries());
|
|
89
|
-
} else if (typeof schemas === 'object') {
|
|
90
|
-
schemaEntries = Object.entries(schemas);
|
|
91
|
-
} else if (schemas.all && typeof schemas.all === 'function') {
|
|
92
|
-
// AsyncAPI parser might have an all() method
|
|
93
|
-
const allSchemas = schemas.all();
|
|
94
|
-
if (Array.isArray(allSchemas)) {
|
|
95
|
-
schemaEntries = allSchemas.map(schema => {
|
|
96
|
-
const name = schema.uid ? schema.uid() : (schema.id ? schema.id() : null);
|
|
97
|
-
return [name, schema];
|
|
98
|
-
}).filter(([name]) => name);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
schemaEntries.forEach(([name, schema]) => {
|
|
103
|
-
// Skip internal AsyncAPI parser objects and numeric keys
|
|
104
|
-
if (!name || name === 'collections' || name === '_meta' || name.startsWith('_') || /^\d+$/.test(name)) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let schemaData = null;
|
|
109
|
-
let description = null;
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
// Handle different schema object types
|
|
113
|
-
if (schema && typeof schema.json === 'function') {
|
|
114
|
-
schemaData = schema.json();
|
|
115
|
-
} else if (schema && typeof schema === 'object') {
|
|
116
|
-
schemaData = schema;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (schema && typeof schema.description === 'function') {
|
|
120
|
-
description = schema.description();
|
|
121
|
-
} else if (schema && schema.description) {
|
|
122
|
-
description = schema.description;
|
|
123
|
-
}
|
|
124
|
-
} catch (e) {
|
|
125
|
-
// Ignore schema extraction errors
|
|
126
|
-
console.warn(`Failed to extract schema for ${name}:`, e.message);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (schemaData && typeof name === 'string' && name.length > 0) {
|
|
130
|
-
schemaRegistry.set(name, schemaData);
|
|
131
|
-
componentSchemas.push({
|
|
132
|
-
name,
|
|
133
|
-
typeName: toTypeScriptTypeName(name),
|
|
134
|
-
schema: schemaData,
|
|
135
|
-
description
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
} catch (e) {
|
|
141
|
-
console.warn('Failed to extract component schemas:', e.message);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// First, build channel to message mapping
|
|
146
|
-
if (asyncapi.channels) {
|
|
147
|
-
const channels = asyncapi.channels();
|
|
148
|
-
if (channels) {
|
|
149
|
-
Object.entries(channels).forEach(([channelName, channel]) => {
|
|
150
|
-
try {
|
|
151
|
-
// Handle AsyncAPI 3.x format
|
|
152
|
-
if (channel.messages) {
|
|
153
|
-
const messages = channel.messages();
|
|
154
|
-
if (messages) {
|
|
155
|
-
Object.entries(messages).forEach(([msgKey, message]) => {
|
|
156
|
-
if (message) {
|
|
157
|
-
let messageName = null;
|
|
158
|
-
if (message.$ref) {
|
|
159
|
-
messageName = message.$ref.split('/').pop();
|
|
160
|
-
} else if (message.name) {
|
|
161
|
-
messageName = typeof message.name === 'function' ? message.name() : message.name;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (messageName) {
|
|
165
|
-
if (!messageToChannels.has(messageName)) {
|
|
166
|
-
messageToChannels.set(messageName, []);
|
|
167
|
-
}
|
|
168
|
-
messageToChannels.get(messageName).push(channelName);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
} catch (e) {
|
|
175
|
-
// Ignore channel processing errors
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Extract messages from components
|
|
182
|
-
if (components && components.messages) {
|
|
183
|
-
const messages = components.messages();
|
|
184
|
-
if (messages) {
|
|
185
|
-
Object.entries(messages).forEach(([name, message]) => {
|
|
186
|
-
// Skip internal AsyncAPI parser objects
|
|
187
|
-
if (name === 'collections' || name === '_meta' || name.startsWith('_')) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
let payload = null;
|
|
191
|
-
let description = null;
|
|
192
|
-
let title = null;
|
|
193
|
-
let messageName = name;
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
if (message.payload && typeof message.payload === 'function') {
|
|
197
|
-
const payloadSchema = message.payload();
|
|
198
|
-
payload = payloadSchema && payloadSchema.json ? payloadSchema.json() : payloadSchema;
|
|
199
|
-
}
|
|
200
|
-
description = message.description && typeof message.description === 'function' ? message.description() : message.description;
|
|
201
|
-
title = message.title && typeof message.title === 'function' ? message.title() : message.title;
|
|
202
|
-
|
|
203
|
-
// Try to get the actual message name
|
|
204
|
-
if (message.name && typeof message.name === 'function') {
|
|
205
|
-
messageName = message.name();
|
|
206
|
-
} else if (message.name) {
|
|
207
|
-
messageName = message.name;
|
|
208
|
-
}
|
|
209
|
-
} catch (e) {
|
|
210
|
-
// Ignore payload extraction errors
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const channels = messageToChannels.get(messageName) || messageToChannels.get(name) || [];
|
|
214
|
-
messageSchemas.push({
|
|
215
|
-
name: messageName,
|
|
216
|
-
typeName: toTypeScriptTypeName(messageName),
|
|
217
|
-
payload,
|
|
218
|
-
description: description || title,
|
|
219
|
-
channels
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Helper function to convert JSON schema to TypeScript type
|
|
226
|
-
function jsonSchemaToTypeScriptType(schema, fieldName = '') {
|
|
227
|
-
if (!schema) return 'any';
|
|
228
|
-
|
|
229
|
-
// Handle $ref - resolve from schema registry
|
|
230
|
-
if (schema.$ref) {
|
|
231
|
-
const refName = schema.$ref.split('/').pop();
|
|
232
|
-
// Always return the type name for $ref, since we generate all component schemas
|
|
233
|
-
const typeName = toTypeScriptTypeName(refName);
|
|
234
|
-
return typeName;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Handle resolved $ref - check for x-parser-schema-id which indicates original schema name
|
|
238
|
-
if (schema['x-parser-schema-id'] && typeof schema['x-parser-schema-id'] === 'string') {
|
|
239
|
-
const schemaId = schema['x-parser-schema-id'];
|
|
240
|
-
// Check if this matches a known component schema
|
|
241
|
-
if (schemaRegistry.has(schemaId)) {
|
|
242
|
-
const typeName = toTypeScriptTypeName(schemaId);
|
|
243
|
-
return typeName;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!schema.type) {
|
|
248
|
-
// If no type specified, check for properties (object) or items (array)
|
|
249
|
-
if (schema.properties) {
|
|
250
|
-
schema.type = 'object';
|
|
251
|
-
} else if (schema.items) {
|
|
252
|
-
schema.type = 'array';
|
|
253
|
-
} else {
|
|
254
|
-
return 'any';
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
switch (schema.type) {
|
|
259
|
-
case 'string':
|
|
260
|
-
if (schema.enum && schema.enum.length > 0) {
|
|
261
|
-
return schema.enum.map(val => `'${val}'`).join(' | ');
|
|
262
|
-
}
|
|
263
|
-
return 'string';
|
|
264
|
-
case 'integer':
|
|
265
|
-
case 'number':
|
|
266
|
-
return 'number';
|
|
267
|
-
case 'boolean':
|
|
268
|
-
return 'boolean';
|
|
269
|
-
case 'array': {
|
|
270
|
-
if (schema.items) {
|
|
271
|
-
const itemType = jsonSchemaToTypeScriptType(schema.items, fieldName);
|
|
272
|
-
return `${itemType}[]`;
|
|
273
|
-
}
|
|
274
|
-
return 'any[]';
|
|
275
|
-
}
|
|
276
|
-
case 'object':
|
|
277
|
-
// For objects with properties, we should generate inline types or check if it's a known schema
|
|
278
|
-
if (schema.properties) {
|
|
279
|
-
// This is a complex object - for now return Record<string, any>
|
|
280
|
-
// In a more sophisticated implementation, we could generate inline types
|
|
281
|
-
return 'Record<string, any>';
|
|
282
|
-
}
|
|
283
|
-
return 'Record<string, any>';
|
|
284
|
-
default:
|
|
285
|
-
return 'any';
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Generate message interfaces
|
|
290
|
-
function generateMessageInterface(schema, messageName) {
|
|
291
|
-
if (!schema || !schema.properties) {
|
|
292
|
-
return ' [key: string]: any;';
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const fields = Object.entries(schema.properties).map(([fieldName, fieldSchema]) => {
|
|
296
|
-
const tsType = jsonSchemaToTypeScriptType(fieldSchema, fieldName);
|
|
297
|
-
const optional = !schema.required || !schema.required.includes(fieldName);
|
|
298
|
-
const optionalMarker = optional ? '?' : '';
|
|
299
|
-
|
|
300
|
-
let fieldDoc = '';
|
|
301
|
-
if (fieldSchema.description) {
|
|
302
|
-
fieldDoc = ` /** ${fieldSchema.description} */\n`;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return `${fieldDoc} ${fieldName}${optionalMarker}: ${tsType};`;
|
|
306
|
-
}).join('\n');
|
|
307
|
-
|
|
308
|
-
return fields;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Generate interfaces for component schemas first (so they can be referenced by messages)
|
|
312
|
-
componentSchemas.forEach(schema => {
|
|
313
|
-
const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} */\n`;
|
|
314
|
-
|
|
315
|
-
// Check if this is a standalone enum schema
|
|
316
|
-
if (schema.schema.type === 'string' && schema.schema.enum && Array.isArray(schema.schema.enum)) {
|
|
317
|
-
// Generate union type for enum
|
|
318
|
-
const enumValues = schema.schema.enum.map(val => `'${val}'`).join(' | ');
|
|
319
|
-
content += `${doc}export type ${schema.typeName} = ${enumValues};\n\n`;
|
|
320
|
-
} else {
|
|
321
|
-
// Generate interface for object schema
|
|
322
|
-
content += `${doc}export interface ${schema.typeName} {\n`;
|
|
323
|
-
content += generateMessageInterface(schema.schema, schema.typeName);
|
|
324
|
-
content += '\n}\n\n';
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Track generated types to avoid duplicates
|
|
328
|
-
generatedTypes.add(schema.typeName);
|
|
5
|
+
export default function ({ asyncapi, params }) {
|
|
6
|
+
// Generate models using the common helper
|
|
7
|
+
const models = generateTypeScriptModels(asyncapi, {
|
|
8
|
+
includeMessageTypes: true // TypeScript client includes message type constants
|
|
329
9
|
});
|
|
330
10
|
|
|
331
|
-
// Generate interfaces for each message (only if not already generated as component schema)
|
|
332
|
-
messageSchemas.forEach(schema => {
|
|
333
|
-
const interfaceName = `${schema.typeName}Payload`;
|
|
334
|
-
|
|
335
|
-
// Check if this is a duplicate of a component schema
|
|
336
|
-
// For message payloads that match component schema names, skip the payload version
|
|
337
|
-
if (generatedTypes.has(schema.typeName) || generatedTypes.has(interfaceName)) {
|
|
338
|
-
// Skip generating the payload version if we already have the component schema
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const doc = schema.description ? `/** ${schema.description} */\n` : `/** ${schema.name} message payload */\n`;
|
|
343
|
-
|
|
344
|
-
content += `${doc}export interface ${interfaceName} {\n`;
|
|
345
|
-
content += generateMessageInterface(schema.payload, schema.typeName);
|
|
346
|
-
content += '\n}\n\n';
|
|
347
|
-
|
|
348
|
-
generatedTypes.add(interfaceName);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// Generate a union type for all message payloads
|
|
352
|
-
if (messageSchemas.length > 0) {
|
|
353
|
-
const payloadTypes = messageSchemas.map(schema => `${schema.typeName}Payload`).join(' | ');
|
|
354
|
-
content += '/** Union type for all message payloads */\n';
|
|
355
|
-
content += `export type MessagePayload = ${payloadTypes};\n\n`;
|
|
356
|
-
|
|
357
|
-
// Generate message type constants
|
|
358
|
-
content += '/** Message type constants */\n';
|
|
359
|
-
content += 'export const MessageTypes = {\n';
|
|
360
|
-
messageSchemas.forEach(schema => {
|
|
361
|
-
content += ` ${schema.typeName.toUpperCase()}: '${schema.name}',\n`;
|
|
362
|
-
});
|
|
363
|
-
content += '} as const;\n\n';
|
|
364
|
-
|
|
365
|
-
content += '/** Message type union */\n';
|
|
366
|
-
content += 'export type MessageType = typeof MessageTypes[keyof typeof MessageTypes];\n\n';
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return content;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
module.exports = function ({ asyncapi, params }) {
|
|
373
11
|
return (
|
|
374
12
|
<File name="models.ts">
|
|
375
|
-
{
|
|
13
|
+
{`// Generated TypeScript models from AsyncAPI specification
|
|
14
|
+
|
|
15
|
+
${models.generateComponentSchemas()}
|
|
16
|
+
${models.generateMessageSchemas()}
|
|
17
|
+
${models.generateMessageTypes()}
|
|
18
|
+
`}
|
|
376
19
|
</File>
|
|
377
20
|
);
|
|
378
|
-
}
|
|
21
|
+
}
|