diff --git a/src/codegen/generators/typescript/channels/openapi.ts b/src/codegen/generators/typescript/channels/openapi.ts index d80dc150..73f801a2 100644 --- a/src/codegen/generators/typescript/channels/openapi.ts +++ b/src/codegen/generators/typescript/channels/openapi.ts @@ -217,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), @@ -236,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..217fdaf1 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,16 @@ 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) => ({ + 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..c9509e52 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,16 @@ 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) => ({ + 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..22f1256e 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,16 @@ 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) => ({ + 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..409c798b 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,17 @@ export function renderExpress({ } ]; - const code = `function ${functionName}({ + const jsDoc = renderChannelJSDoc({ + description, + deprecated, + fallbackDescription: `Register EventSource endpoint for \`${topic}\``, + parameters: functionParameters.map((param) => ({ + 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..998cbec1 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,21 @@ 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) => ({ + jsDoc: param.jsDoc + })), + { + 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..b981c35d 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} +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..d059bdb5 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,16 @@ 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) => ({ + 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..679679e9 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,27 @@ 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) => ({ + 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..517468a0 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,16 @@ 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) => ({ + 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..ca1ca724 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,19 @@ 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) => ({ + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -168,11 +176,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..a69c5e56 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,16 @@ 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) => ({ + 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..971e1247 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,19 @@ 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) => ({ + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving the request * @@ -116,11 +124,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..1080a325 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,16 @@ 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) => ({ + 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..a267268d 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,19 @@ 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) => ({ + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -126,11 +134,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..cca66e79 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,16 @@ 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) => ({ + 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..7b18ae13 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,19 @@ 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) => ({ + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -130,11 +138,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..a7724b15 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,19 @@ 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) => ({ + jsDoc: param.jsDoc + })) + }); + const code = `/** * Callback for when receiving messages * @@ -133,11 +141,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..f2fa01ae 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,16 @@ 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) => ({ + 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..cfadfc52 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,16 @@ 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) => ({ + 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..ef33eb3d 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,16 @@ 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) => ({ + 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..39d10626 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<{jsDoc: string}>; +}): string { + const { + description, + deprecated, + fallbackDescription, + parameters = [] + } = params; + + const desc = description + ? escapeJSDocDescription(description) + : escapeJSDocDescription(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..6625c92b 100644 --- a/src/codegen/generators/typescript/utils.ts +++ b/src/codegen/generators/typescript/utils.ts @@ -94,21 +94,6 @@ function realizeParameterForChannelWithType( return `${parameterName}${requiredType}: ${parameterType})}`; } -/** - * Render channel parameters for JSDoc - * - * @param {Object.} channelParameters to render - */ -export function renderJSDocParameters( - channelParameters: ConstrainedObjectModel -) { - return Object.keys(channelParameters.properties) - .map((paramName) => { - return `* @param ${paramName} parameter to use in topic`; - }) - .join('\n'); -} - /** * Convert RFC 6570 URI with parameters to NATS topic. */ 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..4d19322f 100644 --- a/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap +++ b/test/codegen/generators/typescript/__snapshots__/channels.spec.ts.snap @@ -1003,6 +1003,9 @@ export interface PostAddPetContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } +/** + * HTTP POST request to /pet + */ async function postAddPet(context: PostAddPetContext): Promise> { // Apply defaults const config = { @@ -1123,6 +1126,9 @@ export interface PutUpdatePetContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } +/** + * HTTP PUT request to /pet + */ async function putUpdatePet(context: PutUpdatePetContext): Promise> { // Apply defaults const config = { @@ -1243,6 +1249,9 @@ export interface GetFindPetsByStatusAndCategoryContext extends HttpClientContext requestHeaders?: { marshal: () => string }; } +/** + * Find pets by status and category with additional filtering options + */ async function getFindPetsByStatusAndCategory(context: GetFindPetsByStatusAndCategoryContext): Promise> { // Apply defaults const config = { @@ -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,6 +2938,9 @@ export interface GetPingRequestContext extends HttpClientContext { requestHeaders?: { marshal: () => string }; } +/** + * HTTP GET request to /ping + */ async function getPingRequest(context: GetPingRequestContext = {}): Promise> { // Apply defaults const config = { @@ -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..f5773b2f --- /dev/null +++ b/test/codegen/generators/typescript/jsdoc-utils.spec.ts @@ -0,0 +1,121 @@ +/** + * 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('*/'); + }); + }); +}); 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'); }); }); 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'); + }); + }); +});