From 039c736d92d9c1b01187de1a271a5432533f6866 Mon Sep 17 00:00:00 2001 From: jonaslagoni Date: Mon, 27 Jul 2026 09:38:15 +0000 Subject: [PATCH] chore(release): v0.80.0 --- docs/README.md | 1 + docs/contributing.md | 1 + docs/migrations/v0.md | 1 + docs/usage.md | 8 +- .../headers/OrderCancelledHeaders.ts | 117 ++++ .../generated/headers/OrderCreatedHeaders.ts | 117 ++++ .../headers/OrderLifecycleHeaders.ts | 5 + .../generated/headers/OrderUpdatedHeaders.ts | 117 ++++ .../src/generated/index.ts | 554 +----------------- .../src/generated/kafka.ts | 251 ++++++++ .../src/generated/nats.ts | 543 +++++++++++++++++ .../parameter/OrderLifecycleParameters.ts | 11 +- .../src/generated/payload/Address.ts | 76 ++- .../src/generated/payload/Money.ts | 61 +- .../src/generated/payload/OrderCancelled.ts | 76 ++- .../src/generated/payload/OrderCreated.ts | 98 ++-- .../src/generated/payload/OrderItem.ts | 76 ++- .../payload/OrderLifecyclePayload.ts | 8 +- .../src/generated/payload/OrderUpdated.ts | 86 +-- .../payload/SubscribeToOrderEventsPayload.ts | 8 +- .../src/generated/NatsClient.ts | 29 +- .../channels/headers/OrderCancelledHeaders.ts | 117 ++++ .../channels/headers/OrderCreatedHeaders.ts | 117 ++++ .../channels/headers/OrderLifecycleHeaders.ts | 5 + .../channels/headers/OrderUpdatedHeaders.ts | 117 ++++ .../src/generated/channels/index.ts | 388 +----------- .../src/generated/channels/nats.ts | 543 +++++++++++++++++ .../parameter/OrderLifecycleParameters.ts | 11 +- .../src/generated/channels/payload/Address.ts | 76 ++- .../src/generated/channels/payload/Money.ts | 61 +- .../channels/payload/OrderCancelled.ts | 76 ++- .../channels/payload/OrderCreated.ts | 98 ++-- .../channels/payload/OrderEventsPayload.ts | 8 +- .../generated/channels/payload/OrderItem.ts | 76 ++- .../channels/payload/OrderLifecyclePayload.ts | 8 +- .../channels/payload/OrderUpdated.ts | 86 +-- .../src/generated/headers/ActorType.ts | 6 + .../headers/AdminActionPerformedHeaders.ts | 91 +-- .../headers/InventoryUpdatedHeaders.ts | 75 ++- .../headers/NotificationSentHeaders.ts | 87 +-- .../generated/headers/OrderCreatedHeaders.ts | 71 ++- .../src/generated/headers/OrderEventType.ts | 6 + .../generated/headers/OrderEventsHeaders.ts | 4 + .../src/generated/headers/OrderReasonCode.ts | 6 + .../src/generated/headers/OrderStatus.ts | 6 + .../headers/OrderStatusChangedHeaders.ts | 209 +++++++ .../headers/PaymentProcessedHeaders.ts | 83 +-- .../src/generated/headers/Priority.ts | 6 + .../headers/UserBehaviorTrackedHeaders.ts | 91 +-- .../parameters/InventoryUpdatesParameters.ts | 19 +- .../parameters/OrderEventsParameters.ts | 15 +- .../parameters/ProductUpdatesParameters.ts | 15 +- .../parameters/SupportTicketsParameters.ts | 19 +- .../parameters/TenantAnalyticsParameters.ts | 23 +- .../parameters/UserActivityParameters.ts | 11 +- .../parameters/UserNotificationsParameters.ts | 19 +- .../src/generated/models/Address.ts | 51 +- .../src/generated/models/Attachment.ts | 43 +- .../src/generated/models/EmailNotification.ts | 57 +- .../src/generated/models/OrderCreated.ts | 75 ++- .../src/generated/models/OrderItem.ts | 53 +- .../generated/models/OrderStatusChanged.ts | 51 +- .../src/generated/models/PaymentProcessed.ts | 61 +- .../src/generated/models/PushNotification.ts | 49 +- .../src/generated/models/SmsNotification.ts | 41 +- .../src/generated/types/Types.ts | 7 + .../src/__gen__/payloads/UserSignedUp.ts | 88 ++- .../src/__gen__/payloads/UserSignedUp.ts | 88 ++- mcp-server/lib/resources/bundled-docs.ts | 16 +- package-lock.json | 4 +- package.json | 2 +- .../inputs/asyncapi/generators/headers.ts | 3 +- website/static/codegen.browser.mjs | 352 +++++++---- 73 files changed, 3910 insertions(+), 2024 deletions(-) create mode 100644 examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCancelledHeaders.ts create mode 100644 examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCreatedHeaders.ts create mode 100644 examples/ecommerce-asyncapi-channels/src/generated/headers/OrderLifecycleHeaders.ts create mode 100644 examples/ecommerce-asyncapi-channels/src/generated/headers/OrderUpdatedHeaders.ts create mode 100644 examples/ecommerce-asyncapi-channels/src/generated/kafka.ts create mode 100644 examples/ecommerce-asyncapi-channels/src/generated/nats.ts create mode 100644 examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCancelledHeaders.ts create mode 100644 examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCreatedHeaders.ts create mode 100644 examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderLifecycleHeaders.ts create mode 100644 examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderUpdatedHeaders.ts create mode 100644 examples/ecommerce-asyncapi-client/src/generated/channels/nats.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/ActorType.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventType.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventsHeaders.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/OrderReasonCode.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatus.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatusChangedHeaders.ts create mode 100644 examples/ecommerce-asyncapi-headers/src/generated/headers/Priority.ts diff --git a/docs/README.md b/docs/README.md index 4fb85dbe..a23091f0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -101,5 +101,6 @@ Connect AI assistants like Claude Code, Cursor, and Windsurf to The Codegen Proj + diff --git a/docs/contributing.md b/docs/contributing.md index 0cfcd986..ed4eeafd 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -219,5 +219,6 @@ Prefix that follows specification is not enough though. Remember that the title + diff --git a/docs/migrations/v0.md b/docs/migrations/v0.md index 4db6171a..5e3a9895 100644 --- a/docs/migrations/v0.md +++ b/docs/migrations/v0.md @@ -247,5 +247,6 @@ import * as NodeFetch from 'node-fetch'; + diff --git a/docs/usage.md b/docs/usage.md index e29a6919..24d3b5e3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -9,7 +9,7 @@ $ npm install -g @the-codegen-project/cli $ codegen COMMAND running command... $ codegen (--version) -@the-codegen-project/cli/0.79.0 darwin-arm64 node-v24.15.0 +@the-codegen-project/cli/0.80.0 linux-x64 node-v22.23.1 $ codegen --help [COMMAND] USAGE $ codegen COMMAND @@ -93,7 +93,7 @@ DESCRIPTION configuration. ``` -_See code: [src/commands/generate.ts](https://github.com/the-codegen-project/cli/blob/v0.79.0/src/commands/generate.ts)_ +_See code: [src/commands/generate.ts](https://github.com/the-codegen-project/cli/blob/v0.80.0/src/commands/generate.ts)_ ## `codegen help [COMMAND]` @@ -167,7 +167,7 @@ DESCRIPTION Initialize The Codegen Project in your project ``` -_See code: [src/commands/init.ts](https://github.com/the-codegen-project/cli/blob/v0.79.0/src/commands/init.ts)_ +_See code: [src/commands/init.ts](https://github.com/the-codegen-project/cli/blob/v0.80.0/src/commands/init.ts)_ ## `codegen telemetry ACTION` @@ -200,7 +200,7 @@ EXAMPLES $ codegen telemetry disable ``` -_See code: [src/commands/telemetry.ts](https://github.com/the-codegen-project/cli/blob/v0.79.0/src/commands/telemetry.ts)_ +_See code: [src/commands/telemetry.ts](https://github.com/the-codegen-project/cli/blob/v0.80.0/src/commands/telemetry.ts)_ ## `codegen version` diff --git a/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCancelledHeaders.ts b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCancelledHeaders.ts new file mode 100644 index 00000000..a31e61e3 --- /dev/null +++ b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCancelledHeaders.ts @@ -0,0 +1,117 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderCancelledHeaders { + private _xCorrelationId: string; + private _xOrderId: string; + private _xCustomerId: string; + private _xSourceService?: string; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xOrderId: string, + xCustomerId: string, + xSourceService?: string, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xOrderId = input.xOrderId; + this._xCustomerId = input.xCustomerId; + this._xSourceService = input.xSourceService; + this._additionalProperties = input.additionalProperties; + } + + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + get xOrderId(): string { return this._xOrderId; } + set xOrderId(xOrderId: string) { this._xOrderId = xOrderId; } + + get xCustomerId(): string { return this._xCustomerId; } + set xCustomerId(xCustomerId: string) { this._xCustomerId = xCustomerId; } + + get xSourceService(): string | undefined { return this._xSourceService; } + set xSourceService(xSourceService: string | undefined) { this._xSourceService = xSourceService; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xOrderId !== undefined) { + json["x-order-id"] = this.xOrderId; + } + if(this.xCustomerId !== undefined) { + json["x-customer-id"] = this.xCustomerId; + } + if(this.xSourceService !== undefined) { + json["x-source-service"] = this.xSourceService; + } + 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(["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCancelledHeaders { + const instance = new OrderCancelledHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-order-id"] !== undefined) { + instance.xOrderId = obj["x-order-id"] as string; + } + if (obj["x-customer-id"] !== undefined) { + instance.xCustomerId = obj["x-customer-id"] as string; + } + if (obj["x-source-service"] !== undefined) { + instance.xSourceService = obj["x-source-service"] as string; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderCancelledHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCancelledHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","required":["x-correlation-id","x-order-id","x-customer-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid"},"x-order-id":{"type":"string","format":"uuid"},"x-customer-id":{"type":"string","format":"uuid"},"x-source-service":{"type":"string"}},"$id":"OrderCancelledHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderCancelledHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCreatedHeaders.ts b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCreatedHeaders.ts new file mode 100644 index 00000000..ccac000b --- /dev/null +++ b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderCreatedHeaders.ts @@ -0,0 +1,117 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderCreatedHeaders { + private _xCorrelationId: string; + private _xOrderId: string; + private _xCustomerId: string; + private _xSourceService?: string; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xOrderId: string, + xCustomerId: string, + xSourceService?: string, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xOrderId = input.xOrderId; + this._xCustomerId = input.xCustomerId; + this._xSourceService = input.xSourceService; + this._additionalProperties = input.additionalProperties; + } + + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + get xOrderId(): string { return this._xOrderId; } + set xOrderId(xOrderId: string) { this._xOrderId = xOrderId; } + + get xCustomerId(): string { return this._xCustomerId; } + set xCustomerId(xCustomerId: string) { this._xCustomerId = xCustomerId; } + + get xSourceService(): string | undefined { return this._xSourceService; } + set xSourceService(xSourceService: string | undefined) { this._xSourceService = xSourceService; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xOrderId !== undefined) { + json["x-order-id"] = this.xOrderId; + } + if(this.xCustomerId !== undefined) { + json["x-customer-id"] = this.xCustomerId; + } + if(this.xSourceService !== undefined) { + json["x-source-service"] = this.xSourceService; + } + 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(["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCreatedHeaders { + const instance = new OrderCreatedHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-order-id"] !== undefined) { + instance.xOrderId = obj["x-order-id"] as string; + } + if (obj["x-customer-id"] !== undefined) { + instance.xCustomerId = obj["x-customer-id"] as string; + } + if (obj["x-source-service"] !== undefined) { + instance.xSourceService = obj["x-source-service"] as string; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderCreatedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCreatedHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","required":["x-correlation-id","x-order-id","x-customer-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid"},"x-order-id":{"type":"string","format":"uuid"},"x-customer-id":{"type":"string","format":"uuid"},"x-source-service":{"type":"string"}},"$id":"OrderCreatedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderCreatedHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderLifecycleHeaders.ts b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderLifecycleHeaders.ts new file mode 100644 index 00000000..e2b9df98 --- /dev/null +++ b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderLifecycleHeaders.ts @@ -0,0 +1,5 @@ +import {OrderCreatedHeaders} from './OrderCreatedHeaders'; +import {OrderUpdatedHeaders} from './OrderUpdatedHeaders'; +import {OrderCancelledHeaders} from './OrderCancelledHeaders'; +type OrderLifecycleHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders; +export { OrderLifecycleHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderUpdatedHeaders.ts b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderUpdatedHeaders.ts new file mode 100644 index 00000000..000c6cfb --- /dev/null +++ b/examples/ecommerce-asyncapi-channels/src/generated/headers/OrderUpdatedHeaders.ts @@ -0,0 +1,117 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderUpdatedHeaders { + private _xCorrelationId: string; + private _xOrderId: string; + private _xCustomerId: string; + private _xSourceService?: string; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xOrderId: string, + xCustomerId: string, + xSourceService?: string, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xOrderId = input.xOrderId; + this._xCustomerId = input.xCustomerId; + this._xSourceService = input.xSourceService; + this._additionalProperties = input.additionalProperties; + } + + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + get xOrderId(): string { return this._xOrderId; } + set xOrderId(xOrderId: string) { this._xOrderId = xOrderId; } + + get xCustomerId(): string { return this._xCustomerId; } + set xCustomerId(xCustomerId: string) { this._xCustomerId = xCustomerId; } + + get xSourceService(): string | undefined { return this._xSourceService; } + set xSourceService(xSourceService: string | undefined) { this._xSourceService = xSourceService; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xOrderId !== undefined) { + json["x-order-id"] = this.xOrderId; + } + if(this.xCustomerId !== undefined) { + json["x-customer-id"] = this.xCustomerId; + } + if(this.xSourceService !== undefined) { + json["x-source-service"] = this.xSourceService; + } + 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(["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderUpdatedHeaders { + const instance = new OrderUpdatedHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-order-id"] !== undefined) { + instance.xOrderId = obj["x-order-id"] as string; + } + if (obj["x-customer-id"] !== undefined) { + instance.xCustomerId = obj["x-customer-id"] as string; + } + if (obj["x-source-service"] !== undefined) { + instance.xSourceService = obj["x-source-service"] as string; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderUpdatedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderUpdatedHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","required":["x-correlation-id","x-order-id","x-customer-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid"},"x-order-id":{"type":"string","format":"uuid"},"x-customer-id":{"type":"string","format":"uuid"},"x-source-service":{"type":"string"}},"$id":"OrderUpdatedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderUpdatedHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/index.ts b/examples/ecommerce-asyncapi-channels/src/generated/index.ts index d87d702b..82add39d 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/index.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/index.ts @@ -1,552 +1,4 @@ -import {OrderCreated} from './payload/OrderCreated'; -import {OrderUpdated} from './payload/OrderUpdated'; -import {OrderCancelled} from './payload/OrderCancelled'; -import * as SubscribeToOrderEventsPayloadModule from './payload/SubscribeToOrderEventsPayload'; -import * as OrderLifecyclePayloadModule from './payload/OrderLifecyclePayload'; -import {OrderItem} from './payload/OrderItem'; -import {Money} from './payload/Money'; -import {Currency} from './payload/Currency'; -import {Address} from './payload/Address'; -import {OrderStatus} from './payload/OrderStatus'; -import {OrderLifecycleParameters} from './parameter/OrderLifecycleParameters'; -import * as Nats from 'nats'; -import * as Kafka from 'kafkajs'; -export const Protocols = { -nats: { - /** - * NATS publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @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 - */ -publishToPublishOrderCreated: ({ - message, - parameters, - nc, - codec = Nats.JSONCodec(), - options -}: { - message: OrderCreated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * JetStream publish operation for `orders.{action}` - * - * @param message to publish over jetstream - * @param parameters for topic substitution - * @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 - */ -jetStreamPublishToPublishOrderCreated: ({ - message, - parameters, - js, - codec = Nats.JSONCodec(), - options = {} -}: { - message: OrderCreated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * NATS publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @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 - */ -publishToPublishOrderUpdated: ({ - message, - parameters, - nc, - codec = Nats.JSONCodec(), - options -}: { - message: OrderUpdated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * JetStream publish operation for `orders.{action}` - * - * @param message to publish over jetstream - * @param parameters for topic substitution - * @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 - */ -jetStreamPublishToPublishOrderUpdated: ({ - message, - parameters, - js, - codec = Nats.JSONCodec(), - options = {} -}: { - message: OrderUpdated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * NATS publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @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 - */ -publishToPublishOrderCancelled: ({ - message, - parameters, - nc, - codec = Nats.JSONCodec(), - options -}: { - message: OrderCancelled, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * JetStream publish operation for `orders.{action}` - * - * @param message to publish over jetstream - * @param parameters for topic substitution - * @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 - */ -jetStreamPublishToPublishOrderCancelled: ({ - message, - parameters, - js, - codec = Nats.JSONCodec(), - options = {} -}: { - message: OrderCancelled, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback subscribeToSubscribeToOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param natsMsg - */ +import * as nats from './nats'; +import * as kafka from './kafka'; -/** - * Core subscription for `orders.{action}` - * - * @param {subscribeToSubscribeToOrderEventsCallback} onDataCallback to call when messages are received - * @param parameters for topic substitution - * @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 - */ -subscribeToSubscribeToOrderEvents: ({ - onDataCallback, - parameters, - nc, - codec = Nats.JSONCodec(), - options, - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, natsMsg?: Nats.Msg) => void, - parameters: OrderLifecycleParameters, - nc: Nats.NatsConnection, - codec?: Nats.Codec, - options?: Nats.SubscriptionOptions, - skipMessageValidation?: boolean -}): Promise => { - return new Promise(async (resolve, reject) => { - try { - const subscription = nc.subscribe(parameters.getChannelWithParameters('orders.{action}'), options); - const validator = SubscribeToOrderEventsPayloadModule.createValidator(); - (async () => { - for await (const msg of subscription) { - const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) - let receivedData: any = codec.decode(msg.data); -if(!skipMessageValidation) { - const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, msg); continue; - } - } -onDataCallback(undefined, SubscribeToOrderEventsPayloadModule.unmarshal(receivedData), parameters, msg); - } - })(); - resolve(subscription); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback jetStreamPullSubscribeToSubscribeToOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param jetstreamMsg - */ - -/** - * JetStream pull subscription for `orders.{action}` - * - * @param {jetStreamPullSubscribeToSubscribeToOrderEventsCallback} 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 - * @param codec the serialization codec to use while transmitting the message - * @param skipMessageValidation turn off runtime validation of incoming messages - */ -jetStreamPullSubscribeToSubscribeToOrderEvents: ({ - onDataCallback, - parameters, - js, - options, - codec = Nats.JSONCodec(), - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, jetstreamMsg?: Nats.JsMsg) => void, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), options); - const validator = SubscribeToOrderEventsPayloadModule.createValidator(); - (async () => { - for await (const msg of subscription) { - const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) - let receivedData: any = codec.decode(msg.data); -if(!skipMessageValidation) { - const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, msg); continue; - } - } -onDataCallback(undefined, SubscribeToOrderEventsPayloadModule.unmarshal(receivedData), parameters, msg); - } - })(); - resolve(subscription); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback jetStreamPushSubscriptionFromSubscribeToOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param jetstreamMsg - */ - -/** - * JetStream push subscription for `orders.{action}` - * - * @param {jetStreamPushSubscriptionFromSubscribeToOrderEventsCallback} 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 - * @param codec the serialization codec to use while transmitting the message - * @param skipMessageValidation turn off runtime validation of incoming messages - */ -jetStreamPushSubscriptionFromSubscribeToOrderEvents: ({ - onDataCallback, - parameters, - js, - options, - codec = Nats.JSONCodec(), - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, jetstreamMsg?: Nats.JsMsg) => void, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), options); - const validator = SubscribeToOrderEventsPayloadModule.createValidator(); - (async () => { - for await (const msg of subscription) { - const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) - let receivedData: any = codec.decode(msg.data); -if(!skipMessageValidation) { - const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, msg); continue; - } - } -onDataCallback(undefined, SubscribeToOrderEventsPayloadModule.unmarshal(receivedData), parameters, msg); - } - })(); - resolve(subscription); - } catch (e: any) { - reject(e); - } - }); -} -}, -kafka: { - /** - * Kafka publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @param kafka the KafkaJS client to publish from - */ -produceToPublishOrderCreated: ({ - message, - parameters, - kafka -}: { - message: OrderCreated, - parameters: OrderLifecycleParameters, - 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: parameters.getChannelWithParameters('orders.{action}'), - messages: [ - { value: dataToSend }, - ], - }); - resolve(producer); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Kafka publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @param kafka the KafkaJS client to publish from - */ -produceToPublishOrderUpdated: ({ - message, - parameters, - kafka -}: { - message: OrderUpdated, - parameters: OrderLifecycleParameters, - 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: parameters.getChannelWithParameters('orders.{action}'), - messages: [ - { value: dataToSend }, - ], - }); - resolve(producer); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Kafka publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @param kafka the KafkaJS client to publish from - */ -produceToPublishOrderCancelled: ({ - message, - parameters, - kafka -}: { - message: OrderCancelled, - parameters: OrderLifecycleParameters, - 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: parameters.getChannelWithParameters('orders.{action}'), - messages: [ - { value: dataToSend }, - ], - }); - resolve(producer); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback consumeFromSubscribeToOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param kafkaMsg - */ - -/** - * Kafka subscription for `orders.{action}` - * - * @param {consumeFromSubscribeToOrderEventsCallback} 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 - * @param skipMessageValidation turn off runtime validation of incoming messages - */ -consumeFromSubscribeToOrderEvents: ({ - onDataCallback, - parameters, - kafka, - options = {fromBeginning: true, groupId: ''}, - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, kafkaMsg?: Kafka.EachMessagePayload) => void, - parameters: OrderLifecycleParameters, - 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 = SubscribeToOrderEventsPayloadModule.createValidator(); - await consumer.connect(); - await consumer.subscribe({ topic: parameters.getChannelWithParameters('orders.{action}'), fromBeginning: options.fromBeginning }); - await consumer.run({ - eachMessage: async (kafkaMessage: Kafka.EachMessagePayload) => { - const { topic, message } = kafkaMessage; - const receivedData = message.value?.toString()!; - const parameters = OrderLifecycleParameters.createFromChannel(topic, 'orders.{action}', /^orders.([^.]*)$/); - if(!skipMessageValidation) { - const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - return onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, kafkaMessage); - } - } -const callbackData = SubscribeToOrderEventsPayloadModule.unmarshal(receivedData); -onDataCallback(undefined, callbackData, parameters, kafkaMessage); - } - }); - resolve(consumer); - } catch (e: any) { - reject(e); - } - }); -} -}}; \ No newline at end of file +export {nats, kafka}; diff --git a/examples/ecommerce-asyncapi-channels/src/generated/kafka.ts b/examples/ecommerce-asyncapi-channels/src/generated/kafka.ts new file mode 100644 index 00000000..86afeed5 --- /dev/null +++ b/examples/ecommerce-asyncapi-channels/src/generated/kafka.ts @@ -0,0 +1,251 @@ +import {OrderCreated, OrderCreatedInterface} from './payload/OrderCreated'; +import {OrderUpdated, OrderUpdatedInterface} from './payload/OrderUpdated'; +import {OrderCancelled, OrderCancelledInterface} from './payload/OrderCancelled'; +import * as SubscribeToOrderEventsPayloadModule from './payload/SubscribeToOrderEventsPayload'; +import * as OrderLifecyclePayloadModule from './payload/OrderLifecyclePayload'; +import {OrderItem, OrderItemInterface} from './payload/OrderItem'; +import {Money, MoneyInterface} from './payload/Money'; +import {Currency} from './payload/Currency'; +import {Address, AddressInterface} from './payload/Address'; +import {OrderStatus} from './payload/OrderStatus'; +import {OrderLifecycleParameters, OrderLifecycleParametersInterface} from './parameter/OrderLifecycleParameters'; +import {OrderLifecycleHeaders} from './headers/OrderLifecycleHeaders'; +import * as Kafka from 'kafkajs'; + +/** + * Kafka publish operation for `orders.{action}` + * + * @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 + */ +function produceToPublishOrderCreated({ + message, + parameters, + headers, + kafka +}: { + message: OrderCreatedInterface | OrderCreated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + kafka: Kafka.Kafka +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCreated ? message : new OrderCreated(message)).marshal(); + const producer = kafka.producer(); + await producer.connect(); + // Set up headers if provided + let messageHeaders: Record | undefined = undefined; + if (headers) { + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + messageHeaders = {}; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + messageHeaders[key] = String(value); + } + } + } + + await producer.send({ + topic: (parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), + messages: [ + { + value: dataToSend, + headers: messageHeaders + }, + ], + }); + resolve(producer); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Kafka publish operation for `orders.{action}` + * + * @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 + */ +function produceToPublishOrderUpdated({ + message, + parameters, + headers, + kafka +}: { + message: OrderUpdatedInterface | OrderUpdated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + kafka: Kafka.Kafka +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderUpdated ? message : new OrderUpdated(message)).marshal(); + const producer = kafka.producer(); + await producer.connect(); + // Set up headers if provided + let messageHeaders: Record | undefined = undefined; + if (headers) { + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + messageHeaders = {}; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + messageHeaders[key] = String(value); + } + } + } + + await producer.send({ + topic: (parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), + messages: [ + { + value: dataToSend, + headers: messageHeaders + }, + ], + }); + resolve(producer); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Kafka publish operation for `orders.{action}` + * + * @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 + */ +function produceToPublishOrderCancelled({ + message, + parameters, + headers, + kafka +}: { + message: OrderCancelledInterface | OrderCancelled, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + kafka: Kafka.Kafka +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCancelled ? message : new OrderCancelled(message)).marshal(); + const producer = kafka.producer(); + await producer.connect(); + // Set up headers if provided + let messageHeaders: Record | undefined = undefined; + if (headers) { + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + messageHeaders = {}; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + messageHeaders[key] = String(value); + } + } + } + + await producer.send({ + topic: (parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), + messages: [ + { + value: dataToSend, + headers: messageHeaders + }, + ], + }); + resolve(producer); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback consumeFromSubscribeToOrderEventsCallback + * @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 + * @param kafkaMsg + */ + +/** + * Kafka subscription for `orders.{action}` + * + * @param {consumeFromSubscribeToOrderEventsCallback} 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 + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function consumeFromSubscribeToOrderEvents({ + onDataCallback, + parameters, + kafka, + options = {fromBeginning: true, groupId: ''}, + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, kafkaMsg?: Kafka.EachMessagePayload) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + 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 = SubscribeToOrderEventsPayloadModule.createValidator(); + await consumer.connect(); + await consumer.subscribe({ topic: (parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), fromBeginning: options.fromBeginning }); + await consumer.run({ + eachMessage: async (kafkaMessage: Kafka.EachMessagePayload) => { + const { topic, message } = kafkaMessage; + const receivedData = message.value?.toString()!; + const parameters = OrderLifecycleParameters.createFromChannel(topic, 'orders.{action}', /^orders.([^.]*)$/); + + // Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (message.headers) { + const headerObj: Record = {}; + for (const [key, value] of Object.entries(message.headers)) { + if (value !== undefined) { + headerObj[key] = value.toString(); + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + return onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, extractedHeaders, kafkaMessage); + } + } +const callbackData = SubscribeToOrderEventsPayloadModule.unmarshal(receivedData); +onDataCallback(undefined, callbackData, parameters, extractedHeaders, kafkaMessage); + } + }); + resolve(consumer); + } catch (e: any) { + reject(e); + } + }); +} + +export { produceToPublishOrderCreated, produceToPublishOrderUpdated, produceToPublishOrderCancelled, consumeFromSubscribeToOrderEvents }; diff --git a/examples/ecommerce-asyncapi-channels/src/generated/nats.ts b/examples/ecommerce-asyncapi-channels/src/generated/nats.ts new file mode 100644 index 00000000..e101d374 --- /dev/null +++ b/examples/ecommerce-asyncapi-channels/src/generated/nats.ts @@ -0,0 +1,543 @@ +import {OrderCreated, OrderCreatedInterface} from './payload/OrderCreated'; +import {OrderUpdated, OrderUpdatedInterface} from './payload/OrderUpdated'; +import {OrderCancelled, OrderCancelledInterface} from './payload/OrderCancelled'; +import * as SubscribeToOrderEventsPayloadModule from './payload/SubscribeToOrderEventsPayload'; +import * as OrderLifecyclePayloadModule from './payload/OrderLifecyclePayload'; +import {OrderItem, OrderItemInterface} from './payload/OrderItem'; +import {Money, MoneyInterface} from './payload/Money'; +import {Currency} from './payload/Currency'; +import {Address, AddressInterface} from './payload/Address'; +import {OrderStatus} from './payload/OrderStatus'; +import {OrderLifecycleParameters, OrderLifecycleParametersInterface} from './parameter/OrderLifecycleParameters'; +import {OrderLifecycleHeaders} from './headers/OrderLifecycleHeaders'; +import * as Nats from 'nats'; + +/** + * NATS publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToPublishOrderCreated({ + message, + parameters, + headers, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: OrderCreatedInterface | OrderCreated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCreated ? message : new OrderCreated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +nc.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * JetStream publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToPublishOrderCreated({ + message, + parameters, + headers, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: OrderCreatedInterface | OrderCreated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCreated ? message : new OrderCreated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +await js.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * NATS publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToPublishOrderUpdated({ + message, + parameters, + headers, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: OrderUpdatedInterface | OrderUpdated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderUpdated ? message : new OrderUpdated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +nc.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * JetStream publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToPublishOrderUpdated({ + message, + parameters, + headers, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: OrderUpdatedInterface | OrderUpdated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderUpdated ? message : new OrderUpdated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +await js.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * NATS publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToPublishOrderCancelled({ + message, + parameters, + headers, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: OrderCancelledInterface | OrderCancelled, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCancelled ? message : new OrderCancelled(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +nc.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * JetStream publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToPublishOrderCancelled({ + message, + parameters, + headers, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: OrderCancelledInterface | OrderCancelled, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCancelled ? message : new OrderCancelled(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +await js.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback subscribeToSubscribeToOrderEventsCallback + * @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 were received with the message + * @param natsMsg + */ + +/** + * Core subscription for `orders.{action}` + * + * @param {subscribeToSubscribeToOrderEventsCallback} onDataCallback to call when messages are received + * @param parameters for topic substitution + * @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 subscribeToSubscribeToOrderEvents({ + onDataCallback, + parameters, + nc, + codec = Nats.JSONCodec(), + options, + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, natsMsg?: Nats.Msg) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.SubscriptionOptions, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const subscription = nc.subscribe((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), options); + const validator = SubscribeToOrderEventsPayloadModule.createValidator(); + (async () => { + for await (const msg of subscription) { + const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) + let receivedData: any = codec.decode(msg.data); +// Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (msg.headers) { + const headerObj: Record = {}; + // NATS headers support both iteration and get() method + if (typeof msg.headers.keys === 'function') { + // Use keys() method if available (NATS MsgHdrs) + for (const key of msg.headers.keys()) { + headerObj[key] = msg.headers.get(key); + } + } else { + // Fallback to Object.entries for plain objects + for (const [key, value] of Object.entries(msg.headers)) { + headerObj[key] = value; + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined,parameters, extractedHeaders, msg); continue; + } + } +onDataCallback(undefined, SubscribeToOrderEventsPayloadModule.unmarshal(receivedData), parameters, extractedHeaders, msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback jetStreamPullSubscribeToSubscribeToOrderEventsCallback + * @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 + * @param jetstreamMsg + */ + +/** + * JetStream pull subscription for `orders.{action}` + * + * @param {jetStreamPullSubscribeToSubscribeToOrderEventsCallback} 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 + * @param codec the serialization codec to use while transmitting the message + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function jetStreamPullSubscribeToSubscribeToOrderEvents({ + onDataCallback, + parameters, + js, + options, + codec = Nats.JSONCodec(), + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, jetstreamMsg?: Nats.JsMsg) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + 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((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), options); + const validator = SubscribeToOrderEventsPayloadModule.createValidator(); + (async () => { + for await (const msg of subscription) { + const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) + let receivedData: any = codec.decode(msg.data); +// Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (msg.headers) { + const headerObj: Record = {}; + // NATS headers support both iteration and get() method + if (typeof msg.headers.keys === 'function') { + // Use keys() method if available (NATS MsgHdrs) + for (const key of msg.headers.keys()) { + headerObj[key] = msg.headers.get(key); + } + } else { + // Fallback to Object.entries for plain objects + for (const [key, value] of Object.entries(msg.headers)) { + headerObj[key] = value; + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined,parameters, extractedHeaders, msg); continue; + } + } +onDataCallback(undefined, SubscribeToOrderEventsPayloadModule.unmarshal(receivedData), parameters, extractedHeaders, msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback jetStreamPushSubscriptionFromSubscribeToOrderEventsCallback + * @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 + * @param jetstreamMsg + */ + +/** + * JetStream push subscription for `orders.{action}` + * + * @param {jetStreamPushSubscriptionFromSubscribeToOrderEventsCallback} 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 + * @param codec the serialization codec to use while transmitting the message + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function jetStreamPushSubscriptionFromSubscribeToOrderEvents({ + onDataCallback, + parameters, + js, + options, + codec = Nats.JSONCodec(), + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: SubscribeToOrderEventsPayloadModule.SubscribeToOrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, jetstreamMsg?: Nats.JsMsg) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + 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((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), options); + const validator = SubscribeToOrderEventsPayloadModule.createValidator(); + (async () => { + for await (const msg of subscription) { + const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) + let receivedData: any = codec.decode(msg.data); +// Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (msg.headers) { + const headerObj: Record = {}; + // NATS headers support both iteration and get() method + if (typeof msg.headers.keys === 'function') { + // Use keys() method if available (NATS MsgHdrs) + for (const key of msg.headers.keys()) { + headerObj[key] = msg.headers.get(key); + } + } else { + // Fallback to Object.entries for plain objects + for (const [key, value] of Object.entries(msg.headers)) { + headerObj[key] = value; + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = SubscribeToOrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined,parameters, extractedHeaders, msg); continue; + } + } +onDataCallback(undefined, SubscribeToOrderEventsPayloadModule.unmarshal(receivedData), parameters, extractedHeaders, msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +export { publishToPublishOrderCreated, jetStreamPublishToPublishOrderCreated, publishToPublishOrderUpdated, jetStreamPublishToPublishOrderUpdated, publishToPublishOrderCancelled, jetStreamPublishToPublishOrderCancelled, subscribeToSubscribeToOrderEvents, jetStreamPullSubscribeToSubscribeToOrderEvents, jetStreamPushSubscriptionFromSubscribeToOrderEvents }; diff --git a/examples/ecommerce-asyncapi-channels/src/generated/parameter/OrderLifecycleParameters.ts b/examples/ecommerce-asyncapi-channels/src/generated/parameter/OrderLifecycleParameters.ts index c60c7a02..f73eafe2 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/parameter/OrderLifecycleParameters.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/parameter/OrderLifecycleParameters.ts @@ -1,10 +1,11 @@ import {Action} from './Action'; +interface OrderLifecycleParametersInterface { + action: Action +} class OrderLifecycleParameters { private _action: Action; - constructor(input: { - action: Action, - }) { + constructor(input: OrderLifecycleParametersInterface) { this._action = input.action; } @@ -33,7 +34,7 @@ class OrderLifecycleParameters { if(actionMatch && actionMatch !== '') { parameters.action = actionMatch as any } else { - throw new Error(`Parameter: 'action' is not valid. Abort! `) + throw new Error(`Parameter: 'action' is not valid in OrderLifecycleParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -41,4 +42,4 @@ class OrderLifecycleParameters { return parameters; } } -export { OrderLifecycleParameters }; \ No newline at end of file +export { OrderLifecycleParameters, OrderLifecycleParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/Address.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/Address.ts index 361e1963..d4b5a267 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/Address.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/Address.ts @@ -1,5 +1,13 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface AddressInterface { + street: string + city: string + state?: string + country: string + postalCode: string + additionalProperties?: Record +} class Address { private _street: string; private _city: string; @@ -8,14 +16,7 @@ class Address { private _postalCode: string; private _additionalProperties?: Record; - constructor(input: { - street: string, - city: string, - state?: string, - country: string, - postalCode: string, - additionalProperties?: Record, - }) { + constructor(input: AddressInterface) { this._street = input.street; this._city = input.city; this._state = input.state; @@ -42,64 +43,74 @@ class Address { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.street !== undefined) { - json += `"street": ${typeof this.street === 'number' || typeof this.street === 'boolean' ? this.street : JSON.stringify(this.street)},`; + json["street"] = this.street; } if(this.city !== undefined) { - json += `"city": ${typeof this.city === 'number' || typeof this.city === 'boolean' ? this.city : JSON.stringify(this.city)},`; + json["city"] = this.city; } if(this.state !== undefined) { - json += `"state": ${typeof this.state === 'number' || typeof this.state === 'boolean' ? this.state : JSON.stringify(this.state)},`; + json["state"] = this.state; } if(this.country !== undefined) { - json += `"country": ${typeof this.country === 'number' || typeof this.country === 'boolean' ? this.country : JSON.stringify(this.country)},`; + json["country"] = this.country; } if(this.postalCode !== undefined) { - json += `"postalCode": ${typeof this.postalCode === 'number' || typeof this.postalCode === 'boolean' ? this.postalCode : JSON.stringify(this.postalCode)},`; + json["postalCode"] = this.postalCode; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["street","city","state","country","postalCode","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): Address { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): Address { const instance = new Address({} as any); if (obj["street"] !== undefined) { - instance.street = obj["street"]; + instance.street = obj["street"] as string; } if (obj["city"] !== undefined) { - instance.city = obj["city"]; + instance.city = obj["city"] as string; } if (obj["state"] !== undefined) { - instance.state = obj["state"]; + instance.state = obj["state"] as string; } if (obj["country"] !== undefined) { - instance.country = obj["country"]; + instance.country = obj["country"] as string; } if (obj["postalCode"] !== undefined) { - instance.postalCode = obj["postalCode"]; + instance.postalCode = obj["postalCode"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["street","city","state","country","postalCode","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): Address { + const obj = typeof json === "object" ? json : JSON.parse(json); + return Address.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}}; 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 { @@ -110,9 +121,10 @@ class Address { 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 { Address }; \ No newline at end of file +export { Address, AddressInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/Money.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/Money.ts index c56f66f1..72c26296 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/Money.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/Money.ts @@ -1,21 +1,25 @@ import {Currency} from './Currency'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface MoneyInterface { + amount: number + currency: Currency + additionalProperties?: Record +} class Money { private _amount: number; private _currency: Currency; private _additionalProperties?: Record; - constructor(input: { - amount: number, - currency: Currency, - additionalProperties?: Record, - }) { + constructor(input: MoneyInterface) { this._amount = input.amount; this._currency = input.currency; this._additionalProperties = input.additionalProperties; } + /** + * Amount in smallest currency unit (e.g., cents for USD) + */ get amount(): number { return this._amount; } set amount(amount: number) { this._amount = amount; } @@ -25,46 +29,56 @@ class Money { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.amount !== undefined) { - json += `"amount": ${typeof this.amount === 'number' || typeof this.amount === 'boolean' ? this.amount : JSON.stringify(this.amount)},`; + json["amount"] = this.amount; } if(this.currency !== undefined) { - json += `"currency": ${typeof this.currency === 'number' || typeof this.currency === 'boolean' ? this.currency : JSON.stringify(this.currency)},`; + json["currency"] = this.currency; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["amount","currency","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): Money { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): Money { const instance = new Money({} as any); if (obj["amount"] !== undefined) { - instance.amount = obj["amount"]; + instance.amount = obj["amount"] as number; } if (obj["currency"] !== undefined) { - instance.currency = obj["currency"]; + instance.currency = obj["currency"] as Currency; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["amount","currency","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): Money { + const obj = typeof json === "object" ? json : JSON.parse(json); + return Money.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}; 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 { @@ -75,9 +89,10 @@ class Money { 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 { Money }; \ No newline at end of file +export { Money, MoneyInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCancelled.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCancelled.ts index 11959f4b..9dcfacc6 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCancelled.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCancelled.ts @@ -1,20 +1,21 @@ import {Money} from './Money'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderCancelledInterface { + orderId: string + reason: string + cancelledAt: Date + refundAmount?: Money + additionalProperties?: Record +} class OrderCancelled { private _orderId: string; private _reason: string; - private _cancelledAt: string; + private _cancelledAt: Date; private _refundAmount?: Money; private _additionalProperties?: Record; - constructor(input: { - orderId: string, - reason: string, - cancelledAt: string, - refundAmount?: Money, - additionalProperties?: Record, - }) { + constructor(input: OrderCancelledInterface) { this._orderId = input.orderId; this._reason = input.reason; this._cancelledAt = input.cancelledAt; @@ -28,8 +29,8 @@ class OrderCancelled { get reason(): string { return this._reason; } set reason(reason: string) { this._reason = reason; } - get cancelledAt(): string { return this._cancelledAt; } - set cancelledAt(cancelledAt: string) { this._cancelledAt = cancelledAt; } + get cancelledAt(): Date { return this._cancelledAt; } + set cancelledAt(cancelledAt: Date) { this._cancelledAt = cancelledAt; } get refundAmount(): Money | undefined { return this._refundAmount; } set refundAmount(refundAmount: Money | undefined) { this._refundAmount = refundAmount; } @@ -37,58 +38,68 @@ class OrderCancelled { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.reason !== undefined) { - json += `"reason": ${typeof this.reason === 'number' || typeof this.reason === 'boolean' ? this.reason : JSON.stringify(this.reason)},`; + json["reason"] = this.reason; } if(this.cancelledAt !== undefined) { - json += `"cancelledAt": ${typeof this.cancelledAt === 'number' || typeof this.cancelledAt === 'boolean' ? this.cancelledAt : JSON.stringify(this.cancelledAt)},`; + json["cancelledAt"] = this.cancelledAt; } if(this.refundAmount !== undefined) { - json += `"refundAmount": ${this.refundAmount.marshal()},`; + json["refundAmount"] = this.refundAmount && typeof this.refundAmount === 'object' && 'toJson' in this.refundAmount && typeof this.refundAmount.toJson === 'function' ? this.refundAmount.toJson() : this.refundAmount; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","reason","cancelledAt","refundAmount","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderCancelled { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCancelled { const instance = new OrderCancelled({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["reason"] !== undefined) { - instance.reason = obj["reason"]; + instance.reason = obj["reason"] as string; } if (obj["cancelledAt"] !== undefined) { - instance.cancelledAt = obj["cancelledAt"]; + instance.cancelledAt = new Date(obj["cancelledAt"] as string); } if (obj["refundAmount"] !== undefined) { - instance.refundAmount = Money.unmarshal(obj["refundAmount"]); + instance.refundAmount = Money.fromJson(obj["refundAmount"] as Record); } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","reason","cancelledAt","refundAmount","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderCancelled { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCancelled.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}; 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 { @@ -99,9 +110,10 @@ class OrderCancelled { 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 { OrderCancelled }; \ No newline at end of file +export { OrderCancelled, OrderCancelledInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCreated.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCreated.ts index 000720cb..ccc37a23 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCreated.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderCreated.ts @@ -2,25 +2,26 @@ import {OrderItem} from './OrderItem'; import {Money} from './Money'; import {Address} from './Address'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderCreatedInterface { + orderId: string + customerId: string + items: OrderItem[] + totalAmount: Money + shippingAddress?: Address + createdAt?: Date + additionalProperties?: Record +} class OrderCreated { private _orderId: string; private _customerId: string; private _items: OrderItem[]; private _totalAmount: Money; private _shippingAddress?: Address; - private _createdAt?: string; + private _createdAt?: Date; private _additionalProperties?: Record; - constructor(input: { - orderId: string, - customerId: string, - items: OrderItem[], - totalAmount: Money, - shippingAddress?: Address, - createdAt?: string, - additionalProperties?: Record, - }) { + constructor(input: OrderCreatedInterface) { this._orderId = input.orderId; this._customerId = input.customerId; this._items = input.items; @@ -45,82 +46,90 @@ class OrderCreated { get shippingAddress(): Address | undefined { return this._shippingAddress; } set shippingAddress(shippingAddress: Address | undefined) { this._shippingAddress = shippingAddress; } - get createdAt(): string | undefined { return this._createdAt; } - set createdAt(createdAt: string | undefined) { this._createdAt = createdAt; } + get createdAt(): Date | undefined { return this._createdAt; } + set createdAt(createdAt: Date | undefined) { this._createdAt = createdAt; } get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.customerId !== undefined) { - json += `"customerId": ${typeof this.customerId === 'number' || typeof this.customerId === 'boolean' ? this.customerId : JSON.stringify(this.customerId)},`; + json["customerId"] = this.customerId; } if(this.items !== undefined) { - let itemsJsonValues: any[] = []; - for (const unionItem of this.items) { - itemsJsonValues.push(`${unionItem.marshal()}`); - } - json += `"items": [${itemsJsonValues.join(',')}],`; + json["items"] = this.items.map((item: any) => + item && typeof item === 'object' && 'toJson' in item && typeof item.toJson === 'function' + ? item.toJson() + : item + ); } if(this.totalAmount !== undefined) { - json += `"totalAmount": ${this.totalAmount.marshal()},`; + json["totalAmount"] = this.totalAmount && typeof this.totalAmount === 'object' && 'toJson' in this.totalAmount && typeof this.totalAmount.toJson === 'function' ? this.totalAmount.toJson() : this.totalAmount; } if(this.shippingAddress !== undefined) { - json += `"shippingAddress": ${this.shippingAddress.marshal()},`; + json["shippingAddress"] = this.shippingAddress && typeof this.shippingAddress === 'object' && 'toJson' in this.shippingAddress && typeof this.shippingAddress.toJson === 'function' ? this.shippingAddress.toJson() : this.shippingAddress; } if(this.createdAt !== undefined) { - json += `"createdAt": ${typeof this.createdAt === 'number' || typeof this.createdAt === 'boolean' ? this.createdAt : JSON.stringify(this.createdAt)},`; + json["createdAt"] = this.createdAt; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","customerId","items","totalAmount","shippingAddress","createdAt","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderCreated { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCreated { const instance = new OrderCreated({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["customerId"] !== undefined) { - instance.customerId = obj["customerId"]; + instance.customerId = obj["customerId"] as string; } if (obj["items"] !== undefined) { - instance.items = obj["items"] == null - ? null - : obj["items"].map((item: any) => OrderItem.unmarshal(item)); + instance.items = (obj["items"] as Record[]).map((item: Record) => OrderItem.fromJson(item)); } if (obj["totalAmount"] !== undefined) { - instance.totalAmount = Money.unmarshal(obj["totalAmount"]); + instance.totalAmount = Money.fromJson(obj["totalAmount"] as Record); } if (obj["shippingAddress"] !== undefined) { - instance.shippingAddress = Address.unmarshal(obj["shippingAddress"]); + instance.shippingAddress = Address.fromJson(obj["shippingAddress"] as Record); } if (obj["createdAt"] !== undefined) { - instance.createdAt = obj["createdAt"]; + instance.createdAt = obj["createdAt"] == null ? undefined : new Date(obj["createdAt"] as string); } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","customerId","items","totalAmount","shippingAddress","createdAt","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderCreated { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCreated.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"}; 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 { @@ -131,9 +140,10 @@ class OrderCreated { 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 { OrderCreated }; \ No newline at end of file +export { OrderCreated, OrderCreatedInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderItem.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderItem.ts index 0d33fe96..b4972a02 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderItem.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderItem.ts @@ -1,6 +1,14 @@ import {Money} from './Money'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderItemInterface { + productId: string + quantity: number + unitPrice: Money + productName?: string + productCategory?: string + additionalProperties?: Record +} class OrderItem { private _productId: string; private _quantity: number; @@ -9,14 +17,7 @@ class OrderItem { private _productCategory?: string; private _additionalProperties?: Record; - constructor(input: { - productId: string, - quantity: number, - unitPrice: Money, - productName?: string, - productCategory?: string, - additionalProperties?: Record, - }) { + constructor(input: OrderItemInterface) { this._productId = input.productId; this._quantity = input.quantity; this._unitPrice = input.unitPrice; @@ -43,64 +44,74 @@ class OrderItem { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.productId !== undefined) { - json += `"productId": ${typeof this.productId === 'number' || typeof this.productId === 'boolean' ? this.productId : JSON.stringify(this.productId)},`; + json["productId"] = this.productId; } if(this.quantity !== undefined) { - json += `"quantity": ${typeof this.quantity === 'number' || typeof this.quantity === 'boolean' ? this.quantity : JSON.stringify(this.quantity)},`; + json["quantity"] = this.quantity; } if(this.unitPrice !== undefined) { - json += `"unitPrice": ${this.unitPrice.marshal()},`; + json["unitPrice"] = this.unitPrice && typeof this.unitPrice === 'object' && 'toJson' in this.unitPrice && typeof this.unitPrice.toJson === 'function' ? this.unitPrice.toJson() : this.unitPrice; } if(this.productName !== undefined) { - json += `"productName": ${typeof this.productName === 'number' || typeof this.productName === 'boolean' ? this.productName : JSON.stringify(this.productName)},`; + json["productName"] = this.productName; } if(this.productCategory !== undefined) { - json += `"productCategory": ${typeof this.productCategory === 'number' || typeof this.productCategory === 'boolean' ? this.productCategory : JSON.stringify(this.productCategory)},`; + json["productCategory"] = this.productCategory; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["productId","quantity","unitPrice","productName","productCategory","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderItem { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderItem { const instance = new OrderItem({} as any); if (obj["productId"] !== undefined) { - instance.productId = obj["productId"]; + instance.productId = obj["productId"] as string; } if (obj["quantity"] !== undefined) { - instance.quantity = obj["quantity"]; + instance.quantity = obj["quantity"] as number; } if (obj["unitPrice"] !== undefined) { - instance.unitPrice = Money.unmarshal(obj["unitPrice"]); + instance.unitPrice = Money.fromJson(obj["unitPrice"] as Record); } if (obj["productName"] !== undefined) { - instance.productName = obj["productName"]; + instance.productName = obj["productName"] as string; } if (obj["productCategory"] !== undefined) { - instance.productCategory = obj["productCategory"]; + instance.productCategory = obj["productCategory"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["productId","quantity","unitPrice","productName","productCategory","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderItem { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderItem.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}; 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 { @@ -111,9 +122,10 @@ class OrderItem { 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 { OrderItem }; \ No newline at end of file +export { OrderItem, OrderItemInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderLifecyclePayload.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderLifecyclePayload.ts index 8d933ee2..dfd62abb 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderLifecyclePayload.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderLifecyclePayload.ts @@ -3,7 +3,7 @@ import {OrderUpdated} from './OrderUpdated'; import {OrderCancelled} from './OrderCancelled'; import {OrderStatus} from './OrderStatus'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; type OrderLifecyclePayload = OrderCreated | OrderUpdated | OrderCancelled; export function unmarshal(json: any): OrderLifecyclePayload { @@ -27,9 +27,12 @@ return payload.marshal(); return JSON.stringify(payload); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"OrderLifecyclePayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"OrderLifecyclePayload"}; export function 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 ?? createValidator(context) return { @@ -40,6 +43,7 @@ export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFu export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; addFormats(ajvInstance); + const validate = ajvInstance.compile(theCodeGenSchema); return validate; } diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderUpdated.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderUpdated.ts index 639035e1..c7dca941 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderUpdated.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/OrderUpdated.ts @@ -1,22 +1,23 @@ import {OrderStatus} from './OrderStatus'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderUpdatedInterface { + orderId: string + status: OrderStatus + updatedAt: Date + reason?: string + updatedFields?: string[] + additionalProperties?: Record +} class OrderUpdated { private _orderId: string; private _status: OrderStatus; - private _updatedAt: string; + private _updatedAt: Date; private _reason?: string; private _updatedFields?: string[]; private _additionalProperties?: Record; - constructor(input: { - orderId: string, - status: OrderStatus, - updatedAt: string, - reason?: string, - updatedFields?: string[], - additionalProperties?: Record, - }) { + constructor(input: OrderUpdatedInterface) { this._orderId = input.orderId; this._status = input.status; this._updatedAt = input.updatedAt; @@ -31,8 +32,8 @@ class OrderUpdated { get status(): OrderStatus { return this._status; } set status(status: OrderStatus) { this._status = status; } - get updatedAt(): string { return this._updatedAt; } - set updatedAt(updatedAt: string) { this._updatedAt = updatedAt; } + get updatedAt(): Date { return this._updatedAt; } + set updatedAt(updatedAt: Date) { this._updatedAt = updatedAt; } get reason(): string | undefined { return this._reason; } set reason(reason: string | undefined) { this._reason = reason; } @@ -43,68 +44,74 @@ class OrderUpdated { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.status !== undefined) { - json += `"status": ${typeof this.status === 'number' || typeof this.status === 'boolean' ? this.status : JSON.stringify(this.status)},`; + json["status"] = this.status; } if(this.updatedAt !== undefined) { - json += `"updatedAt": ${typeof this.updatedAt === 'number' || typeof this.updatedAt === 'boolean' ? this.updatedAt : JSON.stringify(this.updatedAt)},`; + json["updatedAt"] = this.updatedAt; } if(this.reason !== undefined) { - json += `"reason": ${typeof this.reason === 'number' || typeof this.reason === 'boolean' ? this.reason : JSON.stringify(this.reason)},`; + json["reason"] = this.reason; } if(this.updatedFields !== undefined) { - let updatedFieldsJsonValues: any[] = []; - for (const unionItem of this.updatedFields) { - updatedFieldsJsonValues.push(`${typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)}`); - } - json += `"updatedFields": [${updatedFieldsJsonValues.join(',')}],`; + json["updatedFields"] = this.updatedFields; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","status","updatedAt","reason","updatedFields","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderUpdated { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderUpdated { const instance = new OrderUpdated({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["status"] !== undefined) { - instance.status = obj["status"]; + instance.status = obj["status"] as OrderStatus; } if (obj["updatedAt"] !== undefined) { - instance.updatedAt = obj["updatedAt"]; + instance.updatedAt = new Date(obj["updatedAt"] as string); } if (obj["reason"] !== undefined) { - instance.reason = obj["reason"]; + instance.reason = obj["reason"] as string; } if (obj["updatedFields"] !== undefined) { - instance.updatedFields = obj["updatedFields"]; + instance.updatedFields = obj["updatedFields"] as string[]; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","status","updatedAt","reason","updatedFields","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderUpdated { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderUpdated.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"}; 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 { @@ -115,9 +122,10 @@ class OrderUpdated { 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 { OrderUpdated }; \ No newline at end of file +export { OrderUpdated, OrderUpdatedInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-channels/src/generated/payload/SubscribeToOrderEventsPayload.ts b/examples/ecommerce-asyncapi-channels/src/generated/payload/SubscribeToOrderEventsPayload.ts index e946d7f5..7281d4fa 100644 --- a/examples/ecommerce-asyncapi-channels/src/generated/payload/SubscribeToOrderEventsPayload.ts +++ b/examples/ecommerce-asyncapi-channels/src/generated/payload/SubscribeToOrderEventsPayload.ts @@ -3,7 +3,7 @@ import {OrderUpdated} from './OrderUpdated'; import {OrderCancelled} from './OrderCancelled'; import {OrderStatus} from './OrderStatus'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; type SubscribeToOrderEventsPayload = OrderCreated | OrderUpdated | OrderCancelled; export function unmarshal(json: any): SubscribeToOrderEventsPayload { @@ -27,9 +27,12 @@ return payload.marshal(); return JSON.stringify(payload); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"SubscribeToOrderEventsPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"SubscribeToOrderEventsPayload"}; export function 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 ?? createValidator(context) return { @@ -40,6 +43,7 @@ export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFu export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; addFormats(ajvInstance); + const validate = ajvInstance.compile(theCodeGenSchema); return validate; } diff --git a/examples/ecommerce-asyncapi-client/src/generated/NatsClient.ts b/examples/ecommerce-asyncapi-client/src/generated/NatsClient.ts index feb4aaae..ff9ecc9a 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/NatsClient.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/NatsClient.ts @@ -1,12 +1,12 @@ -import {OrderCreated} from './channels/payload/OrderCreated'; -import {OrderUpdated} from './channels/payload/OrderUpdated'; -import {OrderCancelled} from './channels/payload/OrderCancelled'; +import {OrderCreated, OrderCreatedInterface} from './channels/payload/OrderCreated'; +import {OrderUpdated, OrderUpdatedInterface} from './channels/payload/OrderUpdated'; +import {OrderCancelled, OrderCancelledInterface} from './channels/payload/OrderCancelled'; import * as OrderEventsPayloadModule from './channels/payload/OrderEventsPayload'; import * as OrderLifecyclePayloadModule from './channels/payload/OrderLifecyclePayload'; -import {OrderItem} from './channels/payload/OrderItem'; -import {Money} from './channels/payload/Money'; +import {OrderItem, OrderItemInterface} from './channels/payload/OrderItem'; +import {Money, MoneyInterface} from './channels/payload/Money'; import {Currency} from './channels/payload/Currency'; -import {Address} from './channels/payload/Address'; +import {Address, AddressInterface} from './channels/payload/Address'; import {OrderStatus} from './channels/payload/OrderStatus'; export {OrderCreated}; export {OrderUpdated}; @@ -18,12 +18,11 @@ export {Money}; export {Currency}; export {Address}; export {OrderStatus}; -import {OrderLifecycleParameters} from './channels/parameter/OrderLifecycleParameters'; +import {OrderLifecycleParameters, OrderLifecycleParametersInterface} from './channels/parameter/OrderLifecycleParameters'; export {OrderLifecycleParameters}; //Import channel functions -import { Protocols } from './channels/index'; -const { nats } = Protocols; +import * as nats from './channels/nats'; import * as Nats from 'nats'; @@ -131,7 +130,7 @@ export class NatsClient { parameters, options }: { - message: OrderCreated, + message: OrderCreatedInterface | OrderCreated, parameters: OrderLifecycleParameters, options?: Nats.PublishOptions }): Promise { @@ -160,7 +159,7 @@ export class NatsClient { parameters, options = {} }: { - message: OrderCreated, + message: OrderCreatedInterface | OrderCreated, parameters: OrderLifecycleParameters, options?: Partial }): Promise { @@ -190,7 +189,7 @@ export class NatsClient { parameters, options }: { - message: OrderUpdated, + message: OrderUpdatedInterface | OrderUpdated, parameters: OrderLifecycleParameters, options?: Nats.PublishOptions }): Promise { @@ -219,7 +218,7 @@ export class NatsClient { parameters, options = {} }: { - message: OrderUpdated, + message: OrderUpdatedInterface | OrderUpdated, parameters: OrderLifecycleParameters, options?: Partial }): Promise { @@ -249,7 +248,7 @@ export class NatsClient { parameters, options }: { - message: OrderCancelled, + message: OrderCancelledInterface | OrderCancelled, parameters: OrderLifecycleParameters, options?: Nats.PublishOptions }): Promise { @@ -278,7 +277,7 @@ export class NatsClient { parameters, options = {} }: { - message: OrderCancelled, + message: OrderCancelledInterface | OrderCancelled, parameters: OrderLifecycleParameters, options?: Partial }): Promise { diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCancelledHeaders.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCancelledHeaders.ts new file mode 100644 index 00000000..a31e61e3 --- /dev/null +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCancelledHeaders.ts @@ -0,0 +1,117 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderCancelledHeaders { + private _xCorrelationId: string; + private _xOrderId: string; + private _xCustomerId: string; + private _xSourceService?: string; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xOrderId: string, + xCustomerId: string, + xSourceService?: string, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xOrderId = input.xOrderId; + this._xCustomerId = input.xCustomerId; + this._xSourceService = input.xSourceService; + this._additionalProperties = input.additionalProperties; + } + + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + get xOrderId(): string { return this._xOrderId; } + set xOrderId(xOrderId: string) { this._xOrderId = xOrderId; } + + get xCustomerId(): string { return this._xCustomerId; } + set xCustomerId(xCustomerId: string) { this._xCustomerId = xCustomerId; } + + get xSourceService(): string | undefined { return this._xSourceService; } + set xSourceService(xSourceService: string | undefined) { this._xSourceService = xSourceService; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xOrderId !== undefined) { + json["x-order-id"] = this.xOrderId; + } + if(this.xCustomerId !== undefined) { + json["x-customer-id"] = this.xCustomerId; + } + if(this.xSourceService !== undefined) { + json["x-source-service"] = this.xSourceService; + } + 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(["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCancelledHeaders { + const instance = new OrderCancelledHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-order-id"] !== undefined) { + instance.xOrderId = obj["x-order-id"] as string; + } + if (obj["x-customer-id"] !== undefined) { + instance.xCustomerId = obj["x-customer-id"] as string; + } + if (obj["x-source-service"] !== undefined) { + instance.xSourceService = obj["x-source-service"] as string; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderCancelledHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCancelledHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","required":["x-correlation-id","x-order-id","x-customer-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid"},"x-order-id":{"type":"string","format":"uuid"},"x-customer-id":{"type":"string","format":"uuid"},"x-source-service":{"type":"string"}},"$id":"OrderCancelledHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderCancelledHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCreatedHeaders.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCreatedHeaders.ts new file mode 100644 index 00000000..ccac000b --- /dev/null +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderCreatedHeaders.ts @@ -0,0 +1,117 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderCreatedHeaders { + private _xCorrelationId: string; + private _xOrderId: string; + private _xCustomerId: string; + private _xSourceService?: string; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xOrderId: string, + xCustomerId: string, + xSourceService?: string, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xOrderId = input.xOrderId; + this._xCustomerId = input.xCustomerId; + this._xSourceService = input.xSourceService; + this._additionalProperties = input.additionalProperties; + } + + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + get xOrderId(): string { return this._xOrderId; } + set xOrderId(xOrderId: string) { this._xOrderId = xOrderId; } + + get xCustomerId(): string { return this._xCustomerId; } + set xCustomerId(xCustomerId: string) { this._xCustomerId = xCustomerId; } + + get xSourceService(): string | undefined { return this._xSourceService; } + set xSourceService(xSourceService: string | undefined) { this._xSourceService = xSourceService; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xOrderId !== undefined) { + json["x-order-id"] = this.xOrderId; + } + if(this.xCustomerId !== undefined) { + json["x-customer-id"] = this.xCustomerId; + } + if(this.xSourceService !== undefined) { + json["x-source-service"] = this.xSourceService; + } + 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(["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCreatedHeaders { + const instance = new OrderCreatedHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-order-id"] !== undefined) { + instance.xOrderId = obj["x-order-id"] as string; + } + if (obj["x-customer-id"] !== undefined) { + instance.xCustomerId = obj["x-customer-id"] as string; + } + if (obj["x-source-service"] !== undefined) { + instance.xSourceService = obj["x-source-service"] as string; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderCreatedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCreatedHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","required":["x-correlation-id","x-order-id","x-customer-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid"},"x-order-id":{"type":"string","format":"uuid"},"x-customer-id":{"type":"string","format":"uuid"},"x-source-service":{"type":"string"}},"$id":"OrderCreatedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderCreatedHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderLifecycleHeaders.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderLifecycleHeaders.ts new file mode 100644 index 00000000..e2b9df98 --- /dev/null +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderLifecycleHeaders.ts @@ -0,0 +1,5 @@ +import {OrderCreatedHeaders} from './OrderCreatedHeaders'; +import {OrderUpdatedHeaders} from './OrderUpdatedHeaders'; +import {OrderCancelledHeaders} from './OrderCancelledHeaders'; +type OrderLifecycleHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders; +export { OrderLifecycleHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderUpdatedHeaders.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderUpdatedHeaders.ts new file mode 100644 index 00000000..000c6cfb --- /dev/null +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/headers/OrderUpdatedHeaders.ts @@ -0,0 +1,117 @@ +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderUpdatedHeaders { + private _xCorrelationId: string; + private _xOrderId: string; + private _xCustomerId: string; + private _xSourceService?: string; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xOrderId: string, + xCustomerId: string, + xSourceService?: string, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xOrderId = input.xOrderId; + this._xCustomerId = input.xCustomerId; + this._xSourceService = input.xSourceService; + this._additionalProperties = input.additionalProperties; + } + + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + get xOrderId(): string { return this._xOrderId; } + set xOrderId(xOrderId: string) { this._xOrderId = xOrderId; } + + get xCustomerId(): string { return this._xCustomerId; } + set xCustomerId(xCustomerId: string) { this._xCustomerId = xCustomerId; } + + get xSourceService(): string | undefined { return this._xSourceService; } + set xSourceService(xSourceService: string | undefined) { this._xSourceService = xSourceService; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xOrderId !== undefined) { + json["x-order-id"] = this.xOrderId; + } + if(this.xCustomerId !== undefined) { + json["x-customer-id"] = this.xCustomerId; + } + if(this.xSourceService !== undefined) { + json["x-source-service"] = this.xSourceService; + } + 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(["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderUpdatedHeaders { + const instance = new OrderUpdatedHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-order-id"] !== undefined) { + instance.xOrderId = obj["x-order-id"] as string; + } + if (obj["x-customer-id"] !== undefined) { + instance.xCustomerId = obj["x-customer-id"] as string; + } + if (obj["x-source-service"] !== undefined) { + instance.xSourceService = obj["x-source-service"] as string; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-order-id","x-customer-id","x-source-service","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderUpdatedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderUpdatedHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","required":["x-correlation-id","x-order-id","x-customer-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid"},"x-order-id":{"type":"string","format":"uuid"},"x-customer-id":{"type":"string","format":"uuid"},"x-source-service":{"type":"string"}},"$id":"OrderUpdatedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderUpdatedHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/index.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/index.ts index a69003dc..ae7a1464 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/index.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/index.ts @@ -1,387 +1,3 @@ -import {OrderCreated} from './payload/OrderCreated'; -import {OrderUpdated} from './payload/OrderUpdated'; -import {OrderCancelled} from './payload/OrderCancelled'; -import * as OrderEventsPayloadModule from './payload/OrderEventsPayload'; -import * as OrderLifecyclePayloadModule from './payload/OrderLifecyclePayload'; -import {OrderItem} from './payload/OrderItem'; -import {Money} from './payload/Money'; -import {Currency} from './payload/Currency'; -import {Address} from './payload/Address'; -import {OrderStatus} from './payload/OrderStatus'; -import {OrderLifecycleParameters} from './parameter/OrderLifecycleParameters'; -import * as Nats from 'nats'; -export const Protocols = { -nats: { - /** - * NATS publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @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 - */ -publishToOrderCreated: ({ - message, - parameters, - nc, - codec = Nats.JSONCodec(), - options -}: { - message: OrderCreated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * JetStream publish operation for `orders.{action}` - * - * @param message to publish over jetstream - * @param parameters for topic substitution - * @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 - */ -jetStreamPublishToOrderCreated: ({ - message, - parameters, - js, - codec = Nats.JSONCodec(), - options = {} -}: { - message: OrderCreated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * NATS publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @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 - */ -publishToOrderUpdated: ({ - message, - parameters, - nc, - codec = Nats.JSONCodec(), - options -}: { - message: OrderUpdated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * JetStream publish operation for `orders.{action}` - * - * @param message to publish over jetstream - * @param parameters for topic substitution - * @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 - */ -jetStreamPublishToOrderUpdated: ({ - message, - parameters, - js, - codec = Nats.JSONCodec(), - options = {} -}: { - message: OrderUpdated, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * NATS publish operation for `orders.{action}` - * - * @param message to publish - * @param parameters for topic substitution - * @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 - */ -publishToOrderCancelled: ({ - message, - parameters, - nc, - codec = Nats.JSONCodec(), - options -}: { - message: OrderCancelled, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * JetStream publish operation for `orders.{action}` - * - * @param message to publish over jetstream - * @param parameters for topic substitution - * @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 - */ -jetStreamPublishToOrderCancelled: ({ - message, - parameters, - js, - codec = Nats.JSONCodec(), - options = {} -}: { - message: OrderCancelled, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), dataToSend, options); - resolve(); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback subscribeToOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param natsMsg - */ +import * as nats from './nats'; -/** - * Core subscription for `orders.{action}` - * - * @param {subscribeToOrderEventsCallback} onDataCallback to call when messages are received - * @param parameters for topic substitution - * @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 - */ -subscribeToOrderEvents: ({ - onDataCallback, - parameters, - nc, - codec = Nats.JSONCodec(), - options, - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: OrderEventsPayloadModule.OrderEventsPayload, parameters?: OrderLifecycleParameters, natsMsg?: Nats.Msg) => void, - parameters: OrderLifecycleParameters, - nc: Nats.NatsConnection, - codec?: Nats.Codec, - options?: Nats.SubscriptionOptions, - skipMessageValidation?: boolean -}): Promise => { - return new Promise(async (resolve, reject) => { - try { - const subscription = nc.subscribe(parameters.getChannelWithParameters('orders.{action}'), options); - const validator = OrderEventsPayloadModule.createValidator(); - (async () => { - for await (const msg of subscription) { - const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) - let receivedData: any = codec.decode(msg.data); -if(!skipMessageValidation) { - const {valid, errors} = OrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, msg); continue; - } - } -onDataCallback(undefined, OrderEventsPayloadModule.unmarshal(receivedData), parameters, msg); - } - })(); - resolve(subscription); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback jetStreamPullSubscribeToOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param jetstreamMsg - */ - -/** - * JetStream pull subscription for `orders.{action}` - * - * @param {jetStreamPullSubscribeToOrderEventsCallback} 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 - * @param codec the serialization codec to use while transmitting the message - * @param skipMessageValidation turn off runtime validation of incoming messages - */ -jetStreamPullSubscribeToOrderEvents: ({ - onDataCallback, - parameters, - js, - options, - codec = Nats.JSONCodec(), - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: OrderEventsPayloadModule.OrderEventsPayload, parameters?: OrderLifecycleParameters, jetstreamMsg?: Nats.JsMsg) => void, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), options); - const validator = OrderEventsPayloadModule.createValidator(); - (async () => { - for await (const msg of subscription) { - const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) - let receivedData: any = codec.decode(msg.data); -if(!skipMessageValidation) { - const {valid, errors} = OrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, msg); continue; - } - } -onDataCallback(undefined, OrderEventsPayloadModule.unmarshal(receivedData), parameters, msg); - } - })(); - resolve(subscription); - } catch (e: any) { - reject(e); - } - }); -}, -/** - * Callback for when receiving messages - * - * @callback jetStreamPushSubscriptionFromOrderEventsCallback - * @param err if any error occurred this will be sat - * @param msg that was received - * @param parameters that was received in the topic - * @param jetstreamMsg - */ - -/** - * JetStream push subscription for `orders.{action}` - * - * @param {jetStreamPushSubscriptionFromOrderEventsCallback} 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 - * @param codec the serialization codec to use while transmitting the message - * @param skipMessageValidation turn off runtime validation of incoming messages - */ -jetStreamPushSubscriptionFromOrderEvents: ({ - onDataCallback, - parameters, - js, - options, - codec = Nats.JSONCodec(), - skipMessageValidation = false -}: { - onDataCallback: (err?: Error, msg?: OrderEventsPayloadModule.OrderEventsPayload, parameters?: OrderLifecycleParameters, jetstreamMsg?: Nats.JsMsg) => void, - parameters: OrderLifecycleParameters, - 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(parameters.getChannelWithParameters('orders.{action}'), options); - const validator = OrderEventsPayloadModule.createValidator(); - (async () => { - for await (const msg of subscription) { - const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) - let receivedData: any = codec.decode(msg.data); -if(!skipMessageValidation) { - const {valid, errors} = OrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); - if(!valid) { - onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined, parameters, msg); continue; - } - } -onDataCallback(undefined, OrderEventsPayloadModule.unmarshal(receivedData), parameters, msg); - } - })(); - resolve(subscription); - } catch (e: any) { - reject(e); - } - }); -} -}}; \ No newline at end of file +export {nats}; diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/nats.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/nats.ts new file mode 100644 index 00000000..4613e172 --- /dev/null +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/nats.ts @@ -0,0 +1,543 @@ +import {OrderCreated, OrderCreatedInterface} from './payload/OrderCreated'; +import {OrderUpdated, OrderUpdatedInterface} from './payload/OrderUpdated'; +import {OrderCancelled, OrderCancelledInterface} from './payload/OrderCancelled'; +import * as OrderEventsPayloadModule from './payload/OrderEventsPayload'; +import * as OrderLifecyclePayloadModule from './payload/OrderLifecyclePayload'; +import {OrderItem, OrderItemInterface} from './payload/OrderItem'; +import {Money, MoneyInterface} from './payload/Money'; +import {Currency} from './payload/Currency'; +import {Address, AddressInterface} from './payload/Address'; +import {OrderStatus} from './payload/OrderStatus'; +import {OrderLifecycleParameters, OrderLifecycleParametersInterface} from './parameter/OrderLifecycleParameters'; +import {OrderLifecycleHeaders} from './headers/OrderLifecycleHeaders'; +import * as Nats from 'nats'; + +/** + * NATS publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToOrderCreated({ + message, + parameters, + headers, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: OrderCreatedInterface | OrderCreated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCreated ? message : new OrderCreated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +nc.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * JetStream publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToOrderCreated({ + message, + parameters, + headers, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: OrderCreatedInterface | OrderCreated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCreated ? message : new OrderCreated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +await js.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * NATS publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToOrderUpdated({ + message, + parameters, + headers, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: OrderUpdatedInterface | OrderUpdated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderUpdated ? message : new OrderUpdated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +nc.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * JetStream publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToOrderUpdated({ + message, + parameters, + headers, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: OrderUpdatedInterface | OrderUpdated, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderUpdated ? message : new OrderUpdated(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +await js.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * NATS publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function publishToOrderCancelled({ + message, + parameters, + headers, + nc, + codec = Nats.JSONCodec(), + options +}: { + message: OrderCancelledInterface | OrderCancelled, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.PublishOptions +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCancelled ? message : new OrderCancelled(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +nc.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * JetStream publish operation for `orders.{action}` + * + * @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 + * @param codec the serialization codec to use while transmitting the message + * @param options to use while publishing the message + */ +function jetStreamPublishToOrderCancelled({ + message, + parameters, + headers, + js, + codec = Nats.JSONCodec(), + options = {} +}: { + message: OrderCancelledInterface | OrderCancelled, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, + js: Nats.JetStreamClient, + codec?: Nats.Codec, + options?: Partial +}): Promise { + return new Promise(async (resolve, reject) => { + try { + let dataToSend: any = (message instanceof OrderCancelled ? message : new OrderCancelled(message)).marshal(); + // Set up headers if provided + if (headers) { + const natsHeaders = Nats.headers(); + const headerData = headers.marshal(); + const parsedHeaders = typeof headerData === 'string' ? JSON.parse(headerData) : headerData; + for (const [key, value] of Object.entries(parsedHeaders)) { + if (value !== undefined) { + natsHeaders.append(key, String(value)); + } + } + options = { ...options, headers: natsHeaders }; + } +dataToSend = codec.encode(dataToSend); +await js.publish((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), dataToSend, options); + resolve(); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback subscribeToOrderEventsCallback + * @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 were received with the message + * @param natsMsg + */ + +/** + * Core subscription for `orders.{action}` + * + * @param {subscribeToOrderEventsCallback} onDataCallback to call when messages are received + * @param parameters for topic substitution + * @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 subscribeToOrderEvents({ + onDataCallback, + parameters, + nc, + codec = Nats.JSONCodec(), + options, + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: OrderEventsPayloadModule.OrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, natsMsg?: Nats.Msg) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + nc: Nats.NatsConnection, + codec?: Nats.Codec, + options?: Nats.SubscriptionOptions, + skipMessageValidation?: boolean +}): Promise { + return new Promise(async (resolve, reject) => { + try { + const subscription = nc.subscribe((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), options); + const validator = OrderEventsPayloadModule.createValidator(); + (async () => { + for await (const msg of subscription) { + const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) + let receivedData: any = codec.decode(msg.data); +// Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (msg.headers) { + const headerObj: Record = {}; + // NATS headers support both iteration and get() method + if (typeof msg.headers.keys === 'function') { + // Use keys() method if available (NATS MsgHdrs) + for (const key of msg.headers.keys()) { + headerObj[key] = msg.headers.get(key); + } + } else { + // Fallback to Object.entries for plain objects + for (const [key, value] of Object.entries(msg.headers)) { + headerObj[key] = value; + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = OrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined,parameters, extractedHeaders, msg); continue; + } + } +onDataCallback(undefined, OrderEventsPayloadModule.unmarshal(receivedData), parameters, extractedHeaders, msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback jetStreamPullSubscribeToOrderEventsCallback + * @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 + * @param jetstreamMsg + */ + +/** + * JetStream pull subscription for `orders.{action}` + * + * @param {jetStreamPullSubscribeToOrderEventsCallback} 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 + * @param codec the serialization codec to use while transmitting the message + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function jetStreamPullSubscribeToOrderEvents({ + onDataCallback, + parameters, + js, + options, + codec = Nats.JSONCodec(), + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: OrderEventsPayloadModule.OrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, jetstreamMsg?: Nats.JsMsg) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + 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((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), options); + const validator = OrderEventsPayloadModule.createValidator(); + (async () => { + for await (const msg of subscription) { + const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) + let receivedData: any = codec.decode(msg.data); +// Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (msg.headers) { + const headerObj: Record = {}; + // NATS headers support both iteration and get() method + if (typeof msg.headers.keys === 'function') { + // Use keys() method if available (NATS MsgHdrs) + for (const key of msg.headers.keys()) { + headerObj[key] = msg.headers.get(key); + } + } else { + // Fallback to Object.entries for plain objects + for (const [key, value] of Object.entries(msg.headers)) { + headerObj[key] = value; + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = OrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined,parameters, extractedHeaders, msg); continue; + } + } +onDataCallback(undefined, OrderEventsPayloadModule.unmarshal(receivedData), parameters, extractedHeaders, msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +/** + * Callback for when receiving messages + * + * @callback jetStreamPushSubscriptionFromOrderEventsCallback + * @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 + * @param jetstreamMsg + */ + +/** + * JetStream push subscription for `orders.{action}` + * + * @param {jetStreamPushSubscriptionFromOrderEventsCallback} 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 + * @param codec the serialization codec to use while transmitting the message + * @param skipMessageValidation turn off runtime validation of incoming messages + */ +function jetStreamPushSubscriptionFromOrderEvents({ + onDataCallback, + parameters, + js, + options, + codec = Nats.JSONCodec(), + skipMessageValidation = false +}: { + onDataCallback: (err?: Error, msg?: OrderEventsPayloadModule.OrderEventsPayload, parameters?: OrderLifecycleParameters, headers?: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders, jetstreamMsg?: Nats.JsMsg) => void, + parameters: OrderLifecycleParametersInterface | OrderLifecycleParameters, + 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((parameters instanceof OrderLifecycleParameters ? parameters : new OrderLifecycleParameters(parameters)).getChannelWithParameters('orders.{action}'), options); + const validator = OrderEventsPayloadModule.createValidator(); + (async () => { + for await (const msg of subscription) { + const parameters = OrderLifecycleParameters.createFromChannel(msg.subject, 'orders.{action}', /^orders.([^.]*)$/) + let receivedData: any = codec.decode(msg.data); +// Extract headers if present + let extractedHeaders: OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders | undefined = undefined; + if (msg.headers) { + const headerObj: Record = {}; + // NATS headers support both iteration and get() method + if (typeof msg.headers.keys === 'function') { + // Use keys() method if available (NATS MsgHdrs) + for (const key of msg.headers.keys()) { + headerObj[key] = msg.headers.get(key); + } + } else { + // Fallback to Object.entries for plain objects + for (const [key, value] of Object.entries(msg.headers)) { + headerObj[key] = value; + } + } + extractedHeaders = OrderCreatedHeaders | OrderUpdatedHeaders | OrderCancelledHeaders.unmarshal(headerObj); + } +if(!skipMessageValidation) { + const {valid, errors} = OrderEventsPayloadModule.validate({data: receivedData, ajvValidatorFunction: validator}); + if(!valid) { + onDataCallback(new Error(`Invalid message payload received; ${JSON.stringify({cause: errors})}`), undefined,parameters, extractedHeaders, msg); continue; + } + } +onDataCallback(undefined, OrderEventsPayloadModule.unmarshal(receivedData), parameters, extractedHeaders, msg); + } + })(); + resolve(subscription); + } catch (e: any) { + reject(e); + } + }); +} + +export { publishToOrderCreated, jetStreamPublishToOrderCreated, publishToOrderUpdated, jetStreamPublishToOrderUpdated, publishToOrderCancelled, jetStreamPublishToOrderCancelled, subscribeToOrderEvents, jetStreamPullSubscribeToOrderEvents, jetStreamPushSubscriptionFromOrderEvents }; diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/parameter/OrderLifecycleParameters.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/parameter/OrderLifecycleParameters.ts index c60c7a02..f73eafe2 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/parameter/OrderLifecycleParameters.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/parameter/OrderLifecycleParameters.ts @@ -1,10 +1,11 @@ import {Action} from './Action'; +interface OrderLifecycleParametersInterface { + action: Action +} class OrderLifecycleParameters { private _action: Action; - constructor(input: { - action: Action, - }) { + constructor(input: OrderLifecycleParametersInterface) { this._action = input.action; } @@ -33,7 +34,7 @@ class OrderLifecycleParameters { if(actionMatch && actionMatch !== '') { parameters.action = actionMatch as any } else { - throw new Error(`Parameter: 'action' is not valid. Abort! `) + throw new Error(`Parameter: 'action' is not valid in OrderLifecycleParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -41,4 +42,4 @@ class OrderLifecycleParameters { return parameters; } } -export { OrderLifecycleParameters }; \ No newline at end of file +export { OrderLifecycleParameters, OrderLifecycleParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Address.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Address.ts index 361e1963..d4b5a267 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Address.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Address.ts @@ -1,5 +1,13 @@ import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface AddressInterface { + street: string + city: string + state?: string + country: string + postalCode: string + additionalProperties?: Record +} class Address { private _street: string; private _city: string; @@ -8,14 +16,7 @@ class Address { private _postalCode: string; private _additionalProperties?: Record; - constructor(input: { - street: string, - city: string, - state?: string, - country: string, - postalCode: string, - additionalProperties?: Record, - }) { + constructor(input: AddressInterface) { this._street = input.street; this._city = input.city; this._state = input.state; @@ -42,64 +43,74 @@ class Address { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.street !== undefined) { - json += `"street": ${typeof this.street === 'number' || typeof this.street === 'boolean' ? this.street : JSON.stringify(this.street)},`; + json["street"] = this.street; } if(this.city !== undefined) { - json += `"city": ${typeof this.city === 'number' || typeof this.city === 'boolean' ? this.city : JSON.stringify(this.city)},`; + json["city"] = this.city; } if(this.state !== undefined) { - json += `"state": ${typeof this.state === 'number' || typeof this.state === 'boolean' ? this.state : JSON.stringify(this.state)},`; + json["state"] = this.state; } if(this.country !== undefined) { - json += `"country": ${typeof this.country === 'number' || typeof this.country === 'boolean' ? this.country : JSON.stringify(this.country)},`; + json["country"] = this.country; } if(this.postalCode !== undefined) { - json += `"postalCode": ${typeof this.postalCode === 'number' || typeof this.postalCode === 'boolean' ? this.postalCode : JSON.stringify(this.postalCode)},`; + json["postalCode"] = this.postalCode; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["street","city","state","country","postalCode","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): Address { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): Address { const instance = new Address({} as any); if (obj["street"] !== undefined) { - instance.street = obj["street"]; + instance.street = obj["street"] as string; } if (obj["city"] !== undefined) { - instance.city = obj["city"]; + instance.city = obj["city"] as string; } if (obj["state"] !== undefined) { - instance.state = obj["state"]; + instance.state = obj["state"] as string; } if (obj["country"] !== undefined) { - instance.country = obj["country"]; + instance.country = obj["country"] as string; } if (obj["postalCode"] !== undefined) { - instance.postalCode = obj["postalCode"]; + instance.postalCode = obj["postalCode"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["street","city","state","country","postalCode","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): Address { + const obj = typeof json === "object" ? json : JSON.parse(json); + return Address.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}}; 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 { @@ -110,9 +121,10 @@ class Address { 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 { Address }; \ No newline at end of file +export { Address, AddressInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Money.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Money.ts index c56f66f1..72c26296 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Money.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/Money.ts @@ -1,21 +1,25 @@ import {Currency} from './Currency'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface MoneyInterface { + amount: number + currency: Currency + additionalProperties?: Record +} class Money { private _amount: number; private _currency: Currency; private _additionalProperties?: Record; - constructor(input: { - amount: number, - currency: Currency, - additionalProperties?: Record, - }) { + constructor(input: MoneyInterface) { this._amount = input.amount; this._currency = input.currency; this._additionalProperties = input.additionalProperties; } + /** + * Amount in smallest currency unit (e.g., cents for USD) + */ get amount(): number { return this._amount; } set amount(amount: number) { this._amount = amount; } @@ -25,46 +29,56 @@ class Money { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.amount !== undefined) { - json += `"amount": ${typeof this.amount === 'number' || typeof this.amount === 'boolean' ? this.amount : JSON.stringify(this.amount)},`; + json["amount"] = this.amount; } if(this.currency !== undefined) { - json += `"currency": ${typeof this.currency === 'number' || typeof this.currency === 'boolean' ? this.currency : JSON.stringify(this.currency)},`; + json["currency"] = this.currency; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["amount","currency","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): Money { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): Money { const instance = new Money({} as any); if (obj["amount"] !== undefined) { - instance.amount = obj["amount"]; + instance.amount = obj["amount"] as number; } if (obj["currency"] !== undefined) { - instance.currency = obj["currency"]; + instance.currency = obj["currency"] as Currency; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["amount","currency","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): Money { + const obj = typeof json === "object" ? json : JSON.parse(json); + return Money.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}; 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 { @@ -75,9 +89,10 @@ class Money { 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 { Money }; \ No newline at end of file +export { Money, MoneyInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCancelled.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCancelled.ts index 11959f4b..9dcfacc6 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCancelled.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCancelled.ts @@ -1,20 +1,21 @@ import {Money} from './Money'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderCancelledInterface { + orderId: string + reason: string + cancelledAt: Date + refundAmount?: Money + additionalProperties?: Record +} class OrderCancelled { private _orderId: string; private _reason: string; - private _cancelledAt: string; + private _cancelledAt: Date; private _refundAmount?: Money; private _additionalProperties?: Record; - constructor(input: { - orderId: string, - reason: string, - cancelledAt: string, - refundAmount?: Money, - additionalProperties?: Record, - }) { + constructor(input: OrderCancelledInterface) { this._orderId = input.orderId; this._reason = input.reason; this._cancelledAt = input.cancelledAt; @@ -28,8 +29,8 @@ class OrderCancelled { get reason(): string { return this._reason; } set reason(reason: string) { this._reason = reason; } - get cancelledAt(): string { return this._cancelledAt; } - set cancelledAt(cancelledAt: string) { this._cancelledAt = cancelledAt; } + get cancelledAt(): Date { return this._cancelledAt; } + set cancelledAt(cancelledAt: Date) { this._cancelledAt = cancelledAt; } get refundAmount(): Money | undefined { return this._refundAmount; } set refundAmount(refundAmount: Money | undefined) { this._refundAmount = refundAmount; } @@ -37,58 +38,68 @@ class OrderCancelled { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.reason !== undefined) { - json += `"reason": ${typeof this.reason === 'number' || typeof this.reason === 'boolean' ? this.reason : JSON.stringify(this.reason)},`; + json["reason"] = this.reason; } if(this.cancelledAt !== undefined) { - json += `"cancelledAt": ${typeof this.cancelledAt === 'number' || typeof this.cancelledAt === 'boolean' ? this.cancelledAt : JSON.stringify(this.cancelledAt)},`; + json["cancelledAt"] = this.cancelledAt; } if(this.refundAmount !== undefined) { - json += `"refundAmount": ${this.refundAmount.marshal()},`; + json["refundAmount"] = this.refundAmount && typeof this.refundAmount === 'object' && 'toJson' in this.refundAmount && typeof this.refundAmount.toJson === 'function' ? this.refundAmount.toJson() : this.refundAmount; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","reason","cancelledAt","refundAmount","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderCancelled { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCancelled { const instance = new OrderCancelled({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["reason"] !== undefined) { - instance.reason = obj["reason"]; + instance.reason = obj["reason"] as string; } if (obj["cancelledAt"] !== undefined) { - instance.cancelledAt = obj["cancelledAt"]; + instance.cancelledAt = new Date(obj["cancelledAt"] as string); } if (obj["refundAmount"] !== undefined) { - instance.refundAmount = Money.unmarshal(obj["refundAmount"]); + instance.refundAmount = Money.fromJson(obj["refundAmount"] as Record); } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","reason","cancelledAt","refundAmount","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderCancelled { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCancelled.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}; 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 { @@ -99,9 +110,10 @@ class OrderCancelled { 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 { OrderCancelled }; \ No newline at end of file +export { OrderCancelled, OrderCancelledInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCreated.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCreated.ts index 000720cb..ccc37a23 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCreated.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderCreated.ts @@ -2,25 +2,26 @@ import {OrderItem} from './OrderItem'; import {Money} from './Money'; import {Address} from './Address'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderCreatedInterface { + orderId: string + customerId: string + items: OrderItem[] + totalAmount: Money + shippingAddress?: Address + createdAt?: Date + additionalProperties?: Record +} class OrderCreated { private _orderId: string; private _customerId: string; private _items: OrderItem[]; private _totalAmount: Money; private _shippingAddress?: Address; - private _createdAt?: string; + private _createdAt?: Date; private _additionalProperties?: Record; - constructor(input: { - orderId: string, - customerId: string, - items: OrderItem[], - totalAmount: Money, - shippingAddress?: Address, - createdAt?: string, - additionalProperties?: Record, - }) { + constructor(input: OrderCreatedInterface) { this._orderId = input.orderId; this._customerId = input.customerId; this._items = input.items; @@ -45,82 +46,90 @@ class OrderCreated { get shippingAddress(): Address | undefined { return this._shippingAddress; } set shippingAddress(shippingAddress: Address | undefined) { this._shippingAddress = shippingAddress; } - get createdAt(): string | undefined { return this._createdAt; } - set createdAt(createdAt: string | undefined) { this._createdAt = createdAt; } + get createdAt(): Date | undefined { return this._createdAt; } + set createdAt(createdAt: Date | undefined) { this._createdAt = createdAt; } get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.customerId !== undefined) { - json += `"customerId": ${typeof this.customerId === 'number' || typeof this.customerId === 'boolean' ? this.customerId : JSON.stringify(this.customerId)},`; + json["customerId"] = this.customerId; } if(this.items !== undefined) { - let itemsJsonValues: any[] = []; - for (const unionItem of this.items) { - itemsJsonValues.push(`${unionItem.marshal()}`); - } - json += `"items": [${itemsJsonValues.join(',')}],`; + json["items"] = this.items.map((item: any) => + item && typeof item === 'object' && 'toJson' in item && typeof item.toJson === 'function' + ? item.toJson() + : item + ); } if(this.totalAmount !== undefined) { - json += `"totalAmount": ${this.totalAmount.marshal()},`; + json["totalAmount"] = this.totalAmount && typeof this.totalAmount === 'object' && 'toJson' in this.totalAmount && typeof this.totalAmount.toJson === 'function' ? this.totalAmount.toJson() : this.totalAmount; } if(this.shippingAddress !== undefined) { - json += `"shippingAddress": ${this.shippingAddress.marshal()},`; + json["shippingAddress"] = this.shippingAddress && typeof this.shippingAddress === 'object' && 'toJson' in this.shippingAddress && typeof this.shippingAddress.toJson === 'function' ? this.shippingAddress.toJson() : this.shippingAddress; } if(this.createdAt !== undefined) { - json += `"createdAt": ${typeof this.createdAt === 'number' || typeof this.createdAt === 'boolean' ? this.createdAt : JSON.stringify(this.createdAt)},`; + json["createdAt"] = this.createdAt; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","customerId","items","totalAmount","shippingAddress","createdAt","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderCreated { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCreated { const instance = new OrderCreated({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["customerId"] !== undefined) { - instance.customerId = obj["customerId"]; + instance.customerId = obj["customerId"] as string; } if (obj["items"] !== undefined) { - instance.items = obj["items"] == null - ? null - : obj["items"].map((item: any) => OrderItem.unmarshal(item)); + instance.items = (obj["items"] as Record[]).map((item: Record) => OrderItem.fromJson(item)); } if (obj["totalAmount"] !== undefined) { - instance.totalAmount = Money.unmarshal(obj["totalAmount"]); + instance.totalAmount = Money.fromJson(obj["totalAmount"] as Record); } if (obj["shippingAddress"] !== undefined) { - instance.shippingAddress = Address.unmarshal(obj["shippingAddress"]); + instance.shippingAddress = Address.fromJson(obj["shippingAddress"] as Record); } if (obj["createdAt"] !== undefined) { - instance.createdAt = obj["createdAt"]; + instance.createdAt = obj["createdAt"] == null ? undefined : new Date(obj["createdAt"] as string); } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","customerId","items","totalAmount","shippingAddress","createdAt","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderCreated { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCreated.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"}; 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 { @@ -131,9 +140,10 @@ class OrderCreated { 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 { OrderCreated }; \ No newline at end of file +export { OrderCreated, OrderCreatedInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderEventsPayload.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderEventsPayload.ts index f243008d..3a3806ab 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderEventsPayload.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderEventsPayload.ts @@ -3,7 +3,7 @@ import {OrderUpdated} from './OrderUpdated'; import {OrderCancelled} from './OrderCancelled'; import {OrderStatus} from './OrderStatus'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; type OrderEventsPayload = OrderCreated | OrderUpdated | OrderCancelled; export function unmarshal(json: any): OrderEventsPayload { @@ -27,9 +27,12 @@ return payload.marshal(); return JSON.stringify(payload); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"OrderEventsPayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"OrderEventsPayload"}; export function 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 ?? createValidator(context) return { @@ -40,6 +43,7 @@ export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFu export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; addFormats(ajvInstance); + const validate = ajvInstance.compile(theCodeGenSchema); return validate; } diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderItem.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderItem.ts index 0d33fe96..b4972a02 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderItem.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderItem.ts @@ -1,6 +1,14 @@ import {Money} from './Money'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderItemInterface { + productId: string + quantity: number + unitPrice: Money + productName?: string + productCategory?: string + additionalProperties?: Record +} class OrderItem { private _productId: string; private _quantity: number; @@ -9,14 +17,7 @@ class OrderItem { private _productCategory?: string; private _additionalProperties?: Record; - constructor(input: { - productId: string, - quantity: number, - unitPrice: Money, - productName?: string, - productCategory?: string, - additionalProperties?: Record, - }) { + constructor(input: OrderItemInterface) { this._productId = input.productId; this._quantity = input.quantity; this._unitPrice = input.unitPrice; @@ -43,64 +44,74 @@ class OrderItem { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.productId !== undefined) { - json += `"productId": ${typeof this.productId === 'number' || typeof this.productId === 'boolean' ? this.productId : JSON.stringify(this.productId)},`; + json["productId"] = this.productId; } if(this.quantity !== undefined) { - json += `"quantity": ${typeof this.quantity === 'number' || typeof this.quantity === 'boolean' ? this.quantity : JSON.stringify(this.quantity)},`; + json["quantity"] = this.quantity; } if(this.unitPrice !== undefined) { - json += `"unitPrice": ${this.unitPrice.marshal()},`; + json["unitPrice"] = this.unitPrice && typeof this.unitPrice === 'object' && 'toJson' in this.unitPrice && typeof this.unitPrice.toJson === 'function' ? this.unitPrice.toJson() : this.unitPrice; } if(this.productName !== undefined) { - json += `"productName": ${typeof this.productName === 'number' || typeof this.productName === 'boolean' ? this.productName : JSON.stringify(this.productName)},`; + json["productName"] = this.productName; } if(this.productCategory !== undefined) { - json += `"productCategory": ${typeof this.productCategory === 'number' || typeof this.productCategory === 'boolean' ? this.productCategory : JSON.stringify(this.productCategory)},`; + json["productCategory"] = this.productCategory; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["productId","quantity","unitPrice","productName","productCategory","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderItem { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderItem { const instance = new OrderItem({} as any); if (obj["productId"] !== undefined) { - instance.productId = obj["productId"]; + instance.productId = obj["productId"] as string; } if (obj["quantity"] !== undefined) { - instance.quantity = obj["quantity"]; + instance.quantity = obj["quantity"] as number; } if (obj["unitPrice"] !== undefined) { - instance.unitPrice = Money.unmarshal(obj["unitPrice"]); + instance.unitPrice = Money.fromJson(obj["unitPrice"] as Record); } if (obj["productName"] !== undefined) { - instance.productName = obj["productName"]; + instance.productName = obj["productName"] as string; } if (obj["productCategory"] !== undefined) { - instance.productCategory = obj["productCategory"]; + instance.productCategory = obj["productCategory"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["productId","quantity","unitPrice","productName","productCategory","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderItem { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderItem.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}; 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 { @@ -111,9 +122,10 @@ class OrderItem { 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 { OrderItem }; \ No newline at end of file +export { OrderItem, OrderItemInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderLifecyclePayload.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderLifecyclePayload.ts index 8d933ee2..dfd62abb 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderLifecyclePayload.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderLifecyclePayload.ts @@ -3,7 +3,7 @@ import {OrderUpdated} from './OrderUpdated'; import {OrderCancelled} from './OrderCancelled'; import {OrderStatus} from './OrderStatus'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; type OrderLifecyclePayload = OrderCreated | OrderUpdated | OrderCancelled; export function unmarshal(json: any): OrderLifecyclePayload { @@ -27,9 +27,12 @@ return payload.marshal(); return JSON.stringify(payload); } -export const theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"OrderLifecyclePayload"}; +export const theCodeGenSchema = {"$schema":"http://json-schema.org/draft-07/schema","oneOf":[{"type":"object","required":["orderId","customerId","items","totalAmount"],"properties":{"orderId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","format":"uuid"},"quantity":{"type":"integer","minimum":1},"unitPrice":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"productName":{"type":"string"},"productCategory":{"type":"string"}}}},"totalAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string"},"postalCode":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}},"$id":"OrderCreated"},{"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"},{"type":"object","required":["orderId","reason","cancelledAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"reason":{"type":"string"},"cancelledAt":{"type":"string","format":"date-time"},"refundAmount":{"type":"object","required":["amount","currency"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount in smallest currency unit (e.g., cents for USD)"},"currency":{"type":"string","enum":["USD","EUR","GBP"]}}}},"$id":"OrderCancelled"}],"$id":"OrderLifecyclePayload"}; export function 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 ?? createValidator(context) return { @@ -40,6 +43,7 @@ export function validate(context?: {data: any, ajvValidatorFunction?: ValidateFu export function createValidator(context?: {ajvInstance?: Ajv, ajvOptions?: AjvOptions}): ValidateFunction { const {ajvInstance} = {...context ?? {}, ajvInstance: new Ajv(context?.ajvOptions ?? {})}; addFormats(ajvInstance); + const validate = ajvInstance.compile(theCodeGenSchema); return validate; } diff --git a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderUpdated.ts b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderUpdated.ts index 639035e1..c7dca941 100644 --- a/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderUpdated.ts +++ b/examples/ecommerce-asyncapi-client/src/generated/channels/payload/OrderUpdated.ts @@ -1,22 +1,23 @@ import {OrderStatus} from './OrderStatus'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; +interface OrderUpdatedInterface { + orderId: string + status: OrderStatus + updatedAt: Date + reason?: string + updatedFields?: string[] + additionalProperties?: Record +} class OrderUpdated { private _orderId: string; private _status: OrderStatus; - private _updatedAt: string; + private _updatedAt: Date; private _reason?: string; private _updatedFields?: string[]; private _additionalProperties?: Record; - constructor(input: { - orderId: string, - status: OrderStatus, - updatedAt: string, - reason?: string, - updatedFields?: string[], - additionalProperties?: Record, - }) { + constructor(input: OrderUpdatedInterface) { this._orderId = input.orderId; this._status = input.status; this._updatedAt = input.updatedAt; @@ -31,8 +32,8 @@ class OrderUpdated { get status(): OrderStatus { return this._status; } set status(status: OrderStatus) { this._status = status; } - get updatedAt(): string { return this._updatedAt; } - set updatedAt(updatedAt: string) { this._updatedAt = updatedAt; } + get updatedAt(): Date { return this._updatedAt; } + set updatedAt(updatedAt: Date) { this._updatedAt = updatedAt; } get reason(): string | undefined { return this._reason; } set reason(reason: string | undefined) { this._reason = reason; } @@ -43,68 +44,74 @@ class OrderUpdated { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.status !== undefined) { - json += `"status": ${typeof this.status === 'number' || typeof this.status === 'boolean' ? this.status : JSON.stringify(this.status)},`; + json["status"] = this.status; } if(this.updatedAt !== undefined) { - json += `"updatedAt": ${typeof this.updatedAt === 'number' || typeof this.updatedAt === 'boolean' ? this.updatedAt : JSON.stringify(this.updatedAt)},`; + json["updatedAt"] = this.updatedAt; } if(this.reason !== undefined) { - json += `"reason": ${typeof this.reason === 'number' || typeof this.reason === 'boolean' ? this.reason : JSON.stringify(this.reason)},`; + json["reason"] = this.reason; } if(this.updatedFields !== undefined) { - let updatedFieldsJsonValues: any[] = []; - for (const unionItem of this.updatedFields) { - updatedFieldsJsonValues.push(`${typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)}`); - } - json += `"updatedFields": [${updatedFieldsJsonValues.join(',')}],`; + json["updatedFields"] = this.updatedFields; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","status","updatedAt","reason","updatedFields","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderUpdated { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderUpdated { const instance = new OrderUpdated({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["status"] !== undefined) { - instance.status = obj["status"]; + instance.status = obj["status"] as OrderStatus; } if (obj["updatedAt"] !== undefined) { - instance.updatedAt = obj["updatedAt"]; + instance.updatedAt = new Date(obj["updatedAt"] as string); } if (obj["reason"] !== undefined) { - instance.reason = obj["reason"]; + instance.reason = obj["reason"] as string; } if (obj["updatedFields"] !== undefined) { - instance.updatedFields = obj["updatedFields"]; + instance.updatedFields = obj["updatedFields"] as string[]; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","status","updatedAt","reason","updatedFields","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderUpdated { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderUpdated.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","status","updatedAt"],"properties":{"orderId":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"]},"updatedAt":{"type":"string","format":"date-time"},"reason":{"type":"string"},"updatedFields":{"type":"array","items":{"type":"string"}}},"$id":"OrderUpdated"}; 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 { @@ -115,9 +122,10 @@ class OrderUpdated { 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 { OrderUpdated }; \ No newline at end of file +export { OrderUpdated, OrderUpdatedInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/ActorType.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/ActorType.ts new file mode 100644 index 00000000..7b59e675 --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/ActorType.ts @@ -0,0 +1,6 @@ + +/** + * Type of actor that triggered the change + */ +type ActorType = "user" | "system" | "admin"; +export { ActorType }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/AdminActionPerformedHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/AdminActionPerformedHeaders.ts index 09d41404..51df54d1 100644 --- a/examples/ecommerce-asyncapi-headers/src/generated/headers/AdminActionPerformedHeaders.ts +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/AdminActionPerformedHeaders.ts @@ -2,11 +2,11 @@ import {AdminActionType} from './AdminActionType'; import {PermissionLevel} from './PermissionLevel'; import {AuditLevel} from './AuditLevel'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; class AdminActionPerformedHeaders { private _xCorrelationId: string; private _xTenantId: string; - private _xTimestamp?: string; + private _xTimestamp?: Date; private _xAdminId: string; private _xActionType: AdminActionType; private _xPermissionLevel?: PermissionLevel; @@ -21,7 +21,7 @@ class AdminActionPerformedHeaders { constructor(input: { xCorrelationId: string, xTenantId: string, - xTimestamp?: string, + xTimestamp?: Date, xAdminId: string, xActionType: AdminActionType, xPermissionLevel?: PermissionLevel, @@ -63,8 +63,8 @@ class AdminActionPerformedHeaders { /** * Event creation timestamp */ - get xTimestamp(): string | undefined { return this._xTimestamp; } - set xTimestamp(xTimestamp: string | undefined) { this._xTimestamp = xTimestamp; } + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } /** * ID of admin performing action @@ -123,100 +123,98 @@ class AdminActionPerformedHeaders { get additionalProperties(): Map | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.xCorrelationId !== undefined) { - json += `"x-correlation-id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`; + json["x-correlation-id"] = this.xCorrelationId; } if(this.xTenantId !== undefined) { - json += `"x-tenant-id": ${typeof this.xTenantId === 'number' || typeof this.xTenantId === 'boolean' ? this.xTenantId : JSON.stringify(this.xTenantId)},`; + json["x-tenant-id"] = this.xTenantId; } if(this.xTimestamp !== undefined) { - json += `"x-timestamp": ${typeof this.xTimestamp === 'number' || typeof this.xTimestamp === 'boolean' ? this.xTimestamp : JSON.stringify(this.xTimestamp)},`; + json["x-timestamp"] = this.xTimestamp; } if(this.xAdminId !== undefined) { - json += `"x-admin-id": ${typeof this.xAdminId === 'number' || typeof this.xAdminId === 'boolean' ? this.xAdminId : JSON.stringify(this.xAdminId)},`; + json["x-admin-id"] = this.xAdminId; } if(this.xActionType !== undefined) { - json += `"x-action-type": ${typeof this.xActionType === 'number' || typeof this.xActionType === 'boolean' ? this.xActionType : JSON.stringify(this.xActionType)},`; + json["x-action-type"] = this.xActionType; } if(this.xPermissionLevel !== undefined) { - json += `"x-permission-level": ${typeof this.xPermissionLevel === 'number' || typeof this.xPermissionLevel === 'boolean' ? this.xPermissionLevel : JSON.stringify(this.xPermissionLevel)},`; + json["x-permission-level"] = this.xPermissionLevel; } if(this.xAuditLevel !== undefined) { - json += `"x-audit-level": ${typeof this.xAuditLevel === 'number' || typeof this.xAuditLevel === 'boolean' ? this.xAuditLevel : JSON.stringify(this.xAuditLevel)},`; + json["x-audit-level"] = this.xAuditLevel; } if(this.xApprovalRequired !== undefined) { - json += `"x-approval-required": ${typeof this.xApprovalRequired === 'number' || typeof this.xApprovalRequired === 'boolean' ? this.xApprovalRequired : JSON.stringify(this.xApprovalRequired)},`; + json["x-approval-required"] = this.xApprovalRequired; } if(this.xApprovedBy !== undefined) { - json += `"x-approved-by": ${typeof this.xApprovedBy === 'number' || typeof this.xApprovedBy === 'boolean' ? this.xApprovedBy : JSON.stringify(this.xApprovedBy)},`; + json["x-approved-by"] = this.xApprovedBy; } if(this.xWebhookSignature !== undefined) { - json += `"x-webhook-signature": ${typeof this.xWebhookSignature === 'number' || typeof this.xWebhookSignature === 'boolean' ? this.xWebhookSignature : JSON.stringify(this.xWebhookSignature)},`; + json["x-webhook-signature"] = this.xWebhookSignature; } if(this.xIpAddress !== undefined) { - json += `"x-ip-address": ${typeof this.xIpAddress === 'number' || typeof this.xIpAddress === 'boolean' ? this.xIpAddress : JSON.stringify(this.xIpAddress)},`; + json["x-ip-address"] = this.xIpAddress; } if(this.xComplianceTags !== undefined) { - let xComplianceTagsJsonValues: any[] = []; - for (const unionItem of this.xComplianceTags) { - xComplianceTagsJsonValues.push(`${typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)}`); - } - json += `"x-compliance-tags": [${xComplianceTagsJsonValues.join(',')}],`; + json["x-compliance-tags"] = this.xComplianceTags; } - if(this.additionalProperties !== undefined) { + 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(["x-correlation-id","x-tenant-id","x-timestamp","x-admin-id","x-action-type","x-permission-level","x-audit-level","x-approval-required","x-approved-by","x-webhook-signature","x-ip-address","x-compliance-tags","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): AdminActionPerformedHeaders { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): AdminActionPerformedHeaders { const instance = new AdminActionPerformedHeaders({} as any); if (obj["x-correlation-id"] !== undefined) { - instance.xCorrelationId = obj["x-correlation-id"]; + instance.xCorrelationId = obj["x-correlation-id"] as string; } if (obj["x-tenant-id"] !== undefined) { - instance.xTenantId = obj["x-tenant-id"]; + instance.xTenantId = obj["x-tenant-id"] as string; } if (obj["x-timestamp"] !== undefined) { - instance.xTimestamp = obj["x-timestamp"]; + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); } if (obj["x-admin-id"] !== undefined) { - instance.xAdminId = obj["x-admin-id"]; + instance.xAdminId = obj["x-admin-id"] as string; } if (obj["x-action-type"] !== undefined) { - instance.xActionType = obj["x-action-type"]; + instance.xActionType = obj["x-action-type"] as AdminActionType; } if (obj["x-permission-level"] !== undefined) { - instance.xPermissionLevel = obj["x-permission-level"]; + instance.xPermissionLevel = obj["x-permission-level"] as PermissionLevel; } if (obj["x-audit-level"] !== undefined) { - instance.xAuditLevel = obj["x-audit-level"]; + instance.xAuditLevel = obj["x-audit-level"] as AuditLevel; } if (obj["x-approval-required"] !== undefined) { - instance.xApprovalRequired = obj["x-approval-required"]; + instance.xApprovalRequired = obj["x-approval-required"] as boolean; } if (obj["x-approved-by"] !== undefined) { - instance.xApprovedBy = obj["x-approved-by"]; + instance.xApprovedBy = obj["x-approved-by"] as string; } if (obj["x-webhook-signature"] !== undefined) { - instance.xWebhookSignature = obj["x-webhook-signature"]; + instance.xWebhookSignature = obj["x-webhook-signature"] as string; } if (obj["x-ip-address"] !== undefined) { - instance.xIpAddress = obj["x-ip-address"]; + instance.xIpAddress = obj["x-ip-address"] as string; } if (obj["x-compliance-tags"] !== undefined) { - instance.xComplianceTags = obj["x-compliance-tags"]; + instance.xComplianceTags = obj["x-compliance-tags"] as string[]; } - + instance.additionalProperties = new Map(); const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","x-admin-id","x-action-type","x-permission-level","x-audit-level","x-approval-required","x-approved-by","x-webhook-signature","x-ip-address","x-compliance-tags","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { @@ -224,9 +222,17 @@ class AdminActionPerformedHeaders { } return instance; } + + public static unmarshal(json: string | object): AdminActionPerformedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return AdminActionPerformedHeaders.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","required":["x-admin-id","x-action-type"],"properties":{"x-admin-id":{"type":"string","format":"uuid","description":"ID of admin performing action"},"x-action-type":{"type":"string","enum":["user-management","order-management","inventory-management","system-config"],"description":"Category of admin action"},"x-permission-level":{"type":"string","enum":["read","write","admin","super-admin"],"description":"Permission level required for action"},"x-audit-level":{"type":"string","enum":["low","medium","high","critical"],"description":"Audit importance level"},"x-approval-required":{"type":"boolean","default":false,"description":"Whether action requires approval"},"x-approved-by":{"type":"string","format":"uuid","description":"ID of approving admin (if applicable)"}}},{"type":"object","properties":{"x-webhook-signature":{"type":"string","description":"Webhook signature for verification"},"x-ip-address":{"type":"string","format":"ipv4","description":"IP address"}}},{"type":"object","properties":{"x-compliance-tags":{"type":"array","items":{"type":"string"},"description":"Compliance/regulatory tags"}}}],"$id":"AdminActionPerformedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; 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 { @@ -237,6 +243,7 @@ class AdminActionPerformedHeaders { 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; } diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/InventoryUpdatedHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/InventoryUpdatedHeaders.ts index 80e74a42..2266e5b7 100644 --- a/examples/ecommerce-asyncapi-headers/src/generated/headers/InventoryUpdatedHeaders.ts +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/InventoryUpdatedHeaders.ts @@ -1,10 +1,10 @@ import {InventoryUpdateType} from './InventoryUpdateType'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; class InventoryUpdatedHeaders { private _xCorrelationId: string; private _xTenantId: string; - private _xTimestamp?: string; + private _xTimestamp?: Date; private _xWarehouseId: string; private _xUpdateType?: InventoryUpdateType; private _xBatchId?: string; @@ -16,7 +16,7 @@ class InventoryUpdatedHeaders { constructor(input: { xCorrelationId: string, xTenantId: string, - xTimestamp?: string, + xTimestamp?: Date, xWarehouseId: string, xUpdateType?: InventoryUpdateType, xBatchId?: string, @@ -52,8 +52,8 @@ class InventoryUpdatedHeaders { /** * Event creation timestamp */ - get xTimestamp(): string | undefined { return this._xTimestamp; } - set xTimestamp(xTimestamp: string | undefined) { this._xTimestamp = xTimestamp; } + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } /** * Warehouse where inventory changed @@ -94,78 +94,80 @@ class InventoryUpdatedHeaders { get additionalProperties(): Map | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.xCorrelationId !== undefined) { - json += `"x-correlation-id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`; + json["x-correlation-id"] = this.xCorrelationId; } if(this.xTenantId !== undefined) { - json += `"x-tenant-id": ${typeof this.xTenantId === 'number' || typeof this.xTenantId === 'boolean' ? this.xTenantId : JSON.stringify(this.xTenantId)},`; + json["x-tenant-id"] = this.xTenantId; } if(this.xTimestamp !== undefined) { - json += `"x-timestamp": ${typeof this.xTimestamp === 'number' || typeof this.xTimestamp === 'boolean' ? this.xTimestamp : JSON.stringify(this.xTimestamp)},`; + json["x-timestamp"] = this.xTimestamp; } if(this.xWarehouseId !== undefined) { - json += `"x-warehouse-id": ${typeof this.xWarehouseId === 'number' || typeof this.xWarehouseId === 'boolean' ? this.xWarehouseId : JSON.stringify(this.xWarehouseId)},`; + json["x-warehouse-id"] = this.xWarehouseId; } if(this.xUpdateType !== undefined) { - json += `"x-update-type": ${typeof this.xUpdateType === 'number' || typeof this.xUpdateType === 'boolean' ? this.xUpdateType : JSON.stringify(this.xUpdateType)},`; + json["x-update-type"] = this.xUpdateType; } if(this.xBatchId !== undefined) { - json += `"x-batch-id": ${typeof this.xBatchId === 'number' || typeof this.xBatchId === 'boolean' ? this.xBatchId : JSON.stringify(this.xBatchId)},`; + json["x-batch-id"] = this.xBatchId; } if(this.xLocation !== undefined) { - json += `"x-location": ${typeof this.xLocation === 'number' || typeof this.xLocation === 'boolean' ? this.xLocation : JSON.stringify(this.xLocation)},`; + json["x-location"] = this.xLocation; } if(this.xOperatorId !== undefined) { - json += `"x-operator-id": ${typeof this.xOperatorId === 'number' || typeof this.xOperatorId === 'boolean' ? this.xOperatorId : JSON.stringify(this.xOperatorId)},`; + json["x-operator-id"] = this.xOperatorId; } if(this.xAuditRequired !== undefined) { - json += `"x-audit-required": ${typeof this.xAuditRequired === 'number' || typeof this.xAuditRequired === 'boolean' ? this.xAuditRequired : JSON.stringify(this.xAuditRequired)},`; + json["x-audit-required"] = this.xAuditRequired; } - if(this.additionalProperties !== undefined) { + 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(["x-correlation-id","x-tenant-id","x-timestamp","x-warehouse-id","x-update-type","x-batch-id","x-location","x-operator-id","x-audit-required","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): InventoryUpdatedHeaders { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): InventoryUpdatedHeaders { const instance = new InventoryUpdatedHeaders({} as any); if (obj["x-correlation-id"] !== undefined) { - instance.xCorrelationId = obj["x-correlation-id"]; + instance.xCorrelationId = obj["x-correlation-id"] as string; } if (obj["x-tenant-id"] !== undefined) { - instance.xTenantId = obj["x-tenant-id"]; + instance.xTenantId = obj["x-tenant-id"] as string; } if (obj["x-timestamp"] !== undefined) { - instance.xTimestamp = obj["x-timestamp"]; + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); } if (obj["x-warehouse-id"] !== undefined) { - instance.xWarehouseId = obj["x-warehouse-id"]; + instance.xWarehouseId = obj["x-warehouse-id"] as string; } if (obj["x-update-type"] !== undefined) { - instance.xUpdateType = obj["x-update-type"]; + instance.xUpdateType = obj["x-update-type"] as InventoryUpdateType; } if (obj["x-batch-id"] !== undefined) { - instance.xBatchId = obj["x-batch-id"]; + instance.xBatchId = obj["x-batch-id"] as string; } if (obj["x-location"] !== undefined) { - instance.xLocation = obj["x-location"]; + instance.xLocation = obj["x-location"] as string; } if (obj["x-operator-id"] !== undefined) { - instance.xOperatorId = obj["x-operator-id"]; + instance.xOperatorId = obj["x-operator-id"] as string; } if (obj["x-audit-required"] !== undefined) { - instance.xAuditRequired = obj["x-audit-required"]; + instance.xAuditRequired = obj["x-audit-required"] as boolean; } - + instance.additionalProperties = new Map(); const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","x-warehouse-id","x-update-type","x-batch-id","x-location","x-operator-id","x-audit-required","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { @@ -173,9 +175,17 @@ class InventoryUpdatedHeaders { } return instance; } + + public static unmarshal(json: string | object): InventoryUpdatedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return InventoryUpdatedHeaders.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","required":["x-warehouse-id"],"properties":{"x-warehouse-id":{"type":"string","description":"Warehouse where inventory changed"},"x-update-type":{"type":"string","enum":["restock","sale","adjustment","damage","return"],"description":"Type of inventory update"},"x-batch-id":{"type":"string","format":"uuid","description":"Batch ID for bulk operations"},"x-location":{"type":"string","description":"Specific location within warehouse"}}},{"type":"object","properties":{"x-operator-id":{"type":"string","format":"uuid","description":"ID of person/system making the change"},"x-audit-required":{"type":"boolean","default":false,"description":"Whether this change requires audit"}}}],"$id":"InventoryUpdatedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; 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 { @@ -186,6 +196,7 @@ class InventoryUpdatedHeaders { 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; } diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/NotificationSentHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/NotificationSentHeaders.ts index 1668b61f..bc20b3e4 100644 --- a/examples/ecommerce-asyncapi-headers/src/generated/headers/NotificationSentHeaders.ts +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/NotificationSentHeaders.ts @@ -2,16 +2,16 @@ import {NotificationType} from './NotificationType'; import {NotificationChannel} from './NotificationChannel'; import {NotificationProvider} from './NotificationProvider'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; class NotificationSentHeaders { private _xCorrelationId: string; private _xTenantId: string; - private _xTimestamp?: string; + private _xTimestamp?: Date; private _xNotificationType: NotificationType; private _xTemplateId?: string; private _xChannelPreference?: NotificationChannel; private _xDeliveryAttempt?: number; - private _xScheduledTime?: string; + private _xScheduledTime?: Date; private _xProvider?: NotificationProvider; private _xLanguage?: string; private _additionalProperties?: Map; @@ -19,12 +19,12 @@ class NotificationSentHeaders { constructor(input: { xCorrelationId: string, xTenantId: string, - xTimestamp?: string, + xTimestamp?: Date, xNotificationType: NotificationType, xTemplateId?: string, xChannelPreference?: NotificationChannel, xDeliveryAttempt?: number, - xScheduledTime?: string, + xScheduledTime?: Date, xProvider?: NotificationProvider, xLanguage?: string, additionalProperties?: Map, @@ -57,8 +57,8 @@ class NotificationSentHeaders { /** * Event creation timestamp */ - get xTimestamp(): string | undefined { return this._xTimestamp; } - set xTimestamp(xTimestamp: string | undefined) { this._xTimestamp = xTimestamp; } + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } /** * Type of notification sent @@ -87,8 +87,8 @@ class NotificationSentHeaders { /** * When notification was scheduled to be sent */ - get xScheduledTime(): string | undefined { return this._xScheduledTime; } - set xScheduledTime(xScheduledTime: string | undefined) { this._xScheduledTime = xScheduledTime; } + get xScheduledTime(): Date | undefined { return this._xScheduledTime; } + set xScheduledTime(xScheduledTime: Date | undefined) { this._xScheduledTime = xScheduledTime; } /** * Notification service provider @@ -105,84 +105,86 @@ class NotificationSentHeaders { get additionalProperties(): Map | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.xCorrelationId !== undefined) { - json += `"x-correlation-id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`; + json["x-correlation-id"] = this.xCorrelationId; } if(this.xTenantId !== undefined) { - json += `"x-tenant-id": ${typeof this.xTenantId === 'number' || typeof this.xTenantId === 'boolean' ? this.xTenantId : JSON.stringify(this.xTenantId)},`; + json["x-tenant-id"] = this.xTenantId; } if(this.xTimestamp !== undefined) { - json += `"x-timestamp": ${typeof this.xTimestamp === 'number' || typeof this.xTimestamp === 'boolean' ? this.xTimestamp : JSON.stringify(this.xTimestamp)},`; + json["x-timestamp"] = this.xTimestamp; } if(this.xNotificationType !== undefined) { - json += `"x-notification-type": ${typeof this.xNotificationType === 'number' || typeof this.xNotificationType === 'boolean' ? this.xNotificationType : JSON.stringify(this.xNotificationType)},`; + json["x-notification-type"] = this.xNotificationType; } if(this.xTemplateId !== undefined) { - json += `"x-template-id": ${typeof this.xTemplateId === 'number' || typeof this.xTemplateId === 'boolean' ? this.xTemplateId : JSON.stringify(this.xTemplateId)},`; + json["x-template-id"] = this.xTemplateId; } if(this.xChannelPreference !== undefined) { - json += `"x-channel-preference": ${typeof this.xChannelPreference === 'number' || typeof this.xChannelPreference === 'boolean' ? this.xChannelPreference : JSON.stringify(this.xChannelPreference)},`; + json["x-channel-preference"] = this.xChannelPreference; } if(this.xDeliveryAttempt !== undefined) { - json += `"x-delivery-attempt": ${typeof this.xDeliveryAttempt === 'number' || typeof this.xDeliveryAttempt === 'boolean' ? this.xDeliveryAttempt : JSON.stringify(this.xDeliveryAttempt)},`; + json["x-delivery-attempt"] = this.xDeliveryAttempt; } if(this.xScheduledTime !== undefined) { - json += `"x-scheduled-time": ${typeof this.xScheduledTime === 'number' || typeof this.xScheduledTime === 'boolean' ? this.xScheduledTime : JSON.stringify(this.xScheduledTime)},`; + json["x-scheduled-time"] = this.xScheduledTime; } if(this.xProvider !== undefined) { - json += `"x-provider": ${typeof this.xProvider === 'number' || typeof this.xProvider === 'boolean' ? this.xProvider : JSON.stringify(this.xProvider)},`; + json["x-provider"] = this.xProvider; } if(this.xLanguage !== undefined) { - json += `"x-language": ${typeof this.xLanguage === 'number' || typeof this.xLanguage === 'boolean' ? this.xLanguage : JSON.stringify(this.xLanguage)},`; + json["x-language"] = this.xLanguage; } - if(this.additionalProperties !== undefined) { + 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(["x-correlation-id","x-tenant-id","x-timestamp","x-notification-type","x-template-id","x-channel-preference","x-delivery-attempt","x-scheduled-time","x-provider","x-language","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): NotificationSentHeaders { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): NotificationSentHeaders { const instance = new NotificationSentHeaders({} as any); if (obj["x-correlation-id"] !== undefined) { - instance.xCorrelationId = obj["x-correlation-id"]; + instance.xCorrelationId = obj["x-correlation-id"] as string; } if (obj["x-tenant-id"] !== undefined) { - instance.xTenantId = obj["x-tenant-id"]; + instance.xTenantId = obj["x-tenant-id"] as string; } if (obj["x-timestamp"] !== undefined) { - instance.xTimestamp = obj["x-timestamp"]; + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); } if (obj["x-notification-type"] !== undefined) { - instance.xNotificationType = obj["x-notification-type"]; + instance.xNotificationType = obj["x-notification-type"] as NotificationType; } if (obj["x-template-id"] !== undefined) { - instance.xTemplateId = obj["x-template-id"]; + instance.xTemplateId = obj["x-template-id"] as string; } if (obj["x-channel-preference"] !== undefined) { - instance.xChannelPreference = obj["x-channel-preference"]; + instance.xChannelPreference = obj["x-channel-preference"] as NotificationChannel; } if (obj["x-delivery-attempt"] !== undefined) { - instance.xDeliveryAttempt = obj["x-delivery-attempt"]; + instance.xDeliveryAttempt = obj["x-delivery-attempt"] as number; } if (obj["x-scheduled-time"] !== undefined) { - instance.xScheduledTime = obj["x-scheduled-time"]; + instance.xScheduledTime = obj["x-scheduled-time"] == null ? undefined : new Date(obj["x-scheduled-time"] as string); } if (obj["x-provider"] !== undefined) { - instance.xProvider = obj["x-provider"]; + instance.xProvider = obj["x-provider"] as NotificationProvider; } if (obj["x-language"] !== undefined) { - instance.xLanguage = obj["x-language"]; + instance.xLanguage = obj["x-language"] as string; } - + instance.additionalProperties = new Map(); const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","x-notification-type","x-template-id","x-channel-preference","x-delivery-attempt","x-scheduled-time","x-provider","x-language","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { @@ -190,9 +192,17 @@ class NotificationSentHeaders { } return instance; } + + public static unmarshal(json: string | object): NotificationSentHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return NotificationSentHeaders.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","required":["x-notification-type"],"properties":{"x-notification-type":{"type":"string","enum":["email","sms","push","webhook"],"description":"Type of notification sent"},"x-template-id":{"type":"string","description":"Template used for notification"},"x-channel-preference":{"type":"string","enum":["email","sms","push","none"],"description":"User's preferred notification channel"},"x-delivery-attempt":{"type":"integer","minimum":1,"maximum":3,"default":1,"description":"Delivery attempt number"},"x-scheduled-time":{"type":"string","format":"date-time","description":"When notification was scheduled to be sent"},"x-provider":{"type":"string","enum":["sendgrid","twilio","firebase","custom"],"description":"Notification service provider"}}},{"type":"object","properties":{"x-language":{"type":"string","pattern":"^[a-z]{2}(-[A-Z]{2})?$","description":"Language code (e.g., en-US, fr-FR)","default":"en-US"}}}],"$id":"NotificationSentHeaders","$schema":"http://json-schema.org/draft-07/schema"}; 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 { @@ -203,6 +213,7 @@ class NotificationSentHeaders { 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; } diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderCreatedHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderCreatedHeaders.ts index f214efd1..e7af1f98 100644 --- a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderCreatedHeaders.ts +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderCreatedHeaders.ts @@ -1,10 +1,10 @@ import {SourceService} from './SourceService'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; class OrderCreatedHeaders { private _xCorrelationId: string; private _xTenantId: string; - private _xTimestamp?: string; + private _xTimestamp?: Date; private _authorization?: string; private _xSourceService?: SourceService; private _xApiVersion?: string; @@ -15,7 +15,7 @@ class OrderCreatedHeaders { constructor(input: { xCorrelationId: string, xTenantId: string, - xTimestamp?: string, + xTimestamp?: Date, authorization?: string, xSourceService?: SourceService, xApiVersion?: string, @@ -49,8 +49,8 @@ class OrderCreatedHeaders { /** * Event creation timestamp */ - get xTimestamp(): string | undefined { return this._xTimestamp; } - set xTimestamp(xTimestamp: string | undefined) { this._xTimestamp = xTimestamp; } + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } /** * JWT token for authentication @@ -85,72 +85,74 @@ class OrderCreatedHeaders { get additionalProperties(): Map | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.xCorrelationId !== undefined) { - json += `"x-correlation-id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`; + json["x-correlation-id"] = this.xCorrelationId; } if(this.xTenantId !== undefined) { - json += `"x-tenant-id": ${typeof this.xTenantId === 'number' || typeof this.xTenantId === 'boolean' ? this.xTenantId : JSON.stringify(this.xTenantId)},`; + json["x-tenant-id"] = this.xTenantId; } if(this.xTimestamp !== undefined) { - json += `"x-timestamp": ${typeof this.xTimestamp === 'number' || typeof this.xTimestamp === 'boolean' ? this.xTimestamp : JSON.stringify(this.xTimestamp)},`; + json["x-timestamp"] = this.xTimestamp; } if(this.authorization !== undefined) { - json += `"authorization": ${typeof this.authorization === 'number' || typeof this.authorization === 'boolean' ? this.authorization : JSON.stringify(this.authorization)},`; + json["authorization"] = this.authorization; } if(this.xSourceService !== undefined) { - json += `"x-source-service": ${typeof this.xSourceService === 'number' || typeof this.xSourceService === 'boolean' ? this.xSourceService : JSON.stringify(this.xSourceService)},`; + json["x-source-service"] = this.xSourceService; } if(this.xApiVersion !== undefined) { - json += `"x-api-version": ${typeof this.xApiVersion === 'number' || typeof this.xApiVersion === 'boolean' ? this.xApiVersion : JSON.stringify(this.xApiVersion)},`; + json["x-api-version"] = this.xApiVersion; } if(this.xRequestId !== undefined) { - json += `"x-request-id": ${typeof this.xRequestId === 'number' || typeof this.xRequestId === 'boolean' ? this.xRequestId : JSON.stringify(this.xRequestId)},`; + json["x-request-id"] = this.xRequestId; } if(this.xUserId !== undefined) { - json += `"x-user-id": ${typeof this.xUserId === 'number' || typeof this.xUserId === 'boolean' ? this.xUserId : JSON.stringify(this.xUserId)},`; + json["x-user-id"] = this.xUserId; } - if(this.additionalProperties !== undefined) { + 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(["x-correlation-id","x-tenant-id","x-timestamp","authorization","x-source-service","x-api-version","x-request-id","x-user-id","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderCreatedHeaders { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCreatedHeaders { const instance = new OrderCreatedHeaders({} as any); if (obj["x-correlation-id"] !== undefined) { - instance.xCorrelationId = obj["x-correlation-id"]; + instance.xCorrelationId = obj["x-correlation-id"] as string; } if (obj["x-tenant-id"] !== undefined) { - instance.xTenantId = obj["x-tenant-id"]; + instance.xTenantId = obj["x-tenant-id"] as string; } if (obj["x-timestamp"] !== undefined) { - instance.xTimestamp = obj["x-timestamp"]; + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); } if (obj["authorization"] !== undefined) { - instance.authorization = obj["authorization"]; + instance.authorization = obj["authorization"] as string; } if (obj["x-source-service"] !== undefined) { - instance.xSourceService = obj["x-source-service"]; + instance.xSourceService = obj["x-source-service"] as SourceService; } if (obj["x-api-version"] !== undefined) { - instance.xApiVersion = obj["x-api-version"]; + instance.xApiVersion = obj["x-api-version"] as string; } if (obj["x-request-id"] !== undefined) { - instance.xRequestId = obj["x-request-id"]; + instance.xRequestId = obj["x-request-id"] as string; } if (obj["x-user-id"] !== undefined) { - instance.xUserId = obj["x-user-id"]; + instance.xUserId = obj["x-user-id"] as string; } - + instance.additionalProperties = new Map(); const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","authorization","x-source-service","x-api-version","x-request-id","x-user-id","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { @@ -158,9 +160,17 @@ class OrderCreatedHeaders { } return instance; } + + public static unmarshal(json: string | object): OrderCreatedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCreatedHeaders.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","properties":{"authorization":{"type":"string","pattern":"^Bearer [A-Za-z0-9\\-\\._~\\+\\/]+=*$","description":"JWT token for authentication"}}},{"type":"object","properties":{"x-source-service":{"type":"string","enum":["web-app","mobile-app","admin-panel"],"description":"Service that originated the event"},"x-api-version":{"type":"string","pattern":"^v[0-9]+$","description":"API version used","default":"v1"},"x-request-id":{"type":"string","format":"uuid","description":"Original request ID from the client"}}},{"type":"object","required":["x-user-id"],"properties":{"x-user-id":{"type":"string","format":"uuid","description":"ID of the user who created the order"}}}],"$id":"OrderCreatedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; 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 { @@ -171,6 +181,7 @@ class OrderCreatedHeaders { 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; } diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventType.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventType.ts new file mode 100644 index 00000000..ecd727a3 --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventType.ts @@ -0,0 +1,6 @@ + +/** + * Type of status change event + */ +type OrderEventType = "status-change" | "cancellation" | "refund"; +export { OrderEventType }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventsHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventsHeaders.ts new file mode 100644 index 00000000..adac64e3 --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderEventsHeaders.ts @@ -0,0 +1,4 @@ +import {OrderCreatedHeaders} from './OrderCreatedHeaders'; +import {OrderStatusChangedHeaders} from './OrderStatusChangedHeaders'; +type OrderEventsHeaders = OrderCreatedHeaders | OrderStatusChangedHeaders; +export { OrderEventsHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderReasonCode.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderReasonCode.ts new file mode 100644 index 00000000..c5e93a6a --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderReasonCode.ts @@ -0,0 +1,6 @@ + +/** + * Reason code for status change + */ +type OrderReasonCode = "customer-request" | "payment-failed" | "inventory-unavailable" | "fraud-detected"; +export { OrderReasonCode }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatus.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatus.ts new file mode 100644 index 00000000..6057e0c9 --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatus.ts @@ -0,0 +1,6 @@ + +/** + * Order status values + */ +type OrderStatus = "pending" | "confirmed" | "processing" | "shipped" | "delivered" | "cancelled"; +export { OrderStatus }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatusChangedHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatusChangedHeaders.ts new file mode 100644 index 00000000..fef8a7dc --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/OrderStatusChangedHeaders.ts @@ -0,0 +1,209 @@ +import {ActorType} from './ActorType'; +import {OrderEventType} from './OrderEventType'; +import {OrderStatus} from './OrderStatus'; +import {OrderReasonCode} from './OrderReasonCode'; +import {Priority} from './Priority'; +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +class OrderStatusChangedHeaders { + private _xCorrelationId: string; + private _xTenantId: string; + private _xTimestamp?: Date; + private _xActorId?: string; + private _xActorType?: ActorType; + private _xEventType: OrderEventType; + private _xPreviousStatus?: OrderStatus; + private _xReasonCode?: OrderReasonCode; + private _xPriority?: Priority; + private _additionalProperties?: Map; + + constructor(input: { + xCorrelationId: string, + xTenantId: string, + xTimestamp?: Date, + xActorId?: string, + xActorType?: ActorType, + xEventType: OrderEventType, + xPreviousStatus?: OrderStatus, + xReasonCode?: OrderReasonCode, + xPriority?: Priority, + additionalProperties?: Map, + }) { + this._xCorrelationId = input.xCorrelationId; + this._xTenantId = input.xTenantId; + this._xTimestamp = input.xTimestamp; + this._xActorId = input.xActorId; + this._xActorType = input.xActorType; + this._xEventType = input.xEventType; + this._xPreviousStatus = input.xPreviousStatus; + this._xReasonCode = input.xReasonCode; + this._xPriority = input.xPriority; + this._additionalProperties = input.additionalProperties; + } + + /** + * Unique correlation ID for request tracing + */ + get xCorrelationId(): string { return this._xCorrelationId; } + set xCorrelationId(xCorrelationId: string) { this._xCorrelationId = xCorrelationId; } + + /** + * Multi-tenant identifier + */ + get xTenantId(): string { return this._xTenantId; } + set xTenantId(xTenantId: string) { this._xTenantId = xTenantId; } + + /** + * Event creation timestamp + */ + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } + + /** + * ID of user/system that triggered the change + */ + get xActorId(): string | undefined { return this._xActorId; } + set xActorId(xActorId: string | undefined) { this._xActorId = xActorId; } + + /** + * Type of actor that triggered the change + */ + get xActorType(): ActorType | undefined { return this._xActorType; } + set xActorType(xActorType: ActorType | undefined) { this._xActorType = xActorType; } + + /** + * Type of status change event + */ + get xEventType(): OrderEventType { return this._xEventType; } + set xEventType(xEventType: OrderEventType) { this._xEventType = xEventType; } + + /** + * Order status values + */ + get xPreviousStatus(): OrderStatus | undefined { return this._xPreviousStatus; } + set xPreviousStatus(xPreviousStatus: OrderStatus | undefined) { this._xPreviousStatus = xPreviousStatus; } + + /** + * Reason code for status change + */ + get xReasonCode(): OrderReasonCode | undefined { return this._xReasonCode; } + set xReasonCode(xReasonCode: OrderReasonCode | undefined) { this._xReasonCode = xReasonCode; } + + /** + * Processing priority + */ + get xPriority(): Priority | undefined { return this._xPriority; } + set xPriority(xPriority: Priority | undefined) { this._xPriority = xPriority; } + + get additionalProperties(): Map | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + + public toJson(): Record { + const json: Record = {}; + if(this.xCorrelationId !== undefined) { + json["x-correlation-id"] = this.xCorrelationId; + } + if(this.xTenantId !== undefined) { + json["x-tenant-id"] = this.xTenantId; + } + if(this.xTimestamp !== undefined) { + json["x-timestamp"] = this.xTimestamp; + } + if(this.xActorId !== undefined) { + json["x-actor-id"] = this.xActorId; + } + if(this.xActorType !== undefined) { + json["x-actor-type"] = this.xActorType; + } + if(this.xEventType !== undefined) { + json["x-event-type"] = this.xEventType; + } + if(this.xPreviousStatus !== undefined) { + json["x-previous-status"] = this.xPreviousStatus; + } + if(this.xReasonCode !== undefined) { + json["x-reason-code"] = this.xReasonCode; + } + if(this.xPriority !== undefined) { + json["x-priority"] = this.xPriority; + } + 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(["x-correlation-id","x-tenant-id","x-timestamp","x-actor-id","x-actor-type","x-event-type","x-previous-status","x-reason-code","x-priority","additionalProperties"].includes(String(key))) continue; + json[key] = value; + } + } + return json; + } + + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderStatusChangedHeaders { + const instance = new OrderStatusChangedHeaders({} as any); + + if (obj["x-correlation-id"] !== undefined) { + instance.xCorrelationId = obj["x-correlation-id"] as string; + } + if (obj["x-tenant-id"] !== undefined) { + instance.xTenantId = obj["x-tenant-id"] as string; + } + if (obj["x-timestamp"] !== undefined) { + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); + } + if (obj["x-actor-id"] !== undefined) { + instance.xActorId = obj["x-actor-id"] as string; + } + if (obj["x-actor-type"] !== undefined) { + instance.xActorType = obj["x-actor-type"] as ActorType; + } + if (obj["x-event-type"] !== undefined) { + instance.xEventType = obj["x-event-type"] as OrderEventType; + } + if (obj["x-previous-status"] !== undefined) { + instance.xPreviousStatus = obj["x-previous-status"] as OrderStatus; + } + if (obj["x-reason-code"] !== undefined) { + instance.xReasonCode = obj["x-reason-code"] as OrderReasonCode; + } + if (obj["x-priority"] !== undefined) { + instance.xPriority = obj["x-priority"] as Priority; + } + + instance.additionalProperties = new Map(); + const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","x-actor-id","x-actor-type","x-event-type","x-previous-status","x-reason-code","x-priority","additionalProperties"].includes(key);})); + for (const [key, value] of propsToCheck) { + instance.additionalProperties.set(key, value as any); + } + return instance; + } + + public static unmarshal(json: string | object): OrderStatusChangedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderStatusChangedHeaders.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","properties":{"x-actor-id":{"type":"string","format":"uuid","description":"ID of user/system that triggered the change"},"x-actor-type":{"type":"string","enum":["user","system","admin"],"description":"Type of actor that triggered the change"}}},{"type":"object","required":["x-event-type"],"properties":{"x-event-type":{"type":"string","enum":["status-change","cancellation","refund"],"description":"Type of status change event"},"x-previous-status":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled"],"description":"Order status values"},"x-reason-code":{"type":"string","enum":["customer-request","payment-failed","inventory-unavailable","fraud-detected"],"description":"Reason code for status change"},"x-priority":{"type":"string","enum":["low","normal","high","urgent"],"default":"normal","description":"Processing priority"}}}],"$id":"OrderStatusChangedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; + 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 { OrderStatusChangedHeaders }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/PaymentProcessedHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/PaymentProcessedHeaders.ts index dab287c9..d77d4d96 100644 --- a/examples/ecommerce-asyncapi-headers/src/generated/headers/PaymentProcessedHeaders.ts +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/PaymentProcessedHeaders.ts @@ -1,11 +1,11 @@ import {PaymentProvider} from './PaymentProvider'; import {PaymentMethod} from './PaymentMethod'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; class PaymentProcessedHeaders { private _xCorrelationId: string; private _xTenantId: string; - private _xTimestamp?: string; + private _xTimestamp?: Date; private _xPaymentProvider: PaymentProvider; private _xPaymentMethod?: PaymentMethod; private _xRiskScore?: number; @@ -19,7 +19,7 @@ class PaymentProcessedHeaders { constructor(input: { xCorrelationId: string, xTenantId: string, - xTimestamp?: string, + xTimestamp?: Date, xPaymentProvider: PaymentProvider, xPaymentMethod?: PaymentMethod, xRiskScore?: number, @@ -59,8 +59,8 @@ class PaymentProcessedHeaders { /** * Event creation timestamp */ - get xTimestamp(): string | undefined { return this._xTimestamp; } - set xTimestamp(xTimestamp: string | undefined) { this._xTimestamp = xTimestamp; } + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } /** * Payment processor used @@ -113,90 +113,92 @@ class PaymentProcessedHeaders { get additionalProperties(): Map | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.xCorrelationId !== undefined) { - json += `"x-correlation-id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`; + json["x-correlation-id"] = this.xCorrelationId; } if(this.xTenantId !== undefined) { - json += `"x-tenant-id": ${typeof this.xTenantId === 'number' || typeof this.xTenantId === 'boolean' ? this.xTenantId : JSON.stringify(this.xTenantId)},`; + json["x-tenant-id"] = this.xTenantId; } if(this.xTimestamp !== undefined) { - json += `"x-timestamp": ${typeof this.xTimestamp === 'number' || typeof this.xTimestamp === 'boolean' ? this.xTimestamp : JSON.stringify(this.xTimestamp)},`; + json["x-timestamp"] = this.xTimestamp; } if(this.xPaymentProvider !== undefined) { - json += `"x-payment-provider": ${typeof this.xPaymentProvider === 'number' || typeof this.xPaymentProvider === 'boolean' ? this.xPaymentProvider : JSON.stringify(this.xPaymentProvider)},`; + json["x-payment-provider"] = this.xPaymentProvider; } if(this.xPaymentMethod !== undefined) { - json += `"x-payment-method": ${typeof this.xPaymentMethod === 'number' || typeof this.xPaymentMethod === 'boolean' ? this.xPaymentMethod : JSON.stringify(this.xPaymentMethod)},`; + json["x-payment-method"] = this.xPaymentMethod; } if(this.xRiskScore !== undefined) { - json += `"x-risk-score": ${typeof this.xRiskScore === 'number' || typeof this.xRiskScore === 'boolean' ? this.xRiskScore : JSON.stringify(this.xRiskScore)},`; + json["x-risk-score"] = this.xRiskScore; } if(this.xProcessorTransactionId !== undefined) { - json += `"x-processor-transaction-id": ${typeof this.xProcessorTransactionId === 'number' || typeof this.xProcessorTransactionId === 'boolean' ? this.xProcessorTransactionId : JSON.stringify(this.xProcessorTransactionId)},`; + json["x-processor-transaction-id"] = this.xProcessorTransactionId; } if(this.xRetryCount !== undefined) { - json += `"x-retry-count": ${typeof this.xRetryCount === 'number' || typeof this.xRetryCount === 'boolean' ? this.xRetryCount : JSON.stringify(this.xRetryCount)},`; + json["x-retry-count"] = this.xRetryCount; } if(this.xIdempotencyKey !== undefined) { - json += `"x-idempotency-key": ${typeof this.xIdempotencyKey === 'number' || typeof this.xIdempotencyKey === 'boolean' ? this.xIdempotencyKey : JSON.stringify(this.xIdempotencyKey)},`; + json["x-idempotency-key"] = this.xIdempotencyKey; } if(this.xWebhookSignature !== undefined) { - json += `"x-webhook-signature": ${typeof this.xWebhookSignature === 'number' || typeof this.xWebhookSignature === 'boolean' ? this.xWebhookSignature : JSON.stringify(this.xWebhookSignature)},`; + json["x-webhook-signature"] = this.xWebhookSignature; } if(this.xIpAddress !== undefined) { - json += `"x-ip-address": ${typeof this.xIpAddress === 'number' || typeof this.xIpAddress === 'boolean' ? this.xIpAddress : JSON.stringify(this.xIpAddress)},`; + json["x-ip-address"] = this.xIpAddress; } - if(this.additionalProperties !== undefined) { + 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(["x-correlation-id","x-tenant-id","x-timestamp","x-payment-provider","x-payment-method","x-risk-score","x-processor-transaction-id","x-retry-count","x-idempotency-key","x-webhook-signature","x-ip-address","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): PaymentProcessedHeaders { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): PaymentProcessedHeaders { const instance = new PaymentProcessedHeaders({} as any); if (obj["x-correlation-id"] !== undefined) { - instance.xCorrelationId = obj["x-correlation-id"]; + instance.xCorrelationId = obj["x-correlation-id"] as string; } if (obj["x-tenant-id"] !== undefined) { - instance.xTenantId = obj["x-tenant-id"]; + instance.xTenantId = obj["x-tenant-id"] as string; } if (obj["x-timestamp"] !== undefined) { - instance.xTimestamp = obj["x-timestamp"]; + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); } if (obj["x-payment-provider"] !== undefined) { - instance.xPaymentProvider = obj["x-payment-provider"]; + instance.xPaymentProvider = obj["x-payment-provider"] as PaymentProvider; } if (obj["x-payment-method"] !== undefined) { - instance.xPaymentMethod = obj["x-payment-method"]; + instance.xPaymentMethod = obj["x-payment-method"] as PaymentMethod; } if (obj["x-risk-score"] !== undefined) { - instance.xRiskScore = obj["x-risk-score"]; + instance.xRiskScore = obj["x-risk-score"] as number; } if (obj["x-processor-transaction-id"] !== undefined) { - instance.xProcessorTransactionId = obj["x-processor-transaction-id"]; + instance.xProcessorTransactionId = obj["x-processor-transaction-id"] as string; } if (obj["x-retry-count"] !== undefined) { - instance.xRetryCount = obj["x-retry-count"]; + instance.xRetryCount = obj["x-retry-count"] as number; } if (obj["x-idempotency-key"] !== undefined) { - instance.xIdempotencyKey = obj["x-idempotency-key"]; + instance.xIdempotencyKey = obj["x-idempotency-key"] as string; } if (obj["x-webhook-signature"] !== undefined) { - instance.xWebhookSignature = obj["x-webhook-signature"]; + instance.xWebhookSignature = obj["x-webhook-signature"] as string; } if (obj["x-ip-address"] !== undefined) { - instance.xIpAddress = obj["x-ip-address"]; + instance.xIpAddress = obj["x-ip-address"] as string; } - + instance.additionalProperties = new Map(); const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","x-payment-provider","x-payment-method","x-risk-score","x-processor-transaction-id","x-retry-count","x-idempotency-key","x-webhook-signature","x-ip-address","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { @@ -204,9 +206,17 @@ class PaymentProcessedHeaders { } return instance; } + + public static unmarshal(json: string | object): PaymentProcessedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return PaymentProcessedHeaders.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","required":["x-payment-provider"],"properties":{"x-payment-provider":{"type":"string","enum":["stripe","paypal","square","adyen"],"description":"Payment processor used"},"x-payment-method":{"type":"string","enum":["credit-card","debit-card","bank-transfer","digital-wallet"],"description":"Payment method used"},"x-risk-score":{"type":"number","minimum":0,"maximum":100,"description":"Fraud risk score (0-100)"},"x-processor-transaction-id":{"type":"string","description":"Transaction ID from payment processor"},"x-retry-count":{"type":"integer","minimum":0,"maximum":5,"default":0,"description":"Number of retry attempts"},"x-idempotency-key":{"type":"string","format":"uuid","description":"Ensures payment processing idempotency"}}},{"type":"object","properties":{"x-webhook-signature":{"type":"string","description":"Webhook signature for verification"},"x-ip-address":{"type":"string","format":"ipv4","description":"IP address"}}}],"$id":"PaymentProcessedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; 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 { @@ -217,6 +227,7 @@ class PaymentProcessedHeaders { 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; } diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/Priority.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/Priority.ts new file mode 100644 index 00000000..197fdf86 --- /dev/null +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/Priority.ts @@ -0,0 +1,6 @@ + +/** + * Processing priority + */ +type Priority = "low" | "normal" | "high" | "urgent"; +export { Priority }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-headers/src/generated/headers/UserBehaviorTrackedHeaders.ts b/examples/ecommerce-asyncapi-headers/src/generated/headers/UserBehaviorTrackedHeaders.ts index 5a01fcf8..632d9bce 100644 --- a/examples/ecommerce-asyncapi-headers/src/generated/headers/UserBehaviorTrackedHeaders.ts +++ b/examples/ecommerce-asyncapi-headers/src/generated/headers/UserBehaviorTrackedHeaders.ts @@ -1,11 +1,11 @@ import {DeviceType} from './DeviceType'; import {Platform} from './Platform'; import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; -import addFormats from 'ajv-formats'; +import {default as addFormats} from 'ajv-formats'; class UserBehaviorTrackedHeaders { private _xCorrelationId: string; private _xTenantId: string; - private _xTimestamp?: string; + private _xTimestamp?: Date; private _xSessionId: string; private _xUserAgent?: string; private _xDeviceType?: DeviceType; @@ -19,7 +19,7 @@ class UserBehaviorTrackedHeaders { constructor(input: { xCorrelationId: string, xTenantId: string, - xTimestamp?: string, + xTimestamp?: Date, xSessionId: string, xUserAgent?: string, xDeviceType?: DeviceType, @@ -59,8 +59,8 @@ class UserBehaviorTrackedHeaders { /** * Event creation timestamp */ - get xTimestamp(): string | undefined { return this._xTimestamp; } - set xTimestamp(xTimestamp: string | undefined) { this._xTimestamp = xTimestamp; } + get xTimestamp(): Date | undefined { return this._xTimestamp; } + set xTimestamp(xTimestamp: Date | undefined) { this._xTimestamp = xTimestamp; } /** * User session identifier @@ -113,98 +113,92 @@ class UserBehaviorTrackedHeaders { get additionalProperties(): Map | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.xCorrelationId !== undefined) { - json += `"x-correlation-id": ${typeof this.xCorrelationId === 'number' || typeof this.xCorrelationId === 'boolean' ? this.xCorrelationId : JSON.stringify(this.xCorrelationId)},`; + json["x-correlation-id"] = this.xCorrelationId; } if(this.xTenantId !== undefined) { - json += `"x-tenant-id": ${typeof this.xTenantId === 'number' || typeof this.xTenantId === 'boolean' ? this.xTenantId : JSON.stringify(this.xTenantId)},`; + json["x-tenant-id"] = this.xTenantId; } if(this.xTimestamp !== undefined) { - json += `"x-timestamp": ${typeof this.xTimestamp === 'number' || typeof this.xTimestamp === 'boolean' ? this.xTimestamp : JSON.stringify(this.xTimestamp)},`; + json["x-timestamp"] = this.xTimestamp; } if(this.xSessionId !== undefined) { - json += `"x-session-id": ${typeof this.xSessionId === 'number' || typeof this.xSessionId === 'boolean' ? this.xSessionId : JSON.stringify(this.xSessionId)},`; + json["x-session-id"] = this.xSessionId; } if(this.xUserAgent !== undefined) { - json += `"x-user-agent": ${typeof this.xUserAgent === 'number' || typeof this.xUserAgent === 'boolean' ? this.xUserAgent : JSON.stringify(this.xUserAgent)},`; + json["x-user-agent"] = this.xUserAgent; } if(this.xDeviceType !== undefined) { - json += `"x-device-type": ${typeof this.xDeviceType === 'number' || typeof this.xDeviceType === 'boolean' ? this.xDeviceType : JSON.stringify(this.xDeviceType)},`; + json["x-device-type"] = this.xDeviceType; } if(this.xPlatform !== undefined) { - json += `"x-platform": ${typeof this.xPlatform === 'number' || typeof this.xPlatform === 'boolean' ? this.xPlatform : JSON.stringify(this.xPlatform)},`; + json["x-platform"] = this.xPlatform; } if(this.xAbTestGroups !== undefined) { - let xAbTestGroupsJsonValues: any[] = []; - for (const unionItem of this.xAbTestGroups) { - xAbTestGroupsJsonValues.push(`${typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)}`); - } - json += `"x-ab-test-groups": [${xAbTestGroupsJsonValues.join(',')}],`; + json["x-ab-test-groups"] = this.xAbTestGroups; } if(this.xFeatureFlags !== undefined) { - let xFeatureFlagsJsonValues: any[] = []; - for (const unionItem of this.xFeatureFlags) { - xFeatureFlagsJsonValues.push(`${typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem)}`); - } - json += `"x-feature-flags": [${xFeatureFlagsJsonValues.join(',')}],`; + json["x-feature-flags"] = this.xFeatureFlags; } if(this.xGdprConsent !== undefined) { - json += `"x-gdpr-consent": ${typeof this.xGdprConsent === 'number' || typeof this.xGdprConsent === 'boolean' ? this.xGdprConsent : JSON.stringify(this.xGdprConsent)},`; + json["x-gdpr-consent"] = this.xGdprConsent; } if(this.xDataRetentionDays !== undefined) { - json += `"x-data-retention-days": ${typeof this.xDataRetentionDays === 'number' || typeof this.xDataRetentionDays === 'boolean' ? this.xDataRetentionDays : JSON.stringify(this.xDataRetentionDays)},`; + json["x-data-retention-days"] = this.xDataRetentionDays; } - if(this.additionalProperties !== undefined) { + 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(["x-correlation-id","x-tenant-id","x-timestamp","x-session-id","x-user-agent","x-device-type","x-platform","x-ab-test-groups","x-feature-flags","x-gdpr-consent","x-data-retention-days","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): UserBehaviorTrackedHeaders { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): UserBehaviorTrackedHeaders { const instance = new UserBehaviorTrackedHeaders({} as any); if (obj["x-correlation-id"] !== undefined) { - instance.xCorrelationId = obj["x-correlation-id"]; + instance.xCorrelationId = obj["x-correlation-id"] as string; } if (obj["x-tenant-id"] !== undefined) { - instance.xTenantId = obj["x-tenant-id"]; + instance.xTenantId = obj["x-tenant-id"] as string; } if (obj["x-timestamp"] !== undefined) { - instance.xTimestamp = obj["x-timestamp"]; + instance.xTimestamp = obj["x-timestamp"] == null ? undefined : new Date(obj["x-timestamp"] as string); } if (obj["x-session-id"] !== undefined) { - instance.xSessionId = obj["x-session-id"]; + instance.xSessionId = obj["x-session-id"] as string; } if (obj["x-user-agent"] !== undefined) { - instance.xUserAgent = obj["x-user-agent"]; + instance.xUserAgent = obj["x-user-agent"] as string; } if (obj["x-device-type"] !== undefined) { - instance.xDeviceType = obj["x-device-type"]; + instance.xDeviceType = obj["x-device-type"] as DeviceType; } if (obj["x-platform"] !== undefined) { - instance.xPlatform = obj["x-platform"]; + instance.xPlatform = obj["x-platform"] as Platform; } if (obj["x-ab-test-groups"] !== undefined) { - instance.xAbTestGroups = obj["x-ab-test-groups"]; + instance.xAbTestGroups = obj["x-ab-test-groups"] as string[]; } if (obj["x-feature-flags"] !== undefined) { - instance.xFeatureFlags = obj["x-feature-flags"]; + instance.xFeatureFlags = obj["x-feature-flags"] as string[]; } if (obj["x-gdpr-consent"] !== undefined) { - instance.xGdprConsent = obj["x-gdpr-consent"]; + instance.xGdprConsent = obj["x-gdpr-consent"] as boolean; } if (obj["x-data-retention-days"] !== undefined) { - instance.xDataRetentionDays = obj["x-data-retention-days"]; + instance.xDataRetentionDays = obj["x-data-retention-days"] as number; } - + instance.additionalProperties = new Map(); const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["x-correlation-id","x-tenant-id","x-timestamp","x-session-id","x-user-agent","x-device-type","x-platform","x-ab-test-groups","x-feature-flags","x-gdpr-consent","x-data-retention-days","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { @@ -212,9 +206,17 @@ class UserBehaviorTrackedHeaders { } return instance; } + + public static unmarshal(json: string | object): UserBehaviorTrackedHeaders { + const obj = typeof json === "object" ? json : JSON.parse(json); + return UserBehaviorTrackedHeaders.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","allOf":[{"type":"object","required":["x-correlation-id","x-tenant-id"],"properties":{"x-correlation-id":{"type":"string","format":"uuid","description":"Unique correlation ID for request tracing"},"x-tenant-id":{"type":"string","description":"Multi-tenant identifier"},"x-timestamp":{"type":"string","format":"date-time","description":"Event creation timestamp"}}},{"type":"object","required":["x-session-id"],"properties":{"x-session-id":{"type":"string","format":"uuid","description":"User session identifier"}}},{"type":"object","properties":{"x-user-agent":{"type":"string","description":"Browser/app user agent string"},"x-device-type":{"type":"string","enum":["desktop","mobile","tablet","tv","watch"],"description":"Type of device used"},"x-platform":{"type":"string","enum":["web","ios","android","api"],"description":"Platform/app used"}}},{"type":"object","properties":{"x-ab-test-groups":{"type":"array","items":{"type":"string"},"description":"A/B test groups user belongs to"},"x-feature-flags":{"type":"array","items":{"type":"string"},"description":"Active feature flags for user"},"x-gdpr-consent":{"type":"boolean","description":"Whether user has given GDPR consent"},"x-data-retention-days":{"type":"integer","minimum":1,"maximum":2555,"default":365,"description":"How long to retain this data"}}}],"$id":"UserBehaviorTrackedHeaders","$schema":"http://json-schema.org/draft-07/schema"}; 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 { @@ -225,6 +227,7 @@ class UserBehaviorTrackedHeaders { 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; } diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/InventoryUpdatesParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/InventoryUpdatesParameters.ts index fbb02ae4..bf46ff76 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/InventoryUpdatesParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/InventoryUpdatesParameters.ts @@ -1,14 +1,15 @@ +interface InventoryUpdatesParametersInterface { + warehouseId: string + zone: string + productId: string +} class InventoryUpdatesParameters { private _warehouseId: string; private _zone: string; private _productId: string; - constructor(input: { - warehouseId: string, - zone: string, - productId: string, - }) { + constructor(input: InventoryUpdatesParametersInterface) { this._warehouseId = input.warehouseId; this._zone = input.zone; this._productId = input.productId; @@ -56,19 +57,19 @@ class InventoryUpdatesParameters { if(warehouseIdMatch && warehouseIdMatch !== '') { parameters.warehouseId = warehouseIdMatch as any } else { - throw new Error(`Parameter: 'warehouseId' is not valid. Abort! `) + throw new Error(`Parameter: 'warehouseId' is not valid in InventoryUpdatesParameters. Aborting parameter extracting! `) } const zoneMatch = match[sequentialParameters.indexOf('{zone}')+1]; if(zoneMatch && zoneMatch !== '') { parameters.zone = zoneMatch as any } else { - throw new Error(`Parameter: 'zone' is not valid. Abort! `) + throw new Error(`Parameter: 'zone' is not valid in InventoryUpdatesParameters. Aborting parameter extracting! `) } const productIdMatch = match[sequentialParameters.indexOf('{productId}')+1]; if(productIdMatch && productIdMatch !== '') { parameters.productId = productIdMatch as any } else { - throw new Error(`Parameter: 'productId' is not valid. Abort! `) + throw new Error(`Parameter: 'productId' is not valid in InventoryUpdatesParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -76,4 +77,4 @@ class InventoryUpdatesParameters { return parameters; } } -export { InventoryUpdatesParameters }; \ No newline at end of file +export { InventoryUpdatesParameters, InventoryUpdatesParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/OrderEventsParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/OrderEventsParameters.ts index 5707fe1d..586a432d 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/OrderEventsParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/OrderEventsParameters.ts @@ -1,12 +1,13 @@ import {EventType} from './EventType'; +interface OrderEventsParametersInterface { + orderId: string + eventType: EventType +} class OrderEventsParameters { private _orderId: string; private _eventType: EventType; - constructor(input: { - orderId: string, - eventType: EventType, - }) { + constructor(input: OrderEventsParametersInterface) { this._orderId = input.orderId; this._eventType = input.eventType; } @@ -44,13 +45,13 @@ class OrderEventsParameters { if(orderIdMatch && orderIdMatch !== '') { parameters.orderId = orderIdMatch as any } else { - throw new Error(`Parameter: 'orderId' is not valid. Abort! `) + throw new Error(`Parameter: 'orderId' is not valid in OrderEventsParameters. Aborting parameter extracting! `) } const eventTypeMatch = match[sequentialParameters.indexOf('{eventType}')+1]; if(eventTypeMatch && eventTypeMatch !== '') { parameters.eventType = eventTypeMatch as any } else { - throw new Error(`Parameter: 'eventType' is not valid. Abort! `) + throw new Error(`Parameter: 'eventType' is not valid in OrderEventsParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -58,4 +59,4 @@ class OrderEventsParameters { return parameters; } } -export { OrderEventsParameters }; \ No newline at end of file +export { OrderEventsParameters, OrderEventsParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/ProductUpdatesParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/ProductUpdatesParameters.ts index 84d1084a..0880f726 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/ProductUpdatesParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/ProductUpdatesParameters.ts @@ -1,12 +1,13 @@ import {Category} from './Category'; +interface ProductUpdatesParametersInterface { + category: Category + productId: string +} class ProductUpdatesParameters { private _category: Category; private _productId: string; - constructor(input: { - category: Category, - productId: string, - }) { + constructor(input: ProductUpdatesParametersInterface) { this._category = input.category; this._productId = input.productId; } @@ -44,13 +45,13 @@ class ProductUpdatesParameters { if(categoryMatch && categoryMatch !== '') { parameters.category = categoryMatch as any } else { - throw new Error(`Parameter: 'category' is not valid. Abort! `) + throw new Error(`Parameter: 'category' is not valid in ProductUpdatesParameters. Aborting parameter extracting! `) } const productIdMatch = match[sequentialParameters.indexOf('{productId}')+1]; if(productIdMatch && productIdMatch !== '') { parameters.productId = productIdMatch as any } else { - throw new Error(`Parameter: 'productId' is not valid. Abort! `) + throw new Error(`Parameter: 'productId' is not valid in ProductUpdatesParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -58,4 +59,4 @@ class ProductUpdatesParameters { return parameters; } } -export { ProductUpdatesParameters }; \ No newline at end of file +export { ProductUpdatesParameters, ProductUpdatesParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/SupportTicketsParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/SupportTicketsParameters.ts index 652e6b31..4bbeb8da 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/SupportTicketsParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/SupportTicketsParameters.ts @@ -1,15 +1,16 @@ import {Priority} from './Priority'; import {Department} from './Department'; +interface SupportTicketsParametersInterface { + priority: Priority + department: Department + ticketId: string +} class SupportTicketsParameters { private _priority: Priority; private _department: Department; private _ticketId: string; - constructor(input: { - priority: Priority, - department: Department, - ticketId: string, - }) { + constructor(input: SupportTicketsParametersInterface) { this._priority = input.priority; this._department = input.department; this._ticketId = input.ticketId; @@ -55,19 +56,19 @@ class SupportTicketsParameters { if(priorityMatch && priorityMatch !== '') { parameters.priority = priorityMatch as any } else { - throw new Error(`Parameter: 'priority' is not valid. Abort! `) + throw new Error(`Parameter: 'priority' is not valid in SupportTicketsParameters. Aborting parameter extracting! `) } const departmentMatch = match[sequentialParameters.indexOf('{department}')+1]; if(departmentMatch && departmentMatch !== '') { parameters.department = departmentMatch as any } else { - throw new Error(`Parameter: 'department' is not valid. Abort! `) + throw new Error(`Parameter: 'department' is not valid in SupportTicketsParameters. Aborting parameter extracting! `) } const ticketIdMatch = match[sequentialParameters.indexOf('{ticketId}')+1]; if(ticketIdMatch && ticketIdMatch !== '') { parameters.ticketId = ticketIdMatch as any } else { - throw new Error(`Parameter: 'ticketId' is not valid. Abort! `) + throw new Error(`Parameter: 'ticketId' is not valid in SupportTicketsParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -75,4 +76,4 @@ class SupportTicketsParameters { return parameters; } } -export { SupportTicketsParameters }; \ No newline at end of file +export { SupportTicketsParameters, SupportTicketsParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/TenantAnalyticsParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/TenantAnalyticsParameters.ts index dd9ba5d5..fa046f88 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/TenantAnalyticsParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/TenantAnalyticsParameters.ts @@ -1,18 +1,19 @@ import {EnvironmentType} from './EnvironmentType'; import {MetricType} from './MetricType'; import {AggregationPeriod} from './AggregationPeriod'; +interface TenantAnalyticsParametersInterface { + tenantId: string + environmentType: EnvironmentType + metricType: MetricType + aggregationPeriod: AggregationPeriod +} class TenantAnalyticsParameters { private _tenantId: string; private _environmentType: EnvironmentType; private _metricType: MetricType; private _aggregationPeriod: AggregationPeriod; - constructor(input: { - tenantId: string, - environmentType: EnvironmentType, - metricType: MetricType, - aggregationPeriod: AggregationPeriod, - }) { + constructor(input: TenantAnalyticsParametersInterface) { this._tenantId = input.tenantId; this._environmentType = input.environmentType; this._metricType = input.metricType; @@ -66,25 +67,25 @@ class TenantAnalyticsParameters { if(tenantIdMatch && tenantIdMatch !== '') { parameters.tenantId = tenantIdMatch as any } else { - throw new Error(`Parameter: 'tenantId' is not valid. Abort! `) + throw new Error(`Parameter: 'tenantId' is not valid in TenantAnalyticsParameters. Aborting parameter extracting! `) } const environmentTypeMatch = match[sequentialParameters.indexOf('{environmentType}')+1]; if(environmentTypeMatch && environmentTypeMatch !== '') { parameters.environmentType = environmentTypeMatch as any } else { - throw new Error(`Parameter: 'environmentType' is not valid. Abort! `) + throw new Error(`Parameter: 'environmentType' is not valid in TenantAnalyticsParameters. Aborting parameter extracting! `) } const metricTypeMatch = match[sequentialParameters.indexOf('{metricType}')+1]; if(metricTypeMatch && metricTypeMatch !== '') { parameters.metricType = metricTypeMatch as any } else { - throw new Error(`Parameter: 'metricType' is not valid. Abort! `) + throw new Error(`Parameter: 'metricType' is not valid in TenantAnalyticsParameters. Aborting parameter extracting! `) } const aggregationPeriodMatch = match[sequentialParameters.indexOf('{aggregationPeriod}')+1]; if(aggregationPeriodMatch && aggregationPeriodMatch !== '') { parameters.aggregationPeriod = aggregationPeriodMatch as any } else { - throw new Error(`Parameter: 'aggregationPeriod' is not valid. Abort! `) + throw new Error(`Parameter: 'aggregationPeriod' is not valid in TenantAnalyticsParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -92,4 +93,4 @@ class TenantAnalyticsParameters { return parameters; } } -export { TenantAnalyticsParameters }; \ No newline at end of file +export { TenantAnalyticsParameters, TenantAnalyticsParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserActivityParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserActivityParameters.ts index 4c0a6487..05dbf459 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserActivityParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserActivityParameters.ts @@ -1,10 +1,11 @@ +interface UserActivityParametersInterface { + userId: string +} class UserActivityParameters { private _userId: string; - constructor(input: { - userId: string, - }) { + constructor(input: UserActivityParametersInterface) { this._userId = input.userId; } @@ -34,7 +35,7 @@ class UserActivityParameters { if(userIdMatch && userIdMatch !== '') { parameters.userId = userIdMatch as any } else { - throw new Error(`Parameter: 'userId' is not valid. Abort! `) + throw new Error(`Parameter: 'userId' is not valid in UserActivityParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -42,4 +43,4 @@ class UserActivityParameters { return parameters; } } -export { UserActivityParameters }; \ No newline at end of file +export { UserActivityParameters, UserActivityParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserNotificationsParameters.ts b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserNotificationsParameters.ts index 8100853c..66eaef18 100644 --- a/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserNotificationsParameters.ts +++ b/examples/ecommerce-asyncapi-parameters/src/generated/parameters/UserNotificationsParameters.ts @@ -1,15 +1,16 @@ import {Region} from './Region'; import {NotificationType} from './NotificationType'; +interface UserNotificationsParametersInterface { + region: Region + userId: string + notificationType: NotificationType +} class UserNotificationsParameters { private _region: Region; private _userId: string; private _notificationType: NotificationType; - constructor(input: { - region: Region, - userId: string, - notificationType: NotificationType, - }) { + constructor(input: UserNotificationsParametersInterface) { this._region = input.region; this._userId = input.userId; this._notificationType = input.notificationType; @@ -55,19 +56,19 @@ class UserNotificationsParameters { if(regionMatch && regionMatch !== '') { parameters.region = regionMatch as any } else { - throw new Error(`Parameter: 'region' is not valid. Abort! `) + throw new Error(`Parameter: 'region' is not valid in UserNotificationsParameters. Aborting parameter extracting! `) } const userIdMatch = match[sequentialParameters.indexOf('{userId}')+1]; if(userIdMatch && userIdMatch !== '') { parameters.userId = userIdMatch as any } else { - throw new Error(`Parameter: 'userId' is not valid. Abort! `) + throw new Error(`Parameter: 'userId' is not valid in UserNotificationsParameters. Aborting parameter extracting! `) } const notificationTypeMatch = match[sequentialParameters.indexOf('{notificationType}')+1]; if(notificationTypeMatch && notificationTypeMatch !== '') { parameters.notificationType = notificationTypeMatch as any } else { - throw new Error(`Parameter: 'notificationType' is not valid. Abort! `) + throw new Error(`Parameter: 'notificationType' is not valid in UserNotificationsParameters. Aborting parameter extracting! `) } } else { throw new Error(`Unable to find parameters in channel/topic, topic was ${channel}`) @@ -75,4 +76,4 @@ class UserNotificationsParameters { return parameters; } } -export { UserNotificationsParameters }; \ No newline at end of file +export { UserNotificationsParameters, UserNotificationsParametersInterface }; \ No newline at end of file diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/Address.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/Address.ts index ba9d6f3b..2d3d7393 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/Address.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/Address.ts @@ -46,61 +46,68 @@ class Address { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.street !== undefined) { - json += `"street": ${typeof this.street === 'number' || typeof this.street === 'boolean' ? this.street : JSON.stringify(this.street)},`; + json["street"] = this.street; } if(this.city !== undefined) { - json += `"city": ${typeof this.city === 'number' || typeof this.city === 'boolean' ? this.city : JSON.stringify(this.city)},`; + json["city"] = this.city; } if(this.state !== undefined) { - json += `"state": ${typeof this.state === 'number' || typeof this.state === 'boolean' ? this.state : JSON.stringify(this.state)},`; + json["state"] = this.state; } if(this.country !== undefined) { - json += `"country": ${typeof this.country === 'number' || typeof this.country === 'boolean' ? this.country : JSON.stringify(this.country)},`; + json["country"] = this.country; } if(this.postalCode !== undefined) { - json += `"postalCode": ${typeof this.postalCode === 'number' || typeof this.postalCode === 'boolean' ? this.postalCode : JSON.stringify(this.postalCode)},`; + json["postalCode"] = this.postalCode; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["street","city","state","country","postalCode","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): Address { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): Address { const instance = new Address({} as any); if (obj["street"] !== undefined) { - instance.street = obj["street"]; + instance.street = obj["street"] as string; } if (obj["city"] !== undefined) { - instance.city = obj["city"]; + instance.city = obj["city"] as string; } if (obj["state"] !== undefined) { - instance.state = obj["state"]; + instance.state = obj["state"] as string; } if (obj["country"] !== undefined) { - instance.country = obj["country"]; + instance.country = obj["country"] as string; } if (obj["postalCode"] !== undefined) { - instance.postalCode = obj["postalCode"]; + instance.postalCode = obj["postalCode"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["street","city","state","country","postalCode","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): Address { + const obj = typeof json === "object" ? json : JSON.parse(json); + return Address.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code"},"postalCode":{"type":"string"}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/Attachment.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/Attachment.ts index fb3b973c..b2665eca 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/Attachment.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/Attachment.ts @@ -31,49 +31,56 @@ class Attachment { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.filename !== undefined) { - json += `"filename": ${typeof this.filename === 'number' || typeof this.filename === 'boolean' ? this.filename : JSON.stringify(this.filename)},`; + json["filename"] = this.filename; } if(this.contentType !== undefined) { - json += `"contentType": ${typeof this.contentType === 'number' || typeof this.contentType === 'boolean' ? this.contentType : JSON.stringify(this.contentType)},`; + json["contentType"] = this.contentType; } if(this.data !== undefined) { - json += `"data": ${typeof this.data === 'number' || typeof this.data === 'boolean' ? this.data : JSON.stringify(this.data)},`; + json["data"] = this.data; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["filename","contentType","data","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): Attachment { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): Attachment { const instance = new Attachment({} as any); if (obj["filename"] !== undefined) { - instance.filename = obj["filename"]; + instance.filename = obj["filename"] as string; } if (obj["contentType"] !== undefined) { - instance.contentType = obj["contentType"]; + instance.contentType = obj["contentType"] as string; } if (obj["data"] !== undefined) { - instance.data = obj["data"]; + instance.data = obj["data"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["filename","contentType","data","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): Attachment { + const obj = typeof json === "object" ? json : JSON.parse(json); + return Attachment.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","properties":{"filename":{"type":"string"},"contentType":{"type":"string"},"data":{"type":"string","contentEncoding":"base64"}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/EmailNotification.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/EmailNotification.ts index fafb3f54..59ba2f39 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/EmailNotification.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/EmailNotification.ts @@ -41,64 +41,71 @@ class EmailNotification { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.type !== undefined) { - json += `"type": ${typeof this.type === 'number' || typeof this.type === 'boolean' ? this.type : JSON.stringify(this.type)},`; + json["type"] = this.type; } if(this.recipientId !== undefined) { - json += `"recipientId": ${typeof this.recipientId === 'number' || typeof this.recipientId === 'boolean' ? this.recipientId : JSON.stringify(this.recipientId)},`; + json["recipientId"] = this.recipientId; } if(this.subject !== undefined) { - json += `"subject": ${typeof this.subject === 'number' || typeof this.subject === 'boolean' ? this.subject : JSON.stringify(this.subject)},`; + json["subject"] = this.subject; } if(this.body !== undefined) { - json += `"body": ${typeof this.body === 'number' || typeof this.body === 'boolean' ? this.body : JSON.stringify(this.body)},`; + json["body"] = this.body; } if(this.attachments !== undefined) { - let attachmentsJsonValues: any[] = []; - for (const unionItem of this.attachments) { - attachmentsJsonValues.push(`${unionItem && typeof unionItem === 'object' && 'marshal' in unionItem && typeof unionItem.marshal === 'function' ? unionItem.marshal() : JSON.stringify(unionItem)}`); - } - json += `"attachments": [${attachmentsJsonValues.join(',')}],`; + json["attachments"] = this.attachments.map((item: any) => + item && typeof item === 'object' && 'toJson' in item && typeof item.toJson === 'function' + ? item.toJson() + : item + ); } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["type","recipientId","subject","body","attachments","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): EmailNotification { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): EmailNotification { const instance = new EmailNotification({} as any); if (obj["recipientId"] !== undefined) { - instance.recipientId = obj["recipientId"]; + instance.recipientId = obj["recipientId"] as string; } if (obj["subject"] !== undefined) { - instance.subject = obj["subject"]; + instance.subject = obj["subject"] as string; } if (obj["body"] !== undefined) { - instance.body = obj["body"]; + instance.body = obj["body"] as string; } if (obj["attachments"] !== undefined) { instance.attachments = obj["attachments"] == null ? undefined - : obj["attachments"].map((item: any) => Attachment.unmarshal(item)); + : (obj["attachments"] as Record[]).map((item: Record) => Attachment.fromJson(item)); } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["type","recipientId","subject","body","attachments","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): EmailNotification { + const obj = typeof json === "object" ? json : JSON.parse(json); + return EmailNotification.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["type","recipientId","subject","body"],"properties":{"type":{"const":"email"},"recipientId":{"type":"string"},"subject":{"type":"string"},"body":{"type":"string"},"attachments":{"type":"array","items":{"type":"object","properties":{"filename":{"type":"string"},"contentType":{"type":"string"},"data":{"type":"string","contentEncoding":"base64"}}}}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/OrderCreated.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/OrderCreated.ts index 44306254..04731593 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/OrderCreated.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/OrderCreated.ts @@ -73,79 +73,90 @@ class OrderCreated { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.customerId !== undefined) { - json += `"customerId": ${typeof this.customerId === 'number' || typeof this.customerId === 'boolean' ? this.customerId : JSON.stringify(this.customerId)},`; + json["customerId"] = this.customerId; } if(this.items !== undefined) { - let itemsJsonValues: any[] = []; - for (const unionItem of this.items) { - itemsJsonValues.push(`${unionItem && typeof unionItem === 'object' && 'marshal' in unionItem && typeof unionItem.marshal === 'function' ? unionItem.marshal() : JSON.stringify(unionItem)}`); - } - json += `"items": [${itemsJsonValues.join(',')}],`; + json["items"] = this.items.map((item: any) => + item && typeof item === 'object' && 'toJson' in item && typeof item.toJson === 'function' + ? item.toJson() + : item + ); } if(this.totalAmount !== undefined) { - json += `"totalAmount": ${typeof this.totalAmount === 'number' || typeof this.totalAmount === 'boolean' ? this.totalAmount : JSON.stringify(this.totalAmount)},`; + json["totalAmount"] = this.totalAmount; } if(this.currency !== undefined) { - json += `"currency": ${typeof this.currency === 'number' || typeof this.currency === 'boolean' ? this.currency : JSON.stringify(this.currency)},`; + json["currency"] = this.currency; } if(this.shippingAddress !== undefined) { - json += `"shippingAddress": ${this.shippingAddress && typeof this.shippingAddress === 'object' && 'marshal' in this.shippingAddress && typeof this.shippingAddress.marshal === 'function' ? this.shippingAddress.marshal() : JSON.stringify(this.shippingAddress)},`; + json["shippingAddress"] = this.shippingAddress && typeof this.shippingAddress === 'object' && 'toJson' in this.shippingAddress && typeof this.shippingAddress.toJson === 'function' ? this.shippingAddress.toJson() : this.shippingAddress; } if(this.metadata !== undefined) { - json += `"metadata": ${typeof this.metadata === 'number' || typeof this.metadata === 'boolean' ? this.metadata : JSON.stringify(this.metadata)},`; + const serializedMap: Record = {}; + for (const [key, value] of Object.entries(this.metadata)) { + serializedMap[key] = value; + } + json["metadata"] = serializedMap; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","customerId","items","totalAmount","currency","shippingAddress","metadata","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderCreated { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderCreated { const instance = new OrderCreated({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["customerId"] !== undefined) { - instance.customerId = obj["customerId"]; + instance.customerId = obj["customerId"] as string; } if (obj["items"] !== undefined) { - instance.items = obj["items"] == null - ? null - : obj["items"].map((item: any) => OrderItem.unmarshal(item)); + instance.items = (obj["items"] as Record[]).map((item: Record) => OrderItem.fromJson(item)); } if (obj["totalAmount"] !== undefined) { - instance.totalAmount = obj["totalAmount"]; + instance.totalAmount = obj["totalAmount"] as number; } if (obj["currency"] !== undefined) { - instance.currency = obj["currency"]; + instance.currency = obj["currency"] as Currency; } if (obj["shippingAddress"] !== undefined) { - instance.shippingAddress = Address.unmarshal(obj["shippingAddress"]); + instance.shippingAddress = Address.fromJson(obj["shippingAddress"] as Record); } if (obj["metadata"] !== undefined) { - instance.metadata = obj["metadata"]; + instance.metadata = obj["metadata"] == null + ? undefined + : obj["metadata"] as Record; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","customerId","items","totalAmount","currency","shippingAddress","metadata","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderCreated { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderCreated.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","customerId","items","totalAmount","currency"],"properties":{"orderId":{"type":"string","format":"uuid","description":"Unique order identifier"},"customerId":{"type":"string","format":"uuid","description":"Customer who placed the order"},"items":{"type":"array","items":{"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","description":"Product identifier"},"quantity":{"type":"integer","minimum":1,"description":"Number of items ordered"},"unitPrice":{"type":"number","minimum":0,"description":"Price per unit in cents"},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata"}}}},"totalAmount":{"type":"number","minimum":0,"description":"Total order amount in cents"},"currency":{"type":"string","enum":["USD","EUR","GBP"],"description":"Currency code"},"shippingAddress":{"type":"object","required":["street","city","country","postalCode"],"properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"country":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code"},"postalCode":{"type":"string"}}},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata"}},"$id":"OrderCreated"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/OrderItem.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/OrderItem.ts index 8f9d67f4..43ec4a96 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/OrderItem.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/OrderItem.ts @@ -49,55 +49,68 @@ class OrderItem { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.productId !== undefined) { - json += `"productId": ${typeof this.productId === 'number' || typeof this.productId === 'boolean' ? this.productId : JSON.stringify(this.productId)},`; + json["productId"] = this.productId; } if(this.quantity !== undefined) { - json += `"quantity": ${typeof this.quantity === 'number' || typeof this.quantity === 'boolean' ? this.quantity : JSON.stringify(this.quantity)},`; + json["quantity"] = this.quantity; } if(this.unitPrice !== undefined) { - json += `"unitPrice": ${typeof this.unitPrice === 'number' || typeof this.unitPrice === 'boolean' ? this.unitPrice : JSON.stringify(this.unitPrice)},`; + json["unitPrice"] = this.unitPrice; } if(this.metadata !== undefined) { - json += `"metadata": ${typeof this.metadata === 'number' || typeof this.metadata === 'boolean' ? this.metadata : JSON.stringify(this.metadata)},`; + const serializedMap: Record = {}; + for (const [key, value] of Object.entries(this.metadata)) { + serializedMap[key] = value; + } + json["metadata"] = serializedMap; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["productId","quantity","unitPrice","metadata","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderItem { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderItem { const instance = new OrderItem({} as any); if (obj["productId"] !== undefined) { - instance.productId = obj["productId"]; + instance.productId = obj["productId"] as string; } if (obj["quantity"] !== undefined) { - instance.quantity = obj["quantity"]; + instance.quantity = obj["quantity"] as number; } if (obj["unitPrice"] !== undefined) { - instance.unitPrice = obj["unitPrice"]; + instance.unitPrice = obj["unitPrice"] as number; } if (obj["metadata"] !== undefined) { - instance.metadata = obj["metadata"]; + instance.metadata = obj["metadata"] == null + ? undefined + : obj["metadata"] as Record; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["productId","quantity","unitPrice","metadata","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderItem { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderItem.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["productId","quantity","unitPrice"],"properties":{"productId":{"type":"string","description":"Product identifier"},"quantity":{"type":"integer","minimum":1,"description":"Number of items ordered"},"unitPrice":{"type":"number","minimum":0,"description":"Price per unit in cents"},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata"}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/OrderStatusChanged.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/OrderStatusChanged.ts index 830e92da..e5c9cbe3 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/OrderStatusChanged.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/OrderStatusChanged.ts @@ -47,61 +47,68 @@ class OrderStatusChanged { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.previousStatus !== undefined) { - json += `"previousStatus": ${typeof this.previousStatus === 'number' || typeof this.previousStatus === 'boolean' ? this.previousStatus : JSON.stringify(this.previousStatus)},`; + json["previousStatus"] = this.previousStatus; } if(this.newStatus !== undefined) { - json += `"newStatus": ${typeof this.newStatus === 'number' || typeof this.newStatus === 'boolean' ? this.newStatus : JSON.stringify(this.newStatus)},`; + json["newStatus"] = this.newStatus; } if(this.timestamp !== undefined) { - json += `"timestamp": ${typeof this.timestamp === 'number' || typeof this.timestamp === 'boolean' ? this.timestamp : JSON.stringify(this.timestamp)},`; + json["timestamp"] = this.timestamp; } if(this.reason !== undefined) { - json += `"reason": ${typeof this.reason === 'number' || typeof this.reason === 'boolean' ? this.reason : JSON.stringify(this.reason)},`; + json["reason"] = this.reason; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["orderId","previousStatus","newStatus","timestamp","reason","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): OrderStatusChanged { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): OrderStatusChanged { const instance = new OrderStatusChanged({} as any); if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["previousStatus"] !== undefined) { - instance.previousStatus = obj["previousStatus"]; + instance.previousStatus = obj["previousStatus"] as OrderStatus; } if (obj["newStatus"] !== undefined) { - instance.newStatus = obj["newStatus"]; + instance.newStatus = obj["newStatus"] as OrderStatus; } if (obj["timestamp"] !== undefined) { - instance.timestamp = obj["timestamp"] == null ? null : new Date(obj["timestamp"]); + instance.timestamp = new Date(obj["timestamp"] as string); } if (obj["reason"] !== undefined) { - instance.reason = obj["reason"]; + instance.reason = obj["reason"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["orderId","previousStatus","newStatus","timestamp","reason","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): OrderStatusChanged { + const obj = typeof json === "object" ? json : JSON.parse(json); + return OrderStatusChanged.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["orderId","previousStatus","newStatus","timestamp"],"properties":{"orderId":{"type":"string","format":"uuid"},"previousStatus":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled","refunded"]},"newStatus":{"type":"string","enum":["pending","confirmed","processing","shipped","delivered","cancelled","refunded"]},"timestamp":{"type":"string","format":"date-time"},"reason":{"type":"string","description":"Reason for status change"}},"$id":"OrderStatusChanged"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/PaymentProcessed.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/PaymentProcessed.ts index dce9cb4e..d1b45ae6 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/PaymentProcessed.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/PaymentProcessed.ts @@ -60,67 +60,80 @@ class PaymentProcessed { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.paymentId !== undefined) { - json += `"paymentId": ${typeof this.paymentId === 'number' || typeof this.paymentId === 'boolean' ? this.paymentId : JSON.stringify(this.paymentId)},`; + json["paymentId"] = this.paymentId; } if(this.orderId !== undefined) { - json += `"orderId": ${typeof this.orderId === 'number' || typeof this.orderId === 'boolean' ? this.orderId : JSON.stringify(this.orderId)},`; + json["orderId"] = this.orderId; } if(this.amount !== undefined) { - json += `"amount": ${typeof this.amount === 'number' || typeof this.amount === 'boolean' ? this.amount : JSON.stringify(this.amount)},`; + json["amount"] = this.amount; } if(this.currency !== undefined) { - json += `"currency": ${typeof this.currency === 'number' || typeof this.currency === 'boolean' ? this.currency : JSON.stringify(this.currency)},`; + json["currency"] = this.currency; } if(this.status !== undefined) { - json += `"status": ${typeof this.status === 'number' || typeof this.status === 'boolean' ? this.status : JSON.stringify(this.status)},`; + json["status"] = this.status; } if(this.processorResponse !== undefined) { - json += `"processorResponse": ${typeof this.processorResponse === 'number' || typeof this.processorResponse === 'boolean' ? this.processorResponse : JSON.stringify(this.processorResponse)},`; + const serializedMap: Record = {}; + for (const [key, value] of Object.entries(this.processorResponse)) { + serializedMap[key] = value; + } + json["processorResponse"] = serializedMap; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["paymentId","orderId","amount","currency","status","processorResponse","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): PaymentProcessed { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): PaymentProcessed { const instance = new PaymentProcessed({} as any); if (obj["paymentId"] !== undefined) { - instance.paymentId = obj["paymentId"]; + instance.paymentId = obj["paymentId"] as string; } if (obj["orderId"] !== undefined) { - instance.orderId = obj["orderId"]; + instance.orderId = obj["orderId"] as string; } if (obj["amount"] !== undefined) { - instance.amount = obj["amount"]; + instance.amount = obj["amount"] as number; } if (obj["currency"] !== undefined) { - instance.currency = obj["currency"]; + instance.currency = obj["currency"] as Currency; } if (obj["status"] !== undefined) { - instance.status = obj["status"]; + instance.status = obj["status"] as PaymentStatus; } if (obj["processorResponse"] !== undefined) { - instance.processorResponse = obj["processorResponse"]; + instance.processorResponse = obj["processorResponse"] == null + ? undefined + : obj["processorResponse"] as Record; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["paymentId","orderId","amount","currency","status","processorResponse","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): PaymentProcessed { + const obj = typeof json === "object" ? json : JSON.parse(json); + return PaymentProcessed.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","required":["paymentId","orderId","amount","currency","status"],"properties":{"paymentId":{"type":"string","format":"uuid"},"orderId":{"type":"string","format":"uuid"},"amount":{"type":"number","minimum":0},"currency":{"type":"string","enum":["USD","EUR","GBP"],"description":"Currency code"},"status":{"type":"string","enum":["success","failed","pending"],"description":"Payment processing status"},"processorResponse":{"type":"object","additionalProperties":true,"description":"Additional metadata"}},"$id":"PaymentProcessed"}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/PushNotification.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/PushNotification.ts index 4f63c501..096ccb15 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/PushNotification.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/PushNotification.ts @@ -40,58 +40,65 @@ class PushNotification { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.type !== undefined) { - json += `"type": ${typeof this.type === 'number' || typeof this.type === 'boolean' ? this.type : JSON.stringify(this.type)},`; + json["type"] = this.type; } if(this.recipientId !== undefined) { - json += `"recipientId": ${typeof this.recipientId === 'number' || typeof this.recipientId === 'boolean' ? this.recipientId : JSON.stringify(this.recipientId)},`; + json["recipientId"] = this.recipientId; } if(this.title !== undefined) { - json += `"title": ${typeof this.title === 'number' || typeof this.title === 'boolean' ? this.title : JSON.stringify(this.title)},`; + json["title"] = this.title; } if(this.body !== undefined) { - json += `"body": ${typeof this.body === 'number' || typeof this.body === 'boolean' ? this.body : JSON.stringify(this.body)},`; + json["body"] = this.body; } if(this.badge !== undefined) { - json += `"badge": ${typeof this.badge === 'number' || typeof this.badge === 'boolean' ? this.badge : JSON.stringify(this.badge)},`; + json["badge"] = this.badge; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["type","recipientId","title","body","badge","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): PushNotification { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): PushNotification { const instance = new PushNotification({} as any); if (obj["recipientId"] !== undefined) { - instance.recipientId = obj["recipientId"]; + instance.recipientId = obj["recipientId"] as string; } if (obj["title"] !== undefined) { - instance.title = obj["title"]; + instance.title = obj["title"] as string; } if (obj["body"] !== undefined) { - instance.body = obj["body"]; + instance.body = obj["body"] as string; } if (obj["badge"] !== undefined) { - instance.badge = obj["badge"]; + instance.badge = obj["badge"] as number; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["type","recipientId","title","body","badge","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): PushNotification { + const obj = typeof json === "object" ? json : JSON.parse(json); + return PushNotification.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["type","recipientId","title","body"],"properties":{"type":{"const":"push"},"recipientId":{"type":"string"},"title":{"type":"string"},"body":{"type":"string"},"badge":{"type":"integer","minimum":0}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-payload/src/generated/models/SmsNotification.ts b/examples/ecommerce-asyncapi-payload/src/generated/models/SmsNotification.ts index 8bd2605c..82a471aa 100644 --- a/examples/ecommerce-asyncapi-payload/src/generated/models/SmsNotification.ts +++ b/examples/ecommerce-asyncapi-payload/src/generated/models/SmsNotification.ts @@ -28,46 +28,53 @@ class SmsNotification { get additionalProperties(): Record | undefined { return this._additionalProperties; } set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.type !== undefined) { - json += `"type": ${typeof this.type === 'number' || typeof this.type === 'boolean' ? this.type : JSON.stringify(this.type)},`; + json["type"] = this.type; } if(this.recipientId !== undefined) { - json += `"recipientId": ${typeof this.recipientId === 'number' || typeof this.recipientId === 'boolean' ? this.recipientId : JSON.stringify(this.recipientId)},`; + json["recipientId"] = this.recipientId; } if(this.message !== undefined) { - json += `"message": ${typeof this.message === 'number' || typeof this.message === 'boolean' ? this.message : JSON.stringify(this.message)},`; + json["message"] = this.message; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["type","recipientId","message","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): SmsNotification { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): SmsNotification { const instance = new SmsNotification({} as any); if (obj["recipientId"] !== undefined) { - instance.recipientId = obj["recipientId"]; + instance.recipientId = obj["recipientId"] as string; } if (obj["message"] !== undefined) { - instance.message = obj["message"]; + instance.message = obj["message"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["type","recipientId","message","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): SmsNotification { + const obj = typeof json === "object" ? json : JSON.parse(json); + return SmsNotification.fromJson(obj as Record); + } public static theCodeGenSchema = {"type":"object","required":["type","recipientId","message"],"properties":{"type":{"const":"sms"},"recipientId":{"type":"string"},"message":{"type":"string","maxLength":160}}}; public static validate(context?: {data: any, ajvValidatorFunction?: ValidateFunction, ajvInstance?: Ajv, ajvOptions?: AjvOptions}): { valid: boolean; errors?: ErrorObject[]; } { const {data, ajvValidatorFunction} = context ?? {}; diff --git a/examples/ecommerce-asyncapi-types/src/generated/types/Types.ts b/examples/ecommerce-asyncapi-types/src/generated/types/Types.ts index 1f58a692..5dedfd30 100644 --- a/examples/ecommerce-asyncapi-types/src/generated/types/Types.ts +++ b/examples/ecommerce-asyncapi-types/src/generated/types/Types.ts @@ -32,3 +32,10 @@ export function ToTopics(topicId: TopicIds): Topics { throw new Error('Unknown topic ID: ' + topicId); } } +export const TopicsMap: Record = { + 'order-events': 'ecommerce.orders.{orderId}', + 'payment-events': 'ecommerce.payments.{paymentId}', + 'inventory-events': 'ecommerce.inventory.{productId}', + 'customer-notifications': 'ecommerce.notifications.{customerId}', + 'analytics-events': 'ecommerce.analytics.events' +}; diff --git a/examples/typescript-library/src/__gen__/payloads/UserSignedUp.ts b/examples/typescript-library/src/__gen__/payloads/UserSignedUp.ts index 5034ee8e..ce77fc5e 100644 --- a/examples/typescript-library/src/__gen__/payloads/UserSignedUp.ts +++ b/examples/typescript-library/src/__gen__/payloads/UserSignedUp.ts @@ -1,64 +1,100 @@ - +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +interface UserSignedUpInterface { + displayName?: string + email?: string + additionalProperties?: Record +} class UserSignedUp { private _displayName?: string; private _email?: string; - private _additionalProperties?: Map; + private _additionalProperties?: Record; - constructor(input: { - displayName?: string, - email?: string, - additionalProperties?: Map, - }) { + constructor(input: UserSignedUpInterface) { this._displayName = input.displayName; this._email = input.email; 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; } - get additionalProperties(): Map | undefined { return this._additionalProperties; } - set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.displayName !== undefined) { - json += `"displayName": ${typeof this.displayName === 'number' || typeof this.displayName === 'boolean' ? this.displayName : JSON.stringify(this.displayName)},`; + json["displayName"] = this.displayName; } if(this.email !== undefined) { - json += `"email": ${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},`; + json["email"] = this.email; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["displayName","email","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): UserSignedUp { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): UserSignedUp { const instance = new UserSignedUp({} as any); if (obj["displayName"] !== undefined) { - instance.displayName = obj["displayName"]; + instance.displayName = obj["displayName"] as string; } if (obj["email"] !== undefined) { - instance.email = obj["email"]; + instance.email = obj["email"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["displayName","email","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): UserSignedUp { + const obj = typeof json === "object" ? json : JSON.parse(json); + return UserSignedUp.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"displayName":{"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. + // 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 { UserSignedUp }; \ No newline at end of file +export { UserSignedUp, UserSignedUpInterface }; \ No newline at end of file diff --git a/examples/typescript-nextjs/src/__gen__/payloads/UserSignedUp.ts b/examples/typescript-nextjs/src/__gen__/payloads/UserSignedUp.ts index 5034ee8e..ce77fc5e 100644 --- a/examples/typescript-nextjs/src/__gen__/payloads/UserSignedUp.ts +++ b/examples/typescript-nextjs/src/__gen__/payloads/UserSignedUp.ts @@ -1,64 +1,100 @@ - +import {Ajv, Options as AjvOptions, ErrorObject, ValidateFunction} from 'ajv'; +import {default as addFormats} from 'ajv-formats'; +interface UserSignedUpInterface { + displayName?: string + email?: string + additionalProperties?: Record +} class UserSignedUp { private _displayName?: string; private _email?: string; - private _additionalProperties?: Map; + private _additionalProperties?: Record; - constructor(input: { - displayName?: string, - email?: string, - additionalProperties?: Map, - }) { + constructor(input: UserSignedUpInterface) { this._displayName = input.displayName; this._email = input.email; 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; } - get additionalProperties(): Map | undefined { return this._additionalProperties; } - set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; } + get additionalProperties(): Record | undefined { return this._additionalProperties; } + set additionalProperties(additionalProperties: Record | undefined) { this._additionalProperties = additionalProperties; } - public marshal() : string { - let json = '{' + public toJson(): Record { + const json: Record = {}; if(this.displayName !== undefined) { - json += `"displayName": ${typeof this.displayName === 'number' || typeof this.displayName === 'boolean' ? this.displayName : JSON.stringify(this.displayName)},`; + json["displayName"] = this.displayName; } if(this.email !== undefined) { - json += `"email": ${typeof this.email === 'number' || typeof this.email === 'boolean' ? this.email : JSON.stringify(this.email)},`; + json["email"] = this.email; } - if(this.additionalProperties !== undefined) { - for (const [key, value] of this.additionalProperties.entries()) { + if(this.additionalProperties !== undefined) { + for (const [key, value] of Object.entries(this.additionalProperties)) { //Only unwrap those that are not already a property in the JSON object if(["displayName","email","additionalProperties"].includes(String(key))) continue; - json += `"${key}": ${typeof value === 'number' || typeof value === 'boolean' ? value : JSON.stringify(value)},`; + json[key] = value; } } - //Remove potential last comma - return `${json.charAt(json.length-1) === ',' ? json.slice(0, json.length-1) : json}}`; + return json; } - public static unmarshal(json: string | object): UserSignedUp { - const obj = typeof json === "object" ? json : JSON.parse(json); + public marshal(): string { + return JSON.stringify(this.toJson()); + } + + public static fromJson(obj: Record): UserSignedUp { const instance = new UserSignedUp({} as any); if (obj["displayName"] !== undefined) { - instance.displayName = obj["displayName"]; + instance.displayName = obj["displayName"] as string; } if (obj["email"] !== undefined) { - instance.email = obj["email"]; + instance.email = obj["email"] as string; } - - instance.additionalProperties = new Map(); + + instance.additionalProperties = {}; const propsToCheck = Object.entries(obj).filter((([key,]) => {return !["displayName","email","additionalProperties"].includes(key);})); for (const [key, value] of propsToCheck) { - instance.additionalProperties.set(key, value as any); + instance.additionalProperties[key] = value as any; } return instance; } + + public static unmarshal(json: string | object): UserSignedUp { + const obj = typeof json === "object" ? json : JSON.parse(json); + return UserSignedUp.fromJson(obj as Record); + } + public static theCodeGenSchema = {"type":"object","$schema":"http://json-schema.org/draft-07/schema","properties":{"displayName":{"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. + // 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 { UserSignedUp }; \ No newline at end of file +export { UserSignedUp, UserSignedUpInterface }; \ No newline at end of file diff --git a/mcp-server/lib/resources/bundled-docs.ts b/mcp-server/lib/resources/bundled-docs.ts index 54fef239..92abcb66 100644 --- a/mcp-server/lib/resources/bundled-docs.ts +++ b/mcp-server/lib/resources/bundled-docs.ts @@ -1,7 +1,7 @@ /** * Auto-generated documentation bundle. * DO NOT EDIT - regenerate with: npm run bundle-docs - * Generated at: 2026-07-23T16:19:30.085Z + * Generated at: 2026-07-27T09:38:13.872Z */ export interface DocEntry { @@ -36,7 +36,7 @@ export const docs: Record = { }, "generators": { title: "Generators", - content: "# Generators\nGenerators, or preset's are the core of **The Codegen Project**, that determines what is generated for your project.\n\nEach language and inputs have specific generators;\n\nAll available generators, across languages and inputs:\n- [`payloads`](./payloads.md)\n- [`parameters`](./parameters.md)\n- [`headers`](./headers.md)\n- [`types`](./types.md)\n- [`channels`](./channels.md)\n- [`client`](./client.md)\n- [`models`](./models.md)\n- [`custom`](./custom.md)\n\n| **Inputs** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`models`](./models.md) | [`custom`](./custom.md) |\n|---|---|---|---|---|---|---|---|---|\n| AsyncAPI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |\n| OpenAPI | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |\n| JSON Schema | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |\n\n| **Languages** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`models`](./models.md) | [`custom`](./custom.md) |\n|---|---|---|---|---|---|---|---|---|\n| TypeScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |", + content: "# Generators\nGenerators, or preset's are the core of **The Codegen Project**, that determines what is generated for your project.\n\nEach language and inputs have specific generators;\n\nAll available generators, across languages and inputs:\n- [`payloads`](./payloads.md)\n- [`parameters`](./parameters.md)\n- [`headers`](./headers.md)\n- [`types`](./types.md)\n- [`channels`](./channels.md)\n- [`client`](./client.md)\n- [`models`](./models.md)\n- [`custom`](./custom.md)\n\n| **Inputs** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`models`](./models.md) | [`custom`](./custom.md) |\n|---|---|---|---|---|---|---|---|---|\n| AsyncAPI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |\n| OpenAPI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |\n| JSON Schema | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |\n\n> OpenAPI `channels` and `client` generate an HTTP client — see the [`openapi-http-client` example](../../examples/openapi-http-client/).\n\n| **Languages** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`models`](./models.md) | [`custom`](./custom.md) |\n|---|---|---|---|---|---|---|---|---|\n| TypeScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |", }, "generators/channels": { title: "Channels", @@ -72,11 +72,11 @@ export const docs: Record = { }, "getting-started": { title: "Getting Started", - content: "# Getting Started\n\nIts simple, [install the CLI](#install) into your project or machine, [setup the Codegen configuration file](#initialize) to include all the code your heart desire, customize it, and generate it at build time or whenever you feel like it.\n\n## Install\nInstalling the CLI can be done inside a project or within your system.\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Package managerMacOS x64MacOS arm64Windows x64Windows x32Linux (Debian)Linux (Others)
\n
\n\n#### NPM\n\n```sh\nnpm install --save-dev @the-codegen-project/cli\n\nnpm install -g @the-codegen-project/cli\n```\n\n#### Yarn\n\n```sh\nyarn add @the-codegen-project/cli\n```\n\n#### Pnpm\n\n```sh\npnpm add @the-codegen-project/cli\n```\n\n#### Bun\n\n```sh\nbun add @the-codegen-project/cli\n```\n\n
\n
\n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.x64.pkg\n```\n\n#### Install\n```sh\nsudo installer -pkg codegen.x64.pkg -target /\n```\n\n
\n
\n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.arm64.pkg\n```\n#### Install\n\n```sh\nsudo installer -pkg codegen.arm64.pkg -target /\n```\n
\n
\n \n \n \n \n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.deb\n```\n\n#### Install\n```sh\nsudo apt install ./codegen.deb\n```\n
\n
\n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.tar.gz\n```\n\n#### Install\n\n```sh\ntar -xzf codegen.tar.gz\n```\n\n#### Symlink\n```sh\nln -s /bin/codegen /usr/local/bin/codegen\n```\n\n
\n
\n\nYou can find all the possible commands in [the usage documentation](../usage.md).\n\n## Initialize\nAdd a configuration file, either manually or through the CLI;\n```sh\ncodegen init\n```\n\n
\n\n\n![Initialize The Codegen Project](../../static/assets/videos/initialize.gif)\n\nCustomize it to your heart's desire! [Each generator has unique set of options](../generators/README.md)\n\n
\n\n## Integrate\nWith your configuration file in hand, time to integrate it into your project and generate some code! Checkout [all the integrations](../../examples/) for inspiration how to do it.\n\n### Generate Code\n\n#### One-time Generation\n```sh\n# Generate code once\ncodegen generate\n\n# Generate with specific config file\ncodegen generate ./my-config.js\n```\n\n#### Development with Watch Mode\nFor active development, use watch mode to automatically regenerate code when your input files change:\n\n```sh\n# Watch for changes in the input file specified in your config\ncodegen generate --watch\n\n# Watch for changes in a specific file or directory\ncodegen generate --watch --watchPath ./my-asyncapi.yaml\n\n# Short form\ncodegen generate -w -p ./schemas/\n```\n\n**Pro tip:** Use watch mode during development to keep your generated code in sync with your API specifications. Press `Ctrl+C` to stop watching.\n\n## What's Next?\n\nNow that you've installed the CLI and generated your first code, here's where to go next:\n\n### Understanding Generators\nLearn how generators work and what they can do for your project. Generators are the core of The Codegen Project - they determine what code gets generated from your API specifications.\n\n👉 **[Learn about Generators →](./generators.md)**\n\n### Protocol Support\nDiscover how The Codegen Project supports various messaging protocols like NATS, Kafka, MQTT, and more. Understand how protocol-specific code generation works and which protocols are available.\n\n👉 **[Learn about Protocol Support →](./protocols.md)**\n\n### Explore Further\n- **[Generator Documentation](../generators/README.md)** - Detailed documentation for each generator type\n- **[Protocol Documentation](../protocols/)** - Complete protocol reference and implementation details\n- **[Input Types](../inputs/)** - Learn about AsyncAPI, OpenAPI, and JSON Schema support\n- **[Examples](../../examples/)** - Real-world examples and integration patterns", + content: "# Getting Started\n\nIts simple, [install the CLI](#install) into your project or machine, [setup the Codegen configuration file](#initialize) to include all the code your heart desire, customize it, and generate it at build time or whenever you feel like it.\n\n## Install\nInstalling the CLI can be done inside a project or within your system.\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Package managerMacOS x64MacOS arm64Windows x64Windows x32Linux (Debian)Linux (Others)
\n
\n\n#### NPM\n\n```sh\nnpm install --save-dev @the-codegen-project/cli\n\nnpm install -g @the-codegen-project/cli\n```\n\n#### Yarn\n\n```sh\nyarn add @the-codegen-project/cli\n```\n\n#### Pnpm\n\n```sh\npnpm add @the-codegen-project/cli\n```\n\n#### Bun\n\n```sh\nbun add @the-codegen-project/cli\n```\n\n
\n
\n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.x64.pkg\n```\n\n#### Install\n```sh\nsudo installer -pkg codegen.x64.pkg -target /\n```\n\n
\n
\n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.arm64.pkg\n```\n#### Install\n\n```sh\nsudo installer -pkg codegen.arm64.pkg -target /\n```\n
\n
\n \n \n \n \n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.deb\n```\n\n#### Install\n```sh\nsudo apt install ./codegen.deb\n```\n
\n
\n
\n\n#### Download\n```sh\ncurl -OL https://github.com/the-codegen-project/cli/releases/latest/download/codegen.tar.gz\n```\n\n#### Install\n\n```sh\ntar -xzf codegen.tar.gz\n```\n\n#### Symlink\n```sh\nln -s /bin/codegen /usr/local/bin/codegen\n```\n\n
\n
\n\nYou can find all the possible commands in [the usage documentation](../usage.md).\n\n## Initialize\nAdd a configuration file, either manually or through the CLI;\n```sh\ncodegen init\n```\n\nFor non-interactive/CI use, pass the generators explicitly (the `languages` flag defaults to `typescript`), for example:\n```sh\ncodegen init --no-tty --input-type asyncapi --input-file ./asyncapi.yml \\\n --include-payloads --include-channels --channels-protocols nats\n```\nAt least one generator must be selected — `codegen init` exits with an error rather than writing a configuration that would generate nothing. For AsyncAPI `channels`/`client`, choose the messaging protocols with `--channels-protocols` (repeatable). See [the usage documentation](../usage.md#codegen-init) for the full flag list.\n\n
\n\n\n![Initialize The Codegen Project](../../static/assets/videos/initialize.gif)\n\nCustomize it to your heart's desire! [Each generator has unique set of options](../generators/README.md)\n\n
\n\n## Integrate\nWith your configuration file in hand, time to integrate it into your project and generate some code! Checkout [all the integrations](../../examples/) for inspiration how to do it.\n\n### Generate Code\n\n#### One-time Generation\n```sh\n# Generate code once\ncodegen generate\n\n# Generate with specific config file\ncodegen generate ./my-config.js\n```\n\n#### Development with Watch Mode\nFor active development, use watch mode to automatically regenerate code when your input files change:\n\n```sh\n# Watch for changes in the input file specified in your config\ncodegen generate --watch\n\n# Watch for changes in a specific file or directory\ncodegen generate --watch --watchPath ./my-asyncapi.yaml\n\n# Short form\ncodegen generate -w -p ./schemas/\n```\n\n**Pro tip:** Use watch mode during development to keep your generated code in sync with your API specifications. Press `Ctrl+C` to stop watching.\n\n## What's Next?\n\nNow that you've installed the CLI and generated your first code, here's where to go next:\n\n### Understanding Generators\nLearn how generators work and what they can do for your project. Generators are the core of The Codegen Project - they determine what code gets generated from your API specifications.\n\n👉 **[Learn about Generators →](./generators.md)**\n\n### Protocol Support\nDiscover how The Codegen Project supports various messaging protocols like NATS, Kafka, MQTT, and more. Understand how protocol-specific code generation works and which protocols are available.\n\n👉 **[Learn about Protocol Support →](./protocols.md)**\n\n### Explore Further\n- **[Generator Documentation](../generators/README.md)** - Detailed documentation for each generator type\n- **[Protocol Documentation](../protocols/)** - Complete protocol reference and implementation details\n- **[Input Types](../inputs/)** - Learn about AsyncAPI, OpenAPI, and JSON Schema support\n- **[Examples](../../examples/)** - Real-world examples and integration patterns", }, "getting-started/generators": { title: "Understanding Generators", - content: "# Understanding Generators\n\nGenerators (also called \"presets\") are the core of **The Codegen Project**. They determine what code gets generated from your inputs. Think of generators as specialized code factories - each one produces a specific type of code that helps you build your application faster.\n\n## What Generators do you have?\n\nEach generator focuses on a specific aspect of your application:\n\n### Model Generators\nThese generators create data models and type definitions:\n\n- [`payloads` preset](../generators/payloads.md) - Type-safe message/payload classes with serialization and validation support\n- [`parameters` preset](../generators/parameters.md) - Type-safe parameter classes for API endpoints that make it easier to work with topics/paths/channels\n- [`headers` preset](../generators/headers.md) - Type-safe header classes for message protocols, with serialization and validation support\n- [`types` preset](../generators/types.md) - Shared type definitions and interfaces, which simplify your code in various ways\n- [`models` preset](../generators/models.md) - General-purpose models from JSON Schema\n\n### Communication Generators\nThese generators create code for interacting with APIs and message brokers:\n\n- [`channels` preset](../generators/channels.md) - Communication functions for message brokers, ensure the right message, headers, and topics/paths/channels are used\n- [`client` preset](../generators/client.md) - Wraps channels into a reusable wrappers, cant get more code then this.\n\n### Custom Generators\n- [`custom` preset](../generators/custom.md) - Your own custom code generation logic\n\n## How Generators Work\n\n### 1. Input Processing\nGenerators take your specifications (AsyncAPI, OpenAPI, or JSON Schema) and extract the relevant information:\n\n```js\nexport default {\n inputType: 'asyncapi',\n inputPath: './my-api.yaml'\n};\n```\n\n### 2. Code Generation\nBased on the generator configuration, The Codegen Project:\n- Parses your API specification\n- Extracts schemas, operations, channels, and other relevant data\n- Use whatever generators you added and outputs files to your specified directory\n\n```js\nexport default {\n inputType: 'asyncapi',\n inputPath: './my-api.yaml',\n generators: [\n {\n preset: 'payloads',\n outputPath: './src/payloads',\n language: 'typescript'\n }\n ]\n};\n```\n\n### 3. Generated Output\nEach generator produces different code, so have a look at each generator to get a full picture, but here is a few examples:\n\n**Payload Generator** produces:\n```typescript\nexport class UserSignup {\n constructor(data: UserSignupData) { /* ... */ }\n marshal(): string { /* ... */ }\n static unmarshal(json: string): UserSignup { /* ... */ }\n}\n```\n\n**Channels Generator** produces:\n```typescript\nexport const Protocols = {\n nats: {\n publishToUserSignup: ...,\n subscribeToUserSignup: ...,\n jetStreamPublishToUserSignup: ...\n },\n kafka: {\n publishToUserSignup: ...,\n subscribeToUserSignup: ...\n },\n // ... other protocols\n};\n```\n\n## Input Type Support\n\nDifferent generators work with different input types:\n\n| Generator | AsyncAPI | OpenAPI | JSON Schema |\n|-----------|----------|---------|-------------|\n| `payloads` | ✅ | ✅ | ❌ |\n| `parameters` | ✅ | ✅ | ❌ |\n| `headers` | ✅ | ✅ | ❌ |\n| `types` | ✅ | ✅ | ❌ |\n| `channels` | ✅ | ❌ | ❌ |\n| `client` | ✅ | ❌ | ❌ |\n| `models` | ✅ | ✅ | ✅ |\n| `custom` | ✅ | ✅ | ✅ |\n\n## Language Support\n\nCurrently, The Codegen Project supports:\n\n- **TypeScript** - Full support for all generators\n\nEach language has specific capabilities and constraints. Check the [generator documentation](../generators/README.md) for details.\n\n## Generator Dependencies\n\nSome generators automatically include dependencies on others:\n\n- **`channels`** generator automatically uses `payloads`, `headers`, and `parameters` generators if they're not already configured\n- This ensures you have all the necessary models and types for your channel functions\n\n## Configuration Options\n\nEach generator has its own set of configuration options, for example here is payloads:\n\n```js\nexport default {\n inputType: 'asyncapi',\n inputPath: './my-api.yaml',\n generators: [\n {\n preset: 'payloads',\n outputPath: './src/payloads',\n language: 'typescript',\n includeValidation: true,\n serializationType: 'json'\n }\n ]\n};\n```\n\n## Next Steps\n\n- **[Explore Generator Documentation](../generators/README.md)** - Detailed docs for each generator\n- **[Learn about Protocol Support](./protocols.md)** - How generators work with messaging protocols\n- **[Check Out Examples](../../examples/)** - See generators in action", + content: "# Understanding Generators\n\nGenerators (also called \"presets\") are the core of **The Codegen Project**. They determine what code gets generated from your inputs. Think of generators as specialized code factories - each one produces a specific type of code that helps you build your application faster.\n\n## What Generators do you have?\n\nEach generator focuses on a specific aspect of your application:\n\n### Model Generators\nThese generators create data models and type definitions:\n\n- [`payloads` preset](../generators/payloads.md) - Type-safe message/payload classes with serialization and validation support\n- [`parameters` preset](../generators/parameters.md) - Type-safe parameter classes for API endpoints that make it easier to work with topics/paths/channels\n- [`headers` preset](../generators/headers.md) - Type-safe header classes for message protocols, with serialization and validation support\n- [`types` preset](../generators/types.md) - Shared type definitions and interfaces, which simplify your code in various ways\n- [`models` preset](../generators/models.md) - General-purpose models from JSON Schema\n\n### Communication Generators\nThese generators create code for interacting with APIs and message brokers:\n\n- [`channels` preset](../generators/channels.md) - Communication functions for message brokers, ensure the right message, headers, and topics/paths/channels are used\n- [`client` preset](../generators/client.md) - Wraps channels into a reusable wrappers, cant get more code then this.\n\n### Custom Generators\n- [`custom` preset](../generators/custom.md) - Your own custom code generation logic\n\n## How Generators Work\n\n### 1. Input Processing\nGenerators take your specifications (AsyncAPI, OpenAPI, or JSON Schema) and extract the relevant information:\n\n```js\nexport default {\n inputType: 'asyncapi',\n inputPath: './my-api.yaml'\n};\n```\n\n### 2. Code Generation\nBased on the generator configuration, The Codegen Project:\n- Parses your API specification\n- Extracts schemas, operations, channels, and other relevant data\n- Use whatever generators you added and outputs files to your specified directory\n\n```js\nexport default {\n inputType: 'asyncapi',\n inputPath: './my-api.yaml',\n generators: [\n {\n preset: 'payloads',\n outputPath: './src/payloads',\n language: 'typescript'\n }\n ]\n};\n```\n\n### 3. Generated Output\nEach generator produces different code, so have a look at each generator to get a full picture, but here is a few examples:\n\n**Payload Generator** produces:\n```typescript\nexport class UserSignup {\n constructor(data: UserSignupData) { /* ... */ }\n marshal(): string { /* ... */ }\n static unmarshal(json: string): UserSignup { /* ... */ }\n}\n```\n\n**Channels Generator** produces:\n```typescript\nexport const Protocols = {\n nats: {\n publishToUserSignup: ...,\n subscribeToUserSignup: ...,\n jetStreamPublishToUserSignup: ...\n },\n kafka: {\n publishToUserSignup: ...,\n subscribeToUserSignup: ...\n },\n // ... other protocols\n};\n```\n\n## Input Type Support\n\nDifferent generators work with different input types:\n\n| Generator | AsyncAPI | OpenAPI | JSON Schema |\n|-----------|----------|---------|-------------|\n| `payloads` | ✅ | ✅ | ❌ |\n| `parameters` | ✅ | ✅ | ❌ |\n| `headers` | ✅ | ✅ | ❌ |\n| `types` | ✅ | ✅ | ❌ |\n| `channels` | ✅ | ✅ | ❌ |\n| `client` | ✅ | ✅ | ❌ |\n| `models` | ✅ | ✅ | ✅ |\n| `custom` | ✅ | ✅ | ✅ |\n\n## Language Support\n\nCurrently, The Codegen Project supports:\n\n- **TypeScript** - Full support for all generators\n\nEach language has specific capabilities and constraints. Check the [generator documentation](../generators/README.md) for details.\n\n## Generator Dependencies\n\nSome generators automatically include dependencies on others:\n\n- **`channels`** generator automatically uses `payloads`, `headers`, and `parameters` generators if they're not already configured\n- This ensures you have all the necessary models and types for your channel functions\n\n## Configuration Options\n\nEach generator has its own set of configuration options, for example here is payloads:\n\n```js\nexport default {\n inputType: 'asyncapi',\n inputPath: './my-api.yaml',\n generators: [\n {\n preset: 'payloads',\n outputPath: './src/payloads',\n language: 'typescript',\n includeValidation: true,\n serializationType: 'json'\n }\n ]\n};\n```\n\n## Next Steps\n\n- **[Explore Generator Documentation](../generators/README.md)** - Detailed docs for each generator\n- **[Learn about Protocol Support](./protocols.md)** - How generators work with messaging protocols\n- **[Check Out Examples](../../examples/)** - See generators in action", }, "getting-started/protocols": { title: "Understanding Protocols", @@ -84,7 +84,7 @@ export const docs: Record = { }, "inputs/asyncapi": { title: "AsyncAPI", - content: "# AsyncAPI\nSupported versions: 2.0 -> 3.1\n\nIf you arrive from the AsyncAPI community, you might be wondering what this project is and how does it relate?\n\nThe Codegen Project was started because of a need for a code generator that;\n1. could easily be integrated into development workflows\n2. can easily be extended or customized to specific use-cases\n3. forms a community across communities in languages and standards\n4. are financially sustainable long term through open source at it's core.\n\nThere is a lot of overlap with existing tooling, however the idea is to form the same level of quality that the OpenAPI Generator provides to OpenAPI community for HTTP, for AsyncAPI and **any** protocol (including HTTP), and the usability of the Apollo GraphQL generator. How are we gonna achieve it? Together.\n\n| **Presets** | AsyncAPI | \n|---|---|\n| [`payloads`](../generators/payloads.md) | ✅ |\n| [`parameters`](../generators/parameters.md) | ✅ |\n| [`headers`](../generators/headers.md) | ✅ |\n| [`types`](../generators/types.md) | ✅ |\n| [`channels`](../generators/channels.md) | ✅ |\n| [`client`](../generators/client.md) | ✅ |\n| [`custom`](../generators/custom.md) | ✅ |\n| [`models`](../generators/custom.md) | ✅ |\n\n## Remote URL inputs\n\n`inputPath` accepts an `http://` or `https://` URL. Optional authentication (bearer token, API key, or custom headers) is configured\nvia the `auth` field. See the [configurations guide](../configurations.md#remote-url-inputs) for examples and the [auth scope and security\nconsiderations](../configurations.md#auth-scope-and-security-considerations) section before using `auth` against a public spec — the configured headers are sent to every `$ref` target as well as the root URL.\n\n## Basic AsyncAPI Document Structure\n\nHere's a complete basic AsyncAPI document example to get you started:\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User Service API\",\n \"version\": \"1.0.0\",\n \"description\": \"API for user management events\"\n },\n \"channels\": {\n \"userSignedup\": {\n \"address\": \"user/signedup/{userId}/{region}\",\n \"parameters\": {\n \"userId\": {\n \"description\": \"The unique identifier for the user\"\n },\n \"region\": {\n \"description\": \"The geographic region\",\n \"enum\": [\"us-east\", \"us-west\", \"eu-central\"]\n }\n },\n \"messages\": {\n \"UserSignedUp\": {\n \"$ref\": \"#/components/messages/UserSignedUp\"\n }\n }\n }\n },\n \"operations\": {\n \"sendUserSignedup\": {\n \"action\": \"send\",\n \"channel\": {\n \"$ref\": \"#/channels/userSignedup\"\n },\n \"messages\": [\n {\n \"$ref\": \"#/channels/userSignedup/messages/UserSignedUp\"\n }\n ]\n },\n \"receiveUserSignedup\": {\n \"action\": \"receive\",\n \"channel\": {\n \"$ref\": \"#/channels/userSignedup\"\n },\n \"messages\": [\n {\n \"$ref\": \"#/channels/userSignedup/messages/UserSignedUp\"\n }\n ]\n }\n },\n \"components\": {\n \"messages\": {\n \"UserSignedUp\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/UserSignedUpPayload\"\n },\n \"headers\": {\n \"$ref\": \"#/components/schemas/UserHeaders\"\n }\n }\n },\n \"schemas\": {\n \"UserSignedUpPayload\": {\n \"type\": \"object\",\n \"properties\": {\n \"display_name\": {\n \"type\": \"string\",\n \"description\": \"Name of the user\"\n },\n \"email\": {\n \"type\": \"string\",\n \"format\": \"email\",\n \"description\": \"Email of the user\"\n },\n \"created_at\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"When the user was created\"\n }\n },\n \"required\": [\"display_name\", \"email\"]\n },\n \"UserHeaders\": {\n \"type\": \"object\",\n \"properties\": {\n \"correlation_id\": {\n \"type\": \"string\",\n \"description\": \"Correlation ID for tracking\"\n },\n \"source\": {\n \"type\": \"string\",\n \"description\": \"Source system\"\n }\n }\n }\n }\n }\n}\n```\n\n## Extensions\n\nTo customize the code generation through the AsyncAPI document, use the `x-the-codegen-project` [extension object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#specificationExtensions) with the following properties:\n\n### Channel Extensions\n\n`channelName`, string, customize the name of the functions generated for the channel, use this to overwrite the automatically determined name for models and functions. This will be used by the following generators; [payloads](../generators/payloads.md), [parameters](../generators/parameters.md) and [channels](../generators/channels.md). \n\n`functionTypeMapping`, [ChannelFunctionTypes](https://the-codegen-project.org/docs/api/enumerations/ChannelFunctionTypes), customize which generators to generate for the given channel, use this to specify further which functions we render. This will be used by the following generators; [channels](../generators/channels.md). \n\n#### Example: Custom Channel Configuration\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Custom Channel Example\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"user-events\": {\n \"address\": \"events/user/{action}\",\n \"parameters\": {\n \"action\": {\n \"enum\": [\"created\", \"updated\", \"deleted\"]\n }\n },\n \"messages\": {\n \"UserEvent\": {\n \"payload\": {\n \"type\": \"object\",\n \"properties\": {\n \"userId\": {\"type\": \"string\"},\n \"action\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n }\n }\n }\n },\n \"x-the-codegen-project\": {\n \"channelName\": \"UserEventChannel\",\n \"functionTypeMapping\": [\"event_source_express\", \"kafka_publish\"]\n }\n }\n }\n}\n```\n\n### Operation Extensions\n\n`functionTypeMapping`, [ChannelFunctionTypes](https://the-codegen-project.org/docs/api/enumerations/ChannelFunctionTypes), customize which generators to generate for the given operation, use this to specify further which functions we render. This will be used by the following generators; [channels](../generators/channels.md). \n\n#### Example: Custom Operation Configuration\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Custom Operation Example\",\n \"version\": \"1.0.0\"\n },\n \"operations\": {\n \"publishUserEvent\": {\n \"action\": \"send\",\n \"channel\": {\n \"$ref\": \"#/channels/user-events\"\n },\n \"messages\": [\n {\"$ref\": \"#/channels/user-events/messages/UserEvent\"}\n ],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_publish\"]\n }\n },\n \"subscribeToUserEvents\": {\n \"action\": \"receive\",\n \"channel\": {\n \"$ref\": \"#/channels/user-events\"\n },\n \"messages\": [\n {\"$ref\": \"#/channels/user-events/messages/UserEvent\"}\n ],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_subscribe\"]\n }\n }\n }\n}\n```\n\n## Protocol Support\n\n### HTTP Client\n\nUse HTTP bindings to generate HTTP client code. Supports all standard HTTP methods and status codes.\n\n#### Example: REST API with Multiple HTTP Methods\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User Management API\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"users\": {\n \"address\": \"/users/{userId}\",\n \"parameters\": {\n \"userId\": {\n \"description\": \"User identifier\"\n }\n },\n \"messages\": {\n \"UserRequest\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/User\"\n }\n },\n \"UserResponse\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/User\"\n },\n \"bindings\": {\n \"http\": {\n \"statusCode\": 200\n }\n }\n },\n \"NotFound\": {\n \"payload\": {\n \"type\": \"object\",\n \"properties\": {\n \"error\": {\"type\": \"string\"},\n \"code\": {\"type\": \"string\"}\n }\n },\n \"bindings\": {\n \"http\": {\n \"statusCode\": 404\n }\n }\n }\n }\n }\n },\n \"operations\": {\n \"createUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserRequest\"}],\n \"bindings\": {\n \"http\": {\"method\": \"POST\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n },\n \"getUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [],\n \"bindings\": {\n \"http\": {\"method\": \"GET\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [\n {\"$ref\": \"#/channels/users/messages/UserResponse\"},\n {\"$ref\": \"#/channels/users/messages/NotFound\"}\n ]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n },\n \"updateUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserRequest\"}],\n \"bindings\": {\n \"http\": {\"method\": \"PUT\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n },\n \"deleteUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [],\n \"bindings\": {\n \"http\": {\"method\": \"DELETE\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"User\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\"type\": \"string\"},\n \"name\": {\"type\": \"string\"},\n \"email\": {\"type\": \"string\", \"format\": \"email\"},\n \"created_at\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"name\", \"email\"]\n }\n }\n }\n}\n```\n\n### Kafka\n\nGenerate Kafka producers and consumers with proper serialization.\n\n#### Example: Kafka Event Streaming\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Order Processing Events\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"order-events\": {\n \"address\": \"orders.{eventType}.{region}\",\n \"parameters\": {\n \"eventType\": {\n \"enum\": [\"created\", \"updated\", \"cancelled\", \"completed\"]\n },\n \"region\": {\n \"enum\": [\"us\", \"eu\", \"asia\"]\n }\n },\n \"messages\": {\n \"OrderEvent\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/OrderEvent\"\n },\n \"headers\": {\n \"$ref\": \"#/components/schemas/EventHeaders\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"publishOrderEvent\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/order-events\"},\n \"messages\": [{\"$ref\": \"#/channels/order-events/messages/OrderEvent\"}],\n \"bindings\": {\n \"kafka\": {\n \"clientId\": \"order-service\",\n \"groupId\": \"order-processors\"\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_publish\"]\n }\n },\n \"subscribeToOrderEvents\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/order-events\"},\n \"messages\": [{\"$ref\": \"#/channels/order-events/messages/OrderEvent\"}],\n \"bindings\": {\n \"kafka\": {\n \"groupId\": \"order-processors\",\n \"clientId\": \"order-consumer\"\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_subscribe\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"OrderEvent\": {\n \"type\": \"object\",\n \"properties\": {\n \"orderId\": {\"type\": \"string\"},\n \"customerId\": {\"type\": \"string\"},\n \"amount\": {\"type\": \"number\"},\n \"currency\": {\"type\": \"string\"},\n \"status\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"orderId\", \"customerId\", \"amount\", \"status\"]\n },\n \"EventHeaders\": {\n \"type\": \"object\",\n \"properties\": {\n \"correlationId\": {\"type\": \"string\"},\n \"source\": {\"type\": \"string\"},\n \"version\": {\"type\": \"string\"}\n }\n }\n }\n }\n}\n```\n\n### NATS\n\nGenerate NATS request/reply patterns and pub/sub functionality.\n\n#### Example: NATS Request-Reply Pattern\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User Service NATS API\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"user-service\": {\n \"address\": \"user.service.{operation}\",\n \"parameters\": {\n \"operation\": {\n \"enum\": [\"get\", \"create\", \"update\", \"delete\"]\n }\n },\n \"messages\": {\n \"UserRequest\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/UserRequest\"\n }\n },\n \"UserResponse\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/UserResponse\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"requestUserOperation\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserRequest\"}],\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"nats_request\"]\n }\n },\n \"replyToUserOperation\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserRequest\"}],\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"nats_reply\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"UserRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"operation\": {\"type\": \"string\"},\n \"userId\": {\"type\": \"string\"},\n \"data\": {\"type\": \"object\"}\n },\n \"required\": [\"operation\"]\n },\n \"UserResponse\": {\n \"type\": \"object\",\n \"properties\": {\n \"success\": {\"type\": \"boolean\"},\n \"data\": {\"type\": \"object\"},\n \"error\": {\"type\": \"string\"}\n },\n \"required\": [\"success\"]\n }\n }\n }\n}\n```\n\n### MQTT\n\nGenerate MQTT publish/subscribe clients with QoS levels.\n\n#### Example: IoT Device Communications\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"IoT Device Management\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"device-telemetry\": {\n \"address\": \"devices/{deviceId}/telemetry/{sensorType}\",\n \"parameters\": {\n \"deviceId\": {\n \"description\": \"Unique device identifier\"\n },\n \"sensorType\": {\n \"enum\": [\"temperature\", \"humidity\", \"pressure\", \"motion\"]\n }\n },\n \"messages\": {\n \"TelemetryData\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/TelemetryData\"\n }\n }\n }\n },\n \"device-commands\": {\n \"address\": \"devices/{deviceId}/commands\",\n \"parameters\": {\n \"deviceId\": {\n \"description\": \"Unique device identifier\"\n }\n },\n \"messages\": {\n \"DeviceCommand\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/DeviceCommand\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"publishTelemetry\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/device-telemetry\"},\n \"messages\": [{\"$ref\": \"#/channels/device-telemetry/messages/TelemetryData\"}],\n \"bindings\": {\n \"mqtt\": {\n \"qos\": 1,\n \"retain\": false\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"mqtt_publish\"]\n }\n },\n \"subscribeToTelemetry\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/device-telemetry\"},\n \"messages\": [{\"$ref\": \"#/channels/device-telemetry/messages/TelemetryData\"}],\n \"bindings\": {\n \"mqtt\": {\n \"qos\": 1\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"mqtt_subscribe\"]\n }\n },\n \"sendCommand\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/device-commands\"},\n \"messages\": [{\"$ref\": \"#/channels/device-commands/messages/DeviceCommand\"}],\n \"bindings\": {\n \"mqtt\": {\n \"qos\": 2,\n \"retain\": true\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"mqtt_publish\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"TelemetryData\": {\n \"type\": \"object\",\n \"properties\": {\n \"deviceId\": {\"type\": \"string\"},\n \"sensorType\": {\"type\": \"string\"},\n \"value\": {\"type\": \"number\"},\n \"unit\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"location\": {\n \"type\": \"object\",\n \"properties\": {\n \"latitude\": {\"type\": \"number\"},\n \"longitude\": {\"type\": \"number\"}\n }\n }\n },\n \"required\": [\"deviceId\", \"sensorType\", \"value\", \"timestamp\"]\n },\n \"DeviceCommand\": {\n \"type\": \"object\",\n \"properties\": {\n \"command\": {\"type\": \"string\"},\n \"parameters\": {\"type\": \"object\"},\n \"commandId\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"command\", \"commandId\"]\n }\n }\n }\n}\n```\n\n### AMQP\n\nGenerate AMQP producers and consumers for message queuing.\n\n#### Example: Order Processing Queue\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Order Processing Queue\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"order-queue\": {\n \"address\": \"orders.processing\",\n \"messages\": {\n \"OrderMessage\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/Order\"\n },\n \"headers\": {\n \"$ref\": \"#/components/schemas/MessageHeaders\"\n }\n }\n }\n },\n \"order-dlq\": {\n \"address\": \"orders.dead-letter\",\n \"messages\": {\n \"FailedOrderMessage\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/FailedOrder\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"publishOrder\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/order-queue\"},\n \"messages\": [{\"$ref\": \"#/channels/order-queue/messages/OrderMessage\"}],\n \"bindings\": {\n \"amqp\": {\n \"exchange\": {\n \"name\": \"orders\",\n \"type\": \"topic\",\n \"durable\": true\n },\n \"routingKey\": \"order.created\"\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"amqp_publish\"]\n }\n },\n \"consumeOrders\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/order-queue\"},\n \"messages\": [{\"$ref\": \"#/channels/order-queue/messages/OrderMessage\"}],\n \"bindings\": {\n \"amqp\": {\n \"queue\": {\n \"name\": \"order-processing-queue\",\n \"durable\": true,\n \"exclusive\": false,\n \"autoDelete\": false\n },\n \"ack\": true\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"amqp_consume\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"Order\": {\n \"type\": \"object\",\n \"properties\": {\n \"orderId\": {\"type\": \"string\"},\n \"customerId\": {\"type\": \"string\"},\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"productId\": {\"type\": \"string\"},\n \"quantity\": {\"type\": \"integer\"},\n \"price\": {\"type\": \"number\"}\n }\n }\n },\n \"totalAmount\": {\"type\": \"number\"},\n \"currency\": {\"type\": \"string\"},\n \"orderDate\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"orderId\", \"customerId\", \"items\", \"totalAmount\"]\n },\n \"FailedOrder\": {\n \"type\": \"object\",\n \"properties\": {\n \"orderId\": {\"type\": \"string\"},\n \"error\": {\"type\": \"string\"},\n \"retryCount\": {\"type\": \"integer\"},\n \"failedAt\": {\"type\": \"string\", \"format\": \"date-time\"}\n }\n },\n \"MessageHeaders\": {\n \"type\": \"object\",\n \"properties\": {\n \"messageId\": {\"type\": \"string\"},\n \"correlationId\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"priority\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 255}\n }\n }\n }\n }\n}\n```\n\n### EventSource\n\nGenerate Server-Sent Events (SSE) implementations for real-time updates.\n\n#### Example: Real-time Notifications\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Real-time Notifications\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"user-notifications\": {\n \"address\": \"/events/users/{userId}/notifications\",\n \"parameters\": {\n \"userId\": {\n \"description\": \"User identifier for targeted notifications\"\n }\n },\n \"messages\": {\n \"Notification\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/Notification\"\n }\n },\n \"SystemAlert\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/SystemAlert\"\n }\n }\n }\n },\n \"live-updates\": {\n \"address\": \"/events/live/{topic}\",\n \"parameters\": {\n \"topic\": {\n \"enum\": [\"stock-prices\", \"sports-scores\", \"weather-alerts\"]\n }\n },\n \"messages\": {\n \"LiveUpdate\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/LiveUpdate\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"streamUserNotifications\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/user-notifications\"},\n \"messages\": [\n {\"$ref\": \"#/channels/user-notifications/messages/Notification\"},\n {\"$ref\": \"#/channels/user-notifications/messages/SystemAlert\"}\n ],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"event_source_express\"]\n }\n },\n \"streamLiveUpdates\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/live-updates\"},\n \"messages\": [{\"$ref\": \"#/channels/live-updates/messages/LiveUpdate\"}],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"event_source_express\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"Notification\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\"type\": \"string\"},\n \"userId\": {\"type\": \"string\"},\n \"type\": {\"type\": \"string\", \"enum\": [\"info\", \"warning\", \"error\", \"success\"]},\n \"title\": {\"type\": \"string\"},\n \"message\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"actionUrl\": {\"type\": \"string\", \"format\": \"uri\"},\n \"read\": {\"type\": \"boolean\", \"default\": false}\n },\n \"required\": [\"id\", \"userId\", \"type\", \"title\", \"message\", \"timestamp\"]\n },\n \"SystemAlert\": {\n \"type\": \"object\",\n \"properties\": {\n \"alertId\": {\"type\": \"string\"},\n \"severity\": {\"type\": \"string\", \"enum\": [\"low\", \"medium\", \"high\", \"critical\"]},\n \"service\": {\"type\": \"string\"},\n \"message\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"resolved\": {\"type\": \"boolean\", \"default\": false}\n },\n \"required\": [\"alertId\", \"severity\", \"service\", \"message\", \"timestamp\"]\n },\n \"LiveUpdate\": {\n \"type\": \"object\",\n \"properties\": {\n \"topic\": {\"type\": \"string\"},\n \"data\": {\"type\": \"object\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"sequence\": {\"type\": \"integer\"}\n },\n \"required\": [\"topic\", \"data\", \"timestamp\"]\n }\n }\n }\n}\n```\n\n## FAQ\n\n### How does it relate to AsyncAPI Generator and templates?\nIt is fairly similar in functionality except in some key areas.\n\nTemplates are similar to presets except you can bind presets together to make it easier to render code down stream.\n\nThe AsyncAPI Generator is like the core of the Codegen Project, however it does not enable different inputs than AsyncAPI documents. \n\n### Can I mix multiple protocols in one document?\nYes! You can define operations with different protocol bindings in the same AsyncAPI document. Use the `x-the-codegen-project` extension to specify which generators to use for each operation.\n\n### How do I handle versioning?\n\nShort answer: Use the `info.version` field in your AsyncAPI document and consider using separate documents for major version changes. You can also use channel addressing patterns to include version information.\n\nLong answer: It's hard to version APIs, there are tons of resources how to handle versioning of your API which is far beyond what we can offer here.\n\n### Can I customize the generated code structure?\nYes, use the `x-the-codegen-project` extension properties to customize channel names, function mappings, and other generation aspects or the configuration file [while taking a look at the different generators](../generators).", + content: "# AsyncAPI\nSupported versions: 2.0 -> 3.1\n\nIf you arrive from the AsyncAPI community, you might be wondering what this project is and how does it relate?\n\nThe Codegen Project was started because of a need for a code generator that;\n1. could easily be integrated into development workflows\n2. can easily be extended or customized to specific use-cases\n3. forms a community across communities in languages and standards\n4. are financially sustainable long term through open source at it's core.\n\nThere is a lot of overlap with existing tooling, however the idea is to form the same level of quality that the OpenAPI Generator provides to OpenAPI community for HTTP, for AsyncAPI and **any** protocol (including HTTP), and the usability of the Apollo GraphQL generator. How are we gonna achieve it? Together.\n\n| **Presets** | AsyncAPI | \n|---|---|\n| [`payloads`](../generators/payloads.md) | ✅ |\n| [`parameters`](../generators/parameters.md) | ✅ |\n| [`headers`](../generators/headers.md) | ✅ |\n| [`types`](../generators/types.md) | ✅ |\n| [`channels`](../generators/channels.md) | ✅ |\n| [`client`](../generators/client.md) | ✅ |\n| [`custom`](../generators/custom.md) | ✅ |\n| [`models`](../generators/custom.md) | ✅ |\n\n## Remote URL inputs\n\n`inputPath` accepts an `http://` or `https://` URL. Optional authentication (bearer token, API key, or custom headers) is configured\nvia the `auth` field. See the [configurations guide](../configurations.md#remote-url-inputs) for examples and the [auth scope and security\nconsiderations](../configurations.md#auth-scope-and-security-considerations) section before using `auth` against a public spec — the configured headers are sent to every `$ref` target as well as the root URL.\n\n## Basic AsyncAPI Document Structure\n\nHere's a complete basic AsyncAPI document example to get you started:\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User Service API\",\n \"version\": \"1.0.0\",\n \"description\": \"API for user management events\"\n },\n \"channels\": {\n \"userSignedup\": {\n \"address\": \"user/signedup/{userId}/{region}\",\n \"parameters\": {\n \"userId\": {\n \"description\": \"The unique identifier for the user\"\n },\n \"region\": {\n \"description\": \"The geographic region\",\n \"enum\": [\"us-east\", \"us-west\", \"eu-central\"]\n }\n },\n \"messages\": {\n \"UserSignedUp\": {\n \"$ref\": \"#/components/messages/UserSignedUp\"\n }\n }\n }\n },\n \"operations\": {\n \"sendUserSignedup\": {\n \"action\": \"send\",\n \"channel\": {\n \"$ref\": \"#/channels/userSignedup\"\n },\n \"messages\": [\n {\n \"$ref\": \"#/channels/userSignedup/messages/UserSignedUp\"\n }\n ]\n },\n \"receiveUserSignedup\": {\n \"action\": \"receive\",\n \"channel\": {\n \"$ref\": \"#/channels/userSignedup\"\n },\n \"messages\": [\n {\n \"$ref\": \"#/channels/userSignedup/messages/UserSignedUp\"\n }\n ]\n }\n },\n \"components\": {\n \"messages\": {\n \"UserSignedUp\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/UserSignedUpPayload\"\n },\n \"headers\": {\n \"$ref\": \"#/components/schemas/UserHeaders\"\n }\n }\n },\n \"schemas\": {\n \"UserSignedUpPayload\": {\n \"type\": \"object\",\n \"properties\": {\n \"display_name\": {\n \"type\": \"string\",\n \"description\": \"Name of the user\"\n },\n \"email\": {\n \"type\": \"string\",\n \"format\": \"email\",\n \"description\": \"Email of the user\"\n },\n \"created_at\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"When the user was created\"\n }\n },\n \"required\": [\"display_name\", \"email\"]\n },\n \"UserHeaders\": {\n \"type\": \"object\",\n \"properties\": {\n \"correlation_id\": {\n \"type\": \"string\",\n \"description\": \"Correlation ID for tracking\"\n },\n \"source\": {\n \"type\": \"string\",\n \"description\": \"Source system\"\n }\n }\n }\n }\n }\n}\n```\n\n## Multi-message channels\n\nA channel may carry more than one message. Both the payload and header models reflect **all** of them:\n\n- **Payloads** — when two or more messages on a channel carry a payload, the generated payload is a `oneOf` union of every payload. A message with no payload is skipped from the union (with a warning) instead of truncating the rest; when exactly one message carries a payload, a plain (non-union) model is produced.\n- **Headers** — likewise, when two or more messages declare headers, the channel's header model is a `oneOf` union of each message's headers. A single header-bearing message produces the same standalone header model as before; header-less messages alongside header-bearing ones are skipped with a warning. Reply-only messages are excluded from this union — the channel headers model the request side, so a request never carries the reply's headers.\n\nThis means adding a payload-less or header-less message to a multi-message channel no longer silently drops the other messages' models.\n\n## Servers and base URL\n\nWhen a channel is generated for the `http_client` protocol, the document's first `http`/`https` server URL becomes the generated client's default `baseUrl`. Non-HTTP servers (e.g. `nats`, `kafka`) are ignored for the HTTP client. See [HTTP client base URL precedence](../protocols/http_client.md#base-url).\n\n## Extensions\n\nTo customize the code generation through the AsyncAPI document, use the `x-the-codegen-project` [extension object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#specificationExtensions) with the following properties:\n\n### Channel Extensions\n\n`channelName`, string, customize the name of the functions generated for the channel, use this to overwrite the automatically determined name for models and functions. This will be used by the following generators; [payloads](../generators/payloads.md), [parameters](../generators/parameters.md) and [channels](../generators/channels.md). \n\n`functionTypeMapping`, [ChannelFunctionTypes](https://the-codegen-project.org/docs/api/enumerations/ChannelFunctionTypes), customize which generators to generate for the given channel, use this to specify further which functions we render. This will be used by the following generators; [channels](../generators/channels.md). \n\n#### Example: Custom Channel Configuration\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Custom Channel Example\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"user-events\": {\n \"address\": \"events/user/{action}\",\n \"parameters\": {\n \"action\": {\n \"enum\": [\"created\", \"updated\", \"deleted\"]\n }\n },\n \"messages\": {\n \"UserEvent\": {\n \"payload\": {\n \"type\": \"object\",\n \"properties\": {\n \"userId\": {\"type\": \"string\"},\n \"action\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n }\n }\n }\n },\n \"x-the-codegen-project\": {\n \"channelName\": \"UserEventChannel\",\n \"functionTypeMapping\": [\"event_source_express\", \"kafka_publish\"]\n }\n }\n }\n}\n```\n\n### Operation Extensions\n\n`functionTypeMapping`, [ChannelFunctionTypes](https://the-codegen-project.org/docs/api/enumerations/ChannelFunctionTypes), customize which generators to generate for the given operation, use this to specify further which functions we render. This will be used by the following generators; [channels](../generators/channels.md). \n\n#### Example: Custom Operation Configuration\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Custom Operation Example\",\n \"version\": \"1.0.0\"\n },\n \"operations\": {\n \"publishUserEvent\": {\n \"action\": \"send\",\n \"channel\": {\n \"$ref\": \"#/channels/user-events\"\n },\n \"messages\": [\n {\"$ref\": \"#/channels/user-events/messages/UserEvent\"}\n ],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_publish\"]\n }\n },\n \"subscribeToUserEvents\": {\n \"action\": \"receive\",\n \"channel\": {\n \"$ref\": \"#/channels/user-events\"\n },\n \"messages\": [\n {\"$ref\": \"#/channels/user-events/messages/UserEvent\"}\n ],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_subscribe\"]\n }\n }\n }\n}\n```\n\n## Protocol Support\n\n### HTTP Client\n\nUse HTTP bindings to generate HTTP client code. Supports all standard HTTP methods and status codes.\n\n#### Example: REST API with Multiple HTTP Methods\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User Management API\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"users\": {\n \"address\": \"/users/{userId}\",\n \"parameters\": {\n \"userId\": {\n \"description\": \"User identifier\"\n }\n },\n \"messages\": {\n \"UserRequest\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/User\"\n }\n },\n \"UserResponse\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/User\"\n },\n \"bindings\": {\n \"http\": {\n \"statusCode\": 200\n }\n }\n },\n \"NotFound\": {\n \"payload\": {\n \"type\": \"object\",\n \"properties\": {\n \"error\": {\"type\": \"string\"},\n \"code\": {\"type\": \"string\"}\n }\n },\n \"bindings\": {\n \"http\": {\n \"statusCode\": 404\n }\n }\n }\n }\n }\n },\n \"operations\": {\n \"createUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserRequest\"}],\n \"bindings\": {\n \"http\": {\"method\": \"POST\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n },\n \"getUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [],\n \"bindings\": {\n \"http\": {\"method\": \"GET\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [\n {\"$ref\": \"#/channels/users/messages/UserResponse\"},\n {\"$ref\": \"#/channels/users/messages/NotFound\"}\n ]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n },\n \"updateUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserRequest\"}],\n \"bindings\": {\n \"http\": {\"method\": \"PUT\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n },\n \"deleteUser\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [],\n \"bindings\": {\n \"http\": {\"method\": \"DELETE\"}\n },\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/users\"},\n \"messages\": [{\"$ref\": \"#/channels/users/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"http_client\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"User\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\"type\": \"string\"},\n \"name\": {\"type\": \"string\"},\n \"email\": {\"type\": \"string\", \"format\": \"email\"},\n \"created_at\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"name\", \"email\"]\n }\n }\n }\n}\n```\n\n### Kafka\n\nGenerate Kafka producers and consumers with proper serialization.\n\n#### Example: Kafka Event Streaming\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Order Processing Events\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"order-events\": {\n \"address\": \"orders.{eventType}.{region}\",\n \"parameters\": {\n \"eventType\": {\n \"enum\": [\"created\", \"updated\", \"cancelled\", \"completed\"]\n },\n \"region\": {\n \"enum\": [\"us\", \"eu\", \"asia\"]\n }\n },\n \"messages\": {\n \"OrderEvent\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/OrderEvent\"\n },\n \"headers\": {\n \"$ref\": \"#/components/schemas/EventHeaders\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"publishOrderEvent\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/order-events\"},\n \"messages\": [{\"$ref\": \"#/channels/order-events/messages/OrderEvent\"}],\n \"bindings\": {\n \"kafka\": {\n \"clientId\": \"order-service\",\n \"groupId\": \"order-processors\"\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_publish\"]\n }\n },\n \"subscribeToOrderEvents\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/order-events\"},\n \"messages\": [{\"$ref\": \"#/channels/order-events/messages/OrderEvent\"}],\n \"bindings\": {\n \"kafka\": {\n \"groupId\": \"order-processors\",\n \"clientId\": \"order-consumer\"\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"kafka_subscribe\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"OrderEvent\": {\n \"type\": \"object\",\n \"properties\": {\n \"orderId\": {\"type\": \"string\"},\n \"customerId\": {\"type\": \"string\"},\n \"amount\": {\"type\": \"number\"},\n \"currency\": {\"type\": \"string\"},\n \"status\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"orderId\", \"customerId\", \"amount\", \"status\"]\n },\n \"EventHeaders\": {\n \"type\": \"object\",\n \"properties\": {\n \"correlationId\": {\"type\": \"string\"},\n \"source\": {\"type\": \"string\"},\n \"version\": {\"type\": \"string\"}\n }\n }\n }\n }\n}\n```\n\n### NATS\n\nGenerate NATS request/reply patterns and pub/sub functionality.\n\n#### Example: NATS Request-Reply Pattern\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User Service NATS API\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"user-service\": {\n \"address\": \"user.service.{operation}\",\n \"parameters\": {\n \"operation\": {\n \"enum\": [\"get\", \"create\", \"update\", \"delete\"]\n }\n },\n \"messages\": {\n \"UserRequest\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/UserRequest\"\n }\n },\n \"UserResponse\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/UserResponse\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"requestUserOperation\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserRequest\"}],\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"nats_request\"]\n }\n },\n \"replyToUserOperation\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserRequest\"}],\n \"reply\": {\n \"channel\": {\"$ref\": \"#/channels/user-service\"},\n \"messages\": [{\"$ref\": \"#/channels/user-service/messages/UserResponse\"}]\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"nats_reply\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"UserRequest\": {\n \"type\": \"object\",\n \"properties\": {\n \"operation\": {\"type\": \"string\"},\n \"userId\": {\"type\": \"string\"},\n \"data\": {\"type\": \"object\"}\n },\n \"required\": [\"operation\"]\n },\n \"UserResponse\": {\n \"type\": \"object\",\n \"properties\": {\n \"success\": {\"type\": \"boolean\"},\n \"data\": {\"type\": \"object\"},\n \"error\": {\"type\": \"string\"}\n },\n \"required\": [\"success\"]\n }\n }\n }\n}\n```\n\n### MQTT\n\nGenerate MQTT publish/subscribe clients with QoS levels.\n\n#### Example: IoT Device Communications\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"IoT Device Management\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"device-telemetry\": {\n \"address\": \"devices/{deviceId}/telemetry/{sensorType}\",\n \"parameters\": {\n \"deviceId\": {\n \"description\": \"Unique device identifier\"\n },\n \"sensorType\": {\n \"enum\": [\"temperature\", \"humidity\", \"pressure\", \"motion\"]\n }\n },\n \"messages\": {\n \"TelemetryData\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/TelemetryData\"\n }\n }\n }\n },\n \"device-commands\": {\n \"address\": \"devices/{deviceId}/commands\",\n \"parameters\": {\n \"deviceId\": {\n \"description\": \"Unique device identifier\"\n }\n },\n \"messages\": {\n \"DeviceCommand\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/DeviceCommand\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"publishTelemetry\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/device-telemetry\"},\n \"messages\": [{\"$ref\": \"#/channels/device-telemetry/messages/TelemetryData\"}],\n \"bindings\": {\n \"mqtt\": {\n \"qos\": 1,\n \"retain\": false\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"mqtt_publish\"]\n }\n },\n \"subscribeToTelemetry\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/device-telemetry\"},\n \"messages\": [{\"$ref\": \"#/channels/device-telemetry/messages/TelemetryData\"}],\n \"bindings\": {\n \"mqtt\": {\n \"qos\": 1\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"mqtt_subscribe\"]\n }\n },\n \"sendCommand\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/device-commands\"},\n \"messages\": [{\"$ref\": \"#/channels/device-commands/messages/DeviceCommand\"}],\n \"bindings\": {\n \"mqtt\": {\n \"qos\": 2,\n \"retain\": true\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"mqtt_publish\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"TelemetryData\": {\n \"type\": \"object\",\n \"properties\": {\n \"deviceId\": {\"type\": \"string\"},\n \"sensorType\": {\"type\": \"string\"},\n \"value\": {\"type\": \"number\"},\n \"unit\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"location\": {\n \"type\": \"object\",\n \"properties\": {\n \"latitude\": {\"type\": \"number\"},\n \"longitude\": {\"type\": \"number\"}\n }\n }\n },\n \"required\": [\"deviceId\", \"sensorType\", \"value\", \"timestamp\"]\n },\n \"DeviceCommand\": {\n \"type\": \"object\",\n \"properties\": {\n \"command\": {\"type\": \"string\"},\n \"parameters\": {\"type\": \"object\"},\n \"commandId\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"command\", \"commandId\"]\n }\n }\n }\n}\n```\n\n### AMQP\n\nGenerate AMQP producers and consumers for message queuing.\n\n#### Example: Order Processing Queue\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Order Processing Queue\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"order-queue\": {\n \"address\": \"orders.processing\",\n \"messages\": {\n \"OrderMessage\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/Order\"\n },\n \"headers\": {\n \"$ref\": \"#/components/schemas/MessageHeaders\"\n }\n }\n }\n },\n \"order-dlq\": {\n \"address\": \"orders.dead-letter\",\n \"messages\": {\n \"FailedOrderMessage\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/FailedOrder\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"publishOrder\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/order-queue\"},\n \"messages\": [{\"$ref\": \"#/channels/order-queue/messages/OrderMessage\"}],\n \"bindings\": {\n \"amqp\": {\n \"exchange\": {\n \"name\": \"orders\",\n \"type\": \"topic\",\n \"durable\": true\n },\n \"routingKey\": \"order.created\"\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"amqp_publish\"]\n }\n },\n \"consumeOrders\": {\n \"action\": \"receive\",\n \"channel\": {\"$ref\": \"#/channels/order-queue\"},\n \"messages\": [{\"$ref\": \"#/channels/order-queue/messages/OrderMessage\"}],\n \"bindings\": {\n \"amqp\": {\n \"queue\": {\n \"name\": \"order-processing-queue\",\n \"durable\": true,\n \"exclusive\": false,\n \"autoDelete\": false\n },\n \"ack\": true\n }\n },\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"amqp_consume\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"Order\": {\n \"type\": \"object\",\n \"properties\": {\n \"orderId\": {\"type\": \"string\"},\n \"customerId\": {\"type\": \"string\"},\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"productId\": {\"type\": \"string\"},\n \"quantity\": {\"type\": \"integer\"},\n \"price\": {\"type\": \"number\"}\n }\n }\n },\n \"totalAmount\": {\"type\": \"number\"},\n \"currency\": {\"type\": \"string\"},\n \"orderDate\": {\"type\": \"string\", \"format\": \"date-time\"}\n },\n \"required\": [\"orderId\", \"customerId\", \"items\", \"totalAmount\"]\n },\n \"FailedOrder\": {\n \"type\": \"object\",\n \"properties\": {\n \"orderId\": {\"type\": \"string\"},\n \"error\": {\"type\": \"string\"},\n \"retryCount\": {\"type\": \"integer\"},\n \"failedAt\": {\"type\": \"string\", \"format\": \"date-time\"}\n }\n },\n \"MessageHeaders\": {\n \"type\": \"object\",\n \"properties\": {\n \"messageId\": {\"type\": \"string\"},\n \"correlationId\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"priority\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 255}\n }\n }\n }\n }\n}\n```\n\n### EventSource\n\nGenerate Server-Sent Events (SSE) implementations for real-time updates.\n\n#### Example: Real-time Notifications\n\n```json\n{\n \"asyncapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"Real-time Notifications\",\n \"version\": \"1.0.0\"\n },\n \"channels\": {\n \"user-notifications\": {\n \"address\": \"/events/users/{userId}/notifications\",\n \"parameters\": {\n \"userId\": {\n \"description\": \"User identifier for targeted notifications\"\n }\n },\n \"messages\": {\n \"Notification\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/Notification\"\n }\n },\n \"SystemAlert\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/SystemAlert\"\n }\n }\n }\n },\n \"live-updates\": {\n \"address\": \"/events/live/{topic}\",\n \"parameters\": {\n \"topic\": {\n \"enum\": [\"stock-prices\", \"sports-scores\", \"weather-alerts\"]\n }\n },\n \"messages\": {\n \"LiveUpdate\": {\n \"payload\": {\n \"$ref\": \"#/components/schemas/LiveUpdate\"\n }\n }\n }\n }\n },\n \"operations\": {\n \"streamUserNotifications\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/user-notifications\"},\n \"messages\": [\n {\"$ref\": \"#/channels/user-notifications/messages/Notification\"},\n {\"$ref\": \"#/channels/user-notifications/messages/SystemAlert\"}\n ],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"event_source_express\"]\n }\n },\n \"streamLiveUpdates\": {\n \"action\": \"send\",\n \"channel\": {\"$ref\": \"#/channels/live-updates\"},\n \"messages\": [{\"$ref\": \"#/channels/live-updates/messages/LiveUpdate\"}],\n \"x-the-codegen-project\": {\n \"functionTypeMapping\": [\"event_source_express\"]\n }\n }\n },\n \"components\": {\n \"schemas\": {\n \"Notification\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\"type\": \"string\"},\n \"userId\": {\"type\": \"string\"},\n \"type\": {\"type\": \"string\", \"enum\": [\"info\", \"warning\", \"error\", \"success\"]},\n \"title\": {\"type\": \"string\"},\n \"message\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"actionUrl\": {\"type\": \"string\", \"format\": \"uri\"},\n \"read\": {\"type\": \"boolean\", \"default\": false}\n },\n \"required\": [\"id\", \"userId\", \"type\", \"title\", \"message\", \"timestamp\"]\n },\n \"SystemAlert\": {\n \"type\": \"object\",\n \"properties\": {\n \"alertId\": {\"type\": \"string\"},\n \"severity\": {\"type\": \"string\", \"enum\": [\"low\", \"medium\", \"high\", \"critical\"]},\n \"service\": {\"type\": \"string\"},\n \"message\": {\"type\": \"string\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"resolved\": {\"type\": \"boolean\", \"default\": false}\n },\n \"required\": [\"alertId\", \"severity\", \"service\", \"message\", \"timestamp\"]\n },\n \"LiveUpdate\": {\n \"type\": \"object\",\n \"properties\": {\n \"topic\": {\"type\": \"string\"},\n \"data\": {\"type\": \"object\"},\n \"timestamp\": {\"type\": \"string\", \"format\": \"date-time\"},\n \"sequence\": {\"type\": \"integer\"}\n },\n \"required\": [\"topic\", \"data\", \"timestamp\"]\n }\n }\n }\n}\n```\n\n## FAQ\n\n### How does it relate to AsyncAPI Generator and templates?\nIt is fairly similar in functionality except in some key areas.\n\nTemplates are similar to presets except you can bind presets together to make it easier to render code down stream.\n\nThe AsyncAPI Generator is like the core of the Codegen Project, however it does not enable different inputs than AsyncAPI documents. \n\n### Can I mix multiple protocols in one document?\nYes! You can define operations with different protocol bindings in the same AsyncAPI document. Use the `x-the-codegen-project` extension to specify which generators to use for each operation.\n\n### How do I handle versioning?\n\nShort answer: Use the `info.version` field in your AsyncAPI document and consider using separate documents for major version changes. You can also use channel addressing patterns to include version information.\n\nLong answer: It's hard to version APIs, there are tons of resources how to handle versioning of your API which is far beyond what we can offer here.\n\n### Can I customize the generated code structure?\nYes, use the `x-the-codegen-project` extension properties to customize channel names, function mappings, and other generation aspects or the configuration file [while taking a look at the different generators](../generators).", }, "inputs/jsonschema": { title: "JSON Schema", @@ -92,7 +92,7 @@ export const docs: Record = { }, "inputs/openapi": { title: "OpenAPI", - content: "# OpenAPI\n\nInput support; `openapi`\n\n- OpenAPI 3.0.x\n- OpenAPI 3.1.x\n- OpenAPI 2.0.0 (Swagger)\n\n| **Presets** | OpenAPI | \n|---|---|\n| [`payloads`](../generators/payloads.md) | ✅ |\n| [`parameters`](../generators/parameters.md) | ✅ |\n| [`headers`](../generators/headers.md) | ✅ |\n| [`types`](../generators/types.md) | ✅ |\n| [`channels`](../generators/channels.md) | ❌ |\n| [`client`](../generators/client.md) | ❌ |\n| [`custom`](../generators/custom.md) | ✅ |\n| [`models`](../generators/custom.md) | ✅ |\n\n## Basic Usage\n\n### Configuration\n\nCreate a configuration file that specifies OpenAPI as the input type:\n\n```json\n{\n \"inputType\": \"openapi\",\n \"inputPath\": \"./api/openapi.yaml\",\n \"language\": \"typescript\",\n \"generators\": [ ... ]\n}\n```\n\n## Remote URL inputs\n\n`inputPath` accepts an `http://` or `https://` URL. Optional authentication (bearer token, API key, or custom headers) is configured via the `auth` field. Cross-spec `$ref` URLs are also resolved through the same auth-aware HTTP client. See the [configurations guide](../configurations.md#remote-url-inputs) for examples and the [auth scope and security considerations](../configurations.md#auth-scope-and-security-considerations) section — the configured headers are sent to every `$ref` target as well as the root URL.\n\n## Troubleshooting\n\n## FAQ\n\n### Can I use both OpenAPI and AsyncAPI in the same project?\n\nYes! You can have separate configuration files for each input type and generate code to different output directories.\n\n### Can I customize the generated code?\n\nYes, use the [custom generator](../generators/custom) preset to create your own generation logic.", + content: "# OpenAPI\n\nInput support; `openapi`\n\n- OpenAPI 3.0.x\n- OpenAPI 3.1.x\n- OpenAPI 2.0.0 (Swagger)\n\n| **Presets** | OpenAPI | \n|---|---|\n| [`payloads`](../generators/payloads.md) | ✅ |\n| [`parameters`](../generators/parameters.md) | ✅ |\n| [`headers`](../generators/headers.md) | ✅ |\n| [`types`](../generators/types.md) | ✅ |\n| [`channels`](../generators/channels.md) | ✅ |\n| [`client`](../generators/client.md) | ✅ |\n| [`custom`](../generators/custom.md) | ✅ |\n| [`models`](../generators/custom.md) | ✅ |\n\n## Basic Usage\n\n### Configuration\n\nCreate a configuration file that specifies OpenAPI as the input type:\n\n```json\n{\n \"inputType\": \"openapi\",\n \"inputPath\": \"./api/openapi.yaml\",\n \"language\": \"typescript\",\n \"generators\": [ ... ]\n}\n```\n\n## Content types\n\nRequest and response bodies are extracted from JSON content types: `application/json`, `text/json`, and any type whose subtype ends in `+json` (for example `application/hal+json` or `application/vnd.api+json`). When several JSON content types are present, `application/json` is preferred.\n\nWhen an operation declares a body but **none** of its content types are JSON (for example an XML-only response), no payload model is generated and a warning naming the operation and its content types is logged — the omission is never silent.\n\n## Servers and base URL\n\nFor the `channels`/`client` HTTP client, the document's first `http`/`https` server URL becomes the generated default `baseUrl`. Server URL variables are substituted with their declared defaults; a server whose variable has no default, or whose URL is relative, is skipped with a log. See [HTTP client base URL precedence](../protocols/http_client.md#base-url).\n\n## Current limitations\n\nThese constructs are not generated and are reported with a warning rather than dropped silently:\n\n- **Cookie parameters** — `in: cookie` parameters have no generated handling and are dropped (path, query, and header parameters are unaffected).\n- **Webhooks** — the OpenAPI 3.1 `webhooks` section is not traversed.\n- **Non-JSON bodies** — only JSON-family content types produce payload models (see [Content types](#content-types)).\n\n## Remote URL inputs\n\n`inputPath` accepts an `http://` or `https://` URL. Optional authentication (bearer token, API key, or custom headers) is configured via the `auth` field. Cross-spec `$ref` URLs are also resolved through the same auth-aware HTTP client. See the [configurations guide](../configurations.md#remote-url-inputs) for examples and the [auth scope and security considerations](../configurations.md#auth-scope-and-security-considerations) section — the configured headers are sent to every `$ref` target as well as the root URL.\n\n## Troubleshooting\n\n## FAQ\n\n### Can I use both OpenAPI and AsyncAPI in the same project?\n\nYes! You can have separate configuration files for each input type and generate code to different output directories.\n\n### Can I customize the generated code?\n\nYes, use the [custom generator](../generators/custom) preset to create your own generation logic.", }, "integrations/nextjs": { title: "Next.JS", @@ -120,7 +120,7 @@ export const docs: Record = { }, "protocols/http_client": { title: "HTTP(S)", - content: "# HTTP(S)\n\nHTTP client generator creates type-safe functions for making HTTP requests based on your API specification. It supports various authentication methods, retry logic, and extensibility hooks.\n\nIt is currently available through the generators ([channels](../generators/channels.md)):\n\nThis is available through [AsyncAPI](../inputs/asyncapi.md) ([requires the HTTP `method` binding for operations and `statusCode` for messages](../inputs/asyncapi.md#http-client)) and directly from [OpenAPI](../inputs/openapi.md) documents (see [From OpenAPI](#from-openapi) below).\n\n## TypeScript\n\n| **Feature** | Is supported? |\n|---|---|\n| Download | ❌ |\n| Upload | ❌ |\n| Retry with backoff | ✅ |\n| OAuth2 Authorization code | ❌ (browser-only) |\n| OAuth2 Implicit | ❌ (browser-only) |\n| OAuth2 Password | ✅ |\n| OAuth2 Client Credentials | ✅ |\n| OAuth2 Token Refresh | ✅ |\n| Username/password Authentication | ✅ |\n| Bearer Authentication | ✅ |\n| Basic Authentication | ✅ |\n| API Key Authentication | ✅ |\n| Request/Response Hooks | ✅ |\n| XML Based API | ❌ |\n| JSON Based API | ✅ |\n| POST | ✅ |\n| GET | ✅ |\n| PATCH | ✅ |\n| DELETE | ✅ |\n| PUT | ✅ |\n| HEAD | ✅ |\n| OPTIONS | ✅ |\n\n## Channels\n\nRead more about the [channels generator here](../generators/channels.md).\n\n\n\n \n \n \n \n\n\n \n \n \n \n\n
Input (AsyncAPI)Using the code
\n\n```yaml\nasyncapi: 3.0.0\ninfo:\n title: User API\n version: 1.0.0\nchannels:\n ping:\n address: /ping\n messages:\n pingRequest:\n $ref: '#/components/messages/PingRequest'\n pongResponse:\n $ref: '#/components/messages/PongResponse'\noperations:\n postPing:\n action: send\n channel:\n $ref: '#/channels/ping'\n bindings:\n http:\n method: POST\n reply:\n channel:\n $ref: '#/channels/ping'\n messages:\n - $ref: '#/channels/ping/messages/pongResponse'\ncomponents:\n messages:\n PingRequest:\n payload:\n type: object\n properties:\n message:\n type: string\n PongResponse:\n payload:\n type: object\n properties:\n response:\n type: string\n bindings:\n http:\n statusCode: 200\n```\n\n\n```ts\n// Location depends on the payload generator configurations\nimport { Ping } from './__gen__/payloads/Ping';\nimport { Pong } from './__gen__/payloads/Pong';\n// Location depends on the channel generator configurations\nimport { Protocols } from './__gen__/channels';\nconst { http_client } = Protocols;\nconst { postPingPostRequest } = http_client;\n\n// Create a request payload\nconst pingMessage = new Ping({ message: 'Hello!' });\n\n// Make a simple request\nconst response = await postPingPostRequest({\n payload: pingMessage,\n baseUrl: 'https://api.example.com'\n});\n\n// Access the response\nconsole.log(response.data.response); // The deserialized Pong\nconsole.log(response.status); // 200\nconsole.log(response.headers); // Response headers\nconsole.log(response.rawData); // Raw JSON response\n```\n
\n\n### From OpenAPI\n\nThe `http_client` protocol is also generated directly from an OpenAPI document (2.0/3.0/3.1). Each path + method becomes one function. Configure the `channels` generator with `inputType: 'openapi'` and `protocols: ['http_client']`.\n\nFunction names come from each operation's `operationId` (camel-cased). When an operation has **no** `operationId`, a name is synthesized from the method and path, e.g. `GET /v2/connect/{referenceId}` → `getV2ConnectReferenceId`. Give your operations `operationId`s for the cleanest client.\n\nAs a consumer you work with three generated pieces: the **call functions** (`http_client.ts`), the **request/response body models** (`payload/`), and the **path/query parameter models** (`parameter/`):\n\n```ts\nimport { http_client } from './__gen__/channels';\nimport { PostV2ConnectRequest } from './__gen__/channels/payload/PostV2ConnectRequest';\nimport { GetV2ConnectReferenceIdParameters } from './__gen__/channels/parameter/GetV2ConnectReferenceIdParameters';\n\n// Request with a body: build the model, pass it as `payload`.\nconst created = await http_client.postV2Connect({\n baseUrl: 'https://api.example.com',\n payload: new PostV2ConnectRequest({ returnUrl: 'https://shop.example/return' })\n});\nconsole.log(created.data.connectUrl); // typed response model\n\n// Request with a path parameter: supply it through the parameter model.\nconst connect = await http_client.getV2ConnectReferenceId({\n baseUrl: 'https://api.example.com',\n parameters: new GetV2ConnectReferenceIdParameters({ referenceId: 'ref_123' })\n});\nconsole.log(connect.data.safepayAccountId);\n```\n\nSee the runnable [`openapi-http-client` example](https://github.com/the-codegen-project/cli/tree/main/examples/openapi-http-client) for a complete, self-contained setup.\n\n## Authentication\n\nThe HTTP client uses a discriminated union for authentication, providing excellent TypeScript autocomplete support.\n\n### Bearer Token\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'bearer',\n token: 'your-jwt-token'\n }\n});\n```\n\n### Basic Authentication\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'basic',\n username: 'user',\n password: 'pass'\n }\n});\n```\n\n### API Key\n\n```typescript\n// API Key in header (default)\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'apiKey',\n key: 'your-api-key',\n name: 'X-API-Key', // Header name (default: 'X-API-Key')\n in: 'header' // 'header' or 'query'\n }\n});\n\n// API Key in query parameter\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'apiKey',\n key: 'your-api-key',\n name: 'api_key',\n in: 'query'\n }\n});\n```\n\n### OAuth2 Client Credentials\n\nFor server-to-server authentication:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'oauth2',\n flow: 'client_credentials',\n clientId: 'your-client-id',\n clientSecret: 'your-client-secret',\n tokenUrl: 'https://auth.example.com/oauth/token',\n scopes: ['read', 'write'],\n onTokenRefresh: (tokens) => {\n // Called when tokens are obtained/refreshed\n console.log('New access token:', tokens.accessToken);\n }\n }\n});\n```\n\n### OAuth2 Password Flow\n\nFor legacy applications requiring username/password:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'oauth2',\n flow: 'password',\n clientId: 'your-client-id',\n username: 'user@example.com',\n password: 'user-password',\n tokenUrl: 'https://auth.example.com/oauth/token',\n onTokenRefresh: (tokens) => {\n // Store tokens for future use\n saveTokens(tokens);\n }\n }\n});\n```\n\n### OAuth2 with Pre-obtained Token\n\nFor tokens obtained via browser-based flows (implicit, authorization code):\n\n```typescript\n// Token obtained from browser OAuth flow\nconst accessToken = getTokenFromBrowserFlow();\n\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'oauth2',\n accessToken: accessToken,\n refreshToken: refreshToken, // Optional: for auto-refresh on 401\n tokenUrl: 'https://auth.example.com/oauth/token',\n clientId: 'your-client-id',\n onTokenRefresh: (tokens) => {\n // Update stored tokens\n updateStoredTokens(tokens);\n }\n }\n});\n```\n\n## Retry with Exponential Backoff\n\nConfigure automatic retry for failed requests:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n retry: {\n maxRetries: 3, // Maximum retry attempts (default: 3)\n initialDelayMs: 1000, // Initial delay before first retry (default: 1000)\n maxDelayMs: 30000, // Maximum delay between retries (default: 30000)\n backoffMultiplier: 2, // Exponential backoff multiplier (default: 2)\n retryableStatusCodes: [408, 429, 500, 502, 503, 504], // Status codes to retry\n retryOnNetworkError: true, // Retry on network failures\n onRetry: (attempt, delay, error) => {\n console.log(`Retry attempt ${attempt} after ${delay}ms: ${error.message}`);\n }\n }\n});\n```\n\n## Error Handling\n\nNon-OK HTTP responses **throw** a typed `HttpError` instead of returning. `HttpError` extends the built-in `Error` and carries the HTTP `status`, `statusText`, and the parsed response `body`:\n\n```typescript\nexport class HttpError extends Error {\n status: number;\n statusText: string;\n body?: unknown; // the parsed JSON error body, when present\n}\n```\n\nConsume it with an `instanceof` check:\n\n```typescript\nimport { getGetUser, HttpError } from './__gen__/channels/http_client';\n\ntry {\n const response = await getGetUser({ baseUrl: 'https://api.example.com' });\n // response.data is the typed, unmarshalled success payload\n} catch (error) {\n if (error instanceof HttpError) {\n console.error(error.status); // e.g. 404\n console.error(error.statusText); // e.g. 'Not Found'\n console.error(error.body); // parsed error body (unknown)\n }\n}\n```\n\n## Request/Response Hooks\n\nCustomize request behavior with hooks:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n hooks: {\n // Modify request before sending\n beforeRequest: async (params) => {\n console.log('Making request to:', params.url);\n // Add custom header\n return {\n ...params,\n headers: {\n ...params.headers,\n 'X-Request-ID': generateRequestId()\n }\n };\n },\n\n // Replace the default fetch implementation\n makeRequest: async (params) => {\n // Use axios, got, or any HTTP client\n const axiosResponse = await axios({\n url: params.url,\n method: params.method,\n headers: params.headers,\n data: params.body\n });\n return {\n ok: axiosResponse.status >= 200 && axiosResponse.status < 300,\n status: axiosResponse.status,\n statusText: axiosResponse.statusText,\n headers: axiosResponse.headers,\n json: () => axiosResponse.data\n };\n },\n\n // Process response after receiving\n afterResponse: async (response, params) => {\n console.log(`Response ${response.status} from ${params.url}`);\n return response;\n },\n\n // Handle errors\n onError: async (error, params) => {\n console.error(`Request failed: ${error.message}`);\n // Optionally transform the error\n return error;\n }\n }\n});\n```\n\n## Path Parameters\n\nFor operations with path parameters, the generator creates typed parameter classes:\n\n```typescript\nimport { UserItemsParameters } from './__gen__/parameters/UserItemsParameters';\n\n// Create parameters with type safety\nconst params = new UserItemsParameters({\n userId: 'user-123',\n itemId: 456\n});\n\nconst response = await getGetUserItem({\n baseUrl: 'https://api.example.com',\n parameters: params // Replaces {userId} and {itemId} in path\n});\n```\n\n## Typed Headers\n\nFor operations with defined headers, the generator creates typed header classes:\n\n```typescript\nimport { ItemRequestHeaders } from './__gen__/headers/ItemRequestHeaders';\n\nconst headers = new ItemRequestHeaders({\n xCorrelationId: 'corr-123',\n xRequestId: 'req-456'\n});\n\nconst response = await putUpdateUserItem({\n baseUrl: 'https://api.example.com',\n parameters: params,\n payload: itemData,\n requestHeaders: headers // Type-safe headers\n});\n```\n\n## Additional Headers and Query Parameters\n\nAdd custom headers or query parameters to any request:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n additionalHeaders: {\n 'X-Custom-Header': 'value',\n 'Accept-Language': 'en-US'\n },\n additionalQueryParams: {\n include: 'metadata',\n format: 'detailed'\n }\n});\n```\n\n## Multi-Status Responses\n\nFor operations that return different payloads based on status code, the generator creates union types:\n\n```yaml\n# AsyncAPI spec with multiple response types\noperations:\n getItem:\n reply:\n messages:\n - $ref: '#/components/messages/ItemResponse' # 200\n - $ref: '#/components/messages/NotFoundError' # 404\n```\n\n```typescript\nconst response = await getItemRequest({\n baseUrl: 'https://api.example.com',\n parameters: params\n});\n\n// Response type is union: ItemResponse | NotFoundError\n// Use response.status to discriminate\nif (response.status === 200) {\n console.log('Item:', response.data); // ItemResponse\n} else if (response.status === 404) {\n console.log('Not found:', response.data); // NotFoundError\n}\n```", + content: "# HTTP(S)\n\nHTTP client generator creates type-safe functions for making HTTP requests based on your API specification. It supports various authentication methods, retry logic, and extensibility hooks.\n\nIt is currently available through the generators ([channels](../generators/channels.md)):\n\nThis is available through [AsyncAPI](../inputs/asyncapi.md) ([requires the HTTP `method` binding for operations and `statusCode` for messages](../inputs/asyncapi.md#http-client)) and directly from [OpenAPI](../inputs/openapi.md) documents (see [From OpenAPI](#from-openapi) below).\n\n## TypeScript\n\n| **Feature** | Is supported? |\n|---|---|\n| Download | ❌ |\n| Upload | ❌ |\n| Retry with backoff | ✅ |\n| OAuth2 Authorization code | ❌ (browser-only) |\n| OAuth2 Implicit | ❌ (browser-only) |\n| OAuth2 Password | ✅ |\n| OAuth2 Client Credentials | ✅ |\n| OAuth2 Token Refresh | ✅ |\n| Username/password Authentication | ✅ |\n| Bearer Authentication | ✅ |\n| Basic Authentication | ✅ |\n| API Key Authentication | ✅ |\n| Request/Response Hooks | ✅ |\n| XML Based API | ❌ |\n| JSON Based API | ✅ |\n| POST | ✅ |\n| GET | ✅ |\n| PATCH | ✅ |\n| DELETE | ✅ |\n| PUT | ✅ |\n| HEAD | ✅ |\n| OPTIONS | ✅ |\n\n## Channels\n\nRead more about the [channels generator here](../generators/channels.md).\n\n\n\n \n \n \n \n\n\n \n \n \n \n\n
Input (AsyncAPI)Using the code
\n\n```yaml\nasyncapi: 3.0.0\ninfo:\n title: User API\n version: 1.0.0\nchannels:\n ping:\n address: /ping\n messages:\n pingRequest:\n $ref: '#/components/messages/PingRequest'\n pongResponse:\n $ref: '#/components/messages/PongResponse'\noperations:\n postPing:\n action: send\n channel:\n $ref: '#/channels/ping'\n bindings:\n http:\n method: POST\n reply:\n channel:\n $ref: '#/channels/ping'\n messages:\n - $ref: '#/channels/ping/messages/pongResponse'\ncomponents:\n messages:\n PingRequest:\n payload:\n type: object\n properties:\n message:\n type: string\n PongResponse:\n payload:\n type: object\n properties:\n response:\n type: string\n bindings:\n http:\n statusCode: 200\n```\n\n\n```ts\n// Location depends on the payload generator configurations\nimport { Ping } from './__gen__/payloads/Ping';\nimport { Pong } from './__gen__/payloads/Pong';\n// Location depends on the channel generator configurations\nimport { Protocols } from './__gen__/channels';\nconst { http_client } = Protocols;\nconst { postPingPostRequest } = http_client;\n\n// Create a request payload\nconst pingMessage = new Ping({ message: 'Hello!' });\n\n// Make a simple request\nconst response = await postPingPostRequest({\n payload: pingMessage,\n baseUrl: 'https://api.example.com'\n});\n\n// Access the response\nconsole.log(response.data.response); // The deserialized Pong\nconsole.log(response.status); // 200\nconsole.log(response.headers); // Response headers\nconsole.log(response.rawData); // Raw JSON response\n```\n
\n\n### From OpenAPI\n\nThe `http_client` protocol is also generated directly from an OpenAPI document (2.0/3.0/3.1). Each path + method becomes one function. Configure the `channels` generator with `inputType: 'openapi'` and `protocols: ['http_client']`.\n\nFunction names come from each operation's `operationId` (camel-cased). When an operation has **no** `operationId`, a name is synthesized from the method and path, e.g. `GET /v2/connect/{referenceId}` → `getV2ConnectReferenceId`. Give your operations `operationId`s for the cleanest client.\n\nAs a consumer you work with three generated pieces: the **call functions** (`http_client.ts`), the **request/response body models** (`payload/`), and the **path/query parameter models** (`parameter/`):\n\n```ts\nimport { http_client } from './__gen__/channels';\nimport { PostV2ConnectRequest } from './__gen__/channels/payload/PostV2ConnectRequest';\nimport { GetV2ConnectReferenceIdParameters } from './__gen__/channels/parameter/GetV2ConnectReferenceIdParameters';\n\n// Request with a body: build the model, pass it as `payload`.\nconst created = await http_client.postV2Connect({\n baseUrl: 'https://api.example.com',\n payload: new PostV2ConnectRequest({ returnUrl: 'https://shop.example/return' })\n});\nconsole.log(created.data.connectUrl); // typed response model\n\n// Request with a path parameter: supply it through the parameter model.\nconst connect = await http_client.getV2ConnectReferenceId({\n baseUrl: 'https://api.example.com',\n parameters: new GetV2ConnectReferenceIdParameters({ referenceId: 'ref_123' })\n});\nconsole.log(connect.data.safepayAccountId);\n```\n\nSee the runnable [`openapi-http-client` example](https://github.com/the-codegen-project/cli/tree/main/examples/openapi-http-client) for a complete, self-contained setup.\n\n## Base URL\n\nEvery generated call accepts an optional `baseUrl`. The value used at runtime follows this precedence, highest first:\n\n1. **`context.baseUrl`** passed to the call (e.g. `getUser({ baseUrl: 'https://api.example.com' })`) — always wins.\n2. **The document's first HTTP(S) server** — when the AsyncAPI `servers` (or OpenAPI `servers`) section declares an `http`/`https` server, its URL becomes the generated default. Non-HTTP servers (nats, kafka, …), relative URLs, and OpenAPI server URLs whose variables have no default are skipped.\n3. **`http://localhost:3000`** — the fallback when the document declares no usable HTTP(S) server.\n\nSo a document with `servers: [{ url: 'https://api.example.com' }]` generates clients that target `https://api.example.com` by default, and you only pass `baseUrl` to override it (for example, to point at a staging environment).\n\n## Authentication\n\nThe HTTP client uses a discriminated union for authentication, providing excellent TypeScript autocomplete support.\n\n### Bearer Token\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'bearer',\n token: 'your-jwt-token'\n }\n});\n```\n\n### Basic Authentication\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'basic',\n username: 'user',\n password: 'pass'\n }\n});\n```\n\n### API Key\n\n```typescript\n// API Key in header (default)\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'apiKey',\n key: 'your-api-key',\n name: 'X-API-Key', // Header name (default: 'X-API-Key')\n in: 'header' // 'header' or 'query'\n }\n});\n\n// API Key in query parameter\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'apiKey',\n key: 'your-api-key',\n name: 'api_key',\n in: 'query'\n }\n});\n```\n\n### OAuth2 Client Credentials\n\nFor server-to-server authentication:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'oauth2',\n flow: 'client_credentials',\n clientId: 'your-client-id',\n clientSecret: 'your-client-secret',\n tokenUrl: 'https://auth.example.com/oauth/token',\n scopes: ['read', 'write'],\n onTokenRefresh: (tokens) => {\n // Called when tokens are obtained/refreshed\n console.log('New access token:', tokens.accessToken);\n }\n }\n});\n```\n\n### OAuth2 Password Flow\n\nFor legacy applications requiring username/password:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'oauth2',\n flow: 'password',\n clientId: 'your-client-id',\n username: 'user@example.com',\n password: 'user-password',\n tokenUrl: 'https://auth.example.com/oauth/token',\n onTokenRefresh: (tokens) => {\n // Store tokens for future use\n saveTokens(tokens);\n }\n }\n});\n```\n\n### OAuth2 with Pre-obtained Token\n\nFor tokens obtained via browser-based flows (implicit, authorization code):\n\n```typescript\n// Token obtained from browser OAuth flow\nconst accessToken = getTokenFromBrowserFlow();\n\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n auth: {\n type: 'oauth2',\n accessToken: accessToken,\n refreshToken: refreshToken, // Optional: for auto-refresh on 401\n tokenUrl: 'https://auth.example.com/oauth/token',\n clientId: 'your-client-id',\n onTokenRefresh: (tokens) => {\n // Update stored tokens\n updateStoredTokens(tokens);\n }\n }\n});\n```\n\n## Retry with Exponential Backoff\n\nConfigure automatic retry for failed requests:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n retry: {\n maxRetries: 3, // Maximum retry attempts (default: 3)\n initialDelayMs: 1000, // Initial delay before first retry (default: 1000)\n maxDelayMs: 30000, // Maximum delay between retries (default: 30000)\n backoffMultiplier: 2, // Exponential backoff multiplier (default: 2)\n retryableStatusCodes: [408, 429, 500, 502, 503, 504], // Status codes to retry\n retryOnNetworkError: true, // Retry on network failures\n onRetry: (attempt, delay, error) => {\n console.log(`Retry attempt ${attempt} after ${delay}ms: ${error.message}`);\n }\n }\n});\n```\n\n## Error Handling\n\nNon-OK HTTP responses **throw** a typed `HttpError` instead of returning. `HttpError` extends the built-in `Error` and carries the HTTP `status`, `statusText`, and the parsed response `body`:\n\n```typescript\nexport class HttpError extends Error {\n status: number;\n statusText: string;\n body?: unknown; // the parsed JSON error body, when present\n}\n```\n\nConsume it with an `instanceof` check:\n\n```typescript\nimport { getGetUser, HttpError } from './__gen__/channels/http_client';\n\ntry {\n const response = await getGetUser({ baseUrl: 'https://api.example.com' });\n // response.data is the typed, unmarshalled success payload\n} catch (error) {\n if (error instanceof HttpError) {\n console.error(error.status); // e.g. 404\n console.error(error.statusText); // e.g. 'Not Found'\n console.error(error.body); // parsed error body (unknown)\n }\n}\n```\n\n## Request/Response Hooks\n\nCustomize request behavior with hooks:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n hooks: {\n // Modify request before sending\n beforeRequest: async (params) => {\n console.log('Making request to:', params.url);\n // Add custom header\n return {\n ...params,\n headers: {\n ...params.headers,\n 'X-Request-ID': generateRequestId()\n }\n };\n },\n\n // Replace the default fetch implementation\n makeRequest: async (params) => {\n // Use axios, got, or any HTTP client\n const axiosResponse = await axios({\n url: params.url,\n method: params.method,\n headers: params.headers,\n data: params.body\n });\n return {\n ok: axiosResponse.status >= 200 && axiosResponse.status < 300,\n status: axiosResponse.status,\n statusText: axiosResponse.statusText,\n headers: axiosResponse.headers,\n json: () => axiosResponse.data\n };\n },\n\n // Process response after receiving\n afterResponse: async (response, params) => {\n console.log(`Response ${response.status} from ${params.url}`);\n return response;\n },\n\n // Handle errors\n onError: async (error, params) => {\n console.error(`Request failed: ${error.message}`);\n // Optionally transform the error\n return error;\n }\n }\n});\n```\n\n## Path Parameters\n\nFor operations with path parameters, the generator creates typed parameter classes:\n\n```typescript\nimport { UserItemsParameters } from './__gen__/parameters/UserItemsParameters';\n\n// Create parameters with type safety\nconst params = new UserItemsParameters({\n userId: 'user-123',\n itemId: 456\n});\n\nconst response = await getGetUserItem({\n baseUrl: 'https://api.example.com',\n parameters: params // Replaces {userId} and {itemId} in path\n});\n```\n\n## Typed Headers\n\nFor operations with defined headers, the generator creates typed header classes:\n\n```typescript\nimport { ItemRequestHeaders } from './__gen__/headers/ItemRequestHeaders';\n\nconst headers = new ItemRequestHeaders({\n xCorrelationId: 'corr-123',\n xRequestId: 'req-456'\n});\n\nconst response = await putUpdateUserItem({\n baseUrl: 'https://api.example.com',\n parameters: params,\n payload: itemData,\n requestHeaders: headers // Type-safe headers\n});\n```\n\n## Additional Headers and Query Parameters\n\nAdd custom headers or query parameters to any request:\n\n```typescript\nconst response = await postPingPostRequest({\n payload: message,\n baseUrl: 'https://api.example.com',\n additionalHeaders: {\n 'X-Custom-Header': 'value',\n 'Accept-Language': 'en-US'\n },\n additionalQueryParams: {\n include: 'metadata',\n format: 'detailed'\n }\n});\n```\n\n## Multi-Status Responses\n\nFor operations that return different payloads based on status code, the generator creates union types:\n\n```yaml\n# AsyncAPI spec with multiple response types\noperations:\n getItem:\n reply:\n messages:\n - $ref: '#/components/messages/ItemResponse' # 200\n - $ref: '#/components/messages/NotFoundError' # 404\n```\n\n```typescript\nconst response = await getItemRequest({\n baseUrl: 'https://api.example.com',\n parameters: params\n});\n\n// Response type is union: ItemResponse | NotFoundError\n// Use response.status to discriminate\nif (response.status === 200) {\n console.log('Item:', response.data); // ItemResponse\n} else if (response.status === 404) {\n console.log('Not found:', response.data); // NotFoundError\n}\n```", }, "protocols/kafka": { title: "Kafka", @@ -144,7 +144,7 @@ export const docs: Record = { }, "usage": { title: "CLI Usage", - content: "# CLI Usage\n\n\n```sh-session\n$ npm install -g @the-codegen-project/cli\n$ codegen COMMAND\nrunning command...\n$ codegen (--version)\n@the-codegen-project/cli/0.79.0 linux-x64 node-v22.23.1\n$ codegen --help [COMMAND]\nUSAGE\n $ codegen COMMAND\n...\n```\n\n\n## Table of contents\n\n\n* [CLI Usage](#cli-usage)\n\n\n## Commands\n\n\n* [`codegen autocomplete [SHELL]`](#codegen-autocomplete-shell)\n* [`codegen base`](#codegen-base)\n* [`codegen generate [FILE]`](#codegen-generate-file)\n* [`codegen help [COMMAND]`](#codegen-help-command)\n* [`codegen init`](#codegen-init)\n* [`codegen telemetry ACTION`](#codegen-telemetry-action)\n* [`codegen version`](#codegen-version)\n\n## `codegen autocomplete [SHELL]`\n\nDisplay autocomplete installation instructions.\n\n```\nUSAGE\n $ codegen autocomplete [SHELL] [-r]\n\nARGUMENTS\n SHELL (zsh|bash|powershell) Shell type\n\nFLAGS\n -r, --refresh-cache Refresh cache (ignores displaying instructions)\n\nDESCRIPTION\n Display autocomplete installation instructions.\n\nEXAMPLES\n $ codegen autocomplete\n\n $ codegen autocomplete bash\n\n $ codegen autocomplete zsh\n\n $ codegen autocomplete powershell\n\n $ codegen autocomplete --refresh-cache\n```\n\n_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.2.45/src/commands/autocomplete/index.ts)_\n\n## `codegen base`\n\n```\nUSAGE\n $ codegen base [--json] [--no-color] [--debug | [-q | -v | --silent] | ]\n\nFLAGS\n -q, --quiet Only show errors and warnings\n -v, --verbose Show detailed output\n --debug Show debug information\n --json Output results as JSON for scripting\n --no-color Disable colored output\n --silent Suppress all output except fatal errors\n```\n\n_See code: [src/commands/base.ts](https://github.com/the-codegen-project/cli/blob/v0.79.0/src/commands/base.ts)_\n\n## `codegen generate [FILE]`\n\nGenerate code based on your configuration, use `init` to get started, `generate` to generate code from the configuration.\n\n```\nUSAGE\n $ codegen generate [FILE] [--json] [--no-color] [--debug | [-q | -v | --silent] | ] [--help] [-w] [-p\n ]\n\nARGUMENTS\n FILE Path or URL to the configuration file, defaults to root of where the command is run\n\nFLAGS\n -p, --watchPath= Optional path to watch for changes when --watch flag is used. If not provided, watches the\n input file from configuration\n -q, --quiet Only show errors and warnings\n -v, --verbose Show detailed output\n -w, --watch Watch for file changes and regenerate code automatically\n --debug Show debug information\n --help Show CLI help.\n --json Output results as JSON for scripting\n --no-color Disable colored output\n --silent Suppress all output except fatal errors\n\nDESCRIPTION\n Generate code based on your configuration, use `init` to get started, `generate` to generate code from the\n configuration.\n```\n\n_See code: [src/commands/generate.ts](https://github.com/the-codegen-project/cli/blob/v0.79.0/src/commands/generate.ts)_\n\n## `codegen help [COMMAND]`\n\nDisplay help for codegen.\n\n```\nUSAGE\n $ codegen help [COMMAND...] [-n]\n\nARGUMENTS\n COMMAND... Command to show help for.\n\nFLAGS\n -n, --nested-commands Include all nested commands in the output.\n\nDESCRIPTION\n Display help for codegen.\n```\n\n_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.0.22/src/commands/help.ts)_\n\n## `codegen init`\n\nInitialize The Codegen Project in your project\n\n```\nUSAGE\n $ codegen init [--json] [--no-color] [--debug | | [--silent | -v | -q]] [--help] [--input-file ]\n [--config-name ] [--input-type asyncapi|openapi|jsonschema] [--output-directory ] [--config-type\n esm|json|yaml|ts] [--languages typescript] [--no-tty] [--include-payloads] [--include-headers] [--include-client]\n [--include-parameters] [--include-channels] [--include-types] [--include-models] [--gitignore-generated]\n\nFLAGS\n -q, --quiet Only show errors and warnings\n -v, --verbose Show detailed output\n --config-name= [default: codegen] The name to use for the configuration file (dont include file\n extension)\n --config-type=