@wisemen/nestjs-async-api 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintcache +1 -0
- package/README.md +121 -0
- package/dist/lib/async-api-definition.types.d.ts +27 -0
- package/dist/lib/async-api-definition.types.js +2 -0
- package/dist/lib/async-api-definition.types.js.map +1 -0
- package/dist/lib/async-api.types.d.ts +148 -0
- package/dist/lib/async-api.types.js +4 -0
- package/dist/lib/async-api.types.js.map +1 -0
- package/dist/lib/create-channel.d.ts +4 -0
- package/dist/lib/create-channel.js +10 -0
- package/dist/lib/create-channel.js.map +1 -0
- package/dist/lib/generate-async-api-html.d.ts +1 -0
- package/dist/lib/generate-async-api-html.js +15 -0
- package/dist/lib/generate-async-api-html.js.map +1 -0
- package/dist/lib/generate-async-api-yaml.d.ts +2 -0
- package/dist/lib/generate-async-api-yaml.js +81 -0
- package/dist/lib/generate-async-api-yaml.js.map +1 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.js.map +1 -0
- package/eslint.config.js +17 -0
- package/lib/async-api-definition.types.ts +29 -0
- package/lib/async-api.types.ts +174 -0
- package/lib/create-channel.ts +21 -0
- package/lib/generate-async-api-html.ts +25 -0
- package/lib/generate-async-api-yaml.ts +102 -0
- package/lib/index.ts +5 -0
- package/package.json +38 -0
- package/tsconfig.json +41 -0
package/.eslintcache
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/eslint.config.js":"1","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-column.ts":"2","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-command.builder.ts":"3","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-command.ts":"4","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-response.ts":"5","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address.builder.ts":"6","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address.ts":"7","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/index.ts":"8","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/is-address.validator.ts":"9","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/address-column.integration.test.ts":"10","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/is-address.validator.unit.test.ts":"11","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/sql/address-test.entity.ts":"12","/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/sql/datasource.ts":"13","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/eslint.config.js":"14","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/async-api-definition.types.ts":"15","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/async-api.types.ts":"16","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/create-channel.ts":"17","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/generate-async-api-html.ts":"18","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/generate-async-api-yaml.ts":"19","/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/index.ts":"20"},{"size":357,"mtime":1768820135069,"results":"21","hashOfConfig":"22"},{"size":887,"mtime":1768819954982,"results":"23","hashOfConfig":"24"},{"size":1035,"mtime":1768819954982,"results":"25","hashOfConfig":"24"},{"size":2013,"mtime":1768819804323,"results":"26","hashOfConfig":"24"},{"size":1420,"mtime":1768819804324,"results":"27","hashOfConfig":"24"},{"size":1352,"mtime":1768819954982,"results":"28","hashOfConfig":"24"},{"size":327,"mtime":1768819804324,"results":"29","hashOfConfig":"24"},{"size":411,"mtime":1768819804324,"results":"30","hashOfConfig":"24"},{"size":2949,"mtime":1768819954982,"results":"31","hashOfConfig":"24"},{"size":5341,"mtime":1768820137303,"results":"32","hashOfConfig":"33"},{"size":4618,"mtime":1768820202242,"results":"34","hashOfConfig":"33"},{"size":283,"mtime":1768820158704,"results":"35","hashOfConfig":"24"},{"size":370,"mtime":1768819804327,"results":"36","hashOfConfig":"24"},{"size":357,"mtime":1770738733468,"results":"37","hashOfConfig":"38"},{"size":955,"mtime":1770739412145,"results":"39","hashOfConfig":"40"},{"size":4322,"mtime":1770739141464,"results":"41","hashOfConfig":"40"},{"size":656,"mtime":1770739431600,"results":"42","hashOfConfig":"40"},{"size":611,"mtime":1770739438631,"results":"43","hashOfConfig":"40"},{"size":3558,"mtime":1770739532981,"results":"44","hashOfConfig":"40"},{"size":211,"mtime":1770739592457,"results":"45","hashOfConfig":"40"},{"filePath":"46","messages":"47","suppressedMessages":"48","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"lwlkib",{"filePath":"49","messages":"50","suppressedMessages":"51","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1o8juwo",{"filePath":"52","messages":"53","suppressedMessages":"54","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"55","messages":"56","suppressedMessages":"57","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"58","messages":"59","suppressedMessages":"60","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"61","messages":"62","suppressedMessages":"63","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"64","messages":"65","suppressedMessages":"66","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"67","messages":"68","suppressedMessages":"69","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"70","messages":"71","suppressedMessages":"72","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"73","messages":"74","suppressedMessages":"75","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"x22v1o",{"filePath":"76","messages":"77","suppressedMessages":"78","errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"79","messages":"80","suppressedMessages":"81","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"82","messages":"83","suppressedMessages":"84","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"85","messages":"86","suppressedMessages":"87","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"5esopc",{"filePath":"88","messages":"89","suppressedMessages":"90","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"yhymis",{"filePath":"91","messages":"92","suppressedMessages":"93","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"94","messages":"95","suppressedMessages":"96","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"97","messages":"98","suppressedMessages":"99","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"100","messages":"101","suppressedMessages":"102","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"103","messages":"104","suppressedMessages":"105","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/eslint.config.js",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-column.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-command.builder.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-command.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address-response.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address.builder.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/address.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/index.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/is-address.validator.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/address-column.integration.test.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/is-address.validator.unit.test.ts",["106","107"],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/sql/address-test.entity.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/address/lib/tests/sql/datasource.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/eslint.config.js",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/async-api-definition.types.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/async-api.types.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/create-channel.ts",[],[],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/generate-async-api-html.ts",[],["108","109","110","111"],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/generate-async-api-yaml.ts",[],["112"],"/Users/kobe/Documents/Projects/project-template/node-core/packages/async-api/lib/index.ts",[],[],{"ruleId":"113","severity":2,"message":"114","line":137,"column":5,"nodeType":"115","messageId":"116","endLine":137,"endColumn":57},{"ruleId":"117","severity":2,"message":"118","line":137,"column":54,"nodeType":"119","messageId":"120","endLine":137,"endColumn":57,"suggestions":"121"},{"ruleId":"113","severity":2,"message":"122","line":12,"column":9,"nodeType":"123","messageId":"116","endLine":22,"endColumn":4,"suppressions":"124"},{"ruleId":"125","severity":2,"message":"126","line":12,"column":21,"nodeType":"127","messageId":"128","endLine":22,"endColumn":4,"suppressions":"129"},{"ruleId":"125","severity":2,"message":"130","line":24,"column":9,"nodeType":"131","messageId":"132","endLine":24,"endColumn":37,"suppressions":"133"},{"ruleId":"134","severity":2,"message":"135","line":24,"column":19,"nodeType":"136","messageId":"137","endLine":24,"endColumn":37,"suppressions":"138"},{"ruleId":"139","severity":2,"message":"140","line":63,"column":33,"nodeType":"136","messageId":"141","endLine":63,"endColumn":41,"suppressions":"142"},"@typescript-eslint/no-unsafe-assignment","Unsafe assignment of an `any` value.","AssignmentExpression","anyAssignment","@typescript-eslint/no-explicit-any","Unexpected any. Specify a different type.","TSAnyKeyword","unexpectedAny",["143","144"],"Unsafe assignment of an error typed value.","VariableDeclarator",["145"],"@typescript-eslint/no-unsafe-call","Unsafe construction of a type that could not be resolved.","NewExpression","errorNew",["146"],"Unsafe call of a type that could not be resolved.","MemberExpression","errorCall",["147"],"@typescript-eslint/no-unsafe-member-access","Unsafe member access .generateFromString on a type that cannot be resolved.","Identifier","errorMemberExpression",["148"],"@typescript-eslint/no-unsafe-function-type","The `Function` type accepts any function-like value.\nPrefer explicitly defining any function parameters and return type.","bannedFunctionType",["149"],{"messageId":"150","fix":"151","desc":"152"},{"messageId":"153","fix":"154","desc":"155"},{"kind":"156","justification":"157"},{"kind":"156","justification":"157"},{"kind":"156","justification":"157"},{"kind":"156","justification":"157"},{"kind":"156","justification":"157"},"suggestUnknown",{"range":"158","text":"159"},"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct.","suggestNever",{"range":"160","text":"161"},"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of.","directive","",[4392,4395],"unknown",[4392,4395],"never"]
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @wisemen/nestjs-async-api
|
|
2
|
+
|
|
3
|
+
A TypeScript package for generating AsyncAPI v3.0.0 documentation for NestJS applications. Automatically generate YAML specifications and HTML documentation for your asynchronous APIs (WebSockets, message queues, event-driven architectures).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **AsyncAPI v3.0.0 Types**: Complete TypeScript type definitions for AsyncAPI specification
|
|
8
|
+
- **Channel Definitions**: Type-safe channel definitions with parameter extraction
|
|
9
|
+
- **YAML Generation**: Automatically generate AsyncAPI YAML specifications from your code
|
|
10
|
+
- **HTML Documentation**: Generate beautiful HTML documentation using AsyncAPI templates
|
|
11
|
+
- **NestJS Integration**: Seamlessly integrates with NestJS decorators and DTOs
|
|
12
|
+
- **Schema Generation**: Automatic schema generation from TypeScript classes using NestJS Swagger decorators
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @wisemen/nestjs-async-api
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Define a Channel
|
|
23
|
+
|
|
24
|
+
Create channels using the `createChannel` function with type-safe address parameters:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createChannel } from '@wisemen/nestjs-async-api'
|
|
28
|
+
|
|
29
|
+
export const userNotificationsChannel = createChannel(
|
|
30
|
+
'users.{userId}.notifications',
|
|
31
|
+
{
|
|
32
|
+
parameters: {
|
|
33
|
+
userId: {
|
|
34
|
+
description: 'The ID of the user receiving notifications'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
operations: {
|
|
38
|
+
subscribe: {
|
|
39
|
+
action: 'receive',
|
|
40
|
+
summary: 'Subscribe to user notifications',
|
|
41
|
+
description: 'Receive real-time notifications for a specific user',
|
|
42
|
+
messages: [
|
|
43
|
+
UserNotificationEvent // a DTO decorated with @nestjs/swagger properties
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Define Your AsyncAPI Specification
|
|
52
|
+
|
|
53
|
+
Create an AsyncAPI definition that points to your channel files:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { AsyncAPIDefinition } from '@wisemen/nestjs-async-api'
|
|
57
|
+
|
|
58
|
+
export const asyncApiDefinition: AsyncAPIDefinition = {
|
|
59
|
+
asyncapi: '3.0.0',
|
|
60
|
+
defaultContentType: 'application/json',
|
|
61
|
+
info: {
|
|
62
|
+
title: 'My Application API',
|
|
63
|
+
version: '1.0.0',
|
|
64
|
+
description: 'Real-time event API for My Application',
|
|
65
|
+
contact: {
|
|
66
|
+
name: 'API Support',
|
|
67
|
+
email: 'support@example.com'
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
channels: 'src/**/*.channel.ts' // Glob pattern to find channel files
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Generate YAML Documentation
|
|
75
|
+
|
|
76
|
+
Generate AsyncAPI YAML specification from your definitions:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { generateAsyncApiYaml } from '@wisemen/nestjs-async-api'
|
|
80
|
+
import { asyncApiDefinition } from './async-api.definition'
|
|
81
|
+
|
|
82
|
+
const yaml = await generateAsyncApiYaml(asyncApiDefinition)
|
|
83
|
+
console.log(yaml)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Generate HTML Documentation
|
|
87
|
+
|
|
88
|
+
Generate beautiful HTML documentation from your YAML specification:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { generateAsyncAPIHTML } from '@wisemen/nestjs-async-api'
|
|
92
|
+
|
|
93
|
+
await generateAsyncAPIHTML(
|
|
94
|
+
yaml, // AsyncAPI YAML string
|
|
95
|
+
'./docs', // Output directory
|
|
96
|
+
'async-api.html' // Output filename
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Integration with NestJS
|
|
101
|
+
|
|
102
|
+
This package works seamlessly with NestJS decorators from `@nestjs/swagger`. Use `@ApiProperty()` decorators on your DTOs, and they will be automatically converted to AsyncAPI schemas:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { ApiProperty } from '@nestjs/swagger'
|
|
106
|
+
|
|
107
|
+
export class UserNotificationEvent {
|
|
108
|
+
@ApiProperty({ description: 'Notification ID' })
|
|
109
|
+
id: string
|
|
110
|
+
|
|
111
|
+
@ApiProperty({ description: 'Notification message' })
|
|
112
|
+
message: string
|
|
113
|
+
|
|
114
|
+
@ApiProperty({ description: 'Timestamp', type: Date })
|
|
115
|
+
timestamp: Date
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
GPL
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AsyncAPIInfo, AsyncAPIOperation, AsyncAPIParameter } from './async-api.types.js';
|
|
2
|
+
type ExtractParams<T> = T extends `{${infer Param}}.${infer Rest}` ? {
|
|
3
|
+
[K in Param]: string;
|
|
4
|
+
} & ExtractParams<Rest> : T extends `${infer _Start}.{${infer Param}}${infer Rest}` ? {
|
|
5
|
+
[K in Param]: string;
|
|
6
|
+
} & ExtractParams<Rest> : object;
|
|
7
|
+
export type AsyncAPIChannelParameters<Address> = {
|
|
8
|
+
[Param in keyof ExtractParams<Address>]: AsyncAPIParameter;
|
|
9
|
+
};
|
|
10
|
+
export type AsyncAPIOperationDefinition = Omit<AsyncAPIOperation, 'messages' | 'channel' | 'reply'> & {
|
|
11
|
+
messages?: {
|
|
12
|
+
name: string;
|
|
13
|
+
constructor: () => unknown;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
16
|
+
export type AsyncAPIChannelDefinition<Address extends string> = {
|
|
17
|
+
address: Address;
|
|
18
|
+
parameters: AsyncAPIChannelParameters<Address>;
|
|
19
|
+
operations?: Record<string, AsyncAPIOperationDefinition>;
|
|
20
|
+
};
|
|
21
|
+
export type AsyncAPIDefinition = {
|
|
22
|
+
asyncapi: string;
|
|
23
|
+
defaultContentType: string;
|
|
24
|
+
info: AsyncAPIInfo;
|
|
25
|
+
channels: string;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-api-definition.types.js","sourceRoot":"","sources":["../../lib/async-api-definition.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export type AsyncApiRef = {
|
|
2
|
+
$ref: string;
|
|
3
|
+
};
|
|
4
|
+
export type AsyncAPIDocument = {
|
|
5
|
+
asyncapi: string;
|
|
6
|
+
id?: string;
|
|
7
|
+
info: AsyncAPIInfo;
|
|
8
|
+
servers?: Record<string, AsyncAPIServer>;
|
|
9
|
+
defaultContentType?: string;
|
|
10
|
+
channels: Record<string, AsyncAPIChannel>;
|
|
11
|
+
operations?: Record<string, AsyncAPIOperation>;
|
|
12
|
+
components?: AsyncAPIComponents;
|
|
13
|
+
tags?: AsyncAPITag[];
|
|
14
|
+
externalDocs?: AsyncAPIExternalDocs;
|
|
15
|
+
};
|
|
16
|
+
export type AsyncAPIInfo = {
|
|
17
|
+
title: string;
|
|
18
|
+
version: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
termsOfService?: string;
|
|
21
|
+
contact?: AsyncAPIContact;
|
|
22
|
+
license?: AsyncAPILicense;
|
|
23
|
+
};
|
|
24
|
+
export type AsyncAPIContact = {
|
|
25
|
+
name?: string;
|
|
26
|
+
url?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
};
|
|
29
|
+
export type AsyncAPILicense = {
|
|
30
|
+
name: string;
|
|
31
|
+
url?: string;
|
|
32
|
+
};
|
|
33
|
+
export type AsyncAPIServer = {
|
|
34
|
+
host: string;
|
|
35
|
+
protocol: string;
|
|
36
|
+
protocolVersion?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
tags?: AsyncAPITag[];
|
|
39
|
+
security?: AsyncAPISecurityRequirement[];
|
|
40
|
+
variables?: Record<string, AsyncAPIServerVariable>;
|
|
41
|
+
bindings?: AsyncAPIServerBindings;
|
|
42
|
+
};
|
|
43
|
+
export type AsyncAPIServerVariable = {
|
|
44
|
+
description?: string;
|
|
45
|
+
default: string;
|
|
46
|
+
examples?: string[];
|
|
47
|
+
enum?: string[];
|
|
48
|
+
};
|
|
49
|
+
export type AsyncAPISecurityRequirement = Record<string, string[]>;
|
|
50
|
+
export type AsyncAPIServerBindings = Record<string, unknown>;
|
|
51
|
+
export type AsyncAPIChannel = {
|
|
52
|
+
address: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
parameters?: Record<string, AsyncAPIParameter>;
|
|
55
|
+
messages?: Record<string, AsyncApiRef>;
|
|
56
|
+
subscribe?: string;
|
|
57
|
+
publish?: string;
|
|
58
|
+
bindings?: AsyncAPIChannelBindings;
|
|
59
|
+
};
|
|
60
|
+
export type AsyncAPIChannelBindings = Record<string, unknown>;
|
|
61
|
+
export type AsyncAPIParameter = {
|
|
62
|
+
enum?: string[];
|
|
63
|
+
default?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
examples?: string[];
|
|
66
|
+
location?: string;
|
|
67
|
+
};
|
|
68
|
+
export type AsyncAPIOperation = {
|
|
69
|
+
action: 'send' | 'receive';
|
|
70
|
+
channel: AsyncApiRef;
|
|
71
|
+
title?: string;
|
|
72
|
+
summary?: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
tags?: AsyncAPITag[];
|
|
75
|
+
externalDocs?: AsyncAPIExternalDocs;
|
|
76
|
+
bindings?: AsyncAPIOperationBindings;
|
|
77
|
+
traits?: (string | AsyncAPIOperationTrait)[];
|
|
78
|
+
messages?: AsyncApiRef[];
|
|
79
|
+
security?: AsyncAPISecurityRequirement[];
|
|
80
|
+
deprecated?: boolean;
|
|
81
|
+
reply?: AsyncAPIOperationReply;
|
|
82
|
+
};
|
|
83
|
+
export type AsyncAPIOperationReply = {
|
|
84
|
+
address?: AsyncAPIOperationReplyAddress;
|
|
85
|
+
channel?: AsyncApiRef;
|
|
86
|
+
messages?: AsyncApiRef;
|
|
87
|
+
};
|
|
88
|
+
export type AsyncAPIOperationReplyAddress = {
|
|
89
|
+
location: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
};
|
|
92
|
+
export type AsyncAPIOperationTrait = Record<string, unknown>;
|
|
93
|
+
export type AsyncAPIOperationBindings = Record<string, unknown>;
|
|
94
|
+
export type AsyncAPIMessage = {
|
|
95
|
+
name?: string;
|
|
96
|
+
title?: string;
|
|
97
|
+
summary?: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
headers?: AsyncAPISchema;
|
|
100
|
+
payload?: AsyncApiRef;
|
|
101
|
+
correlationId?: AsyncAPICorrelationId;
|
|
102
|
+
schemaFormat?: string;
|
|
103
|
+
contentType?: string;
|
|
104
|
+
tags?: AsyncAPITag[];
|
|
105
|
+
externalDocs?: AsyncAPIExternalDocs;
|
|
106
|
+
bindings?: AsyncAPIMessageBindings;
|
|
107
|
+
examples?: unknown[];
|
|
108
|
+
traits?: (string | AsyncAPIMessageTrait)[];
|
|
109
|
+
};
|
|
110
|
+
export type AsyncAPIMessageTrait = Record<string, unknown>;
|
|
111
|
+
export type AsyncAPIMessageBindings = Record<string, unknown>;
|
|
112
|
+
export type AsyncAPICorrelationId = {
|
|
113
|
+
description?: string;
|
|
114
|
+
location: string;
|
|
115
|
+
};
|
|
116
|
+
export type AsyncAPISchema = Record<string, unknown>;
|
|
117
|
+
export type AsyncAPIComponents = {
|
|
118
|
+
schemas?: Record<string, AsyncAPISchema>;
|
|
119
|
+
messages?: Record<string, AsyncAPIMessage>;
|
|
120
|
+
securitySchemes?: Record<string, AsyncAPISecurityScheme>;
|
|
121
|
+
parameters?: Record<string, AsyncAPIParameter>;
|
|
122
|
+
correlationIds?: Record<string, AsyncAPICorrelationId>;
|
|
123
|
+
operationTraits?: Record<string, AsyncAPIOperationTrait>;
|
|
124
|
+
messageTraits?: Record<string, AsyncAPIMessageTrait>;
|
|
125
|
+
serverBindings?: Record<string, AsyncAPIServerBindings>;
|
|
126
|
+
channelBindings?: Record<string, AsyncAPIChannelBindings>;
|
|
127
|
+
operationBindings?: Record<string, AsyncAPIOperationBindings>;
|
|
128
|
+
messageBindings?: Record<string, AsyncAPIMessageBindings>;
|
|
129
|
+
};
|
|
130
|
+
export type AsyncAPISecurityScheme = {
|
|
131
|
+
type: string;
|
|
132
|
+
description?: string;
|
|
133
|
+
name?: string;
|
|
134
|
+
in?: string;
|
|
135
|
+
scheme?: string;
|
|
136
|
+
bearerFormat?: string;
|
|
137
|
+
flows?: unknown;
|
|
138
|
+
openIdConnectUrl?: string;
|
|
139
|
+
};
|
|
140
|
+
export type AsyncAPITag = {
|
|
141
|
+
name: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
externalDocs?: AsyncAPIExternalDocs;
|
|
144
|
+
};
|
|
145
|
+
export type AsyncAPIExternalDocs = {
|
|
146
|
+
description?: string;
|
|
147
|
+
url: string;
|
|
148
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-api.types.js","sourceRoot":"","sources":["../../lib/async-api.types.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,0EAA0E"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AsyncAPIChannelDefinition } from './async-api-definition.types.js';
|
|
2
|
+
export type ChannelConfig<Address extends string> = Omit<AsyncAPIChannelDefinition<Address>, 'address'>;
|
|
3
|
+
export declare function isChannel(value: object): value is AsyncAPIChannelDefinition<string>;
|
|
4
|
+
export declare function createChannel<Address extends string>(address: Address, config: ChannelConfig<Address>): AsyncAPIChannelDefinition<Address>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const CHANNEL_METADATA_KEY = 'async-api:channel';
|
|
2
|
+
export function isChannel(value) {
|
|
3
|
+
return Reflect.hasMetadata(CHANNEL_METADATA_KEY, value);
|
|
4
|
+
}
|
|
5
|
+
export function createChannel(address, config) {
|
|
6
|
+
const channel = { address, ...config };
|
|
7
|
+
Reflect.defineMetadata(CHANNEL_METADATA_KEY, true, channel);
|
|
8
|
+
return channel;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=create-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-channel.js","sourceRoot":"","sources":["../../lib/create-channel.ts"],"names":[],"mappings":"AAEA,MAAM,oBAAoB,GAAG,mBAAmB,CAAA;AAKhD,MAAM,UAAU,SAAS,CAAE,KAAa;IACtC,OAAO,OAAO,CAAC,WAAW,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,MAA8B;IAE9B,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAAA;IAEtC,OAAO,CAAC,cAAc,CAAC,oBAAoB,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IAE3D,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateAsyncAPIHTML(yaml: string, outputPath: string, fileName: string): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
|
+
import Generator from '@asyncapi/generator';
|
|
5
|
+
export async function generateAsyncAPIHTML(yaml, outputPath, fileName) {
|
|
6
|
+
const generator = new Generator('@asyncapi/html-template', outputPath, {
|
|
7
|
+
forceWrite: true,
|
|
8
|
+
templateParams: {
|
|
9
|
+
singleFile: true,
|
|
10
|
+
outFilename: fileName
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
await generator.generateFromString(yaml, 'yaml');
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=generate-async-api-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-async-api-html.js","sourceRoot":"","sources":["../../lib/generate-async-api-html.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,sDAAsD;AACtD,4DAA4D;AAE5D,OAAO,SAAS,MAAM,qBAAqB,CAAA;AAE3C,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAY,EACZ,UAAkB,EAClB,QAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,SAAS,CAC7B,yBAAyB,EACzB,UAAU,EACV;QACE,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE;YACd,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,QAAQ;SACtB;KACF,CACF,CAAA;IAED,MAAM,SAAS,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AAClD,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { globSync } from 'fs';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
import { SchemaObjectFactory } from '@nestjs/swagger/dist/services/schema-object-factory.js';
|
|
5
|
+
import { ModelPropertiesAccessor } from '@nestjs/swagger/dist/services/model-properties-accessor.js';
|
|
6
|
+
import { SwaggerTypesMapper } from '@nestjs/swagger/dist/services/swagger-types-mapper.js';
|
|
7
|
+
import { pascalCase } from 'change-case';
|
|
8
|
+
import { isChannel } from './create-channel.js';
|
|
9
|
+
export async function generateAsyncApiYaml(api) {
|
|
10
|
+
const apiDocs = {
|
|
11
|
+
asyncapi: api.asyncapi,
|
|
12
|
+
info: api.info,
|
|
13
|
+
defaultContentType: api.defaultContentType,
|
|
14
|
+
channels: {},
|
|
15
|
+
operations: {},
|
|
16
|
+
components: { messages: {}, schemas: {} }
|
|
17
|
+
};
|
|
18
|
+
const channelFiles = globSync(api.channels);
|
|
19
|
+
const channels = {};
|
|
20
|
+
for (const channelFile of channelFiles) {
|
|
21
|
+
const moduleExports = await import('../../../../' + channelFile);
|
|
22
|
+
for (const exportedKey in moduleExports) {
|
|
23
|
+
const exported = moduleExports[exportedKey];
|
|
24
|
+
if (typeof exported !== 'object') {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (isChannel(exported)) {
|
|
28
|
+
const channelName = pascalCase(exportedKey.substring(0, exportedKey.indexOf('Channel')));
|
|
29
|
+
assert(channels[channelName] === undefined, `duplicate channel name ${channelName}`);
|
|
30
|
+
channels[channelName] = exported;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const accessor = new ModelPropertiesAccessor();
|
|
35
|
+
const typeMapper = new SwaggerTypesMapper();
|
|
36
|
+
for (const channelName in channels) {
|
|
37
|
+
const channel = channels[channelName];
|
|
38
|
+
const channelRef = { $ref: '#/channels/' + channelName };
|
|
39
|
+
const channelMessages = {};
|
|
40
|
+
for (const operationName in channel.operations) {
|
|
41
|
+
const operation = channel.operations[operationName];
|
|
42
|
+
const operationMessages = [];
|
|
43
|
+
for (const message of operation.messages) {
|
|
44
|
+
const name = message.name;
|
|
45
|
+
const factory = new SchemaObjectFactory(accessor, typeMapper);
|
|
46
|
+
factory.exploreModelSchema(
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
48
|
+
message, apiDocs.components.schemas);
|
|
49
|
+
// Add the message itself
|
|
50
|
+
apiDocs.components.messages[name] = {
|
|
51
|
+
name: name,
|
|
52
|
+
contentType: 'application/json',
|
|
53
|
+
payload: { $ref: `#/components/schemas/${name}` }
|
|
54
|
+
};
|
|
55
|
+
operationMessages.push({ $ref: `#/channels/${channelName}/messages/${name}` });
|
|
56
|
+
channelMessages[name] = { $ref: '#/components/messages/' + name };
|
|
57
|
+
}
|
|
58
|
+
apiDocs.operations[operationName] = {
|
|
59
|
+
channel: channelRef,
|
|
60
|
+
action: operation.action,
|
|
61
|
+
bindings: operation.bindings,
|
|
62
|
+
deprecated: operation.deprecated,
|
|
63
|
+
description: operation.description,
|
|
64
|
+
externalDocs: operation.externalDocs,
|
|
65
|
+
security: operation.security,
|
|
66
|
+
summary: operation.summary,
|
|
67
|
+
title: operation.title,
|
|
68
|
+
tags: operation.tags,
|
|
69
|
+
traits: operation.traits,
|
|
70
|
+
messages: operationMessages
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
apiDocs.channels[channelName] = {
|
|
74
|
+
address: channel.address,
|
|
75
|
+
parameters: channel.parameters,
|
|
76
|
+
messages: channelMessages
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return YAML.stringify(apiDocs);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=generate-async-api-yaml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-async-api-yaml.js","sourceRoot":"","sources":["../../lib/generate-async-api-yaml.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAA;AAC/C,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,8BAA8B,CAAA"}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import eslintNestJSConfig from '@wisemen/eslint-config-nestjs'
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...eslintNestJSConfig,
|
|
5
|
+
{
|
|
6
|
+
rules: {
|
|
7
|
+
'import-typescript/no-relative-parent-imports': 'off'
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.test.ts'],
|
|
12
|
+
rules: {
|
|
13
|
+
'@typescript-eslint/unbound-method': 'off',
|
|
14
|
+
'@typescript-eslint/no-floating-promises': 'off'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AsyncAPIInfo, AsyncAPIOperation, AsyncAPIParameter } from './async-api.types.js'
|
|
2
|
+
|
|
3
|
+
type ExtractParams<T>
|
|
4
|
+
= T extends `{${infer Param}}.${infer Rest}`
|
|
5
|
+
? { [K in Param]: string } & ExtractParams<Rest>
|
|
6
|
+
: T extends `${infer _Start}.{${infer Param}}${infer Rest}`
|
|
7
|
+
? { [K in Param]: string } & ExtractParams<Rest>
|
|
8
|
+
: object
|
|
9
|
+
|
|
10
|
+
export type AsyncAPIChannelParameters<Address> = {
|
|
11
|
+
[Param in keyof ExtractParams<Address>]: AsyncAPIParameter
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type AsyncAPIOperationDefinition
|
|
15
|
+
= Omit<AsyncAPIOperation, 'messages' | 'channel' | 'reply'>
|
|
16
|
+
& { messages?: { name: string, constructor: () => unknown }[] }
|
|
17
|
+
|
|
18
|
+
export type AsyncAPIChannelDefinition<Address extends string> = {
|
|
19
|
+
address: Address
|
|
20
|
+
parameters: AsyncAPIChannelParameters<Address>
|
|
21
|
+
operations?: Record<string, AsyncAPIOperationDefinition>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type AsyncAPIDefinition = {
|
|
25
|
+
asyncapi: string
|
|
26
|
+
defaultContentType: string
|
|
27
|
+
info: AsyncAPIInfo
|
|
28
|
+
channels: string
|
|
29
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// AsyncAPI v3.0.0 Document Format as TypeScript Types
|
|
2
|
+
// Reference: https://www.asyncapi.com/docs/reference/specification/v3.0.0
|
|
3
|
+
|
|
4
|
+
export type AsyncApiRef = {
|
|
5
|
+
$ref: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type AsyncAPIDocument = {
|
|
9
|
+
asyncapi: string
|
|
10
|
+
id?: string
|
|
11
|
+
info: AsyncAPIInfo
|
|
12
|
+
servers?: Record<string, AsyncAPIServer>
|
|
13
|
+
defaultContentType?: string
|
|
14
|
+
channels: Record<string, AsyncAPIChannel>
|
|
15
|
+
operations?: Record<string, AsyncAPIOperation>
|
|
16
|
+
components?: AsyncAPIComponents
|
|
17
|
+
tags?: AsyncAPITag[]
|
|
18
|
+
externalDocs?: AsyncAPIExternalDocs
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type AsyncAPIInfo = {
|
|
22
|
+
title: string
|
|
23
|
+
version: string
|
|
24
|
+
description?: string
|
|
25
|
+
termsOfService?: string
|
|
26
|
+
contact?: AsyncAPIContact
|
|
27
|
+
license?: AsyncAPILicense
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AsyncAPIContact = {
|
|
31
|
+
name?: string
|
|
32
|
+
url?: string
|
|
33
|
+
email?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type AsyncAPILicense = {
|
|
37
|
+
name: string
|
|
38
|
+
url?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type AsyncAPIServer = {
|
|
42
|
+
host: string
|
|
43
|
+
protocol: string
|
|
44
|
+
protocolVersion?: string
|
|
45
|
+
description?: string
|
|
46
|
+
tags?: AsyncAPITag[]
|
|
47
|
+
security?: AsyncAPISecurityRequirement[]
|
|
48
|
+
variables?: Record<string, AsyncAPIServerVariable>
|
|
49
|
+
bindings?: AsyncAPIServerBindings
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type AsyncAPIServerVariable = {
|
|
53
|
+
description?: string
|
|
54
|
+
default: string
|
|
55
|
+
examples?: string[]
|
|
56
|
+
enum?: string[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type AsyncAPISecurityRequirement = Record<string, string[]>
|
|
60
|
+
|
|
61
|
+
export type AsyncAPIServerBindings = Record<string, unknown>
|
|
62
|
+
|
|
63
|
+
export type AsyncAPIChannel = {
|
|
64
|
+
address: string
|
|
65
|
+
description?: string
|
|
66
|
+
parameters?: Record<string, AsyncAPIParameter>
|
|
67
|
+
messages?: Record<string, AsyncApiRef>
|
|
68
|
+
subscribe?: string // Operation ID
|
|
69
|
+
publish?: string // Operation ID
|
|
70
|
+
bindings?: AsyncAPIChannelBindings
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type AsyncAPIChannelBindings = Record<string, unknown>
|
|
74
|
+
|
|
75
|
+
export type AsyncAPIParameter = {
|
|
76
|
+
enum?: string[]
|
|
77
|
+
default?: string
|
|
78
|
+
description?: string
|
|
79
|
+
examples?: string[]
|
|
80
|
+
location?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type AsyncAPIOperation = {
|
|
84
|
+
action: 'send' | 'receive'
|
|
85
|
+
channel: AsyncApiRef
|
|
86
|
+
title?: string
|
|
87
|
+
summary?: string
|
|
88
|
+
description?: string
|
|
89
|
+
tags?: AsyncAPITag[]
|
|
90
|
+
externalDocs?: AsyncAPIExternalDocs
|
|
91
|
+
bindings?: AsyncAPIOperationBindings
|
|
92
|
+
traits?: (string | AsyncAPIOperationTrait)[]
|
|
93
|
+
messages?: AsyncApiRef[]
|
|
94
|
+
security?: AsyncAPISecurityRequirement[]
|
|
95
|
+
deprecated?: boolean
|
|
96
|
+
reply?: AsyncAPIOperationReply
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type AsyncAPIOperationReply = {
|
|
100
|
+
address?: AsyncAPIOperationReplyAddress
|
|
101
|
+
channel?: AsyncApiRef
|
|
102
|
+
messages?: AsyncApiRef
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type AsyncAPIOperationReplyAddress = {
|
|
106
|
+
location: string
|
|
107
|
+
description?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type AsyncAPIOperationTrait = Record<string, unknown>
|
|
111
|
+
export type AsyncAPIOperationBindings = Record<string, unknown>
|
|
112
|
+
|
|
113
|
+
export type AsyncAPIMessage = {
|
|
114
|
+
name?: string
|
|
115
|
+
title?: string
|
|
116
|
+
summary?: string
|
|
117
|
+
description?: string
|
|
118
|
+
headers?: AsyncAPISchema
|
|
119
|
+
payload?: AsyncApiRef
|
|
120
|
+
correlationId?: AsyncAPICorrelationId
|
|
121
|
+
schemaFormat?: string
|
|
122
|
+
contentType?: string
|
|
123
|
+
tags?: AsyncAPITag[]
|
|
124
|
+
externalDocs?: AsyncAPIExternalDocs
|
|
125
|
+
bindings?: AsyncAPIMessageBindings
|
|
126
|
+
examples?: unknown[]
|
|
127
|
+
traits?: (string | AsyncAPIMessageTrait)[]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type AsyncAPIMessageTrait = Record<string, unknown>
|
|
131
|
+
export type AsyncAPIMessageBindings = Record<string, unknown>
|
|
132
|
+
|
|
133
|
+
export type AsyncAPICorrelationId = {
|
|
134
|
+
description?: string
|
|
135
|
+
location: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type AsyncAPISchema = Record<string, unknown>
|
|
139
|
+
|
|
140
|
+
export type AsyncAPIComponents = {
|
|
141
|
+
schemas?: Record<string, AsyncAPISchema>
|
|
142
|
+
messages?: Record<string, AsyncAPIMessage>
|
|
143
|
+
securitySchemes?: Record<string, AsyncAPISecurityScheme>
|
|
144
|
+
parameters?: Record<string, AsyncAPIParameter>
|
|
145
|
+
correlationIds?: Record<string, AsyncAPICorrelationId>
|
|
146
|
+
operationTraits?: Record<string, AsyncAPIOperationTrait>
|
|
147
|
+
messageTraits?: Record<string, AsyncAPIMessageTrait>
|
|
148
|
+
serverBindings?: Record<string, AsyncAPIServerBindings>
|
|
149
|
+
channelBindings?: Record<string, AsyncAPIChannelBindings>
|
|
150
|
+
operationBindings?: Record<string, AsyncAPIOperationBindings>
|
|
151
|
+
messageBindings?: Record<string, AsyncAPIMessageBindings>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type AsyncAPISecurityScheme = {
|
|
155
|
+
type: string
|
|
156
|
+
description?: string
|
|
157
|
+
name?: string
|
|
158
|
+
in?: string
|
|
159
|
+
scheme?: string
|
|
160
|
+
bearerFormat?: string
|
|
161
|
+
flows?: unknown
|
|
162
|
+
openIdConnectUrl?: string
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type AsyncAPITag = {
|
|
166
|
+
name: string
|
|
167
|
+
description?: string
|
|
168
|
+
externalDocs?: AsyncAPIExternalDocs
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type AsyncAPIExternalDocs = {
|
|
172
|
+
description?: string
|
|
173
|
+
url: string
|
|
174
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AsyncAPIChannelDefinition } from './async-api-definition.types.js'
|
|
2
|
+
|
|
3
|
+
const CHANNEL_METADATA_KEY = 'async-api:channel'
|
|
4
|
+
|
|
5
|
+
export type ChannelConfig<Address extends string>
|
|
6
|
+
= Omit<AsyncAPIChannelDefinition<Address>, 'address'>
|
|
7
|
+
|
|
8
|
+
export function isChannel (value: object): value is AsyncAPIChannelDefinition<string> {
|
|
9
|
+
return Reflect.hasMetadata(CHANNEL_METADATA_KEY, value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createChannel<Address extends string> (
|
|
13
|
+
address: Address,
|
|
14
|
+
config: ChannelConfig<Address>
|
|
15
|
+
): AsyncAPIChannelDefinition<Address> {
|
|
16
|
+
const channel = { address, ...config }
|
|
17
|
+
|
|
18
|
+
Reflect.defineMetadata(CHANNEL_METADATA_KEY, true, channel)
|
|
19
|
+
|
|
20
|
+
return channel
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
|
+
|
|
5
|
+
import Generator from '@asyncapi/generator'
|
|
6
|
+
|
|
7
|
+
export async function generateAsyncAPIHTML (
|
|
8
|
+
yaml: string,
|
|
9
|
+
outputPath: string,
|
|
10
|
+
fileName: string
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const generator = new Generator(
|
|
13
|
+
'@asyncapi/html-template',
|
|
14
|
+
outputPath,
|
|
15
|
+
{
|
|
16
|
+
forceWrite: true,
|
|
17
|
+
templateParams: {
|
|
18
|
+
singleFile: true,
|
|
19
|
+
outFilename: fileName
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
await generator.generateFromString(yaml, 'yaml')
|
|
25
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { globSync } from 'fs'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import YAML from 'yaml'
|
|
4
|
+
import { SchemaObjectFactory } from '@nestjs/swagger/dist/services/schema-object-factory.js'
|
|
5
|
+
import { ModelPropertiesAccessor } from '@nestjs/swagger/dist/services/model-properties-accessor.js'
|
|
6
|
+
import { SwaggerTypesMapper } from '@nestjs/swagger/dist/services/swagger-types-mapper.js'
|
|
7
|
+
import { pascalCase } from 'change-case'
|
|
8
|
+
import { AsyncAPIDocument, AsyncApiRef } from './async-api.types.js'
|
|
9
|
+
import { AsyncAPIChannelDefinition, AsyncAPIDefinition } from './async-api-definition.types.js'
|
|
10
|
+
import { isChannel } from './create-channel.js'
|
|
11
|
+
|
|
12
|
+
export async function generateAsyncApiYaml (api: AsyncAPIDefinition): Promise<string> {
|
|
13
|
+
const apiDocs: AsyncAPIDocument = {
|
|
14
|
+
asyncapi: api.asyncapi,
|
|
15
|
+
info: api.info,
|
|
16
|
+
defaultContentType: api.defaultContentType,
|
|
17
|
+
channels: {},
|
|
18
|
+
operations: {},
|
|
19
|
+
components: { messages: {}, schemas: {} }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const channelFiles = globSync(api.channels)
|
|
23
|
+
const channels: Record<string, AsyncAPIChannelDefinition<string>> = {}
|
|
24
|
+
|
|
25
|
+
for (const channelFile of channelFiles) {
|
|
26
|
+
const moduleExports = await import('../../../../' + channelFile) as Record<string, object>
|
|
27
|
+
|
|
28
|
+
for (const exportedKey in moduleExports) {
|
|
29
|
+
const exported = moduleExports[exportedKey]
|
|
30
|
+
|
|
31
|
+
if (typeof exported !== 'object') {
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isChannel(exported)) {
|
|
36
|
+
const channelName = pascalCase(exportedKey.substring(0, exportedKey.indexOf('Channel')))
|
|
37
|
+
|
|
38
|
+
assert(channels[channelName] === undefined, `duplicate channel name ${channelName}`)
|
|
39
|
+
channels[channelName] = exported
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const accessor = new ModelPropertiesAccessor()
|
|
45
|
+
const typeMapper = new SwaggerTypesMapper()
|
|
46
|
+
|
|
47
|
+
for (const channelName in channels) {
|
|
48
|
+
const channel = channels[channelName]
|
|
49
|
+
const channelRef: AsyncApiRef = { $ref: '#/channels/' + channelName }
|
|
50
|
+
const channelMessages: Record<string, AsyncApiRef> = {}
|
|
51
|
+
|
|
52
|
+
for (const operationName in channel.operations) {
|
|
53
|
+
const operation = channel.operations[operationName]
|
|
54
|
+
const operationMessages: AsyncApiRef[] = []
|
|
55
|
+
|
|
56
|
+
for (const message of operation.messages!) {
|
|
57
|
+
const name = message.name
|
|
58
|
+
|
|
59
|
+
const factory = new SchemaObjectFactory(accessor, typeMapper)
|
|
60
|
+
|
|
61
|
+
factory.exploreModelSchema(
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
63
|
+
message as unknown as Function,
|
|
64
|
+
apiDocs.components!.schemas!
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// Add the message itself
|
|
68
|
+
apiDocs.components!.messages![name] = {
|
|
69
|
+
name: name,
|
|
70
|
+
contentType: 'application/json',
|
|
71
|
+
payload: { $ref: `#/components/schemas/${name}` }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
operationMessages.push({ $ref: `#/channels/${channelName}/messages/${name}` })
|
|
75
|
+
channelMessages[name] = { $ref: '#/components/messages/' + name }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
apiDocs.operations![operationName] = {
|
|
79
|
+
channel: channelRef,
|
|
80
|
+
action: operation.action,
|
|
81
|
+
bindings: operation.bindings,
|
|
82
|
+
deprecated: operation.deprecated,
|
|
83
|
+
description: operation.description,
|
|
84
|
+
externalDocs: operation.externalDocs,
|
|
85
|
+
security: operation.security,
|
|
86
|
+
summary: operation.summary,
|
|
87
|
+
title: operation.title,
|
|
88
|
+
tags: operation.tags,
|
|
89
|
+
traits: operation.traits,
|
|
90
|
+
messages: operationMessages
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
apiDocs.channels[channelName] = {
|
|
95
|
+
address: channel.address,
|
|
96
|
+
parameters: channel.parameters,
|
|
97
|
+
messages: channelMessages
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return YAML.stringify(apiDocs)
|
|
102
|
+
}
|
package/lib/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wisemen/nestjs-async-api",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"imports": {
|
|
9
|
+
"#*": "./lib/*"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"prebuild": "rm -rf ./dist",
|
|
13
|
+
"clean": "rm -rf ./dist",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"pretest": "npm run clean && npm run build",
|
|
16
|
+
"test": "node --env-file=.env.test --test ./**/*.test.js",
|
|
17
|
+
"lint": "eslint --cache",
|
|
18
|
+
"prerelease": "npm run build",
|
|
19
|
+
"release": "pnpx release-it"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"change-case": "^5.4.4",
|
|
23
|
+
"@asyncapi/generator": "3.1.2",
|
|
24
|
+
"yaml": "2.8.2",
|
|
25
|
+
"@nestjs/swagger": "11.2.6"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@wisemen/eslint-config-nestjs": "^0.2.8",
|
|
29
|
+
"eslint": "9.39.2",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
},
|
|
32
|
+
"author": "Kobe Kwanten",
|
|
33
|
+
"license": "GPL",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git@github.com:wisemen-digital/node-core.git"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"target": "esnext",
|
|
7
|
+
"module": "NodeNext",
|
|
8
|
+
"types": [
|
|
9
|
+
"node",
|
|
10
|
+
],
|
|
11
|
+
"rootDir": "./",
|
|
12
|
+
"moduleResolution": "nodenext",
|
|
13
|
+
"sourceMap": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"emitDecoratorMetadata": true,
|
|
16
|
+
"experimentalDecorators": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"strictNullChecks": true,
|
|
19
|
+
"skipLibCheck": true
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"./index.ts",
|
|
23
|
+
"./lib/**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"./lib/test-utils.ts",
|
|
27
|
+
"./lib/**/*.spec.ts"
|
|
28
|
+
],
|
|
29
|
+
"ts-node": {
|
|
30
|
+
"transpileOnly": true
|
|
31
|
+
},
|
|
32
|
+
"typedocOptions": {
|
|
33
|
+
"entryPoints": [
|
|
34
|
+
"./index.ts",
|
|
35
|
+
"./lib"
|
|
36
|
+
],
|
|
37
|
+
"entryPointStrategy": "expand",
|
|
38
|
+
"out": "../../documentation/client"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|