diff --git a/api/src/chat/helpers/README.md b/api/src/chat/helpers/README.md index ac3de66b..883043ac 100644 --- a/api/src/chat/helpers/README.md +++ b/api/src/chat/helpers/README.md @@ -60,12 +60,12 @@ The envelope helpers introduce two key components to streamline outgoing message ```typescript // Build a simple text envelope: const env1 = EnvelopeBuilder(OutgoingMessageFormat.text) - .text('Hello') + .setText('Hello') .build(); // Append multiple quick replies: const env2 = EnvelopeBuilder(OutgoingMessageFormat.quickReplies) - .text('Are you interested?') + .setText('Are you interested?') .appendToQuickReplies({ content_type: QuickReplyType.text, title: 'Yes', @@ -91,8 +91,8 @@ const textEnvelope = envelopeFactory.buildTextEnvelope('Hello, {context.user.nam const quickRepliesEnvelope = envelopeFactory.buildQuickRepliesEnvelope( 'Do you want to proceed?', [ - { content_type: 'text', title: 'Yes', payload: 'yes' }, - { content_type: 'text', title: 'No', payload: 'no' } + { content_type: QuickReplyType.text, title: 'Yes', payload: 'yes' }, + { content_type: QuickReplyType.text, title: 'No', payload: 'no' } ] ); ``` diff --git a/api/src/chat/helpers/envelope-builder.spec.ts b/api/src/chat/helpers/envelope-builder.spec.ts index c106265a..78b03532 100644 --- a/api/src/chat/helpers/envelope-builder.spec.ts +++ b/api/src/chat/helpers/envelope-builder.spec.ts @@ -28,7 +28,7 @@ describe('EnvelopeBuilder', () => { stdOutgoingTextEnvelopeSchema, ); - builder.text('Hello'); + builder.setText('Hello'); const result = builder.build(); expect(result).toEqual({ @@ -46,9 +46,9 @@ describe('EnvelopeBuilder', () => { stdOutgoingTextEnvelopeSchema, ); - builder.text('Hello world'); + builder.setText('Hello world'); // Retrieve current value with no argument - expect(builder.text()).toBe('Hello world'); + expect(builder.getText()).toBe('Hello world'); }); it('should append items to array fields with appendToX methods', () => { @@ -58,7 +58,7 @@ describe('EnvelopeBuilder', () => { stdOutgoingQuickRepliesEnvelopeSchema, ); - builder.text('Choose an option'); + builder.setText('Choose an option'); builder.appendToQuickReplies({ content_type: QuickReplyType.text, title: 'Yes', @@ -76,8 +76,8 @@ describe('EnvelopeBuilder', () => { message: { text: 'Choose an option', quickReplies: [ - { content_type: 'text', title: 'Yes', payload: 'yes' }, - { content_type: 'text', title: 'No', payload: 'no' }, + { content_type: QuickReplyType.text, title: 'Yes', payload: 'yes' }, + { content_type: QuickReplyType.text, title: 'No', payload: 'no' }, ], }, }); @@ -97,7 +97,7 @@ describe('EnvelopeBuilder', () => { describe('getEnvelopeBuilder', () => { it('should return a builder for text format that passes validation with required field', () => { const builder = getEnvelopeBuilder(OutgoingMessageFormat.text); - builder.text('Hello from text envelope!'); + builder.setText('Hello from text envelope!'); const envelope = builder.build(); expect(envelope.format).toBe(OutgoingMessageFormat.text); @@ -106,7 +106,7 @@ describe('getEnvelopeBuilder', () => { it('should return a builder for quickReplies format that can append items', () => { const builder = getEnvelopeBuilder(OutgoingMessageFormat.quickReplies); - builder.text('Pick an option'); + builder.setText('Pick an option'); builder.appendToQuickReplies({ content_type: QuickReplyType.text, title: 'Option A', diff --git a/api/src/chat/helpers/envelope-builder.ts b/api/src/chat/helpers/envelope-builder.ts index 17cdb473..692ff018 100644 --- a/api/src/chat/helpers/envelope-builder.ts +++ b/api/src/chat/helpers/envelope-builder.ts @@ -31,8 +31,11 @@ type ArrayKeys = { }[keyof T]; export type IEnvelopeBuilder = { - [k in keyof T['message']]-?: ((arg: T['message'][k]) => IEnvelopeBuilder) & - (() => T['message'][k]); + [K in keyof T['message'] as `set${Capitalize}`]-?: ( + arg: T['message'][K], + ) => IEnvelopeBuilder; +} & { + [K in keyof T['message'] as `get${Capitalize}`]-?: () => T['message'][K]; } & { [K in ArrayKeys as `appendTo${Capitalize}`]: ( item: NonNullable extends (infer U)[] ? U : never, @@ -41,6 +44,21 @@ export type IEnvelopeBuilder = { build(): T; }; +/** + * Extracts and transforms a property name into a standardized attribute name. + * + * @param prop - The property name from which to derive the attribute name. + * @param prefix - A regular expression that matches the prefix to remove from the property. + * @returns The transformed attribute name with its first character in lowercase. + */ +function getAttributeNameFromProp(prop: string, prefix: RegExp) { + // e.g. "appendToButtons" => "Buttons" + const rawKey = prop.toString().replace(prefix, ''); + // e.g. "Buttons" -> "buttons" + const messageKey = rawKey.charAt(0).toLowerCase() + rawKey.slice(1); + return messageKey; +} + /** * Builds an envelope object (containing a `format` and a `message` property) * and returns a proxy-based builder interface with chainable setter methods. @@ -60,20 +78,20 @@ export type IEnvelopeBuilder = { * @example * // Build a simple text envelope: * const env1 = EnvelopeBuilder(OutgoingMessageFormat.text) - * .text('Hello') + * .setText('Hello') * .build(); * * @example * // Build a text envelope with quick replies: * const env2 = EnvelopeBuilder(OutgoingMessageFormat.quickReplies) - * .text('Hello') - * .quickReplies([]) + * .setText('Hello') + * .setQuickReplies([]) * .build(); * * @example * // Append multiple quickReplies items: * const env3 = EnvelopeBuilder(OutgoingMessageFormat.quickReplies) - * .text('Are you interested?') + * .setText('Are you interested?') * .appendToQuickReplies({ * content_type: QuickReplyType.text, * title: 'Yes', @@ -89,7 +107,7 @@ export type IEnvelopeBuilder = { * @example * // Build a system envelope with an outcome: * const env4 = EnvelopeBuilder(OutgoingMessageFormat.system) - * .outcome('success') + * .setOutcome('success') * .build(); */ export function EnvelopeBuilder( @@ -119,10 +137,7 @@ export function EnvelopeBuilder( } if (typeof prop === 'string' && prop.startsWith('appendTo')) { - // e.g. "appendToButtons" => "Buttons" - const rawKey = prop.replace(/^appendTo/, ''); - // e.g. "Buttons" -> "buttons" - const messageKey = rawKey.charAt(0).toLowerCase() + rawKey.slice(1); + const messageKey = getAttributeNameFromProp(prop, /^appendTo/); return (item: unknown) => { // Initialize the array if needed @@ -137,12 +152,16 @@ export function EnvelopeBuilder( return (...args: unknown[]): unknown => { // If no arguments passed return current value. if (0 === args.length) { - return built.message[prop.toString()]; + const messageKey = getAttributeNameFromProp( + prop.toString(), + /^get/, + ); + return built.message[messageKey]; } const value = args[0]; - - built.message[prop.toString()] = value; + const messageKey = getAttributeNameFromProp(prop.toString(), /^set/); + built.message[messageKey] = value; return builder; }; }, diff --git a/api/src/chat/helpers/envelope-factory.spec.ts b/api/src/chat/helpers/envelope-factory.spec.ts index b68f9765..a334104c 100644 --- a/api/src/chat/helpers/envelope-factory.spec.ts +++ b/api/src/chat/helpers/envelope-factory.spec.ts @@ -15,7 +15,7 @@ import { OutgoingMessageFormat, } from '../schemas/types/message'; import { ContentOptions } from '../schemas/types/options'; -import { StdQuickReply } from '../schemas/types/quick-reply'; +import { QuickReplyType, StdQuickReply } from '../schemas/types/quick-reply'; import { EnvelopeFactory } from './envelope-factory'; @@ -245,7 +245,7 @@ describe('EnvelopeFactory', () => { } as AttachmentPayload; const quickReplies = [ { - content_type: 'text', + content_type: QuickReplyType.text, title: 'Yes {contact.company_name}', payload: 'do_{context.user.id}', }, diff --git a/api/src/chat/helpers/envelope-factory.ts b/api/src/chat/helpers/envelope-factory.ts index 1435e585..8dab8656 100644 --- a/api/src/chat/helpers/envelope-factory.ts +++ b/api/src/chat/helpers/envelope-factory.ts @@ -130,7 +130,7 @@ export class EnvelopeFactory { buildTextEnvelope(text: string | string[]): StdOutgoingTextEnvelope { const builder = this.getBuilder(OutgoingMessageFormat.text); const processedText = this.processText(text); - return builder.text(processedText).build(); + return builder.setText(processedText).build(); } /** @@ -149,7 +149,7 @@ export class EnvelopeFactory { ): StdOutgoingQuickRepliesEnvelope { const builder = this.getBuilder(OutgoingMessageFormat.quickReplies); const processedText = this.processText(text); - const envelope = builder.text(processedText); + const envelope = builder.setText(processedText); quickReplies.forEach((qr) => { envelope.appendToQuickReplies({ @@ -179,7 +179,7 @@ export class EnvelopeFactory { ): StdOutgoingButtonsEnvelope { const builder = this.getBuilder(OutgoingMessageFormat.buttons); const processedText = this.processText(text); - const envelope = builder.text(processedText); + const envelope = builder.setText(processedText); buttons.forEach((btn) => { if (btn.type === ButtonType.postback) { @@ -214,7 +214,7 @@ export class EnvelopeFactory { quickReplies: StdQuickReply[] = [], ): StdOutgoingAttachmentEnvelope { const builder = this.getBuilder(OutgoingMessageFormat.attachment); - const envelope = builder.attachment(attachment); + const envelope = builder.setAttachment(attachment); quickReplies.forEach((qr) => { envelope.appendToQuickReplies({ @@ -247,9 +247,9 @@ export class EnvelopeFactory { ): StdOutgoingListEnvelope { const builder = this.getBuilder(format); return builder - .options(options) - .elements(elements) - .pagination(pagination) + .setOptions(options) + .setElements(elements) + .setPagination(pagination) .build(); } @@ -268,6 +268,6 @@ export class EnvelopeFactory { data?: unknown, ): StdOutgoingSystemEnvelope { const builder = this.getBuilder(OutgoingMessageFormat.system); - return builder.outcome(outcome).data(data).build(); + return builder.setOutcome(outcome).setData(data).build(); } }