whatsapp-cloud 0.0.3 โ 0.0.5
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.
- package/CHANGELOG.md +13 -0
- package/README.md +11 -0
- package/agent_docs/DESIGN.md +707 -0
- package/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
- package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
- package/agent_docs/NAMING_DECISION.md +78 -0
- package/agent_docs/STRUCTURE.md +711 -0
- package/agent_docs/messages-namespace-design.md +357 -0
- package/cloud-api-docs/webhooks/endpoint.md +112 -0
- package/cloud-api-docs/webhooks/overview.md +154 -0
- package/package.json +10 -3
- package/src/client/HttpClient.ts +159 -0
- package/src/client/WhatsAppClient.ts +58 -0
- package/src/client/index.ts +2 -0
- package/src/errors.ts +58 -0
- package/src/examples/main.ts +9 -0
- package/src/examples/template.ts +134 -0
- package/src/index.ts +16 -1
- package/src/schemas/accounts/index.ts +1 -0
- package/src/schemas/accounts/phone-number.ts +20 -0
- package/src/schemas/business/account.ts +43 -0
- package/src/schemas/business/index.ts +2 -0
- package/src/schemas/client.ts +50 -0
- package/src/schemas/debug.ts +25 -0
- package/src/schemas/index.ts +6 -0
- package/src/schemas/messages/index.ts +2 -0
- package/src/schemas/messages/request.ts +82 -0
- package/src/schemas/messages/response.ts +19 -0
- package/src/schemas/templates/component.ts +145 -0
- package/src/schemas/templates/index.ts +4 -0
- package/src/schemas/templates/request.ts +78 -0
- package/src/schemas/templates/response.ts +64 -0
- package/src/services/accounts/AccountsClient.ts +34 -0
- package/src/services/accounts/AccountsService.ts +45 -0
- package/src/services/accounts/index.ts +2 -0
- package/src/services/accounts/methods/list-phone-numbers.ts +15 -0
- package/src/services/business/BusinessClient.ts +34 -0
- package/src/services/business/BusinessService.ts +45 -0
- package/src/services/business/index.ts +3 -0
- package/src/services/business/methods/list-accounts.ts +17 -0
- package/src/services/index.ts +2 -0
- package/src/services/messages/MessagesClient.ts +34 -0
- package/src/services/messages/MessagesService.ts +97 -0
- package/src/services/messages/index.ts +8 -0
- package/src/services/messages/methods/send-image.ts +33 -0
- package/src/services/messages/methods/send-location.ts +32 -0
- package/src/services/messages/methods/send-reaction.ts +33 -0
- package/src/services/messages/methods/send-text.ts +32 -0
- package/src/services/messages/utils/build-message-payload.ts +32 -0
- package/src/services/templates/TemplatesClient.ts +35 -0
- package/src/services/templates/TemplatesService.ts +117 -0
- package/src/services/templates/index.ts +3 -0
- package/src/services/templates/methods/create.ts +27 -0
- package/src/services/templates/methods/delete.ts +38 -0
- package/src/services/templates/methods/get.ts +23 -0
- package/src/services/templates/methods/list.ts +36 -0
- package/src/services/templates/methods/update.ts +35 -0
- package/src/types/accounts/index.ts +1 -0
- package/src/types/accounts/phone-number.ts +9 -0
- package/src/types/business/account.ts +10 -0
- package/src/types/business/index.ts +2 -0
- package/src/types/client.ts +8 -0
- package/src/types/debug.ts +8 -0
- package/src/types/index.ts +6 -0
- package/src/types/messages/index.ts +2 -0
- package/src/types/messages/request.ts +27 -0
- package/src/types/messages/response.ts +7 -0
- package/src/types/templates/component.ts +33 -0
- package/src/types/templates/index.ts +4 -0
- package/src/types/templates/request.ts +28 -0
- package/src/types/templates/response.ts +34 -0
- package/src/utils/zod-error.ts +28 -0
- package/tsconfig.json +6 -4
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
# WhatsApp Cloud API SDK - Package Structure
|
|
2
|
+
|
|
3
|
+
## ๐ฏ Design Principles
|
|
4
|
+
|
|
5
|
+
1. **Schemas First** - Zod schemas are the single source of truth, types are inferred
|
|
6
|
+
2. **AI-Ready** - Zod schemas enable LLM function calling and validation
|
|
7
|
+
3. **Type Safety** - TypeScript types inferred from schemas for full type safety
|
|
8
|
+
4. **Discriminated Unions** - Type-safe message/response variants using Zod
|
|
9
|
+
5. **Best-Practice Structure** - Inspired by Vercel AI SDK & Stripe
|
|
10
|
+
6. **Namespace Organization** - Clear separation of concerns
|
|
11
|
+
|
|
12
|
+
## ๐ Complete Package Structure
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
whatsapp-cloud/
|
|
16
|
+
โโโ src/
|
|
17
|
+
โ โโโ index.ts # Main entry point
|
|
18
|
+
โ โ
|
|
19
|
+
โ โโโ client/
|
|
20
|
+
โ โ โโโ index.ts # Client exports
|
|
21
|
+
โ โ โโโ WhatsAppClient.ts # Main client class
|
|
22
|
+
โ โ โโโ HttpClient.ts # HTTP request abstraction
|
|
23
|
+
โ โ
|
|
24
|
+
โ โโโ schemas/ # Zod schemas (PRIMARY - single source of truth)
|
|
25
|
+
โ โ โโโ index.ts # Re-export all schemas
|
|
26
|
+
โ โ โโโ common.ts # Shared schemas
|
|
27
|
+
โ โ โ
|
|
28
|
+
โ โ โโโ messages/ # Message schemas
|
|
29
|
+
โ โ โ โโโ index.ts
|
|
30
|
+
โ โ โ โโโ request.ts # Request validation schemas
|
|
31
|
+
โ โ โ โโโ response.ts # Response validation schemas
|
|
32
|
+
โ โ โ โโโ message.ts # Message entity schemas
|
|
33
|
+
โ โ โ
|
|
34
|
+
โ โ โโโ templates/ # Template schemas
|
|
35
|
+
โ โ โ โโโ index.ts
|
|
36
|
+
โ โ โ โโโ request.ts
|
|
37
|
+
โ โ โ โโโ response.ts
|
|
38
|
+
โ โ โ โโโ template.ts
|
|
39
|
+
โ โ โ
|
|
40
|
+
โ โ โโโ accounts/ # Account schemas
|
|
41
|
+
โ โ โโโ index.ts
|
|
42
|
+
โ โ โโโ request.ts
|
|
43
|
+
โ โ โโโ response.ts
|
|
44
|
+
โ โ โโโ waba.ts
|
|
45
|
+
โ โ โโโ phone-number.ts
|
|
46
|
+
โ โ
|
|
47
|
+
โ โโโ types/ # TypeScript types (inferred from schemas)
|
|
48
|
+
โ โ โโโ index.ts # Re-export all types
|
|
49
|
+
โ โ โโโ client.ts # Client config types (may need explicit types)
|
|
50
|
+
โ โ โโโ common.ts # Shared/common types (re-exports from schemas)
|
|
51
|
+
โ โ โโโ errors.ts # Error types (inferred from schemas)
|
|
52
|
+
โ โ โ
|
|
53
|
+
โ โ โโโ messages/ # Message-related types (re-exports)
|
|
54
|
+
โ โ โ โโโ index.ts
|
|
55
|
+
โ โ โ โโโ request.ts # Re-export from schemas
|
|
56
|
+
โ โ โ โโโ response.ts # Re-export from schemas
|
|
57
|
+
โ โ โ โโโ message.ts # Re-export from schemas
|
|
58
|
+
โ โ โ
|
|
59
|
+
โ โ โโโ templates/ # Template-related types (re-exports)
|
|
60
|
+
โ โ โ โโโ index.ts
|
|
61
|
+
โ โ โ โโโ request.ts
|
|
62
|
+
โ โ โ โโโ response.ts
|
|
63
|
+
โ โ โ โโโ template.ts
|
|
64
|
+
โ โ โ
|
|
65
|
+
โ โ โโโ accounts/ # Account-related types (re-exports)
|
|
66
|
+
โ โ โโโ index.ts
|
|
67
|
+
โ โ โโโ request.ts
|
|
68
|
+
โ โ โโโ response.ts
|
|
69
|
+
โ โ โโโ waba.ts
|
|
70
|
+
โ โ โโโ phone-number.ts
|
|
71
|
+
โ โ
|
|
72
|
+
โ โโโ services/ # Service implementations
|
|
73
|
+
โ โ โโโ index.ts # Service exports
|
|
74
|
+
โ โ โ
|
|
75
|
+
โ โ โโโ messages/
|
|
76
|
+
โ โ โ โโโ index.ts
|
|
77
|
+
โ โ โ โโโ MessagesService.ts # Main service class
|
|
78
|
+
โ โ โ โโโ methods/ # Individual method implementations
|
|
79
|
+
โ โ โ โโโ send-text.ts
|
|
80
|
+
โ โ โ โโโ send-image.ts
|
|
81
|
+
โ โ โ โโโ send-video.ts
|
|
82
|
+
โ โ โ โโโ send-audio.ts
|
|
83
|
+
โ โ โ โโโ send-document.ts
|
|
84
|
+
โ โ โ โโโ send-location.ts
|
|
85
|
+
โ โ โ โโโ send-contacts.ts
|
|
86
|
+
โ โ โ โโโ send-template.ts
|
|
87
|
+
โ โ โ โโโ send-interactive.ts
|
|
88
|
+
โ โ โ โโโ send-reaction.ts
|
|
89
|
+
โ โ โ โโโ mark-as-read.ts
|
|
90
|
+
โ โ โ
|
|
91
|
+
โ โ โโโ templates/
|
|
92
|
+
โ โ โ โโโ index.ts
|
|
93
|
+
โ โ โ โโโ TemplatesService.ts
|
|
94
|
+
โ โ โ โโโ methods/
|
|
95
|
+
โ โ โ โโโ create.ts
|
|
96
|
+
โ โ โ โโโ list.ts
|
|
97
|
+
โ โ โ โโโ get.ts
|
|
98
|
+
โ โ โ โโโ update.ts
|
|
99
|
+
โ โ โ โโโ delete.ts
|
|
100
|
+
โ โ โ
|
|
101
|
+
โ โ โโโ accounts/
|
|
102
|
+
โ โ โโโ index.ts
|
|
103
|
+
โ โ โโโ AccountsService.ts
|
|
104
|
+
โ โ โโโ methods/
|
|
105
|
+
โ โ โ โโโ get-profile.ts
|
|
106
|
+
โ โ โ โโโ update-profile.ts
|
|
107
|
+
โ โ โโโ phone-numbers/
|
|
108
|
+
โ โ โโโ index.ts
|
|
109
|
+
โ โ โโโ PhoneNumbersService.ts
|
|
110
|
+
โ โ โโโ methods/
|
|
111
|
+
โ โ โโโ list.ts
|
|
112
|
+
โ โ โโโ get.ts
|
|
113
|
+
โ โ โโโ update.ts
|
|
114
|
+
โ โ
|
|
115
|
+
โ โโโ utils/
|
|
116
|
+
โ โโโ index.ts
|
|
117
|
+
โ โโโ errors.ts # Custom error classes
|
|
118
|
+
โ โโโ validators.ts # Validation helpers
|
|
119
|
+
โ โโโ constants.ts # API constants
|
|
120
|
+
โ
|
|
121
|
+
โโโ tests/ # Test files
|
|
122
|
+
โ โโโ unit/
|
|
123
|
+
โ โ โโโ services/
|
|
124
|
+
โ โ โโโ schemas/
|
|
125
|
+
โ โ โโโ utils/
|
|
126
|
+
โ โโโ integration/
|
|
127
|
+
โ
|
|
128
|
+
โโโ examples/ # Usage examples
|
|
129
|
+
โ โโโ basic-messaging.ts
|
|
130
|
+
โ โโโ templates.ts
|
|
131
|
+
โ โโโ accounts.ts
|
|
132
|
+
โ
|
|
133
|
+
โโโ package.json
|
|
134
|
+
โโโ tsconfig.json
|
|
135
|
+
โโโ tsconfig.build.json # Build-specific config
|
|
136
|
+
โโโ README.md
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## ๐ Key Design Patterns
|
|
140
|
+
|
|
141
|
+
### 1. Schemas First, Then Types, Then Services
|
|
142
|
+
|
|
143
|
+
**Pattern**: Define Zod schemas โ Infer TypeScript types โ Implement services
|
|
144
|
+
|
|
145
|
+
**Why Schema-First?**
|
|
146
|
+
|
|
147
|
+
- โ
Single source of truth (schema)
|
|
148
|
+
- โ
AI-ready (LLMs work with Zod schemas)
|
|
149
|
+
- โ
Types automatically stay in sync
|
|
150
|
+
- โ
Less duplication
|
|
151
|
+
- โ
Modern best practice (tRPC, Next.js, etc.)
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// src/schemas/messages/request.ts
|
|
155
|
+
import { z } from "zod";
|
|
156
|
+
|
|
157
|
+
// 1. Define schema first (single source of truth)
|
|
158
|
+
export const sendTextRequestSchema = z.object({
|
|
159
|
+
to: z.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format"),
|
|
160
|
+
body: z.string().min(1).max(4096),
|
|
161
|
+
previewUrl: z.boolean().optional(),
|
|
162
|
+
phoneNumberId: z.string().optional(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// 2. Infer type from schema
|
|
166
|
+
export type SendTextRequest = z.infer<typeof sendTextRequestSchema>;
|
|
167
|
+
|
|
168
|
+
// src/types/messages/request.ts (optional convenience re-export)
|
|
169
|
+
export type { SendTextRequest } from "../../schemas/messages/request";
|
|
170
|
+
|
|
171
|
+
// src/services/messages/methods/send-text.ts
|
|
172
|
+
import { sendTextRequestSchema } from "../../../schemas/messages/request";
|
|
173
|
+
import type { SendTextRequest } from "../../../schemas/messages/request";
|
|
174
|
+
|
|
175
|
+
export async function sendText(client: HttpClient, request: SendTextRequest) {
|
|
176
|
+
// Schema validates at runtime, type ensures compile-time safety
|
|
177
|
+
const validated = sendTextRequestSchema.parse(request);
|
|
178
|
+
// Implementation...
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 2. Discriminated Unions for Message Types
|
|
183
|
+
|
|
184
|
+
**Pattern**: Use discriminated unions for type-safe message variants
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// src/types/messages/message.ts
|
|
188
|
+
export type TextMessage = {
|
|
189
|
+
type: "text";
|
|
190
|
+
text: {
|
|
191
|
+
body: string;
|
|
192
|
+
previewUrl?: boolean;
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export type ImageMessage = {
|
|
197
|
+
type: "image";
|
|
198
|
+
image: {
|
|
199
|
+
link?: string;
|
|
200
|
+
id?: string;
|
|
201
|
+
caption?: string;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export type VideoMessage = {
|
|
206
|
+
type: "video";
|
|
207
|
+
video: {
|
|
208
|
+
link?: string;
|
|
209
|
+
id?: string;
|
|
210
|
+
caption?: string;
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export type AudioMessage = {
|
|
215
|
+
type: "audio";
|
|
216
|
+
audio: {
|
|
217
|
+
link?: string;
|
|
218
|
+
id?: string;
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export type DocumentMessage = {
|
|
223
|
+
type: "document";
|
|
224
|
+
document: {
|
|
225
|
+
link?: string;
|
|
226
|
+
id?: string;
|
|
227
|
+
caption?: string;
|
|
228
|
+
filename?: string;
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export type LocationMessage = {
|
|
233
|
+
type: "location";
|
|
234
|
+
location: {
|
|
235
|
+
longitude: number;
|
|
236
|
+
latitude: number;
|
|
237
|
+
name?: string;
|
|
238
|
+
address?: string;
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export type ContactsMessage = {
|
|
243
|
+
type: "contacts";
|
|
244
|
+
contacts: Array<{
|
|
245
|
+
name: {
|
|
246
|
+
formatted_name: string;
|
|
247
|
+
first_name?: string;
|
|
248
|
+
last_name?: string;
|
|
249
|
+
};
|
|
250
|
+
phones?: Array<{ phone: string; type?: string }>;
|
|
251
|
+
emails?: Array<{ email: string; type?: string }>;
|
|
252
|
+
}>;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export type TemplateMessage = {
|
|
256
|
+
type: "template";
|
|
257
|
+
template: {
|
|
258
|
+
name: string;
|
|
259
|
+
language: {
|
|
260
|
+
code: string;
|
|
261
|
+
policy?: "deterministic";
|
|
262
|
+
};
|
|
263
|
+
components?: Array<TemplateComponent>;
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export type InteractiveMessage = {
|
|
268
|
+
type: "interactive";
|
|
269
|
+
interactive: InteractiveContent;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export type ReactionMessage = {
|
|
273
|
+
type: "reaction";
|
|
274
|
+
reaction: {
|
|
275
|
+
message_id: string;
|
|
276
|
+
emoji: string;
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Discriminated union
|
|
281
|
+
export type MessageContent =
|
|
282
|
+
| TextMessage
|
|
283
|
+
| ImageMessage
|
|
284
|
+
| VideoMessage
|
|
285
|
+
| AudioMessage
|
|
286
|
+
| DocumentMessage
|
|
287
|
+
| LocationMessage
|
|
288
|
+
| ContactsMessage
|
|
289
|
+
| TemplateMessage
|
|
290
|
+
| InteractiveMessage
|
|
291
|
+
| ReactionMessage;
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Corresponding Zod Schema**:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// src/schemas/messages/message.ts
|
|
298
|
+
import { z } from "zod";
|
|
299
|
+
|
|
300
|
+
export const textMessageSchema = z.object({
|
|
301
|
+
type: z.literal("text"),
|
|
302
|
+
text: z.object({
|
|
303
|
+
body: z.string().min(1).max(4096),
|
|
304
|
+
previewUrl: z.boolean().optional(),
|
|
305
|
+
}),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
export const imageMessageSchema = z.object({
|
|
309
|
+
type: z.literal("image"),
|
|
310
|
+
image: z
|
|
311
|
+
.object({
|
|
312
|
+
link: z.string().url().optional(),
|
|
313
|
+
id: z.string().optional(),
|
|
314
|
+
caption: z.string().max(1024).optional(),
|
|
315
|
+
})
|
|
316
|
+
.refine(
|
|
317
|
+
(data) => data.link || data.id,
|
|
318
|
+
"Either link or id must be provided"
|
|
319
|
+
),
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ... other message schemas
|
|
323
|
+
|
|
324
|
+
export const messageContentSchema = z.discriminatedUnion("type", [
|
|
325
|
+
textMessageSchema,
|
|
326
|
+
imageMessageSchema,
|
|
327
|
+
videoMessageSchema,
|
|
328
|
+
audioMessageSchema,
|
|
329
|
+
documentMessageSchema,
|
|
330
|
+
locationMessageSchema,
|
|
331
|
+
contactsMessageSchema,
|
|
332
|
+
templateMessageSchema,
|
|
333
|
+
interactiveMessageSchema,
|
|
334
|
+
reactionMessageSchema,
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
export type MessageContent = z.infer<typeof messageContentSchema>;
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 3. Service Structure Pattern
|
|
341
|
+
|
|
342
|
+
**Pattern**: Service class with typed methods, each method in separate file
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// src/services/messages/MessagesService.ts
|
|
346
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
347
|
+
import * as sendText from "./methods/send-text";
|
|
348
|
+
import * as sendImage from "./methods/send-image";
|
|
349
|
+
// ... other methods
|
|
350
|
+
|
|
351
|
+
export class MessagesService {
|
|
352
|
+
constructor(private httpClient: HttpClient) {}
|
|
353
|
+
|
|
354
|
+
async sendText(request: sendText.SendTextRequest) {
|
|
355
|
+
return sendText.sendText(this.httpClient, request);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async sendImage(request: sendImage.SendImageRequest) {
|
|
359
|
+
return sendImage.sendImage(this.httpClient, request);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ... other methods
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// src/services/messages/methods/send-text.ts
|
|
368
|
+
import type { HttpClient } from "../../../client/HttpClient";
|
|
369
|
+
import { sendTextRequestSchema } from "../../../schemas/messages/request";
|
|
370
|
+
import type { SendTextRequest } from "../../../types/messages/request";
|
|
371
|
+
import type { MessageResponse } from "../../../types/messages/response";
|
|
372
|
+
|
|
373
|
+
export type { SendTextRequest };
|
|
374
|
+
|
|
375
|
+
export async function sendText(
|
|
376
|
+
client: HttpClient,
|
|
377
|
+
request: SendTextRequest
|
|
378
|
+
): Promise<MessageResponse> {
|
|
379
|
+
const validated = sendTextRequestSchema.parse(request);
|
|
380
|
+
|
|
381
|
+
return client.post<MessageResponse>(
|
|
382
|
+
`/${validated.phoneNumberId || client.phoneNumberId}/messages`,
|
|
383
|
+
{
|
|
384
|
+
messaging_product: "whatsapp",
|
|
385
|
+
recipient_type: "individual",
|
|
386
|
+
to: validated.to,
|
|
387
|
+
type: "text",
|
|
388
|
+
text: {
|
|
389
|
+
body: validated.body,
|
|
390
|
+
preview_url: validated.previewUrl,
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 4. Client Structure
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// src/client/WhatsAppClient.ts
|
|
401
|
+
import type { ClientConfig } from "../types/client";
|
|
402
|
+
import { HttpClient } from "./HttpClient";
|
|
403
|
+
import { MessagesService } from "../services/messages";
|
|
404
|
+
import { TemplatesService } from "../services/templates";
|
|
405
|
+
import { AccountsService } from "../services/accounts";
|
|
406
|
+
|
|
407
|
+
export class WhatsAppClient {
|
|
408
|
+
public readonly messages: MessagesService;
|
|
409
|
+
public readonly templates: TemplatesService;
|
|
410
|
+
public readonly accounts: AccountsService;
|
|
411
|
+
|
|
412
|
+
private readonly httpClient: HttpClient;
|
|
413
|
+
|
|
414
|
+
constructor(config: ClientConfig) {
|
|
415
|
+
this.httpClient = new HttpClient(config);
|
|
416
|
+
this.messages = new MessagesService(this.httpClient);
|
|
417
|
+
this.templates = new TemplatesService(this.httpClient);
|
|
418
|
+
this.accounts = new AccountsService(this.httpClient);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### 5. HttpClient Pattern
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
// src/client/HttpClient.ts
|
|
427
|
+
import type { ClientConfig } from "../types/client";
|
|
428
|
+
import type { APIError } from "../types/errors";
|
|
429
|
+
|
|
430
|
+
export class HttpClient {
|
|
431
|
+
private readonly baseURL: string;
|
|
432
|
+
private readonly accessToken: string;
|
|
433
|
+
public readonly phoneNumberId?: string;
|
|
434
|
+
public readonly businessAccountId?: string;
|
|
435
|
+
public readonly apiVersion: string;
|
|
436
|
+
|
|
437
|
+
constructor(config: ClientConfig) {
|
|
438
|
+
this.accessToken = config.accessToken;
|
|
439
|
+
this.phoneNumberId = config.phoneNumberId;
|
|
440
|
+
this.businessAccountId = config.businessAccountId;
|
|
441
|
+
this.apiVersion = config.apiVersion || "v18.0";
|
|
442
|
+
this.baseURL = config.baseURL || "https://graph.facebook.com";
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async get<T>(path: string, params?: Record<string, unknown>): Promise<T> {
|
|
446
|
+
// Implementation with error handling
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async post<T>(path: string, body: unknown): Promise<T> {
|
|
450
|
+
// Implementation with error handling
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async patch<T>(path: string, body: unknown): Promise<T> {
|
|
454
|
+
// Implementation with error handling
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async delete<T>(path: string): Promise<T> {
|
|
458
|
+
// Implementation with error handling
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## ๐ Type & Schema Organization
|
|
464
|
+
|
|
465
|
+
### Request/Response Pattern
|
|
466
|
+
|
|
467
|
+
For each API endpoint, we define:
|
|
468
|
+
|
|
469
|
+
1. **Request Type** (`types/*/request.ts`)
|
|
470
|
+
2. **Request Schema** (`schemas/*/request.ts`)
|
|
471
|
+
3. **Response Type** (`types/*/response.ts`)
|
|
472
|
+
4. **Response Schema** (`schemas/*/response.ts`)
|
|
473
|
+
|
|
474
|
+
### Example: Send Text Message
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// schemas/messages/request.ts (PRIMARY - single source of truth)
|
|
478
|
+
import { z } from "zod";
|
|
479
|
+
|
|
480
|
+
export const sendTextRequestSchema = z.object({
|
|
481
|
+
to: z.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format"),
|
|
482
|
+
body: z.string().min(1).max(4096),
|
|
483
|
+
previewUrl: z.boolean().optional(),
|
|
484
|
+
phoneNumberId: z.string().optional(),
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Infer type from schema
|
|
488
|
+
export type SendTextRequest = z.infer<typeof sendTextRequestSchema>;
|
|
489
|
+
|
|
490
|
+
// schemas/messages/response.ts
|
|
491
|
+
export const messageResponseSchema = z.object({
|
|
492
|
+
messaging_product: z.literal("whatsapp"),
|
|
493
|
+
contacts: z.array(
|
|
494
|
+
z.object({
|
|
495
|
+
input: z.string(),
|
|
496
|
+
wa_id: z.string(),
|
|
497
|
+
})
|
|
498
|
+
),
|
|
499
|
+
messages: z.array(
|
|
500
|
+
z.object({
|
|
501
|
+
id: z.string(),
|
|
502
|
+
})
|
|
503
|
+
),
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Infer type from schema
|
|
507
|
+
export type MessageResponse = z.infer<typeof messageResponseSchema>;
|
|
508
|
+
|
|
509
|
+
// types/messages/request.ts (convenience re-export)
|
|
510
|
+
export type { SendTextRequest } from "../../schemas/messages/request";
|
|
511
|
+
|
|
512
|
+
// types/messages/response.ts (convenience re-export)
|
|
513
|
+
export type { MessageResponse } from "../../schemas/messages/response";
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## ๐ฏ Index File Pattern
|
|
517
|
+
|
|
518
|
+
Each directory has an `index.ts` that re-exports:
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
// src/types/messages/index.ts
|
|
522
|
+
export type * from "./request";
|
|
523
|
+
export type * from "./response";
|
|
524
|
+
export type * from "./message";
|
|
525
|
+
|
|
526
|
+
// src/schemas/messages/index.ts
|
|
527
|
+
export * from "./request";
|
|
528
|
+
export * from "./response";
|
|
529
|
+
export * from "./message";
|
|
530
|
+
|
|
531
|
+
// src/services/messages/index.ts
|
|
532
|
+
export { MessagesService } from "./MessagesService";
|
|
533
|
+
export type * from "./methods/send-text";
|
|
534
|
+
export type * from "./methods/send-image";
|
|
535
|
+
// ... etc
|
|
536
|
+
|
|
537
|
+
// src/index.ts
|
|
538
|
+
export { WhatsAppClient } from "./client";
|
|
539
|
+
export type { ClientConfig } from "./types/client";
|
|
540
|
+
export type * from "./types";
|
|
541
|
+
export * from "./schemas";
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## ๐ Discriminated Union Examples
|
|
545
|
+
|
|
546
|
+
### Message Types (Request)
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
// types/messages/request.ts
|
|
550
|
+
export type SendMessageRequest =
|
|
551
|
+
| { type: "text"; to: string; body: string; previewUrl?: boolean }
|
|
552
|
+
| { type: "image"; to: string; imageUrl: string; caption?: string }
|
|
553
|
+
| { type: "video"; to: string; videoUrl: string; caption?: string }
|
|
554
|
+
| {
|
|
555
|
+
type: "template";
|
|
556
|
+
to: string;
|
|
557
|
+
templateName: string;
|
|
558
|
+
language: string;
|
|
559
|
+
parameters?: unknown[];
|
|
560
|
+
};
|
|
561
|
+
// ... etc
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Template Component Types
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// types/templates/template.ts
|
|
568
|
+
export type TemplateComponent =
|
|
569
|
+
| {
|
|
570
|
+
type: "header";
|
|
571
|
+
format: "text" | "image" | "video" | "document";
|
|
572
|
+
text?: string;
|
|
573
|
+
example?: unknown;
|
|
574
|
+
}
|
|
575
|
+
| { type: "body"; text: string; example?: { body_text: unknown[][] } }
|
|
576
|
+
| {
|
|
577
|
+
type: "button";
|
|
578
|
+
sub_type: "quick_reply" | "url" | "phone_number";
|
|
579
|
+
text: string;
|
|
580
|
+
url?: string;
|
|
581
|
+
phone_number?: string;
|
|
582
|
+
};
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### API Error Types
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// types/errors.ts
|
|
589
|
+
export type APIError =
|
|
590
|
+
| { code: 131056; type: "rate_limit"; message: string; retry_after?: number }
|
|
591
|
+
| { code: 100; type: "invalid_parameter"; message: string; field?: string }
|
|
592
|
+
| { code: 190; type: "invalid_token"; message: string }
|
|
593
|
+
| {
|
|
594
|
+
code: 80007;
|
|
595
|
+
type: "template_rejected";
|
|
596
|
+
message: string;
|
|
597
|
+
reason?: string;
|
|
598
|
+
};
|
|
599
|
+
// ... etc
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## ๐งช Testing Structure
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
tests/
|
|
606
|
+
โโโ unit/
|
|
607
|
+
โ โโโ schemas/
|
|
608
|
+
โ โ โโโ messages.test.ts
|
|
609
|
+
โ โ โโโ templates.test.ts
|
|
610
|
+
โ โ โโโ accounts.test.ts
|
|
611
|
+
โ โโโ services/
|
|
612
|
+
โ โ โโโ messages.test.ts
|
|
613
|
+
โ โ โโโ templates.test.ts
|
|
614
|
+
โ โ โโโ accounts.test.ts
|
|
615
|
+
โ โโโ utils/
|
|
616
|
+
โ โโโ errors.test.ts
|
|
617
|
+
โโโ integration/
|
|
618
|
+
โโโ messages.test.ts
|
|
619
|
+
โโโ templates.test.ts
|
|
620
|
+
โโโ accounts.test.ts
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
## ๐ฆ Package.json Structure
|
|
624
|
+
|
|
625
|
+
```json
|
|
626
|
+
{
|
|
627
|
+
"name": "whatsapp-cloud",
|
|
628
|
+
"version": "0.1.0",
|
|
629
|
+
"type": "module",
|
|
630
|
+
"main": "./dist/index.js",
|
|
631
|
+
"module": "./dist/index.mjs",
|
|
632
|
+
"types": "./dist/index.d.ts",
|
|
633
|
+
"exports": {
|
|
634
|
+
".": {
|
|
635
|
+
"types": "./dist/index.d.ts",
|
|
636
|
+
"import": "./dist/index.mjs",
|
|
637
|
+
"require": "./dist/index.js"
|
|
638
|
+
},
|
|
639
|
+
"./schemas": {
|
|
640
|
+
"types": "./dist/schemas/index.d.ts",
|
|
641
|
+
"import": "./dist/schemas/index.mjs",
|
|
642
|
+
"require": "./dist/schemas/index.js"
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
"files": ["dist", "README.md"],
|
|
646
|
+
"dependencies": {
|
|
647
|
+
"zod": "^3.23.8"
|
|
648
|
+
},
|
|
649
|
+
"devDependencies": {
|
|
650
|
+
"@types/node": "^20.0.0",
|
|
651
|
+
"tsup": "^8.5.1",
|
|
652
|
+
"typescript": "^5.9.3"
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
## ๐ Implementation Order
|
|
658
|
+
|
|
659
|
+
1. **Foundation**
|
|
660
|
+
|
|
661
|
+
- [ ] Types: `client.ts`, `common.ts`, `errors.ts`
|
|
662
|
+
- [ ] Schemas: `common.ts`
|
|
663
|
+
- [ ] `HttpClient` class
|
|
664
|
+
- [ ] `WhatsAppClient` class (skeleton)
|
|
665
|
+
|
|
666
|
+
2. **Messages Service**
|
|
667
|
+
|
|
668
|
+
- [ ] Types: `messages/request.ts`, `messages/response.ts`, `messages/message.ts`
|
|
669
|
+
- [ ] Schemas: All message schemas with discriminated unions
|
|
670
|
+
- [ ] Service: `MessagesService` + all send methods
|
|
671
|
+
|
|
672
|
+
3. **Templates Service**
|
|
673
|
+
|
|
674
|
+
- [ ] Types: `templates/*`
|
|
675
|
+
- [ ] Schemas: `templates/*`
|
|
676
|
+
- [ ] Service: `TemplatesService` + CRUD methods
|
|
677
|
+
|
|
678
|
+
4. **Accounts Service**
|
|
679
|
+
|
|
680
|
+
- [ ] Types: `accounts/*`
|
|
681
|
+
- [ ] Schemas: `accounts/*`
|
|
682
|
+
- [ ] Service: `AccountsService` + `PhoneNumbersService`
|
|
683
|
+
|
|
684
|
+
5. **Polish**
|
|
685
|
+
- [ ] Error handling
|
|
686
|
+
- [ ] Documentation
|
|
687
|
+
- [ ] Examples
|
|
688
|
+
- [ ] Tests
|
|
689
|
+
|
|
690
|
+
## ๐ก Key Benefits
|
|
691
|
+
|
|
692
|
+
1. **Type Safety**: Full TypeScript coverage, no `any` types
|
|
693
|
+
2. **AI Ready**: Zod schemas enable LLM tool calling and validation
|
|
694
|
+
3. **Discriminated Unions**: Type narrowing for better DX
|
|
695
|
+
4. **Modular**: Easy to extend and maintain
|
|
696
|
+
5. **Best Practices**: Follows patterns from industry-leading SDKs
|
|
697
|
+
6. **Tree Shakeable**: Only import what you need
|
|
698
|
+
7. **Well Organized**: Clear separation of concerns
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
This structure ensures:
|
|
703
|
+
|
|
704
|
+
- โ
**Schemas are the single source of truth** (defined first)
|
|
705
|
+
- โ
**Types are inferred from schemas** (automatic sync)
|
|
706
|
+
- โ
**Zod schemas for everything** (AI-ready, LLM function calling)
|
|
707
|
+
- โ
**Discriminated unions** for type-safe variants
|
|
708
|
+
- โ
**Clean namespace organization**
|
|
709
|
+
- โ
**Best-practice package structure** (inspired by Vercel AI SDK & Stripe)
|
|
710
|
+
- โ
**Easy to extend and maintain**
|
|
711
|
+
- โ
**Less duplication** (define once, infer type)
|