@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 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=async-api-definition.types.js.map
@@ -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,4 @@
1
+ // AsyncAPI v3.0.0 Document Format as TypeScript Types
2
+ // Reference: https://www.asyncapi.com/docs/reference/specification/v3.0.0
3
+ export {};
4
+ //# sourceMappingURL=async-api.types.js.map
@@ -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,2 @@
1
+ import { AsyncAPIDefinition } from './async-api-definition.types.js';
2
+ export declare function generateAsyncApiYaml(api: AsyncAPIDefinition): Promise<string>;
@@ -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,5 @@
1
+ export * from './async-api-definition.types.js';
2
+ export * from './async-api.types.js';
3
+ export * from './create-channel.js';
4
+ export * from './generate-async-api-html.js';
5
+ export * from './generate-async-api-yaml.js';
@@ -0,0 +1,6 @@
1
+ export * from './async-api-definition.types.js';
2
+ export * from './async-api.types.js';
3
+ export * from './create-channel.js';
4
+ export * from './generate-async-api-html.js';
5
+ export * from './generate-async-api-yaml.js';
6
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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
@@ -0,0 +1,5 @@
1
+ export * from './async-api-definition.types.js'
2
+ export * from './async-api.types.js'
3
+ export * from './create-channel.js'
4
+ export * from './generate-async-api-html.js'
5
+ export * from './generate-async-api-yaml.js'
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
+