feat: define getters and setters

This commit is contained in:
Mohamed Marrouchi 2025-03-24 12:11:54 +01:00
parent 8bef5e521b
commit 172a167296
5 changed files with 55 additions and 36 deletions

View File

@ -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' }
]
);
```

View File

@ -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',

View File

@ -31,8 +31,11 @@ type ArrayKeys<T> = {
}[keyof T];
export type IEnvelopeBuilder<T extends StdOutgoingEnvelope> = {
[k in keyof T['message']]-?: ((arg: T['message'][k]) => IEnvelopeBuilder<T>) &
(() => T['message'][k]);
[K in keyof T['message'] as `set${Capitalize<string & K>}`]-?: (
arg: T['message'][K],
) => IEnvelopeBuilder<T>;
} & {
[K in keyof T['message'] as `get${Capitalize<string & K>}`]-?: () => T['message'][K];
} & {
[K in ArrayKeys<T['message']> as `appendTo${Capitalize<string & K>}`]: (
item: NonNullable<T['message'][K]> extends (infer U)[] ? U : never,
@ -41,6 +44,21 @@ export type IEnvelopeBuilder<T extends StdOutgoingEnvelope> = {
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<T extends StdOutgoingEnvelope> = {
* @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<T extends StdOutgoingEnvelope> = {
* @example
* // Build a system envelope with an outcome:
* const env4 = EnvelopeBuilder(OutgoingMessageFormat.system)
* .outcome('success')
* .setOutcome('success')
* .build();
*/
export function EnvelopeBuilder<T extends StdOutgoingEnvelope>(
@ -119,10 +137,7 @@ export function EnvelopeBuilder<T extends StdOutgoingEnvelope>(
}
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<T extends StdOutgoingEnvelope>(
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;
};
},

View File

@ -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}',
},

View File

@ -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();
}
}