From eff11fb0804f091e738f057ae7aee41fddd91a72 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:21:00 +0100 Subject: [PATCH 1/8] feat: Generate JSDoc comments from schema descriptions (including @deprecated) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #338 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../generators/typescript/channels/openapi.ts | 13 +- .../channels/protocols/amqp/index.ts | 12 +- .../protocols/amqp/publishExchange.ts | 21 +- .../channels/protocols/amqp/publishQueue.ts | 21 +- .../channels/protocols/amqp/subscribeQueue.ts | 22 +- .../channels/protocols/eventsource/express.ts | 18 +- .../channels/protocols/eventsource/fetch.ts | 29 +- .../channels/protocols/eventsource/index.ts | 12 +- .../channels/protocols/http/client.ts | 22 +- .../channels/protocols/http/index.ts | 8 +- .../channels/protocols/kafka/index.ts | 12 +- .../channels/protocols/kafka/publish.ts | 21 +- .../channels/protocols/kafka/subscribe.ts | 27 +- .../channels/protocols/mqtt/index.ts | 12 +- .../channels/protocols/mqtt/publish.ts | 21 +- .../channels/protocols/mqtt/subscribe.ts | 25 +- .../channels/protocols/nats/corePublish.ts | 21 +- .../channels/protocols/nats/coreReply.ts | 25 +- .../channels/protocols/nats/coreRequest.ts | 24 +- .../channels/protocols/nats/coreSubscribe.ts | 25 +- .../channels/protocols/nats/index.ts | 9 +- .../protocols/nats/jetstreamPublish.ts | 21 +- .../protocols/nats/jetstreamPullSubscribe.ts | 25 +- .../nats/jetstreamPushSubscription.ts | 25 +- .../channels/protocols/websocket/index.ts | 12 +- .../channels/protocols/websocket/publish.ts | 21 +- .../channels/protocols/websocket/register.ts | 21 +- .../channels/protocols/websocket/subscribe.ts | 21 +- .../generators/typescript/channels/types.ts | 12 + .../generators/typescript/channels/utils.ts | 51 +++ src/codegen/generators/typescript/payloads.ts | 3 +- src/codegen/generators/typescript/utils.ts | 14 +- src/codegen/utils.ts | 25 ++ .../__snapshots__/channels.spec.ts.snap | 80 +++-- .../__snapshots__/payload.spec.ts.snap | 18 ++ .../generators/typescript/jsdoc-utils.spec.ts | 181 +++++++++++ test/runtime/asyncapi-regular.json | 62 ++++ test/runtime/typescript/src/Types.ts | 11 +- test/runtime/typescript/src/channels/amqp.ts | 156 ++++++++-- .../typescript/src/channels/event_source.ts | 152 ++++++++- test/runtime/typescript/src/channels/kafka.ts | 137 ++++++-- test/runtime/typescript/src/channels/mqtt.ts | 115 ++++++- test/runtime/typescript/src/channels/nats.ts | 293 ++++++++++++++++-- .../typescript/src/channels/websocket.ts | 191 +++++++++++- .../typescript/src/client/NatsClient.ts | 158 ++++++++++ .../src/openapi/channels/http_client.ts | 15 +- .../typescript/src/openapi/payloads/APet.ts | 9 + .../typescript/src/openapi/payloads/AUser.ts | 6 + .../openapi/payloads/AnUploadedResponse.ts | 3 + .../src/openapi/payloads/ItemStatus.ts | 3 + .../src/openapi/payloads/PetCategory.ts | 3 + .../src/openapi/payloads/PetOrder.ts | 6 + .../typescript/src/openapi/payloads/PetTag.ts | 3 + .../typescript/src/openapi/payloads/Status.ts | 3 + .../payload-types/payloads/AllOfThreeTypes.ts | 3 + .../payload-types/payloads/AllOfTwoTypes.ts | 3 + .../payload-types/payloads/AnyOfTwoTypes.ts | 3 + .../payloads/ArrayWithMaxItems.ts | 3 + .../payloads/ArrayWithMinItems.ts | 3 + .../payloads/ArrayWithUniqueItems.ts | 3 + .../payload-types/payloads/BooleanPlain.ts | 3 + .../payloads/DeeplyNestedObject.ts | 3 + .../src/payload-types/payloads/FormatDate.ts | 3 + .../payload-types/payloads/FormatDateTime.ts | 3 + .../src/payload-types/payloads/FormatEmail.ts | 3 + .../payload-types/payloads/FormatHostname.ts | 3 + .../src/payload-types/payloads/FormatIpv4.ts | 3 + .../src/payload-types/payloads/FormatIpv6.ts | 3 + .../src/payload-types/payloads/FormatTime.ts | 3 + .../src/payload-types/payloads/FormatUri.ts | 3 + .../src/payload-types/payloads/FormatUuid.ts | 3 + .../src/payload-types/payloads/IntegerEnum.ts | 3 + .../payload-types/payloads/IntegerPlain.ts | 3 + .../payloads/IntegerWithRange.ts | 3 + .../payload-types/payloads/MixedTypeEnum.ts | 3 + .../payloads/MultipleStringEnum.ts | 3 + .../src/payload-types/payloads/NestedArray.ts | 3 + .../payload-types/payloads/NestedObject.ts | 3 + .../src/payload-types/payloads/NullPlain.ts | 3 + .../payload-types/payloads/NullableEnum.ts | 3 + .../src/payload-types/payloads/NumberPlain.ts | 3 + .../payloads/NumberWithMaximum.ts | 3 + .../payloads/NumberWithMinimum.ts | 3 + .../payload-types/payloads/NumberWithRange.ts | 3 + .../payloads/ObjectAdditionalPropsFalse.ts | 3 + .../payloads/ObjectAdditionalPropsSchema.ts | 3 + .../payloads/ObjectAdditionalPropsTrue.ts | 3 + .../src/payload-types/payloads/ObjectArray.ts | 3 + .../payloads/ObjectWithDefaults.ts | 3 + .../payloads/ObjectWithFormats.ts | 3 + .../payloads/ObjectWithOptional.ts | 3 + .../payloads/ObjectWithRequired.ts | 3 + .../payload-types/payloads/OneOfThreeTypes.ts | 3 + .../payload-types/payloads/OneOfTwoTypes.ts | 3 + .../payloads/OneOfWithDiscriminator.ts | 3 + .../payload-types/payloads/SimpleObject.ts | 3 + .../payloads/SingleStringEnum.ts | 3 + .../src/payload-types/payloads/StringArray.ts | 3 + .../src/payload-types/payloads/StringPlain.ts | 3 + .../payloads/StringWithMaxLength.ts | 3 + .../payloads/StringWithMinLength.ts | 3 + .../payloads/StringWithPattern.ts | 3 + .../src/payload-types/payloads/TupleArray.ts | 3 + .../typescript/src/payloads/ArrayMessage.ts | 3 + .../src/payloads/LegacyNotification.ts | 96 ++++++ .../LegacyNotificationPayloadLevelEnum.ts | 10 + .../typescript/src/payloads/StringMessage.ts | 3 + .../typescript/src/payloads/UnionMessage.ts | 3 + .../typescript/src/payloads/UserSignedUp.ts | 11 +- .../src/request-reply/channels/http_client.ts | 50 ++- .../src/request-reply/channels/nats.ts | 8 +- .../src/request-reply/payloads/ItemRequest.ts | 9 + .../request-reply/payloads/ItemResponse.ts | 15 + .../src/request-reply/payloads/NotFound.ts | 6 + .../src/request-reply/payloads/Ping.ts | 3 + .../src/request-reply/payloads/Pong.ts | 3 + test/runtime/typescript/test/jsdoc.spec.ts | 214 +++++++++++++ 117 files changed, 2535 insertions(+), 302 deletions(-) create mode 100644 test/codegen/generators/typescript/jsdoc-utils.spec.ts create mode 100644 test/runtime/typescript/src/payloads/LegacyNotification.ts create mode 100644 test/runtime/typescript/src/payloads/LegacyNotificationPayloadLevelEnum.ts create mode 100644 test/runtime/typescript/test/jsdoc.spec.ts diff --git a/src/codegen/generators/typescript/channels/openapi.ts b/src/codegen/generators/typescript/channels/openapi.ts index a42c2506..73f801a2 100644 --- a/src/codegen/generators/typescript/channels/openapi.ts +++ b/src/codegen/generators/typescript/channels/openapi.ts @@ -13,10 +13,7 @@ import { } from './types'; import {ConstrainedObjectModel} from '@asyncapi/modelina'; import {collectProtocolDependencies} from './utils'; -import { - renderHttpFetchClient, - renderHttpCommonTypes -} from './protocols/http'; +import {renderHttpFetchClient, renderHttpCommonTypes} from './protocols/http'; import {getMessageTypeAndModule} from './utils'; import {pascalCase} from '../utils'; import {createMissingInputDocumentError} from '../../../errors'; @@ -220,6 +217,10 @@ function processOperation( return undefined; } + // Extract operation metadata for JSDoc + const description = operation.description ?? operation.summary; + const deprecated = operation.deprecated === true; + // Generate the HTTP client function return renderHttpFetchClient({ subName: pascalCase(operationId), @@ -239,7 +240,9 @@ function processOperation( channelParameters: parameterModel?.model as | ConstrainedObjectModel | undefined, - includesStatusCodes: replyIncludesStatusCodes + includesStatusCodes: replyIncludesStatusCodes, + description, + deprecated }); } diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/index.ts b/src/codegen/generators/typescript/channels/protocols/amqp/index.ts index f8a918c6..8ba135d9 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/index.ts @@ -6,7 +6,11 @@ import { ChannelFunctionTypes, TypeScriptChannelsGeneratorContext } from '../../types'; -import {findNameFromOperation, findOperationId} from '../../../../../utils'; +import { + findNameFromOperation, + findOperationId, + getOperationMetadata +} from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import { shouldRenderFunctionType, @@ -113,11 +117,15 @@ async function generateForOperations( `Could not find message type for channel typescript generator for AMQP` ); } + // Extract operation metadata for JSDoc + const {description, deprecated} = getOperationMetadata(operation); const updatedContext = { ...amqpContext, messageType, messageModule, - subName: findNameFromOperation(operation, channel) + subName: findNameFromOperation(operation, channel), + description, + deprecated }; renders.push( diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts b/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts index 56f19b0c..efee8081 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts @@ -3,6 +3,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderPublishExchange({ topic, @@ -12,7 +13,9 @@ export function renderPublishExchange({ channelHeaders, subName = pascalCase(topic), functionName = `publishTo${subName}Exchange`, - additionalProperties + additionalProperties, + description, + deprecated }: RenderRegularParameters<{ exchange: string | undefined; }>): SingleFunctionRenderType { @@ -83,11 +86,17 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions);` } ]; - const code = `/** - * AMQP publish operation for exchange \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `AMQP publish operation for exchange \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts b/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts index 15dece26..f87fa550 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts @@ -3,6 +3,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderPublishQueue({ topic, @@ -11,7 +12,9 @@ export function renderPublishQueue({ channelParameters, channelHeaders, subName = pascalCase(topic), - functionName = `publishTo${subName}Queue` + functionName = `publishTo${subName}Queue`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const addressToUse = channelParameters ? `parameters.getChannelWithParameters('${topic}')` @@ -80,11 +83,17 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions);`; } ]; - const code = `/** - * AMQP publish operation for queue \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `AMQP publish operation for queue \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts b/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts index 451583e3..c3e6c6cb 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts @@ -2,7 +2,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; export function renderSubscribeQueue({ topic, @@ -12,7 +12,9 @@ export function renderSubscribeQueue({ channelHeaders, subName = pascalCase(topic), functionName = `subscribeTo${subName}Queue`, - payloadGenerator + payloadGenerator, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -116,11 +118,17 @@ channel.consume(queue, (msg) => { } ]; - const code = `/** - * AMQP subscribe operation for queue \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `AMQP subscribe operation for queue \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts b/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts index 09bcfcad..7067e932 100644 --- a/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts +++ b/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts @@ -3,6 +3,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderExpress({ topic, @@ -11,7 +12,9 @@ export function renderExpress({ channelParameters, channelHeaders, subName = pascalCase(topic), - functionName = `register${subName}` + functionName = `register${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { let addressToUse = topic.replace(/{([^}]+)}/g, ':$1'); addressToUse = addressToUse.startsWith('/') @@ -64,7 +67,18 @@ export function renderExpress({ } ]; - const code = `function ${functionName}({ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `Register EventSource endpoint for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} +function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { ${functionParameters.map((param) => param.parameterType).join(', \n ')} diff --git a/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts b/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts index a559cff7..03d08e1d 100644 --- a/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts +++ b/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts @@ -6,7 +6,7 @@ import { defaultTypeScriptChannelsGenerator, RenderRegularParameters } from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; export function renderFetch({ topic, @@ -19,7 +19,9 @@ export function renderFetch({ additionalProperties = { fetchDependency: defaultTypeScriptChannelsGenerator.eventSourceDependency }, - payloadGenerator + payloadGenerator, + description, + deprecated }: RenderRegularParameters<{ fetchDependency: string; }>): SingleFunctionRenderType { @@ -75,12 +77,23 @@ export function renderFetch({ } ]; - const code = `/** - * Event source fetch for \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - * @returns A cleanup function to abort the connection - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `Event source fetch for \`${topic}\``, + parameters: [ + ...functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })), + { + name: 'returns', + jsDoc: ' * @returns A cleanup function to abort the connection' + } + ] + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/eventsource/index.ts b/src/codegen/generators/typescript/channels/protocols/eventsource/index.ts index 1ca31c45..f04e4f49 100644 --- a/src/codegen/generators/typescript/channels/protocols/eventsource/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/eventsource/index.ts @@ -6,7 +6,11 @@ import { ChannelFunctionTypes, TypeScriptChannelsGeneratorContext } from '../../types'; -import {findNameFromOperation, findOperationId} from '../../../../../utils'; +import { + findNameFromOperation, + findOperationId, + getOperationMetadata +} from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import { shouldRenderFunctionType, @@ -112,11 +116,15 @@ async function generateForOperations( `Could not find message type for channel typescript generator for EventSource` ); } + // Extract operation metadata for JSDoc + const {description, deprecated} = getOperationMetadata(operation); const updatedContext = { ...eventSourceContext, messageType, messageModule, - subName: findNameFromOperation(operation, channel) + subName: findNameFromOperation(operation, channel), + description, + deprecated }; renders.push( diff --git a/src/codegen/generators/typescript/channels/protocols/http/client.ts b/src/codegen/generators/typescript/channels/protocols/http/client.ts index 44ac8da6..f7b7a502 100644 --- a/src/codegen/generators/typescript/channels/protocols/http/client.ts +++ b/src/codegen/generators/typescript/channels/protocols/http/client.ts @@ -5,6 +5,7 @@ import {HttpRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {ChannelFunctionTypes, RenderHttpParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; /** * Renders an HTTP fetch client function for a specific API operation. @@ -20,7 +21,9 @@ export function renderHttpFetchClient({ servers = [], subName = pascalCase(requestTopic), functionName = `${method.toLowerCase()}${subName}`, - includesStatusCodes = false + includesStatusCodes = false, + description, + deprecated }: RenderHttpParameters): HttpRenderType { const messageType = requestMessageModule ? `${requestMessageModule}.${requestMessageType}` @@ -43,6 +46,13 @@ export function renderHttpFetchClient({ method ); + // Generate JSDoc for the function + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `HTTP ${method} request to ${requestTopic}` + }); + // Generate the function implementation const functionCode = generateFunctionImplementation({ functionName, @@ -55,7 +65,8 @@ export function renderHttpFetchClient({ hasParameters, method, servers, - includesStatusCodes + includesStatusCodes, + jsDoc }); const code = `${contextInterface} @@ -122,6 +133,7 @@ function generateFunctionImplementation(params: { method: string; servers: string[]; includesStatusCodes: boolean; + jsDoc: string; }): string { const { functionName, @@ -134,7 +146,8 @@ function generateFunctionImplementation(params: { hasParameters, method, servers, - includesStatusCodes + includesStatusCodes, + jsDoc } = params; const defaultServer = servers[0] ?? "'localhost:3000'"; @@ -170,7 +183,8 @@ function generateFunctionImplementation(params: { // Generate default context for optional context parameter const contextDefault = !hasBody && !hasParameters ? ' = {}' : ''; - return `async function ${functionName}(context: ${contextInterfaceName}${contextDefault}): Promise> { + return `${jsDoc} +export async function ${functionName}(context: ${contextInterfaceName}${contextDefault}): Promise> { // Apply defaults const config = { path: '${requestTopic}', diff --git a/src/codegen/generators/typescript/channels/protocols/http/index.ts b/src/codegen/generators/typescript/channels/protocols/http/index.ts index 8a6e9aec..149aadc0 100644 --- a/src/codegen/generators/typescript/channels/protocols/http/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/http/index.ts @@ -7,7 +7,8 @@ import { import { findNameFromOperation, findOperationId, - findReplyId + findReplyId, + getOperationMetadata } from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import { @@ -149,6 +150,7 @@ function generateForOperations( `Could not find reply message type for channel typescript generator for HTTP` ); } + const {description, deprecated} = getOperationMetadata(operation); renders.push( renderHttpFetchClient({ subName: findNameFromOperation(operation, channel), @@ -160,7 +162,9 @@ function generateForOperations( method: httpMethod.toUpperCase(), channelParameters: parameters !== undefined ? parameters : undefined, - includesStatusCodes: replyIncludesStatusCodes + includesStatusCodes: replyIncludesStatusCodes, + description, + deprecated }) ); } diff --git a/src/codegen/generators/typescript/channels/protocols/kafka/index.ts b/src/codegen/generators/typescript/channels/protocols/kafka/index.ts index 01f5577b..37cef535 100644 --- a/src/codegen/generators/typescript/channels/protocols/kafka/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/kafka/index.ts @@ -6,7 +6,11 @@ import { ChannelFunctionTypes, TypeScriptChannelsGeneratorContext } from '../../types'; -import {findNameFromOperation, findOperationId} from '../../../../../utils'; +import { + findNameFromOperation, + findOperationId, + getOperationMetadata +} from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import { shouldRenderFunctionType, @@ -113,11 +117,15 @@ async function generateForOperations( `Could not find message type for channel typescript generator for Kafka` ); } + // Extract operation metadata for JSDoc + const {description, deprecated} = getOperationMetadata(operation); const updatedContext = { ...kafkaContext, messageType, messageModule, - subName: findNameFromOperation(operation, channel) + subName: findNameFromOperation(operation, channel), + description, + deprecated }; renders.push( diff --git a/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts b/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts index 6ed24022..a9228a7e 100644 --- a/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts +++ b/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts @@ -3,6 +3,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderPublish({ topic, @@ -11,7 +12,9 @@ export function renderPublish({ channelParameters, channelHeaders, subName = pascalCase(topic), - functionName = `produceTo${subName}` + functionName = `produceTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const addressToUse = channelParameters ? `parameters.getChannelWithParameters('${topic}')` @@ -75,11 +78,17 @@ export function renderPublish({ const headersInMessage = channelHeaders ? 'headers: messageHeaders' : ''; - const code = `/** - * Kafka publish operation for \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `Kafka publish operation for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts b/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts index 6492a5e9..46618f9a 100644 --- a/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts @@ -4,7 +4,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; import {generateKafkaMessageReceivingCode} from './utils'; export function renderSubscribe({ @@ -15,7 +15,9 @@ export function renderSubscribe({ channelHeaders, subName = pascalCase(topic), functionName = `consumeFrom${subName}`, - payloadGenerator + payloadGenerator, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -110,25 +112,28 @@ export function renderSubscribe({ messageUnmarshalling, potentialValidationFunction }); - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); const callbackJsDocParameters = callbackFunctionParameters .map((param) => param.jsDoc) .join('\n'); + const mainJsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `Kafka subscription for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * * @callback ${functionName}Callback - ${callbackJsDocParameters} +${callbackJsDocParameters} */ -/** - * Kafka subscription for \`${topic}\` - * - ${jsDocParameters} - */ +${mainJsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/mqtt/index.ts b/src/codegen/generators/typescript/channels/protocols/mqtt/index.ts index a3dcecf6..76573624 100644 --- a/src/codegen/generators/typescript/channels/protocols/mqtt/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/mqtt/index.ts @@ -6,7 +6,11 @@ import { ChannelFunctionTypes, TypeScriptChannelsGeneratorContext } from '../../types'; -import {findNameFromOperation, findOperationId} from '../../../../../utils'; +import { + findNameFromOperation, + findOperationId, + getOperationMetadata +} from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import {renderPublish} from './publish'; import {renderSubscribe} from './subscribe'; @@ -110,11 +114,15 @@ function generateForOperations( `Could not find message type for ${payloadId} for mqtt channel typescript generator` ); } + // Extract operation metadata for JSDoc + const {description, deprecated} = getOperationMetadata(operation); const updatedContext = { ...mqttContext, messageType, messageModule, - subName: findNameFromOperation(operation, channel) + subName: findNameFromOperation(operation, channel), + description, + deprecated }; const action = operation.action(); diff --git a/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts b/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts index 271364d3..540a8d05 100644 --- a/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts +++ b/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts @@ -3,6 +3,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderPublish({ topic, @@ -11,7 +12,9 @@ export function renderPublish({ channelParameters, channelHeaders, subName = pascalCase(topic), - functionName = `publishTo${subName}` + functionName = `publishTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const addressToUse = channelParameters ? `parameters.getChannelWithParameters('${topic}')` @@ -78,11 +81,17 @@ export function renderPublish({ } ]; - const code = `/** - * MQTT publish operation for \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `MQTT publish operation for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts b/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts index e479273a..d36701ee 100644 --- a/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts @@ -4,7 +4,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; /* eslint-disable sonarjs/cognitive-complexity */ export function renderSubscribe({ @@ -15,7 +15,9 @@ export function renderSubscribe({ channelHeaders, subName = pascalCase(topic), functionName = `subscribeTo${subName}`, - payloadGenerator + payloadGenerator, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -154,13 +156,20 @@ export function renderSubscribe({ onDataCallback({err: new Error(\`${PARSE_MESSAGE_ERROR}\`), msg: undefined${channelParameters ? PARAMETERS_PARAM : ''}${channelHeaders ? HEADERS_EXTRACTED_PARAM : ''}, mqttMsg: packet}); }`; - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); const callbackJsDocParameters = callbackFunctionParameters .map((param) => param.jsDoc) .join('\n'); + const mainJsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `MQTT subscription for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -168,11 +177,7 @@ export function renderSubscribe({ ${callbackJsDocParameters} */ -/** - * MQTT subscription for \`${topic}\` - * -${jsDocParameters} - */ +${mainJsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts b/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts index 785a165f..a7c143ba 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts @@ -4,6 +4,7 @@ import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; import {generateHeaderSetupCode, generateHeaderParameter} from './utils'; +import {renderChannelJSDoc} from '../../utils'; export function renderCorePublish({ topic, @@ -12,7 +13,9 @@ export function renderCorePublish({ channelParameters, channelHeaders, subName = pascalCase(topic), - functionName = `publishTo${subName}` + functionName = `publishTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const addressToUse = channelParameters ? `parameters.getChannelWithParameters('${topic}')` @@ -68,11 +71,17 @@ nc.publish(${addressToUse}, dataToSend, options);`; } ]; - const code = `/** - * NATS publish operation for \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `NATS publish operation for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts b/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts index 1285d623..57a0f9de 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts @@ -3,7 +3,7 @@ import {ChannelFunctionTypes, RenderRequestReplyParameters} from '../../types'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; export function renderCoreReply({ requestTopic, @@ -14,7 +14,9 @@ export function renderCoreReply({ channelParameters, subName = pascalCase(requestTopic), payloadGenerator, - functionName = `replyTo${subName}` + functionName = `replyTo${subName}`, + description, + deprecated }: RenderRequestReplyParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -102,13 +104,20 @@ const replyMessage = await onDataCallback(undefined, ${requestMessageModule ?? r dataToSend = codec.encode(dataToSend); msg.respond(dataToSend);`; - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); const callbackJsDocParameters = callbackFunctionParameters .map((param) => param.jsDoc) .join('\n'); + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `NATS reply operation for \`${requestTopic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving the request * @@ -116,11 +125,7 @@ msg.respond(dataToSend);`; ${callbackJsDocParameters} */ -/** - * Reply for \`${requestTopic}\` - * - ${jsDocParameters} - */ +${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts b/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts index 4f4e0eb8..887928bd 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts @@ -3,7 +3,7 @@ import {ChannelFunctionTypes, RenderRequestReplyParameters} from '../../types'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; export function renderCoreRequest({ requestTopic, @@ -14,7 +14,9 @@ export function renderCoreRequest({ channelParameters, subName = pascalCase(requestTopic), payloadGenerator, - functionName = `requestTo${subName}` + functionName = `requestTo${subName}`, + description, + deprecated }: RenderRequestReplyParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -75,15 +77,17 @@ export function renderCoreRequest({ } ]; - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `NATS request operation for \`${requestTopic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); - const code = `/** - * Core request for \`${requestTopic}\` - * - ${jsDocParameters} - */ + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts b/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts index 2d717f3e..56e8c712 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts @@ -4,7 +4,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; import { generateHeaderExtractionCode, generateHeaderCallbackParameter, @@ -19,7 +19,9 @@ export function renderCoreSubscribe({ channelHeaders, subName = pascalCase(topic), payloadGenerator, - functionName = `subscribeTo${subName}` + functionName = `subscribeTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -112,13 +114,20 @@ export function renderCoreSubscribe({ headerExtraction, potentialValidationFunction }); - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); const callbackJsDocParameters = callbackFunctionParameters .map((param) => param.jsDoc) .join('\n'); + const mainJsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `Core subscription for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -126,11 +135,7 @@ export function renderCoreSubscribe({ ${callbackJsDocParameters} */ -/** - * Core subscription for \`${topic}\` - * -${jsDocParameters} - */ +${mainJsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/index.ts b/src/codegen/generators/typescript/channels/protocols/nats/index.ts index a81e5594..c99a1af2 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/index.ts @@ -9,7 +9,8 @@ import { import { findNameFromOperation, findOperationId, - findReplyId + findReplyId, + getOperationMetadata } from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import { @@ -147,11 +148,15 @@ async function generateForOperations( `Could not find message type for channel typescript generator for NATS` ); } + // Extract operation metadata for JSDoc + const {description, deprecated} = getOperationMetadata(operation); const updatedContext = { ...natsContext, messageType, messageModule, - subName: findNameFromOperation(operation, channel) + subName: findNameFromOperation(operation, channel), + description, + deprecated }; renders.push( diff --git a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts index 1c45f402..7174a888 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts @@ -4,6 +4,7 @@ import {pascalCase} from '../../../utils'; import {ChannelFunctionTypes} from '../../index'; import {RenderRegularParameters} from '../../types'; import {generateHeaderSetupCode, generateHeaderParameter} from './utils'; +import {renderChannelJSDoc} from '../../utils'; export function renderJetstreamPublish({ topic, messageType, @@ -11,7 +12,9 @@ export function renderJetstreamPublish({ channelParameters, channelHeaders, subName = pascalCase(topic), - functionName = `jetStreamPublishTo${subName}` + functionName = `jetStreamPublishTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const addressToUse = channelParameters ? `parameters.getChannelWithParameters('${topic}')` @@ -68,11 +71,17 @@ await js.publish(${addressToUse}, dataToSend, options);`; } ]; - const code = `/** - * JetStream publish operation for \`${topic}\` - * - ${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `JetStream publish operation for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts index caed761f..aa9d2fc7 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts @@ -4,7 +4,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; import { generateHeaderExtractionCode, generateMessageReceivingCode @@ -18,7 +18,9 @@ export function renderJetstreamPullSubscribe({ channelHeaders, subName = pascalCase(topic), payloadGenerator, - functionName = `jetStreamPullSubscribeTo${subName}` + functionName = `jetStreamPullSubscribeTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -116,13 +118,20 @@ export function renderJetstreamPullSubscribe({ potentialValidationFunction }); - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); const callbackJsDocParameters = callbackFunctionParameters .map((param) => param.jsDoc) .join('\n'); + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `JetStream pull subscription for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -130,11 +139,7 @@ export function renderJetstreamPullSubscribe({ ${callbackJsDocParameters} */ -/** - * JetStream pull subscription for \`${topic}\` - * - ${jsDocParameters} - */ +${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts index 3c0578f7..9c22ee66 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts @@ -4,7 +4,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {findRegexFromChannel, pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; -import {getValidationFunctions} from '../../utils'; +import {getValidationFunctions, renderChannelJSDoc} from '../../utils'; import { generateHeaderExtractionCode, generateMessageReceivingCode @@ -18,7 +18,9 @@ export function renderJetstreamPushSubscription({ channelHeaders, subName = pascalCase(topic), payloadGenerator, - functionName = `jetStreamPushSubscriptionFrom${subName}` + functionName = `jetStreamPushSubscriptionFrom${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { const includeValidation = payloadGenerator.generator.includeValidation; const addressToUse = channelParameters @@ -119,13 +121,20 @@ export function renderJetstreamPushSubscription({ potentialValidationFunction }); - const jsDocParameters = functionParameters - .map((param) => param.jsDoc) - .join('\n'); const callbackJsDocParameters = callbackFunctionParameters .map((param) => param.jsDoc) .join('\n'); + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `JetStream push subscription for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -133,11 +142,7 @@ export function renderJetstreamPushSubscription({ ${callbackJsDocParameters} */ -/** - * JetStream push subscription for \`${topic}\` - * - ${jsDocParameters} - */ +${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(', \n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/index.ts b/src/codegen/generators/typescript/channels/protocols/websocket/index.ts index 504dd9fd..952e0bd2 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/index.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/index.ts @@ -6,7 +6,11 @@ import { TypeScriptChannelsGeneratorContext, TypeScriptChannelRenderedFunctionType } from '../../types'; -import {findNameFromOperation, findOperationId} from '../../../../../utils'; +import { + findNameFromOperation, + findOperationId, + getOperationMetadata +} from '../../../../../utils'; import {getMessageTypeAndModule} from '../../utils'; import { getFunctionTypeMappingFromAsyncAPI, @@ -133,11 +137,15 @@ async function generateForOperations( `Could not find message type for channel typescript generator for WebSocket` ); } + // Extract operation metadata for JSDoc + const {description, deprecated} = getOperationMetadata(operation); const updatedContext = { ...websocketContext, messageType, messageModule, - subName: findNameFromOperation(operation, channel) + subName: findNameFromOperation(operation, channel), + description, + deprecated }; renders.push( diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts b/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts index 2a9010bd..9bda7846 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts @@ -3,13 +3,16 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderWebSocketPublish({ topic, messageType, messageModule, subName = pascalCase(topic), - functionName = `publishTo${subName}` + functionName = `publishTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { let messageMarshalling = 'message.marshal()'; if (messageModule) { @@ -31,11 +34,17 @@ export function renderWebSocketPublish({ } ]; - const code = `/** - * WebSocket client-side function to publish messages to \`${topic}\` - * -${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `WebSocket client-side function to publish messages to \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(',\n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/register.ts b/src/codegen/generators/typescript/channels/protocols/websocket/register.ts index 04becfdb..f63cc2cc 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/register.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/register.ts @@ -3,6 +3,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderWebSocketRegister({ topic, @@ -10,7 +11,9 @@ export function renderWebSocketRegister({ messageModule, channelParameters, subName = pascalCase(topic), - functionName = `register${subName}` + functionName = `register${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { let messageUnmarshalling = `${messageType}.unmarshal(receivedData)`; if (messageModule) { @@ -67,11 +70,17 @@ export function renderWebSocketRegister({ } ]; - const code = `/** - * WebSocket server-side function to handle messages for \`${topic}\` - * -${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `WebSocket server-side function to handle messages for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(',\n ')} }: { diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts b/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts index a86b4cba..54c911d8 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts @@ -4,6 +4,7 @@ import {ChannelFunctionTypes} from '../..'; import {SingleFunctionRenderType} from '../../../../../types'; import {pascalCase} from '../../../utils'; import {RenderRegularParameters} from '../../types'; +import {renderChannelJSDoc} from '../../utils'; export function renderWebSocketSubscribe({ topic, @@ -11,7 +12,9 @@ export function renderWebSocketSubscribe({ messageModule, channelParameters, subName = pascalCase(topic), - functionName = `subscribeTo${subName}` + functionName = `subscribeTo${subName}`, + description, + deprecated }: RenderRegularParameters): SingleFunctionRenderType { let messageUnmarshalling = `${messageType}.unmarshal(receivedData)`; if (messageModule) { @@ -69,11 +72,17 @@ export function renderWebSocketSubscribe({ } ]; - const code = `/** - * WebSocket client-side function to subscribe to messages from \`${topic}\` - * -${functionParameters.map((param) => param.jsDoc).join('\n')} - */ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `WebSocket client-side function to subscribe to messages from \`${topic}\``, + parameters: functionParameters.map((param) => ({ + name: param.parameter, + jsDoc: param.jsDoc + })) + }); + + const code = `${jsDoc} function ${functionName}({ ${functionParameters.map((param) => param.parameter).join(',\n ')} }: { diff --git a/src/codegen/generators/typescript/channels/types.ts b/src/codegen/generators/typescript/channels/types.ts index 0273d4e8..1f629743 100644 --- a/src/codegen/generators/typescript/channels/types.ts +++ b/src/codegen/generators/typescript/channels/types.ts @@ -220,6 +220,10 @@ export interface RenderRegularParameters { functionName?: string; payloadGenerator: TypeScriptPayloadRenderType; additionalProperties?: T; + /** Operation description from API specification for JSDoc generation */ + description?: string; + /** Whether the operation is marked as deprecated in the API specification */ + deprecated?: boolean; } export interface RenderRequestReplyParameters { @@ -233,6 +237,10 @@ export interface RenderRequestReplyParameters { subName?: string; functionName?: string; payloadGenerator: TypeScriptPayloadRenderType; + /** Operation description from API specification for JSDoc generation */ + description?: string; + /** Whether the operation is marked as deprecated in the API specification */ + deprecated?: boolean; } export interface RenderHttpParameters { @@ -251,6 +259,10 @@ export interface RenderHttpParameters { * When true, use unmarshalByStatusCode(json, statusCode) instead of unmarshal(json). */ includesStatusCodes?: boolean; + /** Operation description from API specification for JSDoc generation */ + description?: string; + /** Whether the operation is marked as deprecated in the API specification */ + deprecated?: boolean; } export type SupportedProtocols = diff --git a/src/codegen/generators/typescript/channels/utils.ts b/src/codegen/generators/typescript/channels/utils.ts index a1f5f2cd..f0296d09 100644 --- a/src/codegen/generators/typescript/channels/utils.ts +++ b/src/codegen/generators/typescript/channels/utils.ts @@ -241,3 +241,54 @@ export function collectProtocolDependencies( ); } } + +/** + * Escapes special characters in description for JSDoc. + * Handles closing comment markers and formats multi-line descriptions. + */ +export function escapeJSDocDescription(description: string): string { + if (!description) { + return ''; + } + return description + .replace(/\*\//g, '*\u2215') // Escape closing comment (use division slash) + .replace(/\n/g, '\n * '); // Format multi-line with JSDoc prefix +} + +/** + * Renders JSDoc block for channel functions. + * Uses operation description from API spec when available, falls back to generic text. + */ +export function renderChannelJSDoc(params: { + description?: string; + deprecated?: boolean; + fallbackDescription: string; + parameters?: Array<{name: string; jsDoc: string}>; +}): string { + const { + description, + deprecated, + fallbackDescription, + parameters = [] + } = params; + + const desc = description + ? escapeJSDocDescription(description) + : fallbackDescription; + + const parts = ['/**', ` * ${desc}`]; + + if (deprecated) { + parts.push(' *'); + parts.push(' * @deprecated'); + } + + if (parameters.length > 0) { + parts.push(' *'); + parameters.forEach((p) => parts.push(p.jsDoc)); + } + + parts.push(' */'); + + return parts.join('\n'); +} diff --git a/src/codegen/generators/typescript/payloads.ts b/src/codegen/generators/typescript/payloads.ts index 0200f650..9a9e3393 100644 --- a/src/codegen/generators/typescript/payloads.ts +++ b/src/codegen/generators/typescript/payloads.ts @@ -14,7 +14,7 @@ import {processOpenAPIPayloads} from '../../inputs/openapi/generators/payloads'; import {z} from 'zod'; import {defaultCodegenTypescriptModelinaOptions} from './utils'; import {OpenAPIV2, OpenAPIV3, OpenAPIV3_1} from 'openapi-types'; -import {TS_COMMON_PRESET} from '@asyncapi/modelina'; +import {TS_COMMON_PRESET, TS_DESCRIPTION_PRESET} from '@asyncapi/modelina'; import { createValidationPreset, createUnionPreset, @@ -130,6 +130,7 @@ export async function generateTypescriptPayloadsCoreFromSchemas({ const modelinaGenerator = new TypeScriptFileGenerator({ ...defaultCodegenTypescriptModelinaOptions, presets: [ + TS_DESCRIPTION_PRESET, { preset: TS_COMMON_PRESET, options: { diff --git a/src/codegen/generators/typescript/utils.ts b/src/codegen/generators/typescript/utils.ts index 0e05b887..cec816a4 100644 --- a/src/codegen/generators/typescript/utils.ts +++ b/src/codegen/generators/typescript/utils.ts @@ -95,16 +95,20 @@ function realizeParameterForChannelWithType( } /** - * Render channel parameters for JSDoc + * Render channel parameters for JSDoc. + * Uses actual descriptions from the parameter properties when available. * - * @param {Object.} channelParameters to render + * @param channelParameters to render */ export function renderJSDocParameters( channelParameters: ConstrainedObjectModel ) { - return Object.keys(channelParameters.properties) - .map((paramName) => { - return `* @param ${paramName} parameter to use in topic`; + return Object.entries(channelParameters.properties) + .map(([paramName, prop]) => { + const description = + prop.property?.originalInput?.description || + 'parameter to use in topic'; + return ` * @param ${paramName} ${description}`; }) .join('\n'); } diff --git a/src/codegen/utils.ts b/src/codegen/utils.ts index da53cb6e..d0c9552f 100644 --- a/src/codegen/utils.ts +++ b/src/codegen/utils.ts @@ -167,6 +167,31 @@ export function findReplyId( return `${findOperationId(operation, reply.channel() ?? channel)}_reply`; } +/** + * Extract JSDoc metadata from an AsyncAPI operation. + * Returns description and deprecated flag for use in generated JSDoc comments. + */ +export function getOperationMetadata(operation: OperationInterface): { + description?: string; + deprecated?: boolean; +} { + // Get description - prefer description() over summary() + let description: string | undefined; + if (operation.hasDescription()) { + description = operation.description(); + } else if (operation.hasSummary()) { + description = operation.summary(); + } + + // Check if operation is deprecated - access from raw JSON since no typed method exists + // The parser doesn't expose a deprecated() method, but the raw JSON contains it + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawJson = (operation as any).json?.() ?? (operation as any)._json; + const deprecated = rawJson?.deprecated === true; + + return {description, deprecated}; +} + export function onlyUnique(array: any[]) { const onlyUnique = (value: any, index: number, array: any[]) => { return array.indexOf(value) === index; diff --git a/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap b/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap index 5eab2593..012bf010 100644 --- a/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap +++ b/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap @@ -1003,7 +1003,10 @@ export interface PostAddPetContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function postAddPet(context: PostAddPetContext): Promise> { +/** + * HTTP POST request to /pet + */ +export async function postAddPet(context: PostAddPetContext): Promise> { // Apply defaults const config = { path: '/pet', @@ -1123,7 +1126,10 @@ export interface PutUpdatePetContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function putUpdatePet(context: PutUpdatePetContext): Promise> { +/** + * HTTP PUT request to /pet + */ +export async function putUpdatePet(context: PutUpdatePetContext): Promise> { // Apply defaults const config = { path: '/pet', @@ -1243,7 +1249,10 @@ export interface GetFindPetsByStatusAndCategoryContext extends HttpClientContext requestHeaders?: { marshal: () => string }; } -async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { +/** + * Find pets by status and category with additional filtering options + */ +export async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { // Apply defaults const config = { path: '/pet/findByStatus/{status}/{categoryId}', @@ -1378,7 +1387,7 @@ import * as Amqp from 'amqplib'; /** * AMQP publish operation for exchange \`user/signedup/{my_parameter}/{enum_parameter}\` * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over @@ -1429,7 +1438,7 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); /** * AMQP publish operation for queue \`user/signedup/{my_parameter}/{enum_parameter}\` * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over @@ -1476,7 +1485,7 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); /** * AMQP subscribe operation for queue \`user/signedup/{my_parameter}/{enum_parameter}\` * - * @param {subscribeToUserSignedupQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToUserSignedupQueueCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation @@ -1530,7 +1539,7 @@ channel.consume(queue, (msg) => { /** * AMQP publish operation for exchange \`noparameters\` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over * @param options for the AMQP publish exchange operation @@ -1578,7 +1587,7 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); /** * AMQP publish operation for queue \`noparameters\` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over * @param options for the AMQP publish queue operation @@ -1622,7 +1631,7 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); /** * AMQP subscribe operation for queue \`noparameters\` * - * @param {subscribeToNoParameterQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToNoParameterQueueCallback} onDataCallback to call when messages are received * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation * @param skipMessageValidation turn off runtime validation of incoming messages @@ -1691,7 +1700,7 @@ import { NextFunction, Request, Response, Router } from 'express'; /** * Event source fetch for \`user/signedup/{my_parameter}/{enum_parameter}\` * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param parameters for listening * @param headers optional headers to include with the EventSource connection * @param options additionally used to handle the event source @@ -1766,6 +1775,12 @@ function listenForUserSignedup({ } +/** + * Register EventSource endpoint for \`user/signedup/{my_parameter}/{enum_parameter}\` + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerUserSignedup({ router, callback @@ -1797,7 +1812,7 @@ function registerUserSignedup({ /** * Event source fetch for \`noparameters\` * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param headers optional headers to include with the EventSource connection * @param options additionally used to handle the event source * @param skipMessageValidation turn off runtime validation of incoming messages @@ -1869,6 +1884,12 @@ function listenForNoParameter({ } +/** + * Register EventSource endpoint for \`noparameters\` + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerNoParameter({ router, callback @@ -2917,7 +2938,10 @@ export interface GetPingRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function getPingRequest(context: GetPingRequestContext = {}): Promise> { +/** + * HTTP GET request to /ping + */ +export async function getPingRequest(context: GetPingRequestContext = {}): Promise> { // Apply defaults const config = { path: '/ping', @@ -3052,7 +3076,7 @@ import * as Kafka from 'kafkajs'; /** * Kafka publish operation for \`user.signedup.{my_parameter}.{enum_parameter}\` * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param kafka the KafkaJS client to publish from @@ -3106,7 +3130,7 @@ function produceToUserSignedup({ * Callback for when receiving messages * * @callback consumeFromUserSignedupCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param parameters that was received in the topic * @param headers that was received with the message @@ -3116,7 +3140,7 @@ function produceToUserSignedup({ /** * Kafka subscription for \`user.signedup.{my_parameter}.{enum_parameter}\` * - * @param {consumeFromUserSignedupCallback} onDataCallback to call when messages are received + * @param {consumeFromUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription @@ -3177,7 +3201,7 @@ onDataCallback(undefined, callbackData, parameters, extractedHeaders, kafkaMessa /** * Kafka publish operation for \`noparameters\` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param kafka the KafkaJS client to publish from */ @@ -3228,7 +3252,7 @@ function produceToNoParameter({ * Callback for when receiving messages * * @callback consumeFromNoParameterCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param headers that was received with the message * @param kafkaMsg @@ -3237,7 +3261,7 @@ function produceToNoParameter({ /** * Kafka subscription for \`noparameters\` * - * @param {consumeFromNoParameterCallback} onDataCallback to call when messages are received + * @param {consumeFromNoParameterCallback} onDataCallback to call when messages are received * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription * @param skipMessageValidation turn off runtime validation of incoming messages @@ -3312,7 +3336,7 @@ import * as Mqtt from 'mqtt'; /** * MQTT publish operation for \`user/signedup/{my_parameter}/{enum_parameter}\` * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message as MQTT user properties * @param mqtt the MQTT client to publish from @@ -3433,7 +3457,7 @@ function subscribeToUserSignedup({ /** * MQTT publish operation for \`noparameters\` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message as MQTT user properties * @param mqtt the MQTT client to publish from */ @@ -3564,7 +3588,7 @@ import * as Nats from 'nats'; /** * NATS publish operation for \`user.signedup.{my_parameter}.{enum_parameter}\` * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param nc the NATS client to publish from @@ -3696,7 +3720,7 @@ onDataCallback(undefined, TestPayloadModelModule.unmarshal(receivedData), parame /** * JetStream pull subscription for \`user.signedup.{my_parameter}.{enum_parameter}\` * - * @param {jetStreamPullSubscribeToUserSignedupCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription @@ -3768,7 +3792,7 @@ onDataCallback(undefined, TestPayloadModelModule.unmarshal(receivedData), parame /** * JetStream push subscription for \`user.signedup.{my_parameter}.{enum_parameter}\` * - * @param {jetStreamPushSubscriptionFromUserSignedupCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription @@ -3829,7 +3853,7 @@ onDataCallback(undefined, TestPayloadModelModule.unmarshal(receivedData), parame /** * JetStream publish operation for \`user.signedup.{my_parameter}.{enum_parameter}\` * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param parameters for topic substitution * @param headers optional headers to include with the message * @param js the JetStream client to publish from @@ -3878,7 +3902,7 @@ await js.publish(parameters.getChannelWithParameters('user.signedup.{my_paramete /** * NATS publish operation for \`noparameters\` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param nc the NATS client to publish from * @param codec the serialization codec to use while transmitting the message @@ -4002,7 +4026,7 @@ onDataCallback(undefined, TestPayloadModelModule.unmarshal(receivedData), extrac /** * JetStream pull subscription for \`noparameters\` * - * @param {jetStreamPullSubscribeToNoParameterCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToNoParameterCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -4070,7 +4094,7 @@ onDataCallback(undefined, TestPayloadModelModule.unmarshal(receivedData), extrac /** * JetStream push subscription for \`noparameters\` * - * @param {jetStreamPushSubscriptionFromNoParameterCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromNoParameterCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -4128,7 +4152,7 @@ onDataCallback(undefined, TestPayloadModelModule.unmarshal(receivedData), extrac /** * JetStream publish operation for \`noparameters\` * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param headers optional headers to include with the message * @param js the JetStream client to publish from * @param codec the serialization codec to use while transmitting the message diff --git a/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap b/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap index 8132dbb3..e3fb64ff 100644 --- a/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap +++ b/test/codegen/generators/typescript/__snapshots__/payload.spec.ts.snap @@ -43,9 +43,15 @@ class SimpleObject2 { this._additionalProperties = input.additionalProperties; } + /** + * Name of the user + */ get displayName(): string | undefined { return this._displayName; } set displayName(displayName: string | undefined) { this._displayName = displayName; } + /** + * Email of the user + */ get email(): string | undefined { return this._email; } set email(email: string | undefined) { this._email = email; } @@ -159,9 +165,15 @@ class SimpleObject2 { this._additionalProperties = input.additionalProperties; } + /** + * Name of the user + */ get displayName(): string | undefined { return this._displayName; } set displayName(displayName: string | undefined) { this._displayName = displayName; } + /** + * Email of the user + */ get email(): string | undefined { return this._email; } set email(email: string | undefined) { this._email = email; } @@ -251,9 +263,15 @@ class SimpleObject { get type(): 'SimpleObject' | undefined { return this._type; } + /** + * Name of the user + */ get displayName(): string | undefined { return this._displayName; } set displayName(displayName: string | undefined) { this._displayName = displayName; } + /** + * Email of the user + */ get email(): string | undefined { return this._email; } set email(email: string | undefined) { this._email = email; } diff --git a/test/codegen/generators/typescript/jsdoc-utils.spec.ts b/test/codegen/generators/typescript/jsdoc-utils.spec.ts new file mode 100644 index 00000000..57dadfbf --- /dev/null +++ b/test/codegen/generators/typescript/jsdoc-utils.spec.ts @@ -0,0 +1,181 @@ +/** + * Unit tests for JSDoc generation utilities. + * Tests the utility functions used to generate JSDoc comments + * from API specification descriptions. + */ + +describe('JSDoc Utilities', () => { + describe('escapeJSDocDescription', () => { + // These tests will verify escapeJSDocDescription from channels/utils.ts + it('should escape closing comment markers', async () => { + const { escapeJSDocDescription } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const input = 'This has a */ in the middle'; + const result = escapeJSDocDescription(input); + // Should escape the closing comment + expect(result).not.toContain('*/'); + }); + + it('should format multi-line descriptions', async () => { + const { escapeJSDocDescription } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const input = 'First line\nSecond line\nThird line'; + const result = escapeJSDocDescription(input); + // Each new line should be formatted with JSDoc prefix + expect(result).toContain('\n * '); + }); + + it('should handle empty strings', async () => { + const { escapeJSDocDescription } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = escapeJSDocDescription(''); + expect(result).toBe(''); + }); + + it('should handle descriptions without special characters', async () => { + const { escapeJSDocDescription } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const input = 'A simple description'; + const result = escapeJSDocDescription(input); + expect(result).toBe(input); + }); + }); + + describe('renderChannelJSDoc', () => { + // These tests will verify renderChannelJSDoc from channels/utils.ts + it('should render JSDoc with description', async () => { + const { renderChannelJSDoc } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = renderChannelJSDoc({ + description: 'My operation description', + deprecated: false, + fallbackDescription: 'Fallback text', + parameters: [] + }); + expect(result).toContain('/**'); + expect(result).toContain('My operation description'); + expect(result).toContain('*/'); + }); + + it('should use fallback when no description provided', async () => { + const { renderChannelJSDoc } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = renderChannelJSDoc({ + description: undefined, + deprecated: false, + fallbackDescription: 'Fallback text', + parameters: [] + }); + expect(result).toContain('Fallback text'); + }); + + it('should include @deprecated tag when deprecated is true', async () => { + const { renderChannelJSDoc } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = renderChannelJSDoc({ + description: 'Some description', + deprecated: true, + fallbackDescription: 'Fallback', + parameters: [] + }); + expect(result).toContain('@deprecated'); + }); + + it('should not include @deprecated when deprecated is false', async () => { + const { renderChannelJSDoc } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = renderChannelJSDoc({ + description: 'Some description', + deprecated: false, + fallbackDescription: 'Fallback', + parameters: [] + }); + expect(result).not.toContain('@deprecated'); + }); + + it('should include parameter JSDoc', async () => { + const { renderChannelJSDoc } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = renderChannelJSDoc({ + description: 'Some description', + deprecated: false, + fallbackDescription: 'Fallback', + parameters: [ + { name: 'userId', jsDoc: ' * @param userId The unique user identifier' }, + { name: 'orderId', jsDoc: ' * @param orderId The order identifier' } + ] + }); + expect(result).toContain('@param userId'); + expect(result).toContain('The unique user identifier'); + expect(result).toContain('@param orderId'); + }); + + it('should produce valid JSDoc format', async () => { + const { renderChannelJSDoc } = await import('../../../../src/codegen/generators/typescript/channels/utils'); + const result = renderChannelJSDoc({ + description: 'Description here', + deprecated: true, + fallbackDescription: 'Fallback', + parameters: [{ name: 'param1', jsDoc: ' * @param param1 description' }] + }); + // Should be valid JSDoc + expect(result.startsWith('/**')).toBe(true); + expect(result.endsWith('*/')).toBe(true); + // Each line except first and last should start with ' *' + const lines = result.split('\n'); + expect(lines[0]).toBe('/**'); + expect(lines[lines.length - 1].trim()).toBe('*/'); + }); + }); + + describe('renderJSDocParameters (updated version)', () => { + // These tests will verify the updated renderJSDocParameters from utils.ts + it('should use actual descriptions from properties', async () => { + const { renderJSDocParameters } = await import('../../../../src/codegen/generators/typescript/utils'); + // Create a mock ConstrainedObjectModel with property descriptions + const mockParameters = { + properties: { + userId: { + propertyName: 'userId', + property: { + originalInput: { + description: 'The unique user identifier' + } + } + }, + orderId: { + propertyName: 'orderId', + property: { + originalInput: { + description: 'The order identifier' + } + } + } + } + }; + const result = renderJSDocParameters(mockParameters as any); + expect(result).toContain('@param userId'); + expect(result).toContain('The unique user identifier'); + expect(result).toContain('@param orderId'); + expect(result).toContain('The order identifier'); + }); + + it('should use fallback text when no description available', async () => { + const { renderJSDocParameters } = await import('../../../../src/codegen/generators/typescript/utils'); + const mockParameters = { + properties: { + paramName: { + propertyName: 'paramName', + property: { + originalInput: {} + } + } + } + }; + const result = renderJSDocParameters(mockParameters as any); + // Should have fallback text + expect(result).toContain('@param paramName'); + expect(result).toContain('parameter to use in topic'); + }); + + it('should handle empty parameters', async () => { + const { renderJSDocParameters } = await import('../../../../src/codegen/generators/typescript/utils'); + const mockParameters = { + properties: {} + }; + const result = renderJSDocParameters(mockParameters as any); + expect(result).toBe(''); + }); + }); +}); diff --git a/test/runtime/asyncapi-regular.json b/test/runtime/asyncapi-regular.json index 66f23506..16b3f048 100644 --- a/test/runtime/asyncapi-regular.json +++ b/test/runtime/asyncapi-regular.json @@ -53,11 +53,21 @@ "$ref": "#/components/messages/UnionMessage" } } + }, + "legacyNotification": { + "address": "legacy/notification", + "messages": { + "LegacyNotification": { + "$ref": "#/components/messages/LegacyNotification" + } + } } }, "operations": { "sendUserSignedup": { "action": "send", + "summary": "Publish user signup event", + "description": "Publishes a user signup event to notify other services that a new user has registered in the system.", "channel": { "$ref": "#/channels/userSignedup" }, @@ -69,6 +79,8 @@ }, "receiveUserSignedup": { "action": "receive", + "summary": "Subscribe to user signup events", + "description": "Receives user signup events to process new user registrations.", "channel": { "$ref": "#/channels/userSignedup" }, @@ -143,6 +155,34 @@ "$ref": "#/channels/unionPayload/messages/UnionMessage" } ] + }, + "sendLegacyNotification": { + "action": "send", + "summary": "Send legacy notification", + "description": "Sends a notification using the legacy notification system. Use the new notification service instead.", + "deprecated": true, + "channel": { + "$ref": "#/channels/legacyNotification" + }, + "messages": [ + { + "$ref": "#/channels/legacyNotification/messages/LegacyNotification" + } + ] + }, + "receiveLegacyNotification": { + "action": "receive", + "summary": "Receive legacy notifications", + "description": "Receives notifications from the legacy notification system. Use the new notification service instead.", + "deprecated": true, + "channel": { + "$ref": "#/channels/legacyNotification" + }, + "messages": [ + { + "$ref": "#/channels/legacyNotification/messages/LegacyNotification" + } + ] } }, "components": { @@ -169,11 +209,17 @@ "payload": { "$ref": "#/components/schemas/UnionPayload" } + }, + "LegacyNotification": { + "payload": { + "$ref": "#/components/schemas/LegacyNotificationPayload" + } } }, "schemas": { "UserSignedUpPayload": { "type": "object", + "description": "Payload for user signup events containing user registration details", "properties": { "display_name": { "type": "string", @@ -224,6 +270,22 @@ } ], "description": "A union type payload" + }, + "LegacyNotificationPayload": { + "type": "object", + "description": "Legacy notification payload - use NewNotificationPayload instead", + "deprecated": true, + "properties": { + "message": { + "type": "string", + "description": "The notification message" + }, + "level": { + "type": "string", + "enum": ["info", "warning", "error"], + "description": "Notification severity level" + } + } } } } diff --git a/test/runtime/typescript/src/Types.ts b/test/runtime/typescript/src/Types.ts index 58ffd8e1..4dc239bf 100644 --- a/test/runtime/typescript/src/Types.ts +++ b/test/runtime/typescript/src/Types.ts @@ -1,5 +1,5 @@ -export type Topics = 'user/signedup/{my_parameter}/{enum_parameter}' | 'noparameters' | 'string/payload' | 'array/payload' | 'union/payload'; -export type TopicIds = 'userSignedup' | 'noParameter' | 'stringPayload' | 'arrayPayload' | 'unionPayload'; +export type Topics = 'user/signedup/{my_parameter}/{enum_parameter}' | 'noparameters' | 'string/payload' | 'array/payload' | 'union/payload' | 'legacy/notification'; +export type TopicIds = 'userSignedup' | 'noParameter' | 'stringPayload' | 'arrayPayload' | 'unionPayload' | 'legacyNotification'; export function ToTopicIds(topic: Topics): TopicIds { switch (topic) { case 'user/signedup/{my_parameter}/{enum_parameter}': @@ -12,6 +12,8 @@ export function ToTopicIds(topic: Topics): TopicIds { return 'arrayPayload'; case 'union/payload': return 'unionPayload'; + case 'legacy/notification': + return 'legacyNotification'; default: throw new Error('Unknown topic: ' + topic); } @@ -28,6 +30,8 @@ export function ToTopics(topicId: TopicIds): Topics { return 'array/payload'; case 'unionPayload': return 'union/payload'; + case 'legacyNotification': + return 'legacy/notification'; default: throw new Error('Unknown topic ID: ' + topicId); } @@ -37,5 +41,6 @@ export const TopicsMap: Record = { 'noParameter': 'noparameters', 'stringPayload': 'string/payload', 'arrayPayload': 'array/payload', - 'unionPayload': 'union/payload' + 'unionPayload': 'union/payload', + 'legacyNotification': 'legacy/notification' }; diff --git a/test/runtime/typescript/src/channels/amqp.ts b/test/runtime/typescript/src/channels/amqp.ts index 85ce7bdd..4b497b5b 100644 --- a/test/runtime/typescript/src/channels/amqp.ts +++ b/test/runtime/typescript/src/channels/amqp.ts @@ -2,15 +2,17 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Amqp from 'amqplib'; /** - * AMQP publish operation for exchange `user/signedup/{my_parameter}/{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over @@ -59,9 +61,9 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); } /** - * AMQP publish operation for queue `user/signedup/{my_parameter}/{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over @@ -106,9 +108,9 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); } /** - * AMQP subscribe operation for queue `user/signedup/{my_parameter}/{enum_parameter}` + * Receives user signup events to process new user registrations. * - * @param {subscribeToReceiveUserSignedupQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToReceiveUserSignedupQueueCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation @@ -167,7 +169,7 @@ channel.consume(queue, (msg) => { /** * AMQP publish operation for exchange `noparameters` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over * @param options for the AMQP publish exchange operation @@ -215,7 +217,7 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); /** * AMQP publish operation for queue `noparameters` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param amqp the AMQP connection to send over * @param options for the AMQP publish queue operation @@ -259,7 +261,7 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); /** * AMQP subscribe operation for queue `noparameters` * - * @param {subscribeToNoParameterQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToNoParameterQueueCallback} onDataCallback to call when messages are received * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation * @param skipMessageValidation turn off runtime validation of incoming messages @@ -315,7 +317,7 @@ channel.consume(queue, (msg) => { /** * AMQP publish operation for exchange `string/payload` * - * @param message to publish + * @param message to publish * @param amqp the AMQP connection to send over * @param options for the AMQP publish exchange operation */ @@ -349,7 +351,7 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); /** * AMQP publish operation for queue `string/payload` * - * @param message to publish + * @param message to publish * @param amqp the AMQP connection to send over * @param options for the AMQP publish queue operation */ @@ -379,7 +381,7 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); /** * AMQP subscribe operation for queue `string/payload` * - * @param {subscribeToReceiveStringPayloadQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToReceiveStringPayloadQueueCallback} onDataCallback to call when messages are received * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation * @param skipMessageValidation turn off runtime validation of incoming messages @@ -425,7 +427,7 @@ channel.consume(queue, (msg) => { /** * AMQP publish operation for exchange `array/payload` * - * @param message to publish + * @param message to publish * @param amqp the AMQP connection to send over * @param options for the AMQP publish exchange operation */ @@ -459,7 +461,7 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); /** * AMQP publish operation for queue `array/payload` * - * @param message to publish + * @param message to publish * @param amqp the AMQP connection to send over * @param options for the AMQP publish queue operation */ @@ -489,7 +491,7 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); /** * AMQP subscribe operation for queue `array/payload` * - * @param {subscribeToReceiveArrayPayloadQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToReceiveArrayPayloadQueueCallback} onDataCallback to call when messages are received * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation * @param skipMessageValidation turn off runtime validation of incoming messages @@ -535,7 +537,7 @@ channel.consume(queue, (msg) => { /** * AMQP publish operation for exchange `union/payload` * - * @param message to publish + * @param message to publish * @param amqp the AMQP connection to send over * @param options for the AMQP publish exchange operation */ @@ -569,7 +571,7 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); /** * AMQP publish operation for queue `union/payload` * - * @param message to publish + * @param message to publish * @param amqp the AMQP connection to send over * @param options for the AMQP publish queue operation */ @@ -599,7 +601,7 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); /** * AMQP subscribe operation for queue `union/payload` * - * @param {subscribeToReceiveUnionPayloadQueueCallback} onDataCallback to call when messages are received + * @param {subscribeToReceiveUnionPayloadQueueCallback} onDataCallback to call when messages are received * @param amqp the AMQP connection to receive from * @param options for the AMQP subscribe queue operation * @param skipMessageValidation turn off runtime validation of incoming messages @@ -642,4 +644,120 @@ channel.consume(queue, (msg) => { }); } -export { publishToSendUserSignedupExchange, publishToSendUserSignedupQueue, subscribeToReceiveUserSignedupQueue, publishToNoParameterExchange, publishToNoParameterQueue, subscribeToNoParameterQueue, publishToSendStringPayloadExchange, publishToSendStringPayloadQueue, subscribeToReceiveStringPayloadQueue, publishToSendArrayPayloadExchange, publishToSendArrayPayloadQueue, subscribeToReceiveArrayPayloadQueue, publishToSendUnionPayloadExchange, publishToSendUnionPayloadQueue, subscribeToReceiveUnionPayloadQueue }; +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish + * @param amqp the AMQP connection to send over + * @param options for the AMQP publish exchange operation + */ +function publishToSendLegacyNotificationExchange({ + message, + amqp, + options +}: { + message: LegacyNotification, + amqp: Amqp.Connection, + options?: {exchange: string | undefined} & Amqp.Options.Publish +}): Promise { + return new Promise(async (resolve, reject) => { + const exchange = options?.exchange ?? 'undefined'; + if(!exchange) { + return reject('No exchange value found, please provide one') + } + try { + let dataToSend: any = message.marshal(); +const channel = await amqp.createChannel(); +const routingKey = 'legacy/notification'; +let publishOptions = { ...options }; +channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish + * @param amqp the AMQP connection to send over + * @param options for the AMQP publish queue operation + */ +function publishToSendLegacyNotificationQueue({ + message, + amqp, + options +}: { + message: LegacyNotification, + amqp: Amqp.Connection, + options?: Amqp.Options.Publish +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = message.marshal(); +const channel = await amqp.createChannel(); +const queue = 'legacy/notification'; +let publishOptions = { ...options }; +channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param {subscribeToReceiveLegacyNotificationQueueCallback} onDataCallback to call when messages are received + * @param amqp the AMQP connection to receive from + * @param options for the AMQP subscribe queue operation + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function subscribeToReceiveLegacyNotificationQueue({ + onDataCallback, + amqp, + options, + skipMessageValidation = false +}: { + onDataCallback: (params: {err?: Error, msg?: LegacyNotification, amqpMsg?: Amqp.ConsumeMessage}) => void, + amqp: Amqp.Connection, + options?: Amqp.Options.Consume, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const channel = await amqp.createChannel(); +const queue = 'legacy/notification'; +await channel.assertQueue(queue, { durable: true }); +const validator = LegacyNotification.createValidator(); +channel.consume(queue, (msg) => { + if (msg !== null) { + const receivedData = msg.content.toString() + + if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback({err: new Error(`Invalid message payload received ${JSON.stringify({cause: errors})}`), msg: undefined, amqpMsg: msg}); return; + } + } + const message = LegacyNotification.unmarshal(receivedData); + onDataCallback({err: undefined, msg: message, amqpMsg: msg}); + } +}, options); + resolve(channel); + } catch (e: any) { + reject(e); + } + }); +} + +export { publishToSendUserSignedupExchange, publishToSendUserSignedupQueue, subscribeToReceiveUserSignedupQueue, publishToNoParameterExchange, publishToNoParameterQueue, subscribeToNoParameterQueue, publishToSendStringPayloadExchange, publishToSendStringPayloadQueue, subscribeToReceiveStringPayloadQueue, publishToSendArrayPayloadExchange, publishToSendArrayPayloadQueue, subscribeToReceiveArrayPayloadQueue, publishToSendUnionPayloadExchange, publishToSendUnionPayloadQueue, subscribeToReceiveUnionPayloadQueue, publishToSendLegacyNotificationExchange, publishToSendLegacyNotificationQueue, subscribeToReceiveLegacyNotificationQueue }; diff --git a/test/runtime/typescript/src/channels/event_source.ts b/test/runtime/typescript/src/channels/event_source.ts index 088e6ea3..31a7e879 100644 --- a/test/runtime/typescript/src/channels/event_source.ts +++ b/test/runtime/typescript/src/channels/event_source.ts @@ -2,12 +2,20 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import { NextFunction, Request, Response, Router } from 'express'; import { fetchEventSource, EventStreamContentType, EventSourceMessage } from '@ai-zen/node-fetch-event-source'; +/** + * Publishes a user signup event to notify other services that a new user has registered in the system. + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerSendUserSignedup({ router, callback @@ -37,9 +45,9 @@ function registerSendUserSignedup({ /** - * Event source fetch for `user/signedup/{my_parameter}/{enum_parameter}` + * Receives user signup events to process new user registrations. * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param parameters for listening * @param headers optional headers to include with the EventSource connection * @param options additionally used to handle the event source @@ -122,7 +130,7 @@ function listenForReceiveUserSignedup({ /** * Event source fetch for `noparameters` * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param headers optional headers to include with the EventSource connection * @param options additionally used to handle the event source * @param skipMessageValidation turn off runtime validation of incoming messages @@ -199,6 +207,12 @@ function listenForNoParameter({ } +/** + * Register EventSource endpoint for `noparameters` + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerNoParameter({ router, callback @@ -227,6 +241,12 @@ function registerNoParameter({ } +/** + * Register EventSource endpoint for `string/payload` + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerSendStringPayload({ router, callback @@ -258,7 +278,7 @@ function registerSendStringPayload({ /** * Event source fetch for `string/payload` * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param options additionally used to handle the event source * @param skipMessageValidation turn off runtime validation of incoming messages * @returns A cleanup function to abort the connection @@ -323,6 +343,12 @@ function listenForReceiveStringPayload({ } +/** + * Register EventSource endpoint for `array/payload` + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerSendArrayPayload({ router, callback @@ -354,7 +380,7 @@ function registerSendArrayPayload({ /** * Event source fetch for `array/payload` * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param options additionally used to handle the event source * @param skipMessageValidation turn off runtime validation of incoming messages * @returns A cleanup function to abort the connection @@ -419,6 +445,12 @@ function listenForReceiveArrayPayload({ } +/** + * Register EventSource endpoint for `union/payload` + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ function registerSendUnionPayload({ router, callback @@ -450,7 +482,7 @@ function registerSendUnionPayload({ /** * Event source fetch for `union/payload` * - * @param callback to call when receiving events + * @param callback to call when receiving events * @param options additionally used to handle the event source * @param skipMessageValidation turn off runtime validation of incoming messages * @returns A cleanup function to abort the connection @@ -515,4 +547,110 @@ function listenForReceiveUnionPayload({ } -export { registerSendUserSignedup, listenForReceiveUserSignedup, listenForNoParameter, registerNoParameter, registerSendStringPayload, listenForReceiveStringPayload, registerSendArrayPayload, listenForReceiveArrayPayload, registerSendUnionPayload, listenForReceiveUnionPayload }; +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param router to attach the event source to + * @param callback to call when receiving events + */ +function registerSendLegacyNotification({ + router, + callback +}: { + router: Router, + callback: ((req: Request, res: Response, next: NextFunction, sendEvent: (message: LegacyNotification) => void) => void) | ((req: Request, res: Response, next: NextFunction, sendEvent: (message: LegacyNotification) => void) => Promise) +}): void { + const event = '/legacy/notification'; + router.get(event, async (req, res, next) => { + + res.writeHead(200, { + 'Cache-Control': 'no-cache, no-transform', + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + 'Access-Control-Allow-Origin': '*', + }) + const sendEventCallback = (message: LegacyNotification) => { + if (res.closed) { + return + } + res.write(`event: ${event}\n`) + res.write(`data: ${message.marshal()}\n\n`) + } + await callback(req, res, next, sendEventCallback) + }) +} + + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param callback to call when receiving events + * @param options additionally used to handle the event source + * @param skipMessageValidation turn off runtime validation of incoming messages + * @returns A cleanup function to abort the connection + */ +function listenForReceiveLegacyNotification({ + callback, + options, + skipMessageValidation = false +}: { + callback: (params: {error?: Error, messageEvent?: LegacyNotification}) => void, + options: {authorization?: string, onClose?: (err?: string) => void, baseUrl: string, headers?: Record}, + skipMessageValidation?: boolean +}): (() => void) { + const controller = new AbortController(); + let eventsUrl: string = 'legacy/notification'; + const url = `${options.baseUrl}/${eventsUrl}` + const requestHeaders: Record = { + ...options.headers ?? {}, + Accept: 'text/event-stream' + } + if(options.authorization) { + requestHeaders['authorization'] = `Bearer ${options?.authorization}`; + } + + const validator = LegacyNotification.createValidator(); + fetchEventSource(`${url}`, { + method: 'GET', + headers: requestHeaders, + signal: controller.signal, + onmessage: (ev: EventSourceMessage) => { + const receivedData = ev.data; + if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + return callback({error: new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), messageEvent: undefined}); + } + } + const callbackData = LegacyNotification.unmarshal(receivedData); + callback({error: undefined, messageEvent: callbackData}); + }, + onerror: (err) => { + options.onClose?.(err); + }, + onclose: () => { + options.onClose?.(); + }, + async onopen(response: { ok: any; headers: any; status: number }) { + if (response.ok && response.headers.get('content-type') === EventStreamContentType) { + return // everything's good + } else if (response.status >= 400 && response.status < 500 && response.status !== 429) { + // client-side errors are usually non-retriable: + callback({error: new Error('Client side error, could not open event connection'), messageEvent: undefined}) + } else { + callback({error: new Error('Unknown error, could not open event connection'), messageEvent: undefined}); + } + }, + }); + + return () => { + controller.abort(); + }; +} + + +export { registerSendUserSignedup, listenForReceiveUserSignedup, listenForNoParameter, registerNoParameter, registerSendStringPayload, listenForReceiveStringPayload, registerSendArrayPayload, listenForReceiveArrayPayload, registerSendUnionPayload, listenForReceiveUnionPayload, registerSendLegacyNotification, listenForReceiveLegacyNotification }; diff --git a/test/runtime/typescript/src/channels/kafka.ts b/test/runtime/typescript/src/channels/kafka.ts index bad1d185..d1884490 100644 --- a/test/runtime/typescript/src/channels/kafka.ts +++ b/test/runtime/typescript/src/channels/kafka.ts @@ -2,15 +2,17 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Kafka from 'kafkajs'; /** - * Kafka publish operation for `user.signedup.{my_parameter}.{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param kafka the KafkaJS client to publish from @@ -64,7 +66,7 @@ function produceToSendUserSignedup({ * Callback for when receiving messages * * @callback consumeFromReceiveUserSignedupCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param parameters that was received in the topic * @param headers that was received with the message @@ -72,9 +74,9 @@ function produceToSendUserSignedup({ */ /** - * Kafka subscription for `user.signedup.{my_parameter}.{enum_parameter}` + * Receives user signup events to process new user registrations. * - * @param {consumeFromReceiveUserSignedupCallback} onDataCallback to call when messages are received + * @param {consumeFromReceiveUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription @@ -140,7 +142,7 @@ onDataCallback(undefined, callbackData, parameters, extractedHeaders, kafkaMessa /** * Kafka publish operation for `noparameters` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param kafka the KafkaJS client to publish from */ @@ -191,7 +193,7 @@ function produceToNoParameter({ * Callback for when receiving messages * * @callback consumeFromNoParameterCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param headers that was received with the message * @param kafkaMsg @@ -200,7 +202,7 @@ function produceToNoParameter({ /** * Kafka subscription for `noparameters` * - * @param {consumeFromNoParameterCallback} onDataCallback to call when messages are received + * @param {consumeFromNoParameterCallback} onDataCallback to call when messages are received * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription * @param skipMessageValidation turn off runtime validation of incoming messages @@ -263,7 +265,7 @@ onDataCallback(undefined, callbackData, extractedHeaders, kafkaMessage); /** * Kafka publish operation for `string.payload` * - * @param message to publish + * @param message to publish * @param kafka the KafkaJS client to publish from */ function produceToSendStringPayload({ @@ -299,7 +301,7 @@ function produceToSendStringPayload({ * Callback for when receiving messages * * @callback consumeFromReceiveStringPayloadCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param kafkaMsg */ @@ -307,7 +309,7 @@ function produceToSendStringPayload({ /** * Kafka subscription for `string.payload` * - * @param {consumeFromReceiveStringPayloadCallback} onDataCallback to call when messages are received + * @param {consumeFromReceiveStringPayloadCallback} onDataCallback to call when messages are received * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription * @param skipMessageValidation turn off runtime validation of incoming messages @@ -358,7 +360,7 @@ onDataCallback(undefined, callbackData, kafkaMessage); /** * Kafka publish operation for `array.payload` * - * @param message to publish + * @param message to publish * @param kafka the KafkaJS client to publish from */ function produceToSendArrayPayload({ @@ -394,7 +396,7 @@ function produceToSendArrayPayload({ * Callback for when receiving messages * * @callback consumeFromReceiveArrayPayloadCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param kafkaMsg */ @@ -402,7 +404,7 @@ function produceToSendArrayPayload({ /** * Kafka subscription for `array.payload` * - * @param {consumeFromReceiveArrayPayloadCallback} onDataCallback to call when messages are received + * @param {consumeFromReceiveArrayPayloadCallback} onDataCallback to call when messages are received * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription * @param skipMessageValidation turn off runtime validation of incoming messages @@ -453,7 +455,7 @@ onDataCallback(undefined, callbackData, kafkaMessage); /** * Kafka publish operation for `union.payload` * - * @param message to publish + * @param message to publish * @param kafka the KafkaJS client to publish from */ function produceToSendUnionPayload({ @@ -489,7 +491,7 @@ function produceToSendUnionPayload({ * Callback for when receiving messages * * @callback consumeFromReceiveUnionPayloadCallback - * @param err if any error occurred this will be sat + * @param err if any error occurred this will be sat * @param msg that was received * @param kafkaMsg */ @@ -497,7 +499,7 @@ function produceToSendUnionPayload({ /** * Kafka subscription for `union.payload` * - * @param {consumeFromReceiveUnionPayloadCallback} onDataCallback to call when messages are received + * @param {consumeFromReceiveUnionPayloadCallback} onDataCallback to call when messages are received * @param kafka the KafkaJS client to subscribe through * @param options when setting up the subscription * @param skipMessageValidation turn off runtime validation of incoming messages @@ -545,4 +547,103 @@ onDataCallback(undefined, callbackData, kafkaMessage); }); } -export { produceToSendUserSignedup, consumeFromReceiveUserSignedup, produceToNoParameter, consumeFromNoParameter, produceToSendStringPayload, consumeFromReceiveStringPayload, produceToSendArrayPayload, consumeFromReceiveArrayPayload, produceToSendUnionPayload, consumeFromReceiveUnionPayload }; +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish + * @param kafka the KafkaJS client to publish from + */ +function produceToSendLegacyNotification({ + message, + kafka +}: { + message: LegacyNotification, + kafka: Kafka.Kafka +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = message.marshal(); + const producer = kafka.producer(); + await producer.connect(); + + + await producer.send({ + topic: 'legacy.notification', + messages: [ + { + value: dataToSend + }, + ], + }); + resolve(producer); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback consumeFromReceiveLegacyNotificationCallback + * @param err if any error occurred this will be sat + * @param msg that was received + * @param kafkaMsg + */ + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param {consumeFromReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param kafka the KafkaJS client to subscribe through + * @param options when setting up the subscription + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function consumeFromReceiveLegacyNotification({ + onDataCallback, + kafka, + options = {fromBeginning: true, groupId: ''}, + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: LegacyNotification, kafkaMsg?: Kafka.EachMessagePayload) => void, + kafka: Kafka.Kafka, + options: {fromBeginning: boolean, groupId: string}, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + if(!options.groupId) { + return reject('No group ID provided'); + } + const consumer = kafka.consumer({ groupId: options.groupId }); + + const validator = LegacyNotification.createValidator(); + await consumer.connect(); + await consumer.subscribe({ topic: 'legacy.notification', fromBeginning: options.fromBeginning }); + await consumer.run({ + eachMessage: async (kafkaMessage: Kafka.EachMessagePayload) => { + const { topic, message } = kafkaMessage; + const receivedData = message.value?.toString()!; + + if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + return onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, kafkaMessage); + } + } +const callbackData = LegacyNotification.unmarshal(receivedData); +onDataCallback(undefined, callbackData, kafkaMessage); + } + }); + resolve(consumer); + } catch (e: any) { + reject(e); + } + }); +} + +export { produceToSendUserSignedup, consumeFromReceiveUserSignedup, produceToNoParameter, consumeFromNoParameter, produceToSendStringPayload, consumeFromReceiveStringPayload, produceToSendArrayPayload, consumeFromReceiveArrayPayload, produceToSendUnionPayload, consumeFromReceiveUnionPayload, produceToSendLegacyNotification, consumeFromReceiveLegacyNotification }; diff --git a/test/runtime/typescript/src/channels/mqtt.ts b/test/runtime/typescript/src/channels/mqtt.ts index d2eaa442..31def5c7 100644 --- a/test/runtime/typescript/src/channels/mqtt.ts +++ b/test/runtime/typescript/src/channels/mqtt.ts @@ -2,15 +2,17 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Mqtt from 'mqtt'; /** - * MQTT publish operation for `user/signedup/{my_parameter}/{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message as MQTT user properties * @param mqtt the MQTT client to publish from @@ -62,7 +64,7 @@ function publishToSendUserSignedup({ */ /** - * MQTT subscription for `user/signedup/{my_parameter}/{enum_parameter}` + * Receives user signup events to process new user registrations. * * @param onDataCallback to call when messages are received * @param parameters for topic substitution @@ -136,7 +138,7 @@ function subscribeToReceiveUserSignedup({ /** * MQTT publish operation for `noparameters` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message as MQTT user properties * @param mqtt the MQTT client to publish from */ @@ -255,7 +257,7 @@ function subscribeToNoParameter({ /** * MQTT publish operation for `string/payload` * - * @param message to publish + * @param message to publish * @param mqtt the MQTT client to publish from */ function publishToSendStringPayload({ @@ -348,7 +350,7 @@ function subscribeToReceiveStringPayload({ /** * MQTT publish operation for `array/payload` * - * @param message to publish + * @param message to publish * @param mqtt the MQTT client to publish from */ function publishToSendArrayPayload({ @@ -441,7 +443,7 @@ function subscribeToReceiveArrayPayload({ /** * MQTT publish operation for `union/payload` * - * @param message to publish + * @param message to publish * @param mqtt the MQTT client to publish from */ function publishToSendUnionPayload({ @@ -531,4 +533,101 @@ function subscribeToReceiveUnionPayload({ }); } -export { publishToSendUserSignedup, subscribeToReceiveUserSignedup, publishToNoParameter, subscribeToNoParameter, publishToSendStringPayload, subscribeToReceiveStringPayload, publishToSendArrayPayload, subscribeToReceiveArrayPayload, publishToSendUnionPayload, subscribeToReceiveUnionPayload }; +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish + * @param mqtt the MQTT client to publish from + */ +function publishToSendLegacyNotification({ + message, + mqtt +}: { + message: LegacyNotification, + mqtt: Mqtt.MqttClient +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = message.marshal(); + let publishOptions: Mqtt.IClientPublishOptions = {}; + mqtt.publish('legacy/notification', dataToSend, publishOptions); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback subscribeToReceiveLegacyNotificationCallback + * @param err if any error occurred this will be set + * @param msg that was received + * @param mqttMsg the raw MQTT message packet + */ + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param onDataCallback to call when messages are received + * @param mqtt the MQTT client to subscribe with + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function subscribeToReceiveLegacyNotification({ + onDataCallback, + mqtt, + skipMessageValidation = false +}: { + onDataCallback: (params: {err?: Error, msg?: LegacyNotification, mqttMsg?: Mqtt.IPublishPacket}) => void, + mqtt: Mqtt.MqttClient, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const validator = LegacyNotification.createValidator(); + + // Set up message listener + const messageHandler = (topic: string, message: Buffer, packet: Mqtt.IPublishPacket) => { + + // Check if the received topic matches this subscription's pattern + const topicPattern = /^legacy\/notification$/; + if (!topicPattern.test(topic)) { + return; // Ignore messages not matching this subscription's topic pattern + } + + const receivedData = message.toString(); + + + + try { + const parsedMessage = LegacyNotification.unmarshal(receivedData); + if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback({err: new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), msg: undefined, mqttMsg: packet}); return; + } + } + onDataCallback({err: undefined, msg: parsedMessage, mqttMsg: packet}); + } catch (err: any) { + onDataCallback({err: new Error(`Failed to parse message: ${err.message}`), msg: undefined, mqttMsg: packet}); + } + }; + + mqtt.on('message', messageHandler); + + // Subscribe to the topic + await mqtt.subscribeAsync('legacy/notification'); + + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +export { publishToSendUserSignedup, subscribeToReceiveUserSignedup, publishToNoParameter, subscribeToNoParameter, publishToSendStringPayload, subscribeToReceiveStringPayload, publishToSendArrayPayload, subscribeToReceiveArrayPayload, publishToSendUnionPayload, subscribeToReceiveUnionPayload, publishToSendLegacyNotification, subscribeToReceiveLegacyNotification }; diff --git a/test/runtime/typescript/src/channels/nats.ts b/test/runtime/typescript/src/channels/nats.ts index 3b3f3b7d..3bd66868 100644 --- a/test/runtime/typescript/src/channels/nats.ts +++ b/test/runtime/typescript/src/channels/nats.ts @@ -2,15 +2,17 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as Nats from 'nats'; /** - * NATS publish operation for `user.signedup.{my_parameter}.{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * - * @param message to publish + * @param message to publish * @param parameters for topic substitution * @param headers optional headers to include with the message * @param nc the NATS client to publish from @@ -57,9 +59,9 @@ nc.publish(parameters.getChannelWithParameters('user.signedup.{my_parameter}.{en } /** - * JetStream publish operation for `user.signedup.{my_parameter}.{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param parameters for topic substitution * @param headers optional headers to include with the message * @param js the JetStream client to publish from @@ -117,7 +119,7 @@ await js.publish(parameters.getChannelWithParameters('user.signedup.{my_paramete */ /** - * Core subscription for `user.signedup.{my_parameter}.{enum_parameter}` + * Receives user signup events to process new user registrations. * * @param {subscribeToReceiveUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution @@ -195,9 +197,9 @@ onDataCallback(undefined, UserSignedUp.unmarshal(receivedData), parameters, extr */ /** - * JetStream pull subscription for `user.signedup.{my_parameter}.{enum_parameter}` + * Receives user signup events to process new user registrations. * - * @param {jetStreamPullSubscribeToReceiveUserSignedupCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToReceiveUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription @@ -273,9 +275,9 @@ onDataCallback(undefined, UserSignedUp.unmarshal(receivedData), parameters, extr */ /** - * JetStream push subscription for `user.signedup.{my_parameter}.{enum_parameter}` + * Receives user signup events to process new user registrations. * - * @param {jetStreamPushSubscriptionFromReceiveUserSignedupCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromReceiveUserSignedupCallback} onDataCallback to call when messages are received * @param parameters for topic substitution * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription @@ -342,7 +344,7 @@ onDataCallback(undefined, UserSignedUp.unmarshal(receivedData), parameters, extr /** * NATS publish operation for `noparameters` * - * @param message to publish + * @param message to publish * @param headers optional headers to include with the message * @param nc the NATS client to publish from * @param codec the serialization codec to use while transmitting the message @@ -472,7 +474,7 @@ onDataCallback(undefined, UserSignedUp.unmarshal(receivedData), extractedHeaders /** * JetStream pull subscription for `noparameters` * - * @param {jetStreamPullSubscribeToNoParameterCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToNoParameterCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -546,7 +548,7 @@ onDataCallback(undefined, UserSignedUp.unmarshal(receivedData), extractedHeaders /** * JetStream push subscription for `noparameters` * - * @param {jetStreamPushSubscriptionFromNoParameterCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromNoParameterCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -610,7 +612,7 @@ onDataCallback(undefined, UserSignedUp.unmarshal(receivedData), extractedHeaders /** * JetStream publish operation for `noparameters` * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param headers optional headers to include with the message * @param js the JetStream client to publish from * @param codec the serialization codec to use while transmitting the message @@ -656,7 +658,7 @@ await js.publish('noparameters', dataToSend, options); /** * NATS publish operation for `string.payload` * - * @param message to publish + * @param message to publish * @param nc the NATS client to publish from * @param codec the serialization codec to use while transmitting the message * @param options to use while publishing the message @@ -688,7 +690,7 @@ nc.publish('string.payload', dataToSend, options); /** * JetStream publish operation for `string.payload` * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param js the JetStream client to publish from * @param codec the serialization codec to use while transmitting the message * @param options to use while publishing the message @@ -784,7 +786,7 @@ onDataCallback(undefined, StringMessageModule.unmarshal(receivedData), msg); /** * JetStream pull subscription for `string.payload` * - * @param {jetStreamPullSubscribeToReceiveStringPayloadCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToReceiveStringPayloadCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -839,7 +841,7 @@ onDataCallback(undefined, StringMessageModule.unmarshal(receivedData), msg); /** * JetStream push subscription for `string.payload` * - * @param {jetStreamPushSubscriptionFromReceiveStringPayloadCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromReceiveStringPayloadCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -885,7 +887,7 @@ onDataCallback(undefined, StringMessageModule.unmarshal(receivedData), msg); /** * NATS publish operation for `array.payload` * - * @param message to publish + * @param message to publish * @param nc the NATS client to publish from * @param codec the serialization codec to use while transmitting the message * @param options to use while publishing the message @@ -917,7 +919,7 @@ nc.publish('array.payload', dataToSend, options); /** * JetStream publish operation for `array.payload` * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param js the JetStream client to publish from * @param codec the serialization codec to use while transmitting the message * @param options to use while publishing the message @@ -1013,7 +1015,7 @@ onDataCallback(undefined, ArrayMessageModule.unmarshal(receivedData), msg); /** * JetStream pull subscription for `array.payload` * - * @param {jetStreamPullSubscribeToReceiveArrayPayloadCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToReceiveArrayPayloadCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -1068,7 +1070,7 @@ onDataCallback(undefined, ArrayMessageModule.unmarshal(receivedData), msg); /** * JetStream push subscription for `array.payload` * - * @param {jetStreamPushSubscriptionFromReceiveArrayPayloadCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromReceiveArrayPayloadCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -1114,7 +1116,7 @@ onDataCallback(undefined, ArrayMessageModule.unmarshal(receivedData), msg); /** * NATS publish operation for `union.payload` * - * @param message to publish + * @param message to publish * @param nc the NATS client to publish from * @param codec the serialization codec to use while transmitting the message * @param options to use while publishing the message @@ -1146,7 +1148,7 @@ nc.publish('union.payload', dataToSend, options); /** * JetStream publish operation for `union.payload` * - * @param message to publish over jetstream + * @param message to publish over jetstream * @param js the JetStream client to publish from * @param codec the serialization codec to use while transmitting the message * @param options to use while publishing the message @@ -1242,7 +1244,7 @@ onDataCallback(undefined, UnionMessageModule.unmarshal(receivedData), msg); /** * JetStream pull subscription for `union.payload` * - * @param {jetStreamPullSubscribeToReceiveUnionPayloadCallback} onDataCallback to call when messages are received + * @param {jetStreamPullSubscribeToReceiveUnionPayloadCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -1297,7 +1299,7 @@ onDataCallback(undefined, UnionMessageModule.unmarshal(receivedData), msg); /** * JetStream push subscription for `union.payload` * - * @param {jetStreamPushSubscriptionFromReceiveUnionPayloadCallback} onDataCallback to call when messages are received + * @param {jetStreamPushSubscriptionFromReceiveUnionPayloadCallback} onDataCallback to call when messages are received * @param js the JetStream client to pull subscribe through * @param options when setting up the subscription * @param codec the serialization codec to use while transmitting the message @@ -1340,4 +1342,243 @@ onDataCallback(undefined, UnionMessageModule.unmarshal(receivedData), msg); }); } -export { publishToSendUserSignedup, jetStreamPublishToSendUserSignedup, subscribeToReceiveUserSignedup, jetStreamPullSubscribeToReceiveUserSignedup, jetStreamPushSubscriptionFromReceiveUserSignedup, publishToNoParameter, subscribeToNoParameter, jetStreamPullSubscribeToNoParameter, jetStreamPushSubscriptionFromNoParameter, jetStreamPublishToNoParameter, publishToSendStringPayload, jetStreamPublishToSendStringPayload, subscribeToReceiveStringPayload, jetStreamPullSubscribeToReceiveStringPayload, jetStreamPushSubscriptionFromReceiveStringPayload, publishToSendArrayPayload, jetStreamPublishToSendArrayPayload, subscribeToReceiveArrayPayload, jetStreamPullSubscribeToReceiveArrayPayload, jetStreamPushSubscriptionFromReceiveArrayPayload, publishToSendUnionPayload, jetStreamPublishToSendUnionPayload, subscribeToReceiveUnionPayload, jetStreamPullSubscribeToReceiveUnionPayload, jetStreamPushSubscriptionFromReceiveUnionPayload }; +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish + * @param nc the NATS client to publish from + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToSendLegacyNotification({ + message, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: LegacyNotification, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = message.marshal(); + +dataToSend = codec.encode(dataToSend); +nc.publish('legacy.notification', dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish over jetstream + * @param js the JetStream client to publish from + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToSendLegacyNotification({ + message, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: LegacyNotification, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = message.marshal(); + +dataToSend = codec.encode(dataToSend); +await js.publish('legacy.notification', dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback subscribeToReceiveLegacyNotificationCallback + * @param err if any error occurred this will be sat + * @param msg that was received + * @param natsMsg + */ + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param {subscribeToReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param nc the nats client to setup the subscribe for + * @param codec the serialization codec to use while receiving the message + * @param options when setting up the subscription + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function subscribeToReceiveLegacyNotification({ + onDataCallback, + nc, + codec = Nats.JSONCodec(), + options, + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: LegacyNotification, natsMsg?: Nats.Msg) => void, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.SubscriptionOptions, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const subscription = nc.subscribe('legacy.notification', options); + const validator = LegacyNotification.createValidator(); + (async () => { + for await (const msg of subscription) { + + let receivedData: any = codec.decode(msg.data); +if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, msg); continue; + } + } +onDataCallback(undefined, LegacyNotification.unmarshal(receivedData), msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback jetStreamPullSubscribeToReceiveLegacyNotificationCallback + * @param err if any error occurred this will be sat + * @param msg that was received + * @param jetstreamMsg + */ + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param {jetStreamPullSubscribeToReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param js the JetStream client to pull subscribe through + * @param options when setting up the subscription + * @param codec the serialization codec to use while transmitting the message + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function jetStreamPullSubscribeToReceiveLegacyNotification({ + onDataCallback, + js, + options, + codec = Nats.JSONCodec(), + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: LegacyNotification, jetstreamMsg?: Nats.JsMsg) => void, + js: Nats.JetStreamClient, + options: Nats.ConsumerOptsBuilder | Partial, + codec?: Nats.Codec, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const subscription = await js.pullSubscribe('legacy.notification', options); + const validator = LegacyNotification.createValidator(); + (async () => { + for await (const msg of subscription) { + + let receivedData: any = codec.decode(msg.data); +if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, msg); continue; + } + } +onDataCallback(undefined, LegacyNotification.unmarshal(receivedData), msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback jetStreamPushSubscriptionFromReceiveLegacyNotificationCallback + * @param err if any error occurred this will be sat + * @param msg that was received + * @param jetstreamMsg + */ + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param {jetStreamPushSubscriptionFromReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param js the JetStream client to pull subscribe through + * @param options when setting up the subscription + * @param codec the serialization codec to use while transmitting the message + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function jetStreamPushSubscriptionFromReceiveLegacyNotification({ + onDataCallback, + js, + options, + codec = Nats.JSONCodec(), + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: LegacyNotification, jetstreamMsg?: Nats.JsMsg) => void, + js: Nats.JetStreamClient, + options: Nats.ConsumerOptsBuilder | Partial, + codec?: Nats.Codec, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const subscription = await js.subscribe('legacy.notification', options); + const validator = LegacyNotification.createValidator(); + (async () => { + for await (const msg of subscription) { + + let receivedData: any = codec.decode(msg.data); +if(!skipMessageValidation) { + const {valid, errors} = LegacyNotification.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, msg); continue; + } + } +onDataCallback(undefined, LegacyNotification.unmarshal(receivedData), msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +export { publishToSendUserSignedup, jetStreamPublishToSendUserSignedup, subscribeToReceiveUserSignedup, jetStreamPullSubscribeToReceiveUserSignedup, jetStreamPushSubscriptionFromReceiveUserSignedup, publishToNoParameter, subscribeToNoParameter, jetStreamPullSubscribeToNoParameter, jetStreamPushSubscriptionFromNoParameter, jetStreamPublishToNoParameter, publishToSendStringPayload, jetStreamPublishToSendStringPayload, subscribeToReceiveStringPayload, jetStreamPullSubscribeToReceiveStringPayload, jetStreamPushSubscriptionFromReceiveStringPayload, publishToSendArrayPayload, jetStreamPublishToSendArrayPayload, subscribeToReceiveArrayPayload, jetStreamPullSubscribeToReceiveArrayPayload, jetStreamPushSubscriptionFromReceiveArrayPayload, publishToSendUnionPayload, jetStreamPublishToSendUnionPayload, subscribeToReceiveUnionPayload, jetStreamPullSubscribeToReceiveUnionPayload, jetStreamPushSubscriptionFromReceiveUnionPayload, publishToSendLegacyNotification, jetStreamPublishToSendLegacyNotification, subscribeToReceiveLegacyNotification, jetStreamPullSubscribeToReceiveLegacyNotification, jetStreamPushSubscriptionFromReceiveLegacyNotification }; diff --git a/test/runtime/typescript/src/channels/websocket.ts b/test/runtime/typescript/src/channels/websocket.ts index a380254d..831a59df 100644 --- a/test/runtime/typescript/src/channels/websocket.ts +++ b/test/runtime/typescript/src/channels/websocket.ts @@ -2,14 +2,16 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; import {UserSignedUpHeaders} from './../headers/UserSignedUpHeaders'; import * as WebSocket from 'ws'; import { IncomingMessage } from 'http'; /** - * WebSocket client-side function to publish messages to `/user/signedup/{my_parameter}/{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * * @param message to publish * @param ws the WebSocket connection (assumed to be already connected) @@ -39,7 +41,7 @@ function publishToSendUserSignedup({ } /** - * WebSocket server-side function to handle messages for `/user/signedup/{my_parameter}/{enum_parameter}` + * Publishes a user signup event to notify other services that a new user has registered in the system. * * @param wss the WebSocket server instance * @param onConnection callback when a client connects to this channel @@ -95,7 +97,7 @@ function registerSendUserSignedup({ } /** - * WebSocket client-side function to subscribe to messages from `/user/signedup/{my_parameter}/{enum_parameter}` + * Receives user signup events to process new user registrations. * * @param onDataCallback callback when messages are received * @param parameters for URL path substitution @@ -895,4 +897,185 @@ function subscribeToReceiveUnionPayload({ } } -export { publishToSendUserSignedup, registerSendUserSignedup, subscribeToReceiveUserSignedup, publishToNoParameter, subscribeToNoParameter, registerNoParameter, publishToSendStringPayload, registerSendStringPayload, subscribeToReceiveStringPayload, publishToSendArrayPayload, registerSendArrayPayload, subscribeToReceiveArrayPayload, publishToSendUnionPayload, registerSendUnionPayload, subscribeToReceiveUnionPayload }; +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param message to publish + * @param ws the WebSocket connection (assumed to be already connected) + */ +function publishToSendLegacyNotification({ + message, + ws +}: { + message: LegacyNotification, + ws: WebSocket.WebSocket +}): Promise { + return new Promise((resolve, reject) => { + // Check if WebSocket is open + if (ws.readyState !== WebSocket.WebSocket.OPEN) { + reject(new Error('WebSocket is not open')); + return; + } + + // Send message directly + ws.send(message.marshal(), (err) => { + if (err) { + reject(new Error(`Failed to send message: ${err.message}`)); + } + resolve(); + }); + }); +} + +/** + * Sends a notification using the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param wss the WebSocket server instance + * @param onConnection callback when a client connects to this channel + * @param onMessage callback when a message is received on this channel + */ +function registerSendLegacyNotification({ + wss, + onConnection, + onMessage +}: { + wss: WebSocket.WebSocketServer, + onConnection: (params: {ws: WebSocket.WebSocket, request: IncomingMessage}) => void, + onMessage: (params: {message: LegacyNotification, ws: WebSocket.WebSocket}) => void +}): void { + const channelPattern = /^\/legacy\/notification(?:\?.*)?$/; + + wss.on('connection', (ws: WebSocket.WebSocket, request: IncomingMessage) => { + try { + const url = request.url || ''; + const match = url.match(channelPattern); + if (match) { + try { + + onConnection({ + ws, + request + }); + } catch (connectionError) { + console.error('Error in onConnection callback:', connectionError); + ws.close(1011, 'Connection error'); + return; + } + + ws.on('message', (data: WebSocket.RawData) => { + try { + const receivedData = data.toString(); + const parsedMessage = LegacyNotification.unmarshal(receivedData); + onMessage({ + message: parsedMessage, + ws + }); + } catch (error: any) { + // Ignore parsing errors + } + }); + } + } catch (error: any) { + ws.close(1011, 'Server error'); + } + }); +} + +/** + * Receives notifications from the legacy notification system. Use the new notification service instead. + * + * @deprecated + * + * @param onDataCallback callback when messages are received + * @param ws the WebSocket connection (assumed to be already connected) + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function subscribeToReceiveLegacyNotification({ + onDataCallback, + ws, + skipMessageValidation = false +}: { + onDataCallback: (params: {err?: Error, msg?: LegacyNotification, ws?: WebSocket.WebSocket}) => void, + ws: WebSocket.WebSocket, + skipMessageValidation?: boolean +}): void { + try { + // Check if WebSocket is open + if (ws.readyState !== WebSocket.WebSocket.OPEN) { + onDataCallback({ + err: new Error('WebSocket is not open'), + msg: undefined, + ws + }); + return; + } + + const validator = LegacyNotification.createValidator(); + + ws.on('message', (data: WebSocket.RawData) => { + try { + const receivedData = data.toString(); + const parsedMessage = LegacyNotification.unmarshal(receivedData); + + // Validate message if validation is enabled + if (!skipMessageValidation) { + const messageToValidate = parsedMessage.marshal(); + const {valid, errors} = LegacyNotification.validate({data: messageToValidate, ajvValidatorFunction: validator}); + if (!valid) { + onDataCallback({ + err: new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), + msg: undefined, + ws + }); + return; + } + } + + onDataCallback({ + err: undefined, + msg: parsedMessage, + ws + }); + + } catch (error: any) { + onDataCallback({ + err: new Error(`Failed to parse message: ${error.message}`), + msg: undefined, + ws + }); + } + }); + + ws.on('error', (error: Error) => { + onDataCallback({ + err: new Error(`WebSocket error: ${error.message}`), + msg: undefined, + ws + }); + }); + + ws.on('close', (code: number, reason: Buffer) => { + // Only report as error if it's not a normal closure (1000) or going away (1001) + if (code !== 1000 && code !== 1001 && code !== 1005) { // 1005 is no status received + onDataCallback({ + err: new Error(`WebSocket closed unexpectedly: ${code} ${reason.toString()}`), + msg: undefined, + ws + }); + } + }); + + } catch (error: any) { + onDataCallback({ + err: new Error(`Failed to set up WebSocket subscription: ${error.message}`), + msg: undefined, + ws + }); + } +} + +export { publishToSendUserSignedup, registerSendUserSignedup, subscribeToReceiveUserSignedup, publishToNoParameter, subscribeToNoParameter, registerNoParameter, publishToSendStringPayload, registerSendStringPayload, subscribeToReceiveStringPayload, publishToSendArrayPayload, registerSendArrayPayload, subscribeToReceiveArrayPayload, publishToSendUnionPayload, registerSendUnionPayload, subscribeToReceiveUnionPayload, publishToSendLegacyNotification, registerSendLegacyNotification, subscribeToReceiveLegacyNotification }; diff --git a/test/runtime/typescript/src/client/NatsClient.ts b/test/runtime/typescript/src/client/NatsClient.ts index 74cacaf8..1d3e548b 100644 --- a/test/runtime/typescript/src/client/NatsClient.ts +++ b/test/runtime/typescript/src/client/NatsClient.ts @@ -2,12 +2,16 @@ import {UserSignedUp} from './../payloads/UserSignedUp'; import * as StringMessageModule from './../payloads/StringMessage'; import * as ArrayMessageModule from './../payloads/ArrayMessage'; import * as UnionMessageModule from './../payloads/UnionMessage'; +import {LegacyNotification} from './../payloads/LegacyNotification'; import {UnionPayloadOneOfOption2} from './../payloads/UnionPayloadOneOfOption2'; +import {LegacyNotificationPayloadLevelEnum} from './../payloads/LegacyNotificationPayloadLevelEnum'; export {UserSignedUp}; export {StringMessageModule}; export {ArrayMessageModule}; export {UnionMessageModule}; +export {LegacyNotification}; export {UnionPayloadOneOfOption2}; +export {LegacyNotificationPayloadLevelEnum}; import {UserSignedupParameters} from './../parameters/UserSignedupParameters'; export {UserSignedupParameters}; @@ -897,4 +901,158 @@ export class NatsClient { } }); } + + /** + * + * + * @param message to publish + * @param options to use while publishing the message + */ + public async publishToSendLegacyNotification({ + message, + options + }: { + message: LegacyNotification, + options?: Nats.PublishOptions + }): Promise { + if (!this.isClosed() && this.nc !== undefined && this.codec !== undefined) { + await nats.publishToSendLegacyNotification({ + message, + nc: this.nc, + codec: this.codec, + options + }); + } else { + Promise.reject('Nats client not available yet, please connect or set the client'); + } + } + + /** + * + * + * @param message to publish + * @param options to use while publishing the message + */ + public async jetStreamPublishToSendLegacyNotification({ + message, + options = {} + }: { + message: LegacyNotification, + options?: Partial + }): Promise { + if (!this.isClosed() && this.nc !== undefined && this.codec !== undefined && this.js !== undefined) { + return nats.jetStreamPublishToSendLegacyNotification({ + message, + js: this.js, + codec: this.codec, + options + }); + } else { + Promise.reject('Nats client not available yet, please connect or set the client'); + } + } + + + /** + * + * + * @param {subscribeToReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param options when setting up the subscription + * @param options when setting up the subscription + */ + public subscribeToReceiveLegacyNotification({ + onDataCallback, + options, + flush + }: { + onDataCallback: (err?: Error, msg?: LegacyNotification, natsMsg?: Nats.Msg) => void, + options?: Nats.SubscriptionOptions, + flush?: boolean + }): Promise { + return new Promise(async (resolve, reject) => { + if(!this.isClosed() && this.nc !== undefined && this.codec !== undefined){ + try { + const sub = await nats.subscribeToReceiveLegacyNotification({ + onDataCallback, + nc: this.nc, + codec: this.codec, + options + }); + if(flush){ + await this.nc.flush(); + } + resolve(sub); + }catch(e: any){ + reject(e); + } + } else { + Promise.reject('Nats client not available yet, please connect or set the client'); + } + }); +} + + /** + * + * + * @param {jetStreamPullSubscribeToReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param options when setting up the subscription + */ + public jetStreamPullSubscribeToReceiveLegacyNotification({ + onDataCallback, + options = {} + }: { + onDataCallback: (err?: Error, msg?: LegacyNotification, jetstreamMsg?: Nats.JsMsg) => void, + options: Nats.ConsumerOptsBuilder | Partial + }): Promise { + return new Promise(async (resolve, reject) => { + if (!this.isClosed() && this.nc !== undefined && this.codec !== undefined && this.js !== undefined) { + try { + const sub = await nats.jetStreamPullSubscribeToReceiveLegacyNotification({ + onDataCallback, + js: this.js, + codec: this.codec, + options + }); + resolve(sub); + } catch (e: any) { + reject(e); + } + } else { + Promise.reject('Nats client not available yet, please connect or set the client'); + } + }); + } + + + /** + * + * + * @param {jetStreamPushSubscriptionFromReceiveLegacyNotificationCallback} onDataCallback to call when messages are received + * @param options when setting up the subscription + */ + public jetStreamPushSubscriptionFromReceiveLegacyNotification({ + onDataCallback, + options = {} + }: { + onDataCallback: (err?: Error, msg?: LegacyNotification, jetstreamMsg?: Nats.JsMsg) => void, + options: Nats.ConsumerOptsBuilder | Partial + }): Promise { + return new Promise(async (resolve, reject) => { + if (!this.isClosed() && this.nc !== undefined && this.codec !== undefined && this.js !== undefined) { + try { + const sub = await nats.jetStreamPushSubscriptionFromReceiveLegacyNotification({ + onDataCallback, + js: this.js, + codec: this.codec, + options + }); + resolve(sub); + } catch (e: any) { + reject(e); + } + } else { + Promise.reject('Nats client not available yet, please connect or set the client'); + } + }); + } } \ No newline at end of file diff --git a/test/runtime/typescript/src/openapi/channels/http_client.ts b/test/runtime/typescript/src/openapi/channels/http_client.ts index 06f1f8a9..fb3c07b2 100644 --- a/test/runtime/typescript/src/openapi/channels/http_client.ts +++ b/test/runtime/typescript/src/openapi/channels/http_client.ts @@ -1001,7 +1001,10 @@ export interface PostAddPetContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function postAddPet(context: PostAddPetContext): Promise> { +/** + * HTTP POST request to /pet + */ +export async function postAddPet(context: PostAddPetContext): Promise> { // Apply defaults const config = { path: '/pet', @@ -1121,7 +1124,10 @@ export interface PutUpdatePetContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function putUpdatePet(context: PutUpdatePetContext): Promise> { +/** + * HTTP PUT request to /pet + */ +export async function putUpdatePet(context: PutUpdatePetContext): Promise> { // Apply defaults const config = { path: '/pet', @@ -1241,7 +1247,10 @@ export interface GetFindPetsByStatusAndCategoryContext extends HttpClientContext requestHeaders?: { marshal: () => string }; } -async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { +/** + * Find pets by status and category with additional filtering options + */ +export async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { // Apply defaults const config = { path: '/pet/findByStatus/{status}/{categoryId}', diff --git a/test/runtime/typescript/src/openapi/payloads/APet.ts b/test/runtime/typescript/src/openapi/payloads/APet.ts index 5a13d10f..e84d8721 100644 --- a/test/runtime/typescript/src/openapi/payloads/APet.ts +++ b/test/runtime/typescript/src/openapi/payloads/APet.ts @@ -3,6 +3,9 @@ import {PetTag} from './PetTag'; import {Status} from './Status'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A pet for sale in the pet store + */ class APet { private _id?: number; private _category?: PetCategory; @@ -33,6 +36,9 @@ class APet { get id(): number | undefined { return this._id; } set id(id: number | undefined) { this._id = id; } + /** + * A category for a pet + */ get category(): PetCategory | undefined { return this._category; } set category(category: PetCategory | undefined) { this._category = category; } @@ -45,6 +51,9 @@ class APet { get tags(): PetTag[] | undefined { return this._tags; } set tags(tags: PetTag[] | undefined) { this._tags = tags; } + /** + * pet status in the store + */ get status(): Status | undefined { return this._status; } set status(status: Status | undefined) { this._status = status; } diff --git a/test/runtime/typescript/src/openapi/payloads/AUser.ts b/test/runtime/typescript/src/openapi/payloads/AUser.ts index c92bee78..939e139a 100644 --- a/test/runtime/typescript/src/openapi/payloads/AUser.ts +++ b/test/runtime/typescript/src/openapi/payloads/AUser.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A User who is purchasing from the pet store + */ class AUser { private _id?: number; private _username?: string; @@ -54,6 +57,9 @@ class AUser { get phone(): string | undefined { return this._phone; } set phone(phone: string | undefined) { this._phone = phone; } + /** + * User Status + */ get userStatus(): number | undefined { return this._userStatus; } set userStatus(userStatus: number | undefined) { this._userStatus = userStatus; } diff --git a/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts b/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts index b3aff656..eaf68676 100644 --- a/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts +++ b/test/runtime/typescript/src/openapi/payloads/AnUploadedResponse.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Describes the result of uploading an image resource + */ class AnUploadedResponse { private _code?: number; private _type?: string; diff --git a/test/runtime/typescript/src/openapi/payloads/ItemStatus.ts b/test/runtime/typescript/src/openapi/payloads/ItemStatus.ts index d99fea16..706c5beb 100644 --- a/test/runtime/typescript/src/openapi/payloads/ItemStatus.ts +++ b/test/runtime/typescript/src/openapi/payloads/ItemStatus.ts @@ -1,4 +1,7 @@ +/** + * pet status in the store + */ enum ItemStatus { AVAILABLE = "available", PENDING = "pending", diff --git a/test/runtime/typescript/src/openapi/payloads/PetCategory.ts b/test/runtime/typescript/src/openapi/payloads/PetCategory.ts index 3d27e778..56f53566 100644 --- a/test/runtime/typescript/src/openapi/payloads/PetCategory.ts +++ b/test/runtime/typescript/src/openapi/payloads/PetCategory.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A category for a pet + */ class PetCategory { private _id?: number; private _name?: string; diff --git a/test/runtime/typescript/src/openapi/payloads/PetOrder.ts b/test/runtime/typescript/src/openapi/payloads/PetOrder.ts index bce58b7f..ec225b6b 100644 --- a/test/runtime/typescript/src/openapi/payloads/PetOrder.ts +++ b/test/runtime/typescript/src/openapi/payloads/PetOrder.ts @@ -1,6 +1,9 @@ import {Status} from './Status'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * An order for a pets from the pet store + */ class PetOrder { private _id?: number; private _petId?: number; @@ -40,6 +43,9 @@ class PetOrder { get shipDate(): Date | undefined { return this._shipDate; } set shipDate(shipDate: Date | undefined) { this._shipDate = shipDate; } + /** + * Order Status + */ get status(): Status | undefined { return this._status; } set status(status: Status | undefined) { this._status = status; } diff --git a/test/runtime/typescript/src/openapi/payloads/PetTag.ts b/test/runtime/typescript/src/openapi/payloads/PetTag.ts index 41542b66..6a7dab71 100644 --- a/test/runtime/typescript/src/openapi/payloads/PetTag.ts +++ b/test/runtime/typescript/src/openapi/payloads/PetTag.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A tag for a pet + */ class PetTag { private _id?: number; private _name?: string; diff --git a/test/runtime/typescript/src/openapi/payloads/Status.ts b/test/runtime/typescript/src/openapi/payloads/Status.ts index a7f0745e..28dc3f54 100644 --- a/test/runtime/typescript/src/openapi/payloads/Status.ts +++ b/test/runtime/typescript/src/openapi/payloads/Status.ts @@ -1,4 +1,7 @@ +/** + * pet status in the store + */ enum Status { AVAILABLE = "available", PENDING = "pending", diff --git a/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts b/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts index 7e7394d1..cc17bea3 100644 --- a/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts +++ b/test/runtime/typescript/src/payload-types/payloads/AllOfThreeTypes.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * AllOf combining three schemas + */ class AllOfThreeTypes { private _id: string; private _name?: string; diff --git a/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts b/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts index f1d734c1..26620127 100644 --- a/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts +++ b/test/runtime/typescript/src/payload-types/payloads/AllOfTwoTypes.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * AllOf combining base entity and timestamp + */ class AllOfTwoTypes { private _id: string; private _name?: string; diff --git a/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts index 1c32ecde..94ad1a78 100644 --- a/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts +++ b/test/runtime/typescript/src/payload-types/payloads/AnyOfTwoTypes.ts @@ -2,6 +2,9 @@ import {AnyOfTwoTypesAnyOfOption0} from './AnyOfTwoTypesAnyOfOption0'; import {AnyOfTwoTypesAnyOfOption1} from './AnyOfTwoTypesAnyOfOption1'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * AnyOf with two object schemas + */ type AnyOfTwoTypes = AnyOfTwoTypesAnyOfOption0 | AnyOfTwoTypesAnyOfOption1; export function unmarshal(json: any): AnyOfTwoTypes { diff --git a/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts index 3e3ecb61..ed67f615 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMaxItems.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Array with maximum 5 items + */ type ArrayWithMaxItems = string[]; export function unmarshal(json: string | any[]): ArrayWithMaxItems { diff --git a/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts index ad8f21e4..b7e1656b 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ArrayWithMinItems.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Array with minimum 1 item + */ type ArrayWithMinItems = string[]; export function unmarshal(json: string | any[]): ArrayWithMinItems { diff --git a/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts b/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts index 48cfcafa..f7560351 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ArrayWithUniqueItems.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Array requiring unique items + */ type ArrayWithUniqueItems = string[]; export function unmarshal(json: string | any[]): ArrayWithUniqueItems { diff --git a/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts b/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts index 4cd8882e..5d05b408 100644 --- a/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts +++ b/test/runtime/typescript/src/payload-types/payloads/BooleanPlain.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A plain boolean type + */ type BooleanPlain = boolean; export function unmarshal(json: string): BooleanPlain { diff --git a/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts index d0faa688..7d459355 100644 --- a/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts +++ b/test/runtime/typescript/src/payload-types/payloads/DeeplyNestedObject.ts @@ -1,6 +1,9 @@ import {DeeplyNestedObjectLevel1} from './DeeplyNestedObjectLevel1'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with 3 levels of nesting + */ class DeeplyNestedObject { private _level1?: DeeplyNestedObjectLevel1; private _additionalProperties?: Record; diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts b/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts index 088631ad..5fe2175e 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatDate.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with date format (YYYY-MM-DD) + */ type FormatDate = Date; export function unmarshal(json: string): FormatDate { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts b/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts index b9e917e3..5163284f 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatDateTime.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with date-time format (ISO 8601) + */ type FormatDateTime = Date; export function unmarshal(json: string): FormatDateTime { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts b/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts index 7b6e9a92..bf0a805f 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatEmail.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with email format + */ type FormatEmail = string; export function unmarshal(json: string): FormatEmail { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts b/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts index 5e6a685e..4b5c1151 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatHostname.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with hostname format + */ type FormatHostname = string; export function unmarshal(json: string): FormatHostname { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts b/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts index 07e444ba..6605a818 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatIpv4.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with IPv4 format + */ type FormatIpv4 = string; export function unmarshal(json: string): FormatIpv4 { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts b/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts index 4567036a..3d3be40e 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatIpv6.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with IPv6 format + */ type FormatIpv6 = string; export function unmarshal(json: string): FormatIpv6 { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts b/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts index fa3b9fe6..e4ed87b0 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatTime.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with time format (HH:MM:SS) + */ type FormatTime = string; export function unmarshal(json: string): FormatTime { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts b/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts index 8b16ec2c..4d9a0365 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatUri.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with URI format + */ type FormatUri = string; export function unmarshal(json: string): FormatUri { diff --git a/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts b/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts index 131cdd5e..7a752b7b 100644 --- a/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts +++ b/test/runtime/typescript/src/payload-types/payloads/FormatUuid.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with UUID format + */ type FormatUuid = string; export function unmarshal(json: string): FormatUuid { diff --git a/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts b/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts index 55147808..c5daf681 100644 --- a/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/IntegerEnum.ts @@ -1,4 +1,7 @@ +/** + * Integer enum + */ enum IntegerEnum { NUMBER_1 = 1, NUMBER_2 = 2, diff --git a/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts b/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts index 7ef7a59a..511b0919 100644 --- a/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts +++ b/test/runtime/typescript/src/payload-types/payloads/IntegerPlain.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A plain integer type + */ type IntegerPlain = number; export function unmarshal(json: string): IntegerPlain { diff --git a/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts b/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts index 8304b6c1..cf4d0996 100644 --- a/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts +++ b/test/runtime/typescript/src/payload-types/payloads/IntegerWithRange.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Integer between 1 and 10 + */ type IntegerWithRange = number; export function unmarshal(json: string): IntegerWithRange { diff --git a/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts b/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts index 77885d12..96b48cb9 100644 --- a/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/MixedTypeEnum.ts @@ -1,4 +1,7 @@ +/** + * Mixed type enum + */ enum MixedTypeEnum { STRING_VALUE = "string_value", NUMBER_123 = 123, diff --git a/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts b/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts index 086f3704..b7c02b5c 100644 --- a/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/MultipleStringEnum.ts @@ -1,4 +1,7 @@ +/** + * Multiple value string enum + */ enum MultipleStringEnum { PENDING = "pending", ACTIVE = "active", diff --git a/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts b/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts index e71f2703..8a063257 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NestedArray.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * 2D array (array of string arrays) + */ type NestedArray = string[][]; export function unmarshal(json: string | any[]): NestedArray { diff --git a/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts b/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts index 1aedc846..32789ae3 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NestedObject.ts @@ -1,6 +1,9 @@ import {NestedObjectOuter} from './NestedObjectOuter'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with one level of nesting + */ class NestedObject { private _outer?: NestedObjectOuter; private _additionalProperties?: Record; diff --git a/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts b/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts index 6e9ecc5f..32cab9bc 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NullPlain.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A null type + */ type NullPlain = any; export function unmarshal(json: string): null { diff --git a/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts b/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts index 94cae99f..df85b011 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NullableEnum.ts @@ -1,4 +1,7 @@ +/** + * Nullable enum + */ enum NullableEnum { OPTION_A = "option_a", OPTION_B = "option_b", diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts b/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts index 1cf05b9b..db2317d7 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NumberPlain.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A plain number type + */ type NumberPlain = number; export function unmarshal(json: string): NumberPlain { diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts b/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts index ed5b3c9e..95dbb0a6 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NumberWithMaximum.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Number with maximum of 100 + */ type NumberWithMaximum = number; export function unmarshal(json: string): NumberWithMaximum { diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts b/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts index 60a2a68b..946cdda3 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NumberWithMinimum.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Number with minimum of 0 + */ type NumberWithMinimum = number; export function unmarshal(json: string): NumberWithMinimum { diff --git a/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts b/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts index 09fe6221..f1dff0ad 100644 --- a/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts +++ b/test/runtime/typescript/src/payload-types/payloads/NumberWithRange.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Number strictly between 0 and 100 + */ type NumberWithRange = number; export function unmarshal(json: string): NumberWithRange { diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts index cfd0e298..1c68e5d9 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsFalse.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object that disallows additional properties + */ class ObjectAdditionalPropsFalse { private _known?: string; diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts index c481c6a2..83b22586 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsSchema.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with typed additional properties (integers) + */ class ObjectAdditionalPropsSchema { private _known?: string; private _additionalProperties?: Record; diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts index 42afa453..8dd61be1 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectAdditionalPropsTrue.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object that allows additional properties + */ class ObjectAdditionalPropsTrue { private _known?: string; private _additionalProperties?: Record; diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts index 425d2327..6936584e 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectArray.ts @@ -1,6 +1,9 @@ import {ObjectArrayItem} from './ObjectArrayItem'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Array of objects + */ type ObjectArray = ObjectArrayItem[]; export function unmarshal(json: string | any[]): ObjectArray { diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts index 72341916..71757323 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithDefaults.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with default values + */ class ObjectWithDefaults { private _status?: string; private _count?: number; diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts index 236fcda7..718406c1 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithFormats.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with multiple format-validated properties + */ class ObjectWithFormats { private _email?: string; private _website?: string; diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts index 8a776f3e..70cdf901 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithOptional.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with all optional properties + */ class ObjectWithOptional { private _field1?: string; private _field2?: string; diff --git a/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts b/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts index d2f4552a..8699bacf 100644 --- a/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts +++ b/test/runtime/typescript/src/payload-types/payloads/ObjectWithRequired.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Object with required and optional properties + */ class ObjectWithRequired { private _id: string; private _name: string; diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts index 26f530d6..bd9ba123 100644 --- a/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfThreeTypes.ts @@ -1,6 +1,9 @@ import {OneOfThreeTypesOneOfOption2} from './OneOfThreeTypesOneOfOption2'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * OneOf with three type options + */ type OneOfThreeTypes = string | number | OneOfThreeTypesOneOfOption2; export function unmarshal(json: any): OneOfThreeTypes { diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts index 65c312b4..53bb0896 100644 --- a/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfTwoTypes.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * OneOf with string or integer + */ type OneOfTwoTypes = string | number; export function unmarshal(json: any): OneOfTwoTypes { diff --git a/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts b/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts index eb104c0a..434ef527 100644 --- a/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts +++ b/test/runtime/typescript/src/payload-types/payloads/OneOfWithDiscriminator.ts @@ -2,6 +2,9 @@ import {DogPayload} from './DogPayload'; import {CatPayload} from './CatPayload'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * OneOf with discriminator property + */ type OneOfWithDiscriminator = DogPayload | CatPayload; export function unmarshal(json: any): OneOfWithDiscriminator { diff --git a/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts b/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts index 76962dea..e21b310a 100644 --- a/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts +++ b/test/runtime/typescript/src/payload-types/payloads/SimpleObject.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Simple object with two properties + */ class SimpleObject { private _name?: string; private _age?: number; diff --git a/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts b/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts index 3d2a1c58..a6813e46 100644 --- a/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts +++ b/test/runtime/typescript/src/payload-types/payloads/SingleStringEnum.ts @@ -1,4 +1,7 @@ +/** + * Single value enum + */ enum SingleStringEnum { ACTIVE = "active", } diff --git a/test/runtime/typescript/src/payload-types/payloads/StringArray.ts b/test/runtime/typescript/src/payload-types/payloads/StringArray.ts index ac7ec2ec..1a7e094d 100644 --- a/test/runtime/typescript/src/payload-types/payloads/StringArray.ts +++ b/test/runtime/typescript/src/payload-types/payloads/StringArray.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Array of strings + */ type StringArray = string[]; export function unmarshal(json: string | any[]): StringArray { diff --git a/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts b/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts index 2f411c5b..60608c65 100644 --- a/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts +++ b/test/runtime/typescript/src/payload-types/payloads/StringPlain.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A plain string type + */ type StringPlain = string; export function unmarshal(json: string): StringPlain { diff --git a/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts b/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts index d314d39a..915c79b5 100644 --- a/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts +++ b/test/runtime/typescript/src/payload-types/payloads/StringWithMaxLength.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with maximum length of 50 + */ type StringWithMaxLength = string; export function unmarshal(json: string): StringWithMaxLength { diff --git a/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts b/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts index ea7ef22b..99057f0a 100644 --- a/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts +++ b/test/runtime/typescript/src/payload-types/payloads/StringWithMinLength.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String with minimum length of 3 + */ type StringWithMinLength = string; export function unmarshal(json: string): StringWithMinLength { diff --git a/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts b/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts index 297ce83d..b45ba329 100644 --- a/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts +++ b/test/runtime/typescript/src/payload-types/payloads/StringWithPattern.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * String matching capitalized word pattern + */ type StringWithPattern = string; export function unmarshal(json: string): StringWithPattern { diff --git a/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts b/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts index bb013451..7c771716 100644 --- a/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts +++ b/test/runtime/typescript/src/payload-types/payloads/TupleArray.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Tuple with fixed position types + */ type TupleArray = (string | number | boolean | any)[]; export function unmarshal(json: string | any[]): TupleArray { diff --git a/test/runtime/typescript/src/payloads/ArrayMessage.ts b/test/runtime/typescript/src/payloads/ArrayMessage.ts index 77e44ce5..e3d8a41c 100644 --- a/test/runtime/typescript/src/payloads/ArrayMessage.ts +++ b/test/runtime/typescript/src/payloads/ArrayMessage.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * An array of strings payload + */ type ArrayMessage = string[]; export function unmarshal(json: string | any[]): ArrayMessage { diff --git a/test/runtime/typescript/src/payloads/LegacyNotification.ts b/test/runtime/typescript/src/payloads/LegacyNotification.ts new file mode 100644 index 00000000..7a50dcf7 --- /dev/null +++ b/test/runtime/typescript/src/payloads/LegacyNotification.ts @@ -0,0 +1,96 @@ +import {LegacyNotificationPayloadLevelEnum} from './LegacyNotificationPayloadLevelEnum'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +/** + * Legacy notification payload - use NewNotificationPayload instead + */ +class LegacyNotification { + private _message?: string; + private _level?: LegacyNotificationPayloadLevelEnum; + private _additionalProperties?: Record; + + constructor(input: { + message?: string, + level?: LegacyNotificationPayloadLevelEnum, + additionalProperties?: Record, + }) { + this._message = input.message; + this._level = input.level; + this._additionalProperties = input.additionalProperties; + } + + /** + * The notification message + */ + get message(): string | undefined { return this._message; } + set message(message: string | undefined) { this._message = message; } + + /** + * Notification severity level + */ + get level(): LegacyNotificationPayloadLevelEnum | undefined { return this._level; } + set level(level: LegacyNotificationPayloadLevelEnum | undefined) { this._level = level; } + + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } + + public marshal() : string { + let json = '{' + if(this.message !== undefined) { + json += `"message": ${typeof this.message === 'number' || typeof this.message === 'boolean' ? this.message : JSON.stringify(this.message)},`; + } + if(this.level !== undefined) { + json += `"level": ${typeof this.level === 'number' || typeof this.level === 'boolean' ? this.level : JSON.stringify(this.level)},`; + } + if(this.additionalProperties !== undefined) { + for (const [key, value] of this.additionalProperties.entries()) { + //Only unwrap those that are not already a property in the JSON object + if(["message","level","additionalProperties"].includes(String(key))) continue; + json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + } + } + //Remove potential last comma + return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + } + + public static unmarshal(json: string | object): LegacyNotification { + const obj = typeof json === "object" ? json : JSON.parse(json); + const instance = new LegacyNotification({} as any); + + if (obj["message"] !== undefined) { + instance.message = obj["message"]; + } + if (obj["level"] !== undefined) { + instance.level = obj["level"]; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["message","level","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","description":"Legacy notification payload - use NewNotificationPayload instead","deprecated":true,"properties":{"message":{"type":"string","description":"The notification message"},"level":{"type":"string","enum":["info","warning","error"],"description":"Notification severity level"}},"$id":"LegacyNotification"}; + public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { + const {data, ajvValidatorFunction} = context ?? {}; + // Intentionally parse JSON strings to support validation of marshalled output. + // Example: validate({data: marshal(obj)}) works because marshal returns JSON string. + // Note: String 'true' will be coerced to boolean true due to JSON.parse. + const parsedData = typeof data === 'string' ? JSON.parse(data) : data; + const validate = ajvValidatorFunction ?? this.createValidator(context) + return { + valid: validate(parsedData), + errors: validate.errors ?? undefined, + }; + } + public static createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { + const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; + addFormats(ajvInstance); + + const validate = ajvInstance.compile(this.theCodeGenSchema); + return validate; + } + +} +export { LegacyNotification }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payloads/LegacyNotificationPayloadLevelEnum.ts b/test/runtime/typescript/src/payloads/LegacyNotificationPayloadLevelEnum.ts new file mode 100644 index 00000000..b7e9ab91 --- /dev/null +++ b/test/runtime/typescript/src/payloads/LegacyNotificationPayloadLevelEnum.ts @@ -0,0 +1,10 @@ + +/** + * Notification severity level + */ +enum LegacyNotificationPayloadLevelEnum { + INFO = "info", + WARNING = "warning", + ERROR = "error", +} +export { LegacyNotificationPayloadLevelEnum }; \ No newline at end of file diff --git a/test/runtime/typescript/src/payloads/StringMessage.ts b/test/runtime/typescript/src/payloads/StringMessage.ts index 8261d7b9..b4efe734 100644 --- a/test/runtime/typescript/src/payloads/StringMessage.ts +++ b/test/runtime/typescript/src/payloads/StringMessage.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A simple string payload + */ type StringMessage = string; export function unmarshal(json: string): StringMessage { diff --git a/test/runtime/typescript/src/payloads/UnionMessage.ts b/test/runtime/typescript/src/payloads/UnionMessage.ts index 0061eab6..f2545d83 100644 --- a/test/runtime/typescript/src/payloads/UnionMessage.ts +++ b/test/runtime/typescript/src/payloads/UnionMessage.ts @@ -1,6 +1,9 @@ import {UnionPayloadOneOfOption2} from './UnionPayloadOneOfOption2'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * A union type payload + */ type UnionMessage = string | number | UnionPayloadOneOfOption2; export function unmarshal(json: any): UnionMessage { diff --git a/test/runtime/typescript/src/payloads/UserSignedUp.ts b/test/runtime/typescript/src/payloads/UserSignedUp.ts index 3e0a553b..13f2c67a 100644 --- a/test/runtime/typescript/src/payloads/UserSignedUp.ts +++ b/test/runtime/typescript/src/payloads/UserSignedUp.ts @@ -1,5 +1,8 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; import {default as addFormats} from 'ajv-formats'; +/** + * Payload for user signup events containing user registration details + */ class UserSignedUp { private _displayName?: string; private _email?: string; @@ -15,9 +18,15 @@ class UserSignedUp { this._additionalProperties = input.additionalProperties; } + /** + * Name of the user + */ get displayName(): string | undefined { return this._displayName; } set displayName(displayName: string | undefined) { this._displayName = displayName; } + /** + * Email of the user + */ get email(): string | undefined { return this._email; } set email(email: string | undefined) { this._email = email; } @@ -61,7 +70,7 @@ class UserSignedUp { } return instance; } - public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"display_name":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"UserSignedUp"}; + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","description":"Payload for user signup events containing user registration details","properties":{"display_name":{"type":"string","description":"Name of the user"},"email":{"type":"string","format":"email","description":"Email of the user"}},"$id":"UserSignedUp"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; // Intentionally parse JSON strings to support validation of marshalled output. diff --git a/test/runtime/typescript/src/request-reply/channels/http_client.ts b/test/runtime/typescript/src/request-reply/channels/http_client.ts index f4d913a8..2b0e7747 100644 --- a/test/runtime/typescript/src/request-reply/channels/http_client.ts +++ b/test/runtime/typescript/src/request-reply/channels/http_client.ts @@ -1018,7 +1018,10 @@ export interface PostPingPostRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function postPingPostRequest(context: PostPingPostRequestContext): Promise> { +/** + * HTTP POST request to /ping + */ +export async function postPingPostRequest(context: PostPingPostRequestContext): Promise> { // Apply defaults const config = { path: '/ping', @@ -1137,7 +1140,10 @@ export interface GetPingGetRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function getPingGetRequest(context: GetPingGetRequestContext = {}): Promise> { +/** + * HTTP GET request to /ping + */ +export async function getPingGetRequest(context: GetPingGetRequestContext = {}): Promise> { // Apply defaults const config = { path: '/ping', @@ -1257,7 +1263,10 @@ export interface PutPingPutRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function putPingPutRequest(context: PutPingPutRequestContext): Promise> { +/** + * HTTP PUT request to /ping + */ +export async function putPingPutRequest(context: PutPingPutRequestContext): Promise> { // Apply defaults const config = { path: '/ping', @@ -1376,7 +1385,10 @@ export interface DeletePingDeleteRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function deletePingDeleteRequest(context: DeletePingDeleteRequestContext = {}): Promise> { +/** + * HTTP DELETE request to /ping + */ +export async function deletePingDeleteRequest(context: DeletePingDeleteRequestContext = {}): Promise> { // Apply defaults const config = { path: '/ping', @@ -1496,7 +1508,10 @@ export interface PatchPingPatchRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function patchPingPatchRequest(context: PatchPingPatchRequestContext): Promise> { +/** + * HTTP PATCH request to /ping + */ +export async function patchPingPatchRequest(context: PatchPingPatchRequestContext): Promise> { // Apply defaults const config = { path: '/ping', @@ -1615,7 +1630,10 @@ export interface HeadPingHeadRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function headPingHeadRequest(context: HeadPingHeadRequestContext = {}): Promise> { +/** + * HTTP HEAD request to /ping + */ +export async function headPingHeadRequest(context: HeadPingHeadRequestContext = {}): Promise> { // Apply defaults const config = { path: '/ping', @@ -1734,7 +1752,10 @@ export interface OptionsPingOptionsRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function optionsPingOptionsRequest(context: OptionsPingOptionsRequestContext = {}): Promise> { +/** + * HTTP OPTIONS request to /ping + */ +export async function optionsPingOptionsRequest(context: OptionsPingOptionsRequestContext = {}): Promise> { // Apply defaults const config = { path: '/ping', @@ -1853,7 +1874,10 @@ export interface GetMultiStatusResponseContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function getMultiStatusResponse(context: GetMultiStatusResponseContext = {}): Promise> { +/** + * HTTP GET request to /ping + */ +export async function getMultiStatusResponse(context: GetMultiStatusResponseContext = {}): Promise> { // Apply defaults const config = { path: '/ping', @@ -1973,7 +1997,10 @@ export interface GetGetUserItemContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function getGetUserItem(context: GetGetUserItemContext): Promise> { +/** + * HTTP GET request to /users/{userId}/items/{itemId} + */ +export async function getGetUserItem(context: GetGetUserItemContext): Promise> { // Apply defaults const config = { path: '/users/{userId}/items/{itemId}', @@ -2094,7 +2121,10 @@ export interface PutUpdateUserItemContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } -async function putUpdateUserItem(context: PutUpdateUserItemContext): Promise> { +/** + * HTTP PUT request to /users/{userId}/items/{itemId} + */ +export async function putUpdateUserItem(context: PutUpdateUserItemContext): Promise> { // Apply defaults const config = { path: '/users/{userId}/items/{itemId}', diff --git a/test/runtime/typescript/src/request-reply/channels/nats.ts b/test/runtime/typescript/src/request-reply/channels/nats.ts index 6f85d9d1..f3b0d3bb 100644 --- a/test/runtime/typescript/src/request-reply/channels/nats.ts +++ b/test/runtime/typescript/src/request-reply/channels/nats.ts @@ -13,9 +13,9 @@ import {ItemRequestHeaders} from './../headers/ItemRequestHeaders'; import * as Nats from 'nats'; /** - * Core request for `ping` + * NATS request operation for `ping` * - * @param requestMessage the message to send + * @param requestMessage the message to send * @param nc the nats client to setup the request for * @param codec the serialization codec to use when transmitting request and receiving reply * @param options when sending the request @@ -64,9 +64,9 @@ function requestToRegularRequest({ */ /** - * Reply for `ping` + * NATS reply operation for `ping` * - * @param {replyToRegularReplyCallback} onDataCallback to call when the request is received + * @param {replyToRegularReplyCallback} onDataCallback to call when the request is received * @param nc the nats client to setup the reply for * @param codec the serialization codec to use when receiving request and transmitting reply * @param options when setting up the reply diff --git a/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts b/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts index 19362e27..dd4100fc 100644 --- a/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts +++ b/test/runtime/typescript/src/request-reply/payloads/ItemRequest.ts @@ -18,12 +18,21 @@ class ItemRequest { this._additionalProperties = input.additionalProperties; } + /** + * Name of the item + */ get name(): string { return this._name; } set name(name: string) { this._name = name; } + /** + * Item description + */ get description(): string | undefined { return this._description; } set description(description: string | undefined) { this._description = description; } + /** + * Item quantity + */ get quantity(): number | undefined { return this._quantity; } set quantity(quantity: number | undefined) { this._quantity = quantity; } diff --git a/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts b/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts index 581e2d3e..b4bbd56f 100644 --- a/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts +++ b/test/runtime/typescript/src/request-reply/payloads/ItemResponse.ts @@ -24,18 +24,33 @@ class ItemResponse { this._additionalProperties = input.additionalProperties; } + /** + * The item ID + */ get id(): string | undefined { return this._id; } set id(id: string | undefined) { this._id = id; } + /** + * Owner user ID + */ get userId(): string | undefined { return this._userId; } set userId(userId: string | undefined) { this._userId = userId; } + /** + * Name of the item + */ get name(): string | undefined { return this._name; } set name(name: string | undefined) { this._name = name; } + /** + * Item description + */ get description(): string | undefined { return this._description; } set description(description: string | undefined) { this._description = description; } + /** + * Item quantity + */ get quantity(): number | undefined { return this._quantity; } set quantity(quantity: number | undefined) { this._quantity = quantity; } diff --git a/test/runtime/typescript/src/request-reply/payloads/NotFound.ts b/test/runtime/typescript/src/request-reply/payloads/NotFound.ts index a3c3bb0a..c41f7837 100644 --- a/test/runtime/typescript/src/request-reply/payloads/NotFound.ts +++ b/test/runtime/typescript/src/request-reply/payloads/NotFound.ts @@ -15,9 +15,15 @@ class NotFound { this._additionalProperties = input.additionalProperties; } + /** + * Error message + */ get error(): string | undefined { return this._error; } set error(error: string | undefined) { this._error = error; } + /** + * Error code + */ get code(): string | undefined { return this._code; } set code(code: string | undefined) { this._code = code; } diff --git a/test/runtime/typescript/src/request-reply/payloads/Ping.ts b/test/runtime/typescript/src/request-reply/payloads/Ping.ts index abe4dfea..f24bc4b5 100644 --- a/test/runtime/typescript/src/request-reply/payloads/Ping.ts +++ b/test/runtime/typescript/src/request-reply/payloads/Ping.ts @@ -12,6 +12,9 @@ class Ping { this._additionalProperties = input.additionalProperties; } + /** + * ping name + */ get ping(): string | undefined { return this._ping; } set ping(ping: string | undefined) { this._ping = ping; } diff --git a/test/runtime/typescript/src/request-reply/payloads/Pong.ts b/test/runtime/typescript/src/request-reply/payloads/Pong.ts index a6251ef5..039f3c31 100644 --- a/test/runtime/typescript/src/request-reply/payloads/Pong.ts +++ b/test/runtime/typescript/src/request-reply/payloads/Pong.ts @@ -12,6 +12,9 @@ class Pong { this._additionalProperties = input.additionalProperties; } + /** + * pong name + */ get pong(): string | undefined { return this._pong; } set pong(pong: string | undefined) { this._pong = pong; } diff --git a/test/runtime/typescript/test/jsdoc.spec.ts b/test/runtime/typescript/test/jsdoc.spec.ts new file mode 100644 index 00000000..cc1e121d --- /dev/null +++ b/test/runtime/typescript/test/jsdoc.spec.ts @@ -0,0 +1,214 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Tests for JSDoc generation from API specification descriptions. + * These tests verify that: + * - Payload models include JSDoc from schema descriptions + * - Channel functions include JSDoc from operation descriptions + * - @deprecated tags appear for deprecated operations/schemas + * - Parameter descriptions appear in JSDoc + */ +describe('JSDoc Generation', () => { + describe('Payload Models', () => { + it('should include schema description in class JSDoc', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/payloads/UserSignedUp.ts'), + 'utf-8' + ); + // UserSignedUpPayload has description: "Payload for user signup events containing user registration details" + expect(content).toContain('/**'); + expect(content).toContain('Payload for user signup events containing user registration details'); + }); + + it('should include field-level JSDoc from property descriptions', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/payloads/UserSignedUp.ts'), + 'utf-8' + ); + // display_name has description: "Name of the user" + // email has description: "Email of the user" + expect(content).toContain('Name of the user'); + expect(content).toContain('Email of the user'); + }); + + it('should include deprecated flag in schema constant', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/payloads/LegacyNotification.ts'), + 'utf-8' + ); + // LegacyNotificationPayload has deprecated: true - this is preserved in the schema constant + // Note: TS_DESCRIPTION_PRESET from Modelina doesn't add @deprecated JSDoc tags to class definitions + // The deprecated flag is accessible via the static theCodeGenSchema property + expect(content).toContain('"deprecated":true'); + }); + + it('should include description for deprecated schema', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/payloads/LegacyNotification.ts'), + 'utf-8' + ); + // LegacyNotificationPayload has description: "Legacy notification payload - use NewNotificationPayload instead" + expect(content).toContain('Legacy notification payload'); + }); + + it('should include primitive payload descriptions', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/payloads/StringMessage.ts'), + 'utf-8' + ); + // StringPayload has description: "A simple string payload" + expect(content).toContain('A simple string payload'); + }); + + it('should include array payload descriptions', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/payloads/ArrayMessage.ts'), + 'utf-8' + ); + // ArrayPayload has description: "An array of strings payload" + expect(content).toContain('An array of strings payload'); + }); + }); + + describe('Channel Functions', () => { + it('should use operation description in JSDoc instead of generic text', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/nats.ts'), + 'utf-8' + ); + // sendUserSignedup has description: "Publishes a user signup event to notify other services that a new user has registered in the system." + expect(content).toContain('Publishes a user signup event to notify other services that a new user has registered in the system.'); + }); + + it('should use operation summary when available', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/nats.ts'), + 'utf-8' + ); + // receiveUserSignedup has summary: "Subscribe to user signup events" + // Either summary or description should appear + expect(content).toMatch(/Subscribe to user signup events|Receives user signup events to process new user registrations/); + }); + + it('should include @deprecated for deprecated operations', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/nats.ts'), + 'utf-8' + ); + // sendLegacyNotification and receiveLegacyNotification have deprecated: true + expect(content).toContain('@deprecated'); + }); + + it('should include description for deprecated operations', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/nats.ts'), + 'utf-8' + ); + // sendLegacyNotification has description: "Sends a notification using the legacy notification system. Use the new notification service instead." + expect(content).toContain('Sends a notification using the legacy notification system'); + }); + + it('should include parameter descriptions in parameter class JSDoc', () => { + // Parameter descriptions are included in the parameter class definition, not channel function @param tags + // Channel function @param tags describe TypeScript function parameters (message, nc, codec, etc.) + const content = fs.readFileSync( + path.join(__dirname, '../src/parameters/UserSignedupParameters.ts'), + 'utf-8' + ); + // my_parameter has description: "parameter description" + // enum_parameter has description: "enum parameter" + expect(content).toContain('parameter description'); + expect(content).toContain('enum parameter'); + }); + }); + + describe('Multi-line and Special Character Handling', () => { + it('should handle descriptions with special characters', () => { + // Ensure descriptions don't break JSDoc syntax + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/nats.ts'), + 'utf-8' + ); + + // All JSDoc blocks should be properly closed + const openComments = (content.match(/\/\*\*/g) || []).length; + const closeComments = (content.match(/\*\//g) || []).length; + expect(openComments).toBe(closeComments); + }); + + it('should properly format JSDoc blocks', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/nats.ts'), + 'utf-8' + ); + + // Verify JSDoc structure is valid (starts with /**, each line starts with *, ends with */) + const jsdocPattern = /\/\*\*[\s\S]*?\*\//g; + const jsdocBlocks = content.match(jsdocPattern) || []; + + expect(jsdocBlocks.length).toBeGreaterThan(0); + + for (const block of jsdocBlocks) { + // Each JSDoc should start with /** and end with */ + expect(block.startsWith('/**')).toBe(true); + expect(block.endsWith('*/')).toBe(true); + } + }); + }); + + describe('Kafka Channel Functions', () => { + it('should include operation descriptions in Kafka channels', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/kafka.ts'), + 'utf-8' + ); + // Should use operation description instead of generic Kafka text + expect(content).toContain('Publishes a user signup event to notify other services'); + }); + + it('should include @deprecated for deprecated Kafka operations', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/kafka.ts'), + 'utf-8' + ); + expect(content).toContain('@deprecated'); + }); + }); + + describe('MQTT Channel Functions', () => { + it('should include operation descriptions in MQTT channels', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/mqtt.ts'), + 'utf-8' + ); + expect(content).toContain('Publishes a user signup event to notify other services'); + }); + + it('should include @deprecated for deprecated MQTT operations', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/mqtt.ts'), + 'utf-8' + ); + expect(content).toContain('@deprecated'); + }); + }); + + describe('AMQP Channel Functions', () => { + it('should include operation descriptions in AMQP channels', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/amqp.ts'), + 'utf-8' + ); + expect(content).toContain('Publishes a user signup event to notify other services'); + }); + + it('should include @deprecated for deprecated AMQP operations', () => { + const content = fs.readFileSync( + path.join(__dirname, '../src/channels/amqp.ts'), + 'utf-8' + ); + expect(content).toContain('@deprecated'); + }); + }); +}); From 1db2d3b716b5abe86548882785b4d49770fb09f1 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:25:48 +0100 Subject: [PATCH 2/8] fix: remove unintended export keyword from HTTP client functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the 'export' keyword from the generated HTTP client function declaration to match the pattern used by all other protocol renderers (NATS, Kafka, MQTT, AMQP, WebSocket, EventSource), which use separate export statements at the bottom of the file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../generators/typescript/channels/protocols/http/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/generators/typescript/channels/protocols/http/client.ts b/src/codegen/generators/typescript/channels/protocols/http/client.ts index f7b7a502..b981c35d 100644 --- a/src/codegen/generators/typescript/channels/protocols/http/client.ts +++ b/src/codegen/generators/typescript/channels/protocols/http/client.ts @@ -184,7 +184,7 @@ function generateFunctionImplementation(params: { const contextDefault = !hasBody && !hasParameters ? ' = {}' : ''; return `${jsDoc} -export async function ${functionName}(context: ${contextInterfaceName}${contextDefault}): Promise> { +async function ${functionName}(context: ${contextInterfaceName}${contextDefault}): Promise> { // Apply defaults const config = { path: '${requestTopic}', From 0d2755095e26d8fb9bed499597fb121252a21a09 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:28:28 +0100 Subject: [PATCH 3/8] fix: update HTTP client channel snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates test snapshots to match the HTTP client function signature change from the previous commit that removed the 'export' keyword from generated HTTP client functions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../typescript/__snapshots__/channels.spec.ts.snap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap b/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap index 012bf010..4d19322f 100644 --- a/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap +++ b/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap @@ -1006,7 +1006,7 @@ export interface PostAddPetContext extends HttpClientContext { /** * HTTP POST request to /pet */ -export async function postAddPet(context: PostAddPetContext): Promise> { +async function postAddPet(context: PostAddPetContext): Promise> { // Apply defaults const config = { path: '/pet', @@ -1129,7 +1129,7 @@ export interface PutUpdatePetContext extends HttpClientContext { /** * HTTP PUT request to /pet */ -export async function putUpdatePet(context: PutUpdatePetContext): Promise> { +async function putUpdatePet(context: PutUpdatePetContext): Promise> { // Apply defaults const config = { path: '/pet', @@ -1252,7 +1252,7 @@ export interface GetFindPetsByStatusAndCategoryContext extends HttpClientContext /** * Find pets by status and category with additional filtering options */ -export async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { +async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { // Apply defaults const config = { path: '/pet/findByStatus/{status}/{categoryId}', @@ -2941,7 +2941,7 @@ export interface GetPingRequestContext extends HttpClientContext { /** * HTTP GET request to /ping */ -export async function getPingRequest(context: GetPingRequestContext = {}): Promise> { +async function getPingRequest(context: GetPingRequestContext = {}): Promise> { // Apply defaults const config = { path: '/ping', From 942893b86c6699d600354aad7f0781a4242bb9b2 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:33:58 +0100 Subject: [PATCH 4/8] fix: tolerate Node.js deprecation warnings in init command tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The init command tests were failing in CI because Node.js outputs deprecation warnings (like the punycode module warning) to stderr. The tests were asserting stderr === '', which failed when these warnings were present. Added a helper function `expectNoActualErrors()` that filters out Node.js deprecation warnings while still failing on actual errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- test/commands/init.spec.ts | 77 +++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/test/commands/init.spec.ts b/test/commands/init.spec.ts index 6070f22e..69d6bc4f 100644 --- a/test/commands/init.spec.ts +++ b/test/commands/init.spec.ts @@ -5,12 +5,27 @@ jest.mock('inquirer', () => ({ prompt: jest.fn() })); +/** + * Helper to check stderr doesn't contain actual errors. + * Allows Node.js deprecation warnings (like punycode) which are not actual errors. + */ +function expectNoActualErrors(stderr: string): void { + // Filter out Node.js deprecation warnings + const actualErrors = stderr + .split('\n') + .filter(line => line.trim() !== '') + .filter(line => !line.includes('DeprecationWarning')) + .filter(line => !line.includes('node --trace-deprecation')) + .join('\n'); + expect(actualErrors).toEqual(''); +} + describe('init', () => { describe('configuration types', () => { it('should generate esm configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -18,7 +33,7 @@ describe('init', () => { it('should generate typescript configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=ts --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -26,7 +41,7 @@ describe('init', () => { it('should generate json configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -34,7 +49,7 @@ describe('init', () => { it('should generate yaml configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=yaml --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -44,7 +59,7 @@ describe('init', () => { it('should generate configuration with headers', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-headers`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -52,7 +67,7 @@ describe('init', () => { it('should generate configuration with payloads', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-payloads`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -60,7 +75,7 @@ describe('init', () => { it('should generate configuration with parameters', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-parameters`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -68,7 +83,7 @@ describe('init', () => { it('should generate configuration with channels', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-channels`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -76,7 +91,7 @@ describe('init', () => { it('should generate configuration with client', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-client`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -84,7 +99,7 @@ describe('init', () => { it('should generate configuration with all include flags', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-headers --include-payloads --include-parameters --include-channels --include-client`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -94,7 +109,7 @@ describe('init', () => { it('should handle asyncapi input type', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -102,7 +117,7 @@ describe('init', () => { it('should handle openapi input type', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./openapi.json' --input-type=openapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -112,7 +127,7 @@ describe('init', () => { it('should use custom config name', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --config-name=my-custom-config --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -120,7 +135,7 @@ describe('init', () => { it('should use custom output directory', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./custom-output' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -148,28 +163,28 @@ describe('init', () => { it('should include correct language in configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('"language": "typescript"'); }); it('should include schema reference in JSON configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('"$schema": "https://raw.githubusercontent.com/the-codegen-project/cli/main/schemas/configuration-schema-0.json"'); }); it('should include schema reference in YAML configuration', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=yaml --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('# yaml-language-server: $schema=https://raw.githubusercontent.com/the-codegen-project/cli/main/schemas/configuration-schema-0.json'); }); it('should have empty generators array when no include flags are specified', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('"generators": []'); }); }); @@ -201,7 +216,7 @@ describe('init', () => { it('should only include TypeScript-specific generators for TypeScript language', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-payloads`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('"generators"'); expect(stdout).toContain('"language": "typescript"'); }); @@ -209,7 +224,7 @@ describe('init', () => { it('should only include AsyncAPI-specific generators for AsyncAPI input type', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-channels`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('"inputType": "asyncapi"'); expect(stdout).toContain('"generators"'); }); @@ -217,7 +232,7 @@ describe('init', () => { it('should handle OpenAPI with TypeScript but no AsyncAPI-specific features', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./openapi.json' --input-type=openapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('"inputType": "openapi"'); expect(stdout).toContain('"generators": []'); // No AsyncAPI-specific generators should be added }); @@ -227,7 +242,7 @@ describe('init', () => { it('should accept gitignore-generated flag', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -235,7 +250,7 @@ describe('init', () => { it('should work without gitignore-generated flag', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -243,7 +258,7 @@ describe('init', () => { it('should accept gitignore-generated with payloads generator', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-payloads --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -251,7 +266,7 @@ describe('init', () => { it('should accept gitignore-generated with channels generator', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-channels --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -259,7 +274,7 @@ describe('init', () => { it('should accept gitignore-generated with headers generator', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-headers --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -267,7 +282,7 @@ describe('init', () => { it('should accept gitignore-generated with parameters generator', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-parameters --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -275,7 +290,7 @@ describe('init', () => { it('should accept gitignore-generated with client generator', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-client --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -283,7 +298,7 @@ describe('init', () => { it('should accept gitignore-generated with all generators', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./' --no-output --include-payloads --include-channels --include-headers --include-parameters --include-client --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).not.toEqual(''); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); @@ -313,14 +328,14 @@ describe('init', () => { it('should handle gitignore-generated with OpenAPI input type', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=json --input-file='./openapi.json' --input-type=openapi --languages=typescript --no-tty --output-directory='./' --no-output --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); it('should handle gitignore-generated with custom output directory', async () => { const {stdout, stderr, error} = await runCommand(`init --config-type=esm --input-file='./asyncapi.json' --input-type=asyncapi --languages=typescript --no-tty --output-directory='./custom-output' --no-output --include-payloads --gitignore-generated`); expect(error).toBeUndefined(); - expect(stderr).toEqual(''); + expectNoActualErrors(stderr); expect(stdout).toContain('Successfully created your sparkling new generation file'); }); }); From 27c1ff0251e25bdfcb98fabca5729bef97ce2068 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:38:21 +0100 Subject: [PATCH 5/8] fix: escape JSDoc descriptions consistently in generated code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add escapeJSDocDescription to renderJSDocParameters for parameter descriptions - Apply escapeJSDocDescription to fallbackDescription in renderChannelJSDoc This prevents JSDoc comment blocks from breaking when API spec descriptions contain `*/` (comment terminator) or newlines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/codegen/generators/typescript/channels/utils.ts | 2 +- src/codegen/generators/typescript/utils.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/codegen/generators/typescript/channels/utils.ts b/src/codegen/generators/typescript/channels/utils.ts index f0296d09..4b118b8f 100644 --- a/src/codegen/generators/typescript/channels/utils.ts +++ b/src/codegen/generators/typescript/channels/utils.ts @@ -274,7 +274,7 @@ export function renderChannelJSDoc(params: { const desc = description ? escapeJSDocDescription(description) - : fallbackDescription; + : escapeJSDocDescription(fallbackDescription); const parts = ['/**', ` * ${desc}`]; diff --git a/src/codegen/generators/typescript/utils.ts b/src/codegen/generators/typescript/utils.ts index cec816a4..511d90a9 100644 --- a/src/codegen/generators/typescript/utils.ts +++ b/src/codegen/generators/typescript/utils.ts @@ -8,6 +8,7 @@ import { TypeScriptOptions } from '@asyncapi/modelina'; import {DeepPartial} from '../../utils'; +import {escapeJSDocDescription} from './channels/utils'; /** * Cast JSON schema variable to typescript type @@ -108,7 +109,7 @@ export function renderJSDocParameters( const description = prop.property?.originalInput?.description || 'parameter to use in topic'; - return ` * @param ${paramName} ${description}`; + return ` * @param ${paramName} ${escapeJSDocDescription(description)}`; }) .join('\n'); } From 6903e7624597233ae0b706b83b6b3fed5192f267 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:41:39 +0100 Subject: [PATCH 6/8] fix: remove dead code renderJSDocParameters function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the unused renderJSDocParameters function from utils.ts and its corresponding tests. This function was never imported or called in production code - only the test file used it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/codegen/generators/typescript/utils.ts | 18 ------ .../generators/typescript/jsdoc-utils.spec.ts | 59 ------------------- 2 files changed, 77 deletions(-) diff --git a/src/codegen/generators/typescript/utils.ts b/src/codegen/generators/typescript/utils.ts index 511d90a9..61f092d0 100644 --- a/src/codegen/generators/typescript/utils.ts +++ b/src/codegen/generators/typescript/utils.ts @@ -95,24 +95,6 @@ function realizeParameterForChannelWithType( return `${parameterName}${requiredType}: ${parameterType})}`; } -/** - * Render channel parameters for JSDoc. - * Uses actual descriptions from the parameter properties when available. - * - * @param channelParameters to render - */ -export function renderJSDocParameters( - channelParameters: ConstrainedObjectModel -) { - return Object.entries(channelParameters.properties) - .map(([paramName, prop]) => { - const description = - prop.property?.originalInput?.description || - 'parameter to use in topic'; - return ` * @param ${paramName} ${escapeJSDocDescription(description)}`; - }) - .join('\n'); -} /** * Convert RFC 6570 URI with parameters to NATS topic. diff --git a/test/codegen/generators/typescript/jsdoc-utils.spec.ts b/test/codegen/generators/typescript/jsdoc-utils.spec.ts index 57dadfbf..f7763415 100644 --- a/test/codegen/generators/typescript/jsdoc-utils.spec.ts +++ b/test/codegen/generators/typescript/jsdoc-utils.spec.ts @@ -119,63 +119,4 @@ describe('JSDoc Utilities', () => { }); }); - describe('renderJSDocParameters (updated version)', () => { - // These tests will verify the updated renderJSDocParameters from utils.ts - it('should use actual descriptions from properties', async () => { - const { renderJSDocParameters } = await import('../../../../src/codegen/generators/typescript/utils'); - // Create a mock ConstrainedObjectModel with property descriptions - const mockParameters = { - properties: { - userId: { - propertyName: 'userId', - property: { - originalInput: { - description: 'The unique user identifier' - } - } - }, - orderId: { - propertyName: 'orderId', - property: { - originalInput: { - description: 'The order identifier' - } - } - } - } - }; - const result = renderJSDocParameters(mockParameters as any); - expect(result).toContain('@param userId'); - expect(result).toContain('The unique user identifier'); - expect(result).toContain('@param orderId'); - expect(result).toContain('The order identifier'); - }); - - it('should use fallback text when no description available', async () => { - const { renderJSDocParameters } = await import('../../../../src/codegen/generators/typescript/utils'); - const mockParameters = { - properties: { - paramName: { - propertyName: 'paramName', - property: { - originalInput: {} - } - } - } - }; - const result = renderJSDocParameters(mockParameters as any); - // Should have fallback text - expect(result).toContain('@param paramName'); - expect(result).toContain('parameter to use in topic'); - }); - - it('should handle empty parameters', async () => { - const { renderJSDocParameters } = await import('../../../../src/codegen/generators/typescript/utils'); - const mockParameters = { - properties: {} - }; - const result = renderJSDocParameters(mockParameters as any); - expect(result).toBe(''); - }); - }); }); From 56844769cba40dc962b45eb41c09e40e090b7e10 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:45:58 +0100 Subject: [PATCH 7/8] fix: resolve lint errors in utils.ts and jsdoc-utils.spec.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unused escapeJSDocDescription import from utils.ts - Remove extra blank line before realizeChannelName function - Remove blank line padding in test block 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/codegen/generators/typescript/utils.ts | 2 -- test/codegen/generators/typescript/jsdoc-utils.spec.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/src/codegen/generators/typescript/utils.ts b/src/codegen/generators/typescript/utils.ts index 61f092d0..6625c92b 100644 --- a/src/codegen/generators/typescript/utils.ts +++ b/src/codegen/generators/typescript/utils.ts @@ -8,7 +8,6 @@ import { TypeScriptOptions } from '@asyncapi/modelina'; import {DeepPartial} from '../../utils'; -import {escapeJSDocDescription} from './channels/utils'; /** * Cast JSON schema variable to typescript type @@ -95,7 +94,6 @@ function realizeParameterForChannelWithType( return `${parameterName}${requiredType}: ${parameterType})}`; } - /** * Convert RFC 6570 URI with parameters to NATS topic. */ diff --git a/test/codegen/generators/typescript/jsdoc-utils.spec.ts b/test/codegen/generators/typescript/jsdoc-utils.spec.ts index f7763415..f5773b2f 100644 --- a/test/codegen/generators/typescript/jsdoc-utils.spec.ts +++ b/test/codegen/generators/typescript/jsdoc-utils.spec.ts @@ -118,5 +118,4 @@ describe('JSDoc Utilities', () => { expect(lines[lines.length - 1].trim()).toBe('*/'); }); }); - }); From 8c44ad277421d894a0f03486e7ca6e99c09ca5f1 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Fri, 13 Mar 2026 16:56:30 +0100 Subject: [PATCH 8/8] fix: remove unused name field from renderChannelJSDoc parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The name field was declared in the parameters type but never accessed inside the function - only jsDoc was used. Removing it eliminates unnecessary boilerplate across all protocol files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../typescript/channels/protocols/amqp/publishExchange.ts | 1 - .../typescript/channels/protocols/amqp/publishQueue.ts | 1 - .../typescript/channels/protocols/amqp/subscribeQueue.ts | 1 - .../typescript/channels/protocols/eventsource/express.ts | 1 - .../typescript/channels/protocols/eventsource/fetch.ts | 2 -- .../generators/typescript/channels/protocols/kafka/publish.ts | 1 - .../generators/typescript/channels/protocols/kafka/subscribe.ts | 1 - .../generators/typescript/channels/protocols/mqtt/publish.ts | 1 - .../generators/typescript/channels/protocols/mqtt/subscribe.ts | 1 - .../typescript/channels/protocols/nats/corePublish.ts | 1 - .../generators/typescript/channels/protocols/nats/coreReply.ts | 1 - .../typescript/channels/protocols/nats/coreRequest.ts | 1 - .../typescript/channels/protocols/nats/coreSubscribe.ts | 1 - .../typescript/channels/protocols/nats/jetstreamPublish.ts | 1 - .../channels/protocols/nats/jetstreamPullSubscribe.ts | 1 - .../channels/protocols/nats/jetstreamPushSubscription.ts | 1 - .../typescript/channels/protocols/websocket/publish.ts | 1 - .../typescript/channels/protocols/websocket/register.ts | 1 - .../typescript/channels/protocols/websocket/subscribe.ts | 1 - src/codegen/generators/typescript/channels/utils.ts | 2 +- 20 files changed, 1 insertion(+), 21 deletions(-) diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts b/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts index efee8081..217fdaf1 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/publishExchange.ts @@ -91,7 +91,6 @@ channel.publish(exchange, routingKey, Buffer.from(dataToSend), publishOptions);` deprecated, fallbackDescription: `AMQP publish operation for exchange \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts b/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts index f87fa550..c9509e52 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/publishQueue.ts @@ -88,7 +88,6 @@ channel.sendToQueue(queue, Buffer.from(dataToSend), publishOptions);`; deprecated, fallbackDescription: `AMQP publish operation for queue \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts b/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts index c3e6c6cb..22f1256e 100644 --- a/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts +++ b/src/codegen/generators/typescript/channels/protocols/amqp/subscribeQueue.ts @@ -123,7 +123,6 @@ channel.consume(queue, (msg) => { deprecated, fallbackDescription: `AMQP subscribe operation for queue \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts b/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts index 7067e932..409c798b 100644 --- a/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts +++ b/src/codegen/generators/typescript/channels/protocols/eventsource/express.ts @@ -72,7 +72,6 @@ export function renderExpress({ deprecated, fallbackDescription: `Register EventSource endpoint for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts b/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts index 03d08e1d..998cbec1 100644 --- a/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts +++ b/src/codegen/generators/typescript/channels/protocols/eventsource/fetch.ts @@ -83,11 +83,9 @@ export function renderFetch({ fallbackDescription: `Event source fetch for \`${topic}\``, parameters: [ ...functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })), { - name: 'returns', jsDoc: ' * @returns A cleanup function to abort the connection' } ] diff --git a/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts b/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts index a9228a7e..d059bdb5 100644 --- a/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts +++ b/src/codegen/generators/typescript/channels/protocols/kafka/publish.ts @@ -83,7 +83,6 @@ export function renderPublish({ deprecated, fallbackDescription: `Kafka publish operation for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts b/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts index 46618f9a..679679e9 100644 --- a/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/kafka/subscribe.ts @@ -121,7 +121,6 @@ export function renderSubscribe({ deprecated, fallbackDescription: `Kafka subscription for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts b/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts index 540a8d05..517468a0 100644 --- a/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts +++ b/src/codegen/generators/typescript/channels/protocols/mqtt/publish.ts @@ -86,7 +86,6 @@ export function renderPublish({ deprecated, fallbackDescription: `MQTT publish operation for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts b/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts index d36701ee..ca1ca724 100644 --- a/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/mqtt/subscribe.ts @@ -165,7 +165,6 @@ export function renderSubscribe({ deprecated, fallbackDescription: `MQTT subscription for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts b/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts index a7c143ba..a69c5e56 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/corePublish.ts @@ -76,7 +76,6 @@ nc.publish(${addressToUse}, dataToSend, options);`; deprecated, fallbackDescription: `NATS publish operation for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts b/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts index 57a0f9de..971e1247 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/coreReply.ts @@ -113,7 +113,6 @@ msg.respond(dataToSend);`; deprecated, fallbackDescription: `NATS reply operation for \`${requestTopic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts b/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts index 887928bd..1080a325 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/coreRequest.ts @@ -82,7 +82,6 @@ export function renderCoreRequest({ deprecated, fallbackDescription: `NATS request operation for \`${requestTopic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts b/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts index 56e8c712..a267268d 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/coreSubscribe.ts @@ -123,7 +123,6 @@ export function renderCoreSubscribe({ deprecated, fallbackDescription: `Core subscription for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts index 7174a888..cca66e79 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPublish.ts @@ -76,7 +76,6 @@ await js.publish(${addressToUse}, dataToSend, options);`; deprecated, fallbackDescription: `JetStream publish operation for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts index aa9d2fc7..7b18ae13 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPullSubscribe.ts @@ -127,7 +127,6 @@ export function renderJetstreamPullSubscribe({ deprecated, fallbackDescription: `JetStream pull subscription for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts index 9c22ee66..a7724b15 100644 --- a/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts +++ b/src/codegen/generators/typescript/channels/protocols/nats/jetstreamPushSubscription.ts @@ -130,7 +130,6 @@ export function renderJetstreamPushSubscription({ deprecated, fallbackDescription: `JetStream push subscription for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts b/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts index 9bda7846..f2fa01ae 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/publish.ts @@ -39,7 +39,6 @@ export function renderWebSocketPublish({ deprecated, fallbackDescription: `WebSocket client-side function to publish messages to \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/register.ts b/src/codegen/generators/typescript/channels/protocols/websocket/register.ts index f63cc2cc..cfadfc52 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/register.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/register.ts @@ -75,7 +75,6 @@ export function renderWebSocketRegister({ deprecated, fallbackDescription: `WebSocket server-side function to handle messages for \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts b/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts index 54c911d8..ef33eb3d 100644 --- a/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts +++ b/src/codegen/generators/typescript/channels/protocols/websocket/subscribe.ts @@ -77,7 +77,6 @@ export function renderWebSocketSubscribe({ deprecated, fallbackDescription: `WebSocket client-side function to subscribe to messages from \`${topic}\``, parameters: functionParameters.map((param) => ({ - name: param.parameter, jsDoc: param.jsDoc })) }); diff --git a/src/codegen/generators/typescript/channels/utils.ts b/src/codegen/generators/typescript/channels/utils.ts index 4b118b8f..39d10626 100644 --- a/src/codegen/generators/typescript/channels/utils.ts +++ b/src/codegen/generators/typescript/channels/utils.ts @@ -263,7 +263,7 @@ export function renderChannelJSDoc(params: { description?: string; deprecated?: boolean; fallbackDescription: string; - parameters?: Array<{name: string; jsDoc: string}>; + parameters?: Array<{jsDoc: string}>; }): string { const { description,