better-zap 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,850 @@
1
+ //#region src/types/whatsapp.types.d.ts
2
+ /**
3
+ * Meta WhatsApp Cloud API v25.0 Type Definitions
4
+ * https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages
5
+ */
6
+ interface WhatsAppTextMessage {
7
+ messaging_product: "whatsapp";
8
+ recipient_type: "individual";
9
+ to: string;
10
+ type: "text";
11
+ text: {
12
+ preview_url?: boolean;
13
+ body: string;
14
+ };
15
+ }
16
+ interface WhatsAppTemplateMessage {
17
+ messaging_product: "whatsapp";
18
+ to: string;
19
+ type: "template";
20
+ template: {
21
+ name: string;
22
+ language: {
23
+ code: string;
24
+ };
25
+ components?: TemplateComponent[];
26
+ };
27
+ }
28
+ interface WhatsAppInteractiveButtonsMessage {
29
+ messaging_product: "whatsapp";
30
+ recipient_type: "individual";
31
+ to: string;
32
+ type: "interactive";
33
+ interactive: {
34
+ type: "button";
35
+ body: {
36
+ text: string;
37
+ };
38
+ action: {
39
+ buttons: Array<{
40
+ type: "reply";
41
+ reply: {
42
+ id: string;
43
+ title: string;
44
+ };
45
+ }>;
46
+ };
47
+ };
48
+ }
49
+ interface WhatsAppInteractiveListMessage {
50
+ messaging_product: "whatsapp";
51
+ recipient_type: "individual";
52
+ to: string;
53
+ type: "interactive";
54
+ interactive: {
55
+ type: "list";
56
+ body: {
57
+ text: string;
58
+ };
59
+ action: {
60
+ button: string;
61
+ sections: Array<{
62
+ title: string;
63
+ rows: Array<{
64
+ id: string;
65
+ title: string;
66
+ description?: string;
67
+ }>;
68
+ }>;
69
+ };
70
+ };
71
+ }
72
+ interface WhatsAppInteractiveMediaCarouselMessage {
73
+ messaging_product: "whatsapp";
74
+ recipient_type: "individual";
75
+ to: string;
76
+ type: "interactive";
77
+ interactive: {
78
+ type: "carousel";
79
+ body: {
80
+ text: string;
81
+ };
82
+ action: {
83
+ cards: WhatsAppCarouselCard[];
84
+ };
85
+ };
86
+ }
87
+ interface InteractiveMediaCarouselCardInput {
88
+ header: {
89
+ type: "image" | "video";
90
+ link: string;
91
+ };
92
+ bodyText?: string;
93
+ button: {
94
+ displayText: string;
95
+ url: string;
96
+ };
97
+ }
98
+ interface SendInteractiveMediaCarouselData {
99
+ to: string;
100
+ body: string;
101
+ cards: InteractiveMediaCarouselCardInput[];
102
+ }
103
+ interface WhatsAppCarouselCard {
104
+ card_index: number;
105
+ type: "cta_url";
106
+ header: {
107
+ type: "image" | "video";
108
+ image?: {
109
+ link: string;
110
+ };
111
+ video?: {
112
+ link: string;
113
+ };
114
+ };
115
+ body?: {
116
+ text: string;
117
+ };
118
+ action: {
119
+ name: "cta_url";
120
+ parameters: {
121
+ display_text: string;
122
+ url: string;
123
+ };
124
+ };
125
+ }
126
+ interface WhatsAppLocationMessage {
127
+ messaging_product: "whatsapp";
128
+ recipient_type: "individual";
129
+ to: string;
130
+ type: "location";
131
+ location: {
132
+ latitude: number;
133
+ longitude: number;
134
+ name: string;
135
+ address: string;
136
+ };
137
+ }
138
+ interface TemplateComponent {
139
+ type: "header" | "body" | "button";
140
+ sub_type?: "url" | "quick_reply";
141
+ index?: string;
142
+ parameters: TemplateParameter[];
143
+ }
144
+ interface TemplateParameter {
145
+ type: "text" | "currency" | "date_time" | "image" | "document" | "video" | "location" | "payload";
146
+ parameter_name?: string;
147
+ text?: string;
148
+ currency?: {
149
+ fallback_value: string;
150
+ code: string;
151
+ amount_1000: number;
152
+ };
153
+ date_time?: {
154
+ fallback_value: string;
155
+ };
156
+ image?: {
157
+ link: string;
158
+ };
159
+ video?: {
160
+ link: string;
161
+ };
162
+ document?: {
163
+ link: string;
164
+ };
165
+ location?: {
166
+ latitude: number;
167
+ longitude: number;
168
+ name: string;
169
+ address: string;
170
+ };
171
+ payload?: string;
172
+ }
173
+ interface SendMessageResponse {
174
+ messaging_product: "whatsapp";
175
+ contacts: Array<{
176
+ input: string;
177
+ wa_id: string;
178
+ }>;
179
+ messages: Array<{
180
+ id: string;
181
+ }>;
182
+ }
183
+ interface SendMessageError {
184
+ error: {
185
+ message: string;
186
+ type: string;
187
+ code: number;
188
+ error_subcode?: number;
189
+ error_data?: {
190
+ messaging_product: string;
191
+ details: string;
192
+ };
193
+ fbtrace_id: string;
194
+ };
195
+ }
196
+ interface WebhookPayload {
197
+ object: "whatsapp_business_account";
198
+ entry: WebhookEntry[];
199
+ }
200
+ interface WebhookEntry {
201
+ id: string;
202
+ changes: WebhookChange[];
203
+ }
204
+ interface WebhookChange {
205
+ value: WebhookValue;
206
+ field: WhatsAppWebhookField;
207
+ }
208
+ type WhatsAppWebhookField = "messages" | "message_template_status_update" | "message_template_quality_update" | "phone_number_name_update" | "phone_number_quality_update" | "account_update" | "security" | "history" | "smb_app_state_sync" | "smb_message_echoes" | "account_offboarded" | "account_reconnected" | (string & {});
209
+ interface WebhookValue {
210
+ messaging_product: "whatsapp";
211
+ metadata: {
212
+ display_phone_number: string;
213
+ phone_number_id: string;
214
+ };
215
+ contacts?: WebhookContact[];
216
+ messages?: IncomingMessage[];
217
+ statuses?: MessageStatus[];
218
+ errors?: WebhookError[];
219
+ }
220
+ interface WebhookContact {
221
+ profile: {
222
+ name: string;
223
+ };
224
+ wa_id: string;
225
+ }
226
+ interface IncomingMessage {
227
+ from: string;
228
+ id: string;
229
+ timestamp: string;
230
+ type: "text" | "image" | "audio" | "video" | "document" | "location" | "contacts" | "interactive" | "button" | "reaction" | "sticker" | "unsupported" | (string & {});
231
+ text?: {
232
+ body: string;
233
+ };
234
+ image?: MediaMessage;
235
+ audio?: MediaMessage;
236
+ video?: MediaMessage;
237
+ document?: MediaMessage & {
238
+ filename?: string;
239
+ };
240
+ location?: {
241
+ latitude: number;
242
+ longitude: number;
243
+ name?: string;
244
+ address?: string;
245
+ };
246
+ button?: {
247
+ text: string;
248
+ payload: string;
249
+ };
250
+ interactive?: {
251
+ type: "button_reply" | "list_reply";
252
+ button_reply?: {
253
+ id: string;
254
+ title: string;
255
+ };
256
+ list_reply?: {
257
+ id: string;
258
+ title: string;
259
+ description?: string;
260
+ };
261
+ };
262
+ context?: {
263
+ from: string;
264
+ id: string;
265
+ };
266
+ referral?: {
267
+ source_url: string;
268
+ source_type: "ad" | "post";
269
+ source_id: string;
270
+ headline: string;
271
+ body: string;
272
+ ctwa_clid?: string;
273
+ };
274
+ errors?: MessageError[];
275
+ edited?: boolean;
276
+ revoked?: boolean;
277
+ unsupported?: true;
278
+ [key: string]: unknown;
279
+ }
280
+ interface MediaMessage {
281
+ id: string;
282
+ mime_type: string;
283
+ sha256?: string;
284
+ caption?: string;
285
+ }
286
+ interface MessageStatus {
287
+ id: string;
288
+ status: "sent" | "delivered" | "read" | "failed";
289
+ timestamp: string;
290
+ recipient_id: string;
291
+ conversation?: {
292
+ id: string;
293
+ origin: {
294
+ type: "business_initiated" | "user_initiated" | "referral_conversion";
295
+ };
296
+ expiration_timestamp?: string;
297
+ };
298
+ pricing?: {
299
+ billable: boolean;
300
+ pricing_model: "CBP";
301
+ category: "business_initiated" | "user_initiated" | "referral_conversion";
302
+ };
303
+ errors?: MessageError[];
304
+ }
305
+ interface MessageError {
306
+ code: number;
307
+ title: string;
308
+ message: string;
309
+ error_data?: {
310
+ details: string;
311
+ };
312
+ }
313
+ interface WebhookError {
314
+ code: number;
315
+ title: string;
316
+ message: string;
317
+ error_data: {
318
+ details: string;
319
+ };
320
+ }
321
+ interface SendResult {
322
+ success: boolean;
323
+ messageId?: string;
324
+ error?: string;
325
+ errorCode?: number;
326
+ code?: string;
327
+ httpStatus?: number;
328
+ details?: Record<string, any>;
329
+ }
330
+ type FreeformMessageWindow = {
331
+ isOpen: boolean;
332
+ lastIncomingMessageAt: string | null;
333
+ expiresAt: string | null;
334
+ };
335
+ type ConversationRecord = {
336
+ id: string;
337
+ phone: string;
338
+ contactName: string | null;
339
+ unreadCount: number;
340
+ status: string;
341
+ lastMessageAt: string;
342
+ lastMessagePreview: string | null;
343
+ lastDirection: string;
344
+ messageCount: number;
345
+ lastIncomingMessageAt: string | null;
346
+ };
347
+ type Conversation = ConversationRecord & {
348
+ freeformMessageWindow: FreeformMessageWindow;
349
+ };
350
+ type UIMessageStatus = "sent" | "delivered" | "read" | "failed";
351
+ type UIMessage = {
352
+ id: string;
353
+ phone?: string;
354
+ content: string | null;
355
+ direction: "incoming" | "outgoing";
356
+ status: UIMessageStatus | string;
357
+ sentAt: string;
358
+ templateName?: string | null;
359
+ messageType?: string | null;
360
+ metadata?: Record<string, any> | null;
361
+ };
362
+ //#endregion
363
+ //#region src/types/config.d.ts
364
+ /** Configuration for {@link WhatsAppService}. */
365
+ interface WhatsAppConfig {
366
+ /** System User access token for the WhatsApp Cloud API. */
367
+ token: string;
368
+ /** Phone number ID registered in Meta Business Manager. */
369
+ phoneId: string;
370
+ /** Set to `"development"` to skip real API calls and return mock responses. */
371
+ environment?: string;
372
+ }
373
+ //#endregion
374
+ //#region src/types/sync-events.d.ts
375
+ type ConversationSummary = Conversation;
376
+ type NewMessageEvent = {
377
+ type: "NEW_MESSAGE";
378
+ message: WhatsAppLogRecord;
379
+ conversation: ConversationSummary;
380
+ };
381
+ type StatusUpdateEvent = {
382
+ type: "STATUS_UPDATE";
383
+ waMessageId: string;
384
+ status: WhatsAppStatus;
385
+ timestamp: string;
386
+ deliveredAt?: string | null;
387
+ readAt?: string | null;
388
+ };
389
+ type ConversationUpdateEvent = {
390
+ type: "CONVERSATION_UPDATE";
391
+ conversationId: string;
392
+ updates: Partial<ConversationSummary>;
393
+ };
394
+ type SyncEvent = NewMessageEvent | StatusUpdateEvent | ConversationUpdateEvent;
395
+ //#endregion
396
+ //#region src/logger.d.ts
397
+ /**
398
+ * Centralized Logger for Better Zap
399
+ *
400
+ * Lightweight, injectable logger with structured JSON output.
401
+ * Cloudflare Workers compatible (no external dependencies).
402
+ */
403
+ type LogLevel = "debug" | "info" | "warn" | "error";
404
+ /** User-facing configuration (on betterZap config). */
405
+ interface LoggerConfig {
406
+ disabled?: boolean;
407
+ level?: LogLevel;
408
+ log?: (level: LogLevel, message: string, context: Record<string, unknown>) => void;
409
+ }
410
+ /** Internal logger interface injected into services. */
411
+ interface Logger {
412
+ debug(message: string, context?: Record<string, unknown>): void;
413
+ info(message: string, context?: Record<string, unknown>): void;
414
+ warn(message: string, context?: Record<string, unknown>): void;
415
+ error(message: string, context?: Record<string, unknown>): void;
416
+ }
417
+ declare function createLogger(config?: LoggerConfig): Logger;
418
+ declare const noopLogger: Logger;
419
+ declare function serializeError(err: unknown): Record<string, unknown>;
420
+ //#endregion
421
+ //#region src/services/message-logger.service.d.ts
422
+ type WhatsAppDirection = "incoming" | "outgoing";
423
+ type WhatsAppStatus = "sent" | "delivered" | "read" | "failed";
424
+ declare const WHATSAPP_MESSAGE_TYPES: readonly ["queue_position", "next_in_line", "queue_optin", "marketing", "bot_reply", "reminder", "satisfaction", "incoming"];
425
+ type WhatsAppMessageType = (typeof WHATSAPP_MESSAGE_TYPES)[number];
426
+ interface WhatsAppLogRecord {
427
+ id: string;
428
+ conversationId: string;
429
+ userId?: string | null;
430
+ phone: string;
431
+ waMessageId?: string | null;
432
+ direction: WhatsAppDirection;
433
+ messageType: WhatsAppMessageType;
434
+ content: string | null;
435
+ templateName?: string | null;
436
+ status: WhatsAppStatus;
437
+ errorMessage?: string | null;
438
+ metadata?: any;
439
+ sentAt: string;
440
+ deliveredAt?: string | null;
441
+ readAt?: string | null;
442
+ }
443
+ type CreateWhatsAppLogParams = {
444
+ phone: string;
445
+ userId?: string;
446
+ contactName?: string;
447
+ direction: WhatsAppDirection;
448
+ messageType: WhatsAppMessageType;
449
+ content: string;
450
+ templateName?: string;
451
+ waMessageId?: string;
452
+ status: WhatsAppStatus;
453
+ errorMessage?: string;
454
+ metadata?: any;
455
+ sentAt: string;
456
+ };
457
+ /**
458
+ * Interface for database persistence of WhatsApp logs.
459
+ * Decouples Better Zap from any specific application database package.
460
+ */
461
+ interface WhatsAppLogStore {
462
+ createWhatsAppLog(params: CreateWhatsAppLogParams): Promise<{
463
+ record: WhatsAppLogRecord;
464
+ created: boolean;
465
+ }>;
466
+ getMessageByWaId(waMessageId: string): Promise<WhatsAppLogRecord | null>;
467
+ updateWhatsAppLogByWaMessageId(waMessageId: string, updates: Partial<WhatsAppLogRecord>): Promise<void>;
468
+ /**
469
+ * Atomically update status only if the new status advances the lifecycle.
470
+ * Returns true if the row was updated, false if skipped (duplicate/regression).
471
+ *
472
+ * Progression: sent(1) → delivered(2) → read(3). failed(4) always wins.
473
+ */
474
+ updateStatusIfProgressed(waMessageId: string, newStatus: WhatsAppStatus, updates: Partial<WhatsAppLogRecord>): Promise<boolean>;
475
+ getConversationById(conversationId: string): Promise<ConversationRecord | null>;
476
+ getConversationByPhone(phone: string): Promise<ConversationRecord | null>;
477
+ getConversations(): Promise<ConversationRecord[]>;
478
+ getMessagesByConversationPaginated(conversationId: string, cursor?: string | null, limit?: number): Promise<Array<{
479
+ id: string;
480
+ phone?: string;
481
+ content: string | null;
482
+ direction: string;
483
+ status: string;
484
+ sentAt: string;
485
+ templateName?: string | null;
486
+ messageType?: string | null;
487
+ metadata?: any;
488
+ }>>;
489
+ /**
490
+ * Check if there's a recent outgoing message to this phone within N hours.
491
+ * Used for consumer-defined cooldown checks.
492
+ */
493
+ hasRecentOutgoingMessage(phone: string, withinHours: number): Promise<boolean>;
494
+ }
495
+ interface MessageLoggerNotifier {
496
+ notify(event: SyncEvent): Promise<void>;
497
+ }
498
+ declare class MessageLoggerService {
499
+ private store;
500
+ private notifier?;
501
+ private log;
502
+ constructor(store: WhatsAppLogStore, log: Logger, notifier?: MessageLoggerNotifier | undefined);
503
+ private notify;
504
+ getConversationById(conversationId: string): Promise<Conversation | null>;
505
+ getConversationByPhone(phone: string): Promise<Conversation | null>;
506
+ getConversations(): Promise<Conversation[]>;
507
+ /** @deprecated Prefer `getFreeformMessageWindow()`. */
508
+ getCustomerCareWindow(phone: string): Promise<FreeformMessageWindow>;
509
+ getFreeformMessageWindow(phone: string): Promise<FreeformMessageWindow>;
510
+ /**
511
+ * Check if a message with this waMessageId was already processed.
512
+ */
513
+ isDuplicate(waMessageId: string): Promise<boolean>;
514
+ /**
515
+ * Log outgoing message for LGPD compliance
516
+ */
517
+ logOutgoing(params: {
518
+ phone: string;
519
+ userId?: string;
520
+ messageType: WhatsAppMessageType;
521
+ content: string;
522
+ result: SendResult;
523
+ templateName?: string;
524
+ metadata?: Record<string, any>;
525
+ }): Promise<string>;
526
+ /**
527
+ * Update message status from webhook callback.
528
+ * Only applies if the new status advances the lifecycle (atomic, no race conditions).
529
+ * Returns true if the update was applied, false if skipped.
530
+ */
531
+ updateStatus(waMessageId: string, status: WhatsAppStatus, timestamp: string, errorMessage?: string): Promise<boolean>;
532
+ /**
533
+ * Check if there's a recent outgoing message to this phone within N hours.
534
+ *
535
+ * @param {string} phone : The phone number to check (in E.164 format)
536
+ * @param {number} [withinHours=24] : Time in hours to look for incoming messages
537
+ */
538
+ hasRecentOutgoingMessage(phone: string, withinHours?: number): Promise<boolean>;
539
+ /**
540
+ * Log incoming message (for audit trail)
541
+ */
542
+ logIncoming(params: {
543
+ phone: string;
544
+ waMessageId: string;
545
+ content: string;
546
+ sentAt: string;
547
+ senderName?: string;
548
+ metadata?: Record<string, unknown>;
549
+ }): Promise<boolean>;
550
+ /**
551
+ * Log an imported WhatsApp message with an explicit direction and timestamp.
552
+ * Used by coexistence history imports and app echo webhooks where the message
553
+ * did not originate from the local send API call.
554
+ */
555
+ logImportedMessage(params: {
556
+ phone: string;
557
+ waMessageId: string;
558
+ direction: WhatsAppDirection;
559
+ content: string;
560
+ sentAt: string;
561
+ senderName?: string;
562
+ messageType?: WhatsAppMessageType;
563
+ metadata?: Record<string, unknown>;
564
+ }): Promise<boolean>;
565
+ }
566
+ //#endregion
567
+ //#region src/services/whatsapp.service.d.ts
568
+ interface OutgoingLoggingMetadata {
569
+ userId?: string;
570
+ messageType: WhatsAppMessageType;
571
+ /** Human-readable content of the message to be stored in the log */
572
+ content: string;
573
+ /** Optional metadata for the log entry (e.g. campaign ID, additional context) */
574
+ metadata?: Record<string, any>;
575
+ }
576
+ declare class WhatsAppService {
577
+ private baseUrl;
578
+ private token;
579
+ private isDev;
580
+ private logger;
581
+ private log;
582
+ constructor(config: WhatsAppConfig, logger: MessageLoggerService, log: Logger);
583
+ /** Send a text message within the 24h free-form message window only. */
584
+ sendText(to: string, body: string, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
585
+ /** Send a template message (works outside service window). */
586
+ sendTemplate(to: string, templateName: string, languageCode?: string, components?: TemplateComponent[], logging?: OutgoingLoggingMetadata): Promise<SendResult>;
587
+ /** Send an interactive message with reply buttons (up to 3). */
588
+ sendInteractiveButtons(to: string, bodyText: string, buttons: Array<{
589
+ id: string;
590
+ title: string;
591
+ }>, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
592
+ /** Send an interactive list message with sections and rows. */
593
+ sendInteractiveList(to: string, bodyText: string, buttonLabel: string, sections: Array<{
594
+ title: string;
595
+ rows: Array<{
596
+ id: string;
597
+ title: string;
598
+ description?: string;
599
+ }>;
600
+ }>, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
601
+ /** Send an interactive media carousel message (2-10 cards). */
602
+ sendInteractiveMediaCarousel(data: SendInteractiveMediaCarouselData, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
603
+ /** Send a location pin message. */
604
+ sendLocation(to: string, latitude: number, longitude: number, name: string, address: string, logging?: Omit<OutgoingLoggingMetadata, "content">): Promise<SendResult>;
605
+ /**
606
+ * Mark an inbound message as read.
607
+ *
608
+ * @see https://developers.facebook.com/docs/whatsapp/cloud-api/messages/mark-messages-as-read
609
+ */
610
+ markAsRead(messageId: string): Promise<SendResult>;
611
+ /**
612
+ * Show or hide a typing indicator in the chat.
613
+ * When starting, the indicator auto-dismisses after 25 seconds or when a message is sent.
614
+ *
615
+ * @see https://developers.facebook.com/docs/whatsapp/cloud-api/typing-indicators/
616
+ */
617
+ typingIndicator(messageId: string, action?: "typing_on" | "typing_off"): Promise<SendResult>;
618
+ /**
619
+ * Add a reaction to a message.
620
+ *
621
+ * @see https://developers.facebook.com/docs/whatsapp/cloud-api/messages/reaction-messages
622
+ */
623
+ sendReaction(to: string, messageId: string, emoji: string): Promise<SendResult>;
624
+ /** Core send method with retry logic (2 retries, exponential backoff). */
625
+ private send;
626
+ private logSendResult;
627
+ /** Actually performs the network request with retries. */
628
+ private performRequest;
629
+ }
630
+ //#endregion
631
+ //#region src/template-registry.d.ts
632
+ type Simplify<TValue> = { [TKey in keyof TValue]: TValue[TKey] } & {};
633
+ type TemplateParameterInputMap = {
634
+ text: string;
635
+ payload: string;
636
+ location: {
637
+ latitude: number;
638
+ longitude: number;
639
+ name: string;
640
+ address: string;
641
+ };
642
+ image: {
643
+ link: string;
644
+ };
645
+ video: {
646
+ link: string;
647
+ };
648
+ document: {
649
+ link: string;
650
+ };
651
+ currency: {
652
+ fallback_value: string;
653
+ code: string;
654
+ amount_1000: number;
655
+ };
656
+ date_time: {
657
+ fallback_value: string;
658
+ };
659
+ };
660
+ type SupportedTemplateParameterType = keyof TemplateParameterInputMap;
661
+ interface TemplateParameterDefinition<TType extends SupportedTemplateParameterType = SupportedTemplateParameterType> {
662
+ name: string;
663
+ parameterName?: string;
664
+ type: TType;
665
+ }
666
+ type TemplateComponentDefinition = {
667
+ type: "header";
668
+ parameters: readonly TemplateParameterDefinition[];
669
+ } | {
670
+ type: "body";
671
+ parameters: readonly TemplateParameterDefinition[];
672
+ } | {
673
+ type: "button";
674
+ subType: "url" | "quick_reply";
675
+ index: string;
676
+ parameters: readonly TemplateParameterDefinition[];
677
+ };
678
+ interface TemplateDefinition {
679
+ language: string;
680
+ components?: readonly TemplateComponentDefinition[];
681
+ }
682
+ type TemplateRegistry = Record<string, TemplateDefinition>;
683
+ type TemplateParameterDefinitionUnion<TTemplate extends TemplateDefinition> = TTemplate["components"] extends readonly TemplateComponentDefinition[] ? TTemplate["components"][number]["parameters"][number] : never;
684
+ type TemplateParameterInput<TParameter extends TemplateParameterDefinition> = TParameter["type"] extends keyof TemplateParameterInputMap ? TemplateParameterInputMap[TParameter["type"]] : never;
685
+ type TemplateParams<TTemplate extends TemplateDefinition> = Simplify<{ [TParameter in TemplateParameterDefinitionUnion<TTemplate> as TParameter["name"]]: TemplateParameterInput<TParameter> }>;
686
+ type TemplateName<TTemplates extends TemplateRegistry> = Extract<keyof TTemplates, string>;
687
+ type TypedTemplateOptions<TTemplates extends TemplateRegistry, TName extends TemplateName<TTemplates>> = {
688
+ language?: string;
689
+ params: TemplateParams<TTemplates[TName]>;
690
+ };
691
+ declare const EMPTY_TEMPLATE_REGISTRY: {};
692
+ declare function defineTemplates<const TTemplates extends TemplateRegistry>(templates: TTemplates): TTemplates;
693
+ declare function hasConfiguredTemplates<TTemplates extends TemplateRegistry>(templates: TTemplates): boolean;
694
+ declare function getTemplateNames<TTemplates extends TemplateRegistry>(templates: TTemplates): TemplateName<TTemplates>[];
695
+ declare function serializeTemplateFromRegistry(templates: TemplateRegistry, templateName: string, options: {
696
+ language?: string;
697
+ params: Record<string, unknown>;
698
+ }): {
699
+ language: string;
700
+ components?: TemplateComponent[];
701
+ };
702
+ declare function serializeTemplateFromRegistry<TTemplates extends TemplateRegistry, TName extends TemplateName<TTemplates>>(templates: TTemplates, templateName: TName, options: TypedTemplateOptions<TTemplates, TName>): {
703
+ language: string;
704
+ components?: TemplateComponent[];
705
+ };
706
+ //#endregion
707
+ //#region src/client.d.ts
708
+ interface ZapClientOptions<TTemplates extends TemplateRegistry = {}> {
709
+ /**
710
+ * Base URL for the API. Defaults to `window.location.origin` in the browser.
711
+ * Useful for SSR or custom proxy setups.
712
+ */
713
+ baseURL?: string;
714
+ /**
715
+ * Base path for all routes. Must match the `basePath` used in `betterZap()`.
716
+ * @default "/api/whatsapp"
717
+ */
718
+ basePath?: string;
719
+ /**
720
+ * Custom fetch implementation. Defaults to the global `fetch`.
721
+ */
722
+ fetch?: typeof fetch;
723
+ /**
724
+ * How requests authenticate to the Better Zap HTTP routes. Defaults to
725
+ * sending requests as-is (ambient same-origin cookies). See {@link ZapTransport}
726
+ * and the {@link sessionTransport} / {@link apiKeyTransport} built-ins.
727
+ */
728
+ transport?: ZapTransport;
729
+ /**
730
+ * Optional template registry used only for type inference.
731
+ * The HTTP server still owns runtime serialization and validation.
732
+ */
733
+ templates?: TTemplates;
734
+ }
735
+ /**
736
+ * Controls how the client authenticates its requests to the Better Zap HTTP
737
+ * routes. This is the seam that lets a browser client talk to the API without
738
+ * embedding an API key: the app proxies Better Zap behind its own
739
+ * session-authenticated routes and the client just forwards the session.
740
+ *
741
+ * Two built-ins cover the common cases:
742
+ * - {@link sessionTransport} — session/proxy auth: sends same-origin
743
+ * credentials (cookies) and no API key. Use when a trusted server owns the
744
+ * session and proxies Better Zap (e.g. a dashboard's own authed routes).
745
+ * - {@link apiKeyTransport} — sends `Authorization: Bearer <key>`. Use for
746
+ * server-to-server calls in a trusted environment.
747
+ *
748
+ * Implement the interface directly for anything else (e.g. a rotating token).
749
+ */
750
+ interface ZapTransport {
751
+ /** Extra headers merged onto every request. Called per request, may be async. */
752
+ headers?(): Record<string, string> | Promise<Record<string, string>>;
753
+ /** Fetch credentials mode; `sessionTransport` sets `"include"`. */
754
+ credentials?: RequestCredentials;
755
+ }
756
+ /**
757
+ * Session/proxy transport: send same-origin credentials (cookies), no API key.
758
+ * The recommended browser default when the app proxies Better Zap behind its
759
+ * own authenticated routes.
760
+ */
761
+ declare function sessionTransport(): ZapTransport;
762
+ /** API-key transport: send `Authorization: Bearer <apiKey>` on every request. */
763
+ declare function apiKeyTransport(apiKey: string): ZapTransport;
764
+ declare class BetterZapClientError extends Error {
765
+ status: number;
766
+ code?: string;
767
+ details?: unknown;
768
+ body?: unknown;
769
+ constructor(message: string, options: {
770
+ status: number;
771
+ code?: string;
772
+ details?: unknown;
773
+ body?: unknown;
774
+ });
775
+ }
776
+ interface SendTextParams {
777
+ to: string;
778
+ body: string;
779
+ messageType?: string;
780
+ userId?: string;
781
+ metadata?: Record<string, unknown>;
782
+ }
783
+ interface SendTemplateRawParams {
784
+ to: string;
785
+ template: string;
786
+ language?: string;
787
+ components?: unknown[];
788
+ logging?: OutgoingLoggingMetadata;
789
+ }
790
+ type SendTemplateTypedParams<TTemplates extends TemplateRegistry, TName extends TemplateName<TTemplates>> = {
791
+ to: string;
792
+ template: TName;
793
+ language?: string;
794
+ params: TemplateParams<TTemplates[TName]>;
795
+ logging?: OutgoingLoggingMetadata;
796
+ };
797
+ type SendTemplateMethod<TTemplates extends TemplateRegistry> = [TemplateName<TTemplates>] extends [never] ? (params: SendTemplateRawParams) => Promise<SendResult> : <TName extends TemplateName<TTemplates>>(params: SendTemplateTypedParams<TTemplates, TName>) => Promise<SendResult>;
798
+ interface SendInteractiveParams {
799
+ to: string;
800
+ type: "button" | "list" | "carousel";
801
+ body: string;
802
+ buttons?: Array<{
803
+ id: string;
804
+ title: string;
805
+ }>;
806
+ buttonLabel?: string;
807
+ sections?: Array<{
808
+ title: string;
809
+ rows: Array<{
810
+ id: string;
811
+ title: string;
812
+ description?: string;
813
+ }>;
814
+ }>;
815
+ cards?: SendInteractiveMediaCarouselData["cards"];
816
+ messageType?: string;
817
+ userId?: string;
818
+ metadata?: Record<string, unknown>;
819
+ }
820
+ interface SendLocationParams {
821
+ to: string;
822
+ latitude: number;
823
+ longitude: number;
824
+ name: string;
825
+ address: string;
826
+ messageType?: string;
827
+ userId?: string;
828
+ metadata?: Record<string, unknown>;
829
+ }
830
+ interface GetMessagesOptions {
831
+ cursor?: string;
832
+ limit?: number;
833
+ }
834
+ interface ZapClient<TTemplates extends TemplateRegistry = {}> {
835
+ send: {
836
+ text(params: SendTextParams): Promise<SendResult>;
837
+ template: SendTemplateMethod<TTemplates>;
838
+ templateRaw(params: SendTemplateRawParams): Promise<SendResult>;
839
+ interactive(params: SendInteractiveParams): Promise<SendResult>;
840
+ location(params: SendLocationParams): Promise<SendResult>;
841
+ };
842
+ conversations: {
843
+ list(): Promise<Conversation[]>;
844
+ get(phone: string): Promise<Conversation | null>;
845
+ messages(phone: string, opts?: GetMessagesOptions): Promise<UIMessage[]>;
846
+ };
847
+ }
848
+ declare function createZapClient<TTemplates extends TemplateRegistry = {}>(options?: ZapClientOptions<TTemplates>): ZapClient<TTemplates>;
849
+ //#endregion
850
+ export { SendResult as $, LogLevel as A, SyncEvent as B, MessageLoggerService as C, WhatsAppLogStore as D, WhatsAppLogRecord as E, serializeError as F, IncomingMessage as G, Conversation as H, ConversationSummary as I, MessageError as J, InteractiveMediaCarouselCardInput as K, ConversationUpdateEvent as L, LoggerConfig as M, createLogger as N, WhatsAppMessageType as O, noopLogger as P, SendMessageResponse as Q, NewMessageEvent as R, MessageLoggerNotifier as S, WhatsAppDirection as T, ConversationRecord as U, WhatsAppConfig as V, FreeformMessageWindow as W, SendInteractiveMediaCarouselData as X, MessageStatus as Y, SendMessageError as Z, getTemplateNames as _, WhatsAppWebhookField as _t, createZapClient as a, WebhookContact as at, OutgoingLoggingMetadata as b, SupportedTemplateParameterType as c, WebhookPayload as ct, TemplateName as d, WhatsAppInteractiveButtonsMessage as dt, TemplateComponent as et, TemplateParameterDefinition as f, WhatsAppInteractiveListMessage as ft, defineTemplates as g, WhatsAppTextMessage as gt, TemplateRegistry as h, WhatsAppTemplateMessage as ht, apiKeyTransport as i, WebhookChange as it, Logger as j, WhatsAppStatus as k, TemplateComponentDefinition as l, WebhookValue as lt, TemplateParams as m, WhatsAppLocationMessage as mt, ZapClient as n, UIMessage as nt, sessionTransport as o, WebhookEntry as ot, TemplateParameterInputMap as p, WhatsAppInteractiveMediaCarouselMessage as pt, MediaMessage as q, ZapTransport as r, UIMessageStatus as rt, EMPTY_TEMPLATE_REGISTRY as s, WebhookError as st, BetterZapClientError as t, TemplateParameter as tt, TemplateDefinition as u, WhatsAppCarouselCard as ut, hasConfiguredTemplates as v, WHATSAPP_MESSAGE_TYPES as w, WhatsAppService as x, serializeTemplateFromRegistry as y, StatusUpdateEvent as z };