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.
Files changed (73) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +11 -0
  3. package/agent_docs/DESIGN.md +707 -0
  4. package/agent_docs/INCOMING_MESSAGES_BRAINSTORM.md +500 -0
  5. package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
  6. package/agent_docs/NAMING_DECISION.md +78 -0
  7. package/agent_docs/STRUCTURE.md +711 -0
  8. package/agent_docs/messages-namespace-design.md +357 -0
  9. package/cloud-api-docs/webhooks/endpoint.md +112 -0
  10. package/cloud-api-docs/webhooks/overview.md +154 -0
  11. package/package.json +10 -3
  12. package/src/client/HttpClient.ts +159 -0
  13. package/src/client/WhatsAppClient.ts +58 -0
  14. package/src/client/index.ts +2 -0
  15. package/src/errors.ts +58 -0
  16. package/src/examples/main.ts +9 -0
  17. package/src/examples/template.ts +134 -0
  18. package/src/index.ts +16 -1
  19. package/src/schemas/accounts/index.ts +1 -0
  20. package/src/schemas/accounts/phone-number.ts +20 -0
  21. package/src/schemas/business/account.ts +43 -0
  22. package/src/schemas/business/index.ts +2 -0
  23. package/src/schemas/client.ts +50 -0
  24. package/src/schemas/debug.ts +25 -0
  25. package/src/schemas/index.ts +6 -0
  26. package/src/schemas/messages/index.ts +2 -0
  27. package/src/schemas/messages/request.ts +82 -0
  28. package/src/schemas/messages/response.ts +19 -0
  29. package/src/schemas/templates/component.ts +145 -0
  30. package/src/schemas/templates/index.ts +4 -0
  31. package/src/schemas/templates/request.ts +78 -0
  32. package/src/schemas/templates/response.ts +64 -0
  33. package/src/services/accounts/AccountsClient.ts +34 -0
  34. package/src/services/accounts/AccountsService.ts +45 -0
  35. package/src/services/accounts/index.ts +2 -0
  36. package/src/services/accounts/methods/list-phone-numbers.ts +15 -0
  37. package/src/services/business/BusinessClient.ts +34 -0
  38. package/src/services/business/BusinessService.ts +45 -0
  39. package/src/services/business/index.ts +3 -0
  40. package/src/services/business/methods/list-accounts.ts +17 -0
  41. package/src/services/index.ts +2 -0
  42. package/src/services/messages/MessagesClient.ts +34 -0
  43. package/src/services/messages/MessagesService.ts +97 -0
  44. package/src/services/messages/index.ts +8 -0
  45. package/src/services/messages/methods/send-image.ts +33 -0
  46. package/src/services/messages/methods/send-location.ts +32 -0
  47. package/src/services/messages/methods/send-reaction.ts +33 -0
  48. package/src/services/messages/methods/send-text.ts +32 -0
  49. package/src/services/messages/utils/build-message-payload.ts +32 -0
  50. package/src/services/templates/TemplatesClient.ts +35 -0
  51. package/src/services/templates/TemplatesService.ts +117 -0
  52. package/src/services/templates/index.ts +3 -0
  53. package/src/services/templates/methods/create.ts +27 -0
  54. package/src/services/templates/methods/delete.ts +38 -0
  55. package/src/services/templates/methods/get.ts +23 -0
  56. package/src/services/templates/methods/list.ts +36 -0
  57. package/src/services/templates/methods/update.ts +35 -0
  58. package/src/types/accounts/index.ts +1 -0
  59. package/src/types/accounts/phone-number.ts +9 -0
  60. package/src/types/business/account.ts +10 -0
  61. package/src/types/business/index.ts +2 -0
  62. package/src/types/client.ts +8 -0
  63. package/src/types/debug.ts +8 -0
  64. package/src/types/index.ts +6 -0
  65. package/src/types/messages/index.ts +2 -0
  66. package/src/types/messages/request.ts +27 -0
  67. package/src/types/messages/response.ts +7 -0
  68. package/src/types/templates/component.ts +33 -0
  69. package/src/types/templates/index.ts +4 -0
  70. package/src/types/templates/request.ts +28 -0
  71. package/src/types/templates/response.ts +34 -0
  72. package/src/utils/zod-error.ts +28 -0
  73. package/tsconfig.json +6 -4
@@ -0,0 +1,159 @@
1
+ import type { ClientConfig } from "../types/client";
2
+
3
+ interface APIErrorResponse {
4
+ error?: {
5
+ message?: string;
6
+ code?: number;
7
+ };
8
+ }
9
+
10
+ /**
11
+ * HTTP client for making requests to the WhatsApp Cloud API
12
+ */
13
+ export class HttpClient {
14
+ private readonly baseURL: string;
15
+ public readonly accessToken: string;
16
+ public readonly phoneNumberId?: string;
17
+ public readonly businessAccountId?: string;
18
+ public readonly businessId?: string;
19
+ public readonly apiVersion: string;
20
+
21
+ constructor(config: ClientConfig) {
22
+ this.accessToken = config.accessToken;
23
+ if (config.phoneNumberId !== undefined) {
24
+ this.phoneNumberId = config.phoneNumberId;
25
+ }
26
+ if (config.businessAccountId !== undefined) {
27
+ this.businessAccountId = config.businessAccountId;
28
+ }
29
+ if (config.businessId !== undefined) {
30
+ this.businessId = config.businessId;
31
+ }
32
+ this.apiVersion = config.apiVersion ?? "v18.0";
33
+ this.baseURL = config.baseURL ?? "https://graph.facebook.com";
34
+ }
35
+
36
+ /**
37
+ * Make a POST request
38
+ */
39
+ async post<T>(path: string, body: unknown): Promise<T> {
40
+ const url = `${this.baseURL}/${this.apiVersion}${path}`;
41
+
42
+ const response = await fetch(url, {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ Authorization: `Bearer ${this.accessToken}`,
47
+ },
48
+ body: JSON.stringify(body),
49
+ });
50
+
51
+ if (!response.ok) {
52
+ const error = (await response.json().catch(() => ({
53
+ error: {
54
+ message: response.statusText,
55
+ code: response.status,
56
+ },
57
+ }))) as APIErrorResponse;
58
+ throw new Error(
59
+ `API Error: ${error.error?.message || response.statusText} (${
60
+ error.error?.code || response.status
61
+ })`
62
+ );
63
+ }
64
+
65
+ return response.json() as Promise<T>;
66
+ }
67
+
68
+ /**
69
+ * Make a GET request
70
+ */
71
+ async get<T>(path: string): Promise<T> {
72
+ const url = `${this.baseURL}/${this.apiVersion}${path}`;
73
+
74
+ const response = await fetch(url, {
75
+ method: "GET",
76
+ headers: {
77
+ Authorization: `Bearer ${this.accessToken}`,
78
+ },
79
+ });
80
+
81
+ if (!response.ok) {
82
+ const error = (await response.json().catch(() => ({
83
+ error: {
84
+ message: response.statusText,
85
+ code: response.status,
86
+ },
87
+ }))) as APIErrorResponse;
88
+ throw new Error(
89
+ `API Error: ${error.error?.message || response.statusText} (${
90
+ error.error?.code || response.status
91
+ })`
92
+ );
93
+ }
94
+
95
+ return response.json() as Promise<T>;
96
+ }
97
+
98
+ /**
99
+ * Make a PATCH request
100
+ */
101
+ async patch<T>(path: string, body: unknown): Promise<T> {
102
+ const url = `${this.baseURL}/${this.apiVersion}${path}`;
103
+
104
+ const response = await fetch(url, {
105
+ method: "PATCH",
106
+ headers: {
107
+ "Content-Type": "application/json",
108
+ Authorization: `Bearer ${this.accessToken}`,
109
+ },
110
+ body: JSON.stringify(body),
111
+ });
112
+
113
+ if (!response.ok) {
114
+ const error = (await response.json().catch(() => ({
115
+ error: {
116
+ message: response.statusText,
117
+ code: response.status,
118
+ },
119
+ }))) as APIErrorResponse;
120
+ throw new Error(
121
+ `API Error: ${error.error?.message || response.statusText} (${
122
+ error.error?.code || response.status
123
+ })`
124
+ );
125
+ }
126
+
127
+ return response.json() as Promise<T>;
128
+ }
129
+
130
+ /**
131
+ * Make a DELETE request
132
+ */
133
+ async delete<T>(path: string): Promise<T> {
134
+ const url = `${this.baseURL}/${this.apiVersion}${path}`;
135
+
136
+ const response = await fetch(url, {
137
+ method: "DELETE",
138
+ headers: {
139
+ Authorization: `Bearer ${this.accessToken}`,
140
+ },
141
+ });
142
+
143
+ if (!response.ok) {
144
+ const error = (await response.json().catch(() => ({
145
+ error: {
146
+ message: response.statusText,
147
+ code: response.status,
148
+ },
149
+ }))) as APIErrorResponse;
150
+ throw new Error(
151
+ `API Error: ${error.error?.message || response.statusText} (${
152
+ error.error?.code || response.status
153
+ })`
154
+ );
155
+ }
156
+
157
+ return response.json() as Promise<T>;
158
+ }
159
+ }
@@ -0,0 +1,58 @@
1
+ import { clientConfigSchema } from "../schemas/client";
2
+ import type { ClientConfig } from "../types/client";
3
+ import { HttpClient } from "./HttpClient";
4
+ import { MessagesService } from "../services/messages/index";
5
+ import { AccountsService } from "../services/accounts/index";
6
+ import { BusinessService } from "../services/business/index";
7
+ import { TemplatesService } from "../services/templates/index";
8
+ import { ZodError } from "zod";
9
+ import { transformZodError } from "../utils/zod-error";
10
+ import type { DebugTokenResponse } from "../types/debug";
11
+
12
+ /**
13
+ * WhatsApp Cloud API client
14
+ */
15
+ export class WhatsAppClient {
16
+ public readonly messages: MessagesService;
17
+ public readonly accounts: AccountsService;
18
+ public readonly business: BusinessService;
19
+ public readonly templates: TemplatesService;
20
+
21
+ private readonly httpClient: HttpClient;
22
+
23
+ constructor(config: ClientConfig) {
24
+ // Validate config with schema - Zod provides detailed error messages
25
+ let validated: ClientConfig;
26
+ try {
27
+ validated = clientConfigSchema.parse(config);
28
+ } catch (error) {
29
+ if (error instanceof ZodError) {
30
+ throw transformZodError(error);
31
+ }
32
+ throw error;
33
+ }
34
+
35
+ // Initialize HTTP client
36
+ this.httpClient = new HttpClient(validated);
37
+
38
+ // Initialize services (namespaces)
39
+ this.messages = new MessagesService(this.httpClient);
40
+ this.accounts = new AccountsService(this.httpClient);
41
+ this.business = new BusinessService(this.httpClient);
42
+ this.templates = new TemplatesService(this.httpClient);
43
+ }
44
+
45
+ /**
46
+ * Debug the current access token
47
+ *
48
+ * This method calls the Graph API debug_token endpoint to inspect the access token
49
+ * used by this client. Useful for understanding token permissions, expiration, and validity.
50
+ *
51
+ * @returns Debug information about the access token
52
+ */
53
+ async debugToken(): Promise<DebugTokenResponse> {
54
+ return this.httpClient.get<DebugTokenResponse>(
55
+ `/debug_token?input_token=${this.httpClient.accessToken}`
56
+ );
57
+ }
58
+ }
@@ -0,0 +1,2 @@
1
+ export { WhatsAppClient } from "./WhatsAppClient";
2
+ export { HttpClient } from "./HttpClient";
package/src/errors.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Base error class for WhatsApp API errors
3
+ */
4
+ export class WhatsAppError extends Error {
5
+ constructor(message: string) {
6
+ super(message);
7
+ this.name = this.constructor.name;
8
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
9
+ const captureStackTrace = (Error as any).captureStackTrace;
10
+ if (typeof captureStackTrace === "function") {
11
+ captureStackTrace(this, this.constructor);
12
+ }
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Error thrown when validation fails (configuration, requests, etc.)
18
+ * Can be used for any Zod validation error
19
+ */
20
+ export class WhatsAppValidationError extends WhatsAppError {
21
+ constructor(
22
+ message: string,
23
+ public readonly field?: string,
24
+ public readonly issues?: Array<{
25
+ path: readonly (string | number)[];
26
+ message: string;
27
+ }>
28
+ ) {
29
+ super(message);
30
+ this.name = "WhatsAppValidationError";
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Error thrown when an API request fails
36
+ */
37
+ export class WhatsAppAPIError extends WhatsAppError {
38
+ constructor(
39
+ public readonly code: number,
40
+ public readonly type: string,
41
+ message: string,
42
+ public readonly statusCode?: number,
43
+ public readonly details?: unknown
44
+ ) {
45
+ super(message);
46
+ this.name = "WhatsAppAPIError";
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Error thrown when rate limit is exceeded
52
+ */
53
+ export class WhatsAppRateLimitError extends WhatsAppAPIError {
54
+ constructor(message: string, public readonly retryAfter?: number) {
55
+ super(131056, "rate_limit", message, 429, { retryAfter });
56
+ this.name = "WhatsAppRateLimitError";
57
+ }
58
+ }
@@ -0,0 +1,9 @@
1
+ import "dotenv/config";
2
+ import { WhatsAppClient } from "../client";
3
+
4
+ const client = new WhatsAppClient({
5
+ accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
6
+ });
7
+
8
+ const debugInfo = await client.debugToken();
9
+ console.log(debugInfo);
@@ -0,0 +1,134 @@
1
+ import "dotenv/config";
2
+ import { WhatsAppClient } from "../client";
3
+
4
+ const client = new WhatsAppClient({
5
+ accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
6
+ businessAccountId: process.env.WHATSAPP_BUSINESS_ACCOUNT_ID!,
7
+ });
8
+
9
+ async function testTemplates() {
10
+ try {
11
+ console.log("🧪 Testing Templates API...\n");
12
+
13
+ // Test 1: Create a template
14
+ console.log("1️⃣ Creating template...");
15
+ const createResponse = await client.templates.create({
16
+ name: `test_template_${Date.now()}`, // Unique name to avoid conflicts
17
+ language: "en",
18
+ category: "UTILITY",
19
+ components: [
20
+ {
21
+ type: "BODY",
22
+ text: "Hello! This is a test template. Thank you for testing our WhatsApp Cloud API SDK.",
23
+ },
24
+ {
25
+ type: "FOOTER",
26
+ text: "This is a test footer",
27
+ },
28
+ {
29
+ type: "BUTTONS",
30
+ buttons: [
31
+ {
32
+ type: "QUICK_REPLY",
33
+ text: "Get Started",
34
+ },
35
+ {
36
+ type: "QUICK_REPLY",
37
+ text: "Learn More",
38
+ },
39
+ ],
40
+ },
41
+ ],
42
+ });
43
+
44
+ console.log("✅ Template created successfully!");
45
+ console.log("Response:", JSON.stringify(createResponse, null, 2));
46
+ console.log("\n");
47
+
48
+ // Verify create response structure
49
+ if (!createResponse.id) {
50
+ throw new Error("Create response missing 'id' field");
51
+ }
52
+ if (!createResponse.status) {
53
+ throw new Error("Create response missing 'status' field");
54
+ }
55
+ if (!createResponse.category) {
56
+ throw new Error("Create response missing 'category' field");
57
+ }
58
+ console.log("✅ Create response structure verified\n");
59
+
60
+ // Test 2: Get template by ID
61
+ console.log("2️⃣ Getting template by ID...");
62
+ const templateId = createResponse.id;
63
+ const getResponse = await client.templates.get(templateId);
64
+
65
+ console.log("✅ Template retrieved successfully!");
66
+ console.log("Response:", JSON.stringify(getResponse, null, 2));
67
+ console.log("\n");
68
+
69
+ // Verify get response structure
70
+ if (!getResponse.id) {
71
+ throw new Error("Get response missing 'id' field");
72
+ }
73
+ if (!getResponse.name) {
74
+ throw new Error("Get response missing 'name' field");
75
+ }
76
+ if (!getResponse.language) {
77
+ throw new Error("Get response missing 'language' field");
78
+ }
79
+ if (!getResponse.status) {
80
+ throw new Error("Get response missing 'status' field");
81
+ }
82
+ if (!getResponse.category) {
83
+ throw new Error("Get response missing 'category' field");
84
+ }
85
+ if (!Array.isArray(getResponse.components)) {
86
+ throw new Error("Get response missing 'components' array");
87
+ }
88
+ console.log("✅ Get response structure verified\n");
89
+
90
+ // Verify components
91
+ const bodyComponent = getResponse.components.find((c) => c.type === "BODY");
92
+ if (!bodyComponent) {
93
+ throw new Error("BODY component not found in response");
94
+ }
95
+ if (bodyComponent.type !== "BODY") {
96
+ throw new Error("BODY component type mismatch");
97
+ }
98
+ console.log("✅ Components verified\n");
99
+
100
+ // Test 3: List templates
101
+ console.log("3️⃣ Listing templates...");
102
+ const listResponse = await client.templates.list();
103
+ console.log(`✅ Found ${listResponse.data.length} template(s)`);
104
+ console.log("Response:", JSON.stringify(listResponse, null, 2));
105
+ console.log("\n");
106
+
107
+ // Verify list response structure
108
+ if (!Array.isArray(listResponse.data)) {
109
+ throw new Error("List response missing 'data' array");
110
+ }
111
+ console.log("✅ List response structure verified\n");
112
+
113
+ // Verify our created template is in the list
114
+ const foundTemplate = listResponse.data.find((t) => t.id === templateId);
115
+ if (!foundTemplate) {
116
+ console.log(
117
+ "⚠️ Note: Created template may not appear in list immediately (async processing)"
118
+ );
119
+ } else {
120
+ console.log("✅ Created template found in list\n");
121
+ }
122
+
123
+ console.log("🎉 All tests passed!");
124
+ } catch (error) {
125
+ console.error("❌ Test failed:", error);
126
+ if (error instanceof Error) {
127
+ console.error("Error message:", error.message);
128
+ console.error("Error stack:", error.stack);
129
+ }
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ testTemplates();
package/src/index.ts CHANGED
@@ -1 +1,16 @@
1
- export const add = (a: number, b: number) => a + b;
1
+ // WhatsApp Cloud API SDK
2
+ export { WhatsAppClient } from "./client/index";
3
+
4
+ // Export schemas (AI-ready)
5
+ export * from "./schemas/index";
6
+
7
+ // Export types (primary export point)
8
+ export type * from "./types/index";
9
+
10
+ // Export errors for error handling
11
+ export {
12
+ WhatsAppError,
13
+ WhatsAppValidationError,
14
+ WhatsAppAPIError,
15
+ WhatsAppRateLimitError,
16
+ } from "./errors";
@@ -0,0 +1 @@
1
+ export * from "./phone-number";
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for phone number response
5
+ * Matches WhatsApp API structure for phone number objects
6
+ */
7
+ export const phoneNumberResponseSchema = z.object({
8
+ verified_name: z.string(),
9
+ display_phone_number: z.string(),
10
+ id: z.string(),
11
+ quality_rating: z.string(),
12
+ });
13
+
14
+ /**
15
+ * Schema for phone number list response
16
+ * Matches WhatsApp API structure for GET /phone_numbers endpoint
17
+ */
18
+ export const phoneNumberListResponseSchema = z.object({
19
+ data: z.array(phoneNumberResponseSchema),
20
+ });
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for WhatsApp Business Account (WABA) response
5
+ * Matches WhatsApp API structure for WABA objects
6
+ */
7
+ export const businessAccountResponseSchema = z.object({
8
+ id: z.string(),
9
+ name: z.string().optional(),
10
+ account_review_status: z.string().optional(),
11
+ currency: z.string().optional(),
12
+ country: z.string().optional(),
13
+ timezone_id: z.string().optional(),
14
+ business_verification_status: z.string().optional(),
15
+ is_enabled_for_insights: z.boolean().optional(),
16
+ message_template_namespace: z.string().optional(),
17
+ });
18
+
19
+ /**
20
+ * Schema for WhatsApp Business Accounts list response
21
+ * Matches WhatsApp API structure for GET /whatsapp_business_accounts endpoint
22
+ *
23
+ * Note: The API returns data as an object with numeric string keys (e.g., "0", "1")
24
+ * or as an array, plus optional paging information
25
+ */
26
+ export const businessAccountsListResponseSchema = z.object({
27
+ data: z.record(z.string(), businessAccountResponseSchema).or(
28
+ z.array(businessAccountResponseSchema)
29
+ ),
30
+ paging: z
31
+ .object({
32
+ cursors: z
33
+ .object({
34
+ before: z.string().optional(),
35
+ after: z.string().optional(),
36
+ })
37
+ .optional(),
38
+ next: z.string().url().optional(),
39
+ previous: z.string().url().optional(),
40
+ })
41
+ .optional(),
42
+ });
43
+
@@ -0,0 +1,2 @@
1
+ export * from "./account";
2
+
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Helper message for access token errors
5
+ */
6
+ const ACCESS_TOKEN_HELP_MESSAGE =
7
+ "Get your access token from Meta for Developers: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started";
8
+
9
+ /**
10
+ * Schema for access token with validation and helpful error messages
11
+ */
12
+ const accessTokenSchema = z
13
+ .string({
14
+ message: `accessToken is required. ${ACCESS_TOKEN_HELP_MESSAGE}`,
15
+ })
16
+ .min(1, {
17
+ message: `accessToken cannot be empty. ${ACCESS_TOKEN_HELP_MESSAGE}`,
18
+ })
19
+ .trim()
20
+ .refine((val) => val.length > 0, {
21
+ message: `accessToken cannot be whitespace only. ${ACCESS_TOKEN_HELP_MESSAGE}`,
22
+ });
23
+
24
+ /**
25
+ * Client configuration schema
26
+ */
27
+ export const clientConfigSchema = z.object({
28
+ accessToken: accessTokenSchema,
29
+ phoneNumberId: z
30
+ .string()
31
+ .optional()
32
+ .refine((val) => val === undefined || val.trim().length > 0, {
33
+ message: "phoneNumberId cannot be empty or whitespace only",
34
+ }),
35
+ businessAccountId: z
36
+ .string()
37
+ .optional()
38
+ .refine((val) => val === undefined || val.trim().length > 0, {
39
+ message: "businessAccountId cannot be empty or whitespace only",
40
+ }),
41
+ businessId: z
42
+ .string()
43
+ .optional()
44
+ .refine((val) => val === undefined || val.trim().length > 0, {
45
+ message: "businessId cannot be empty or whitespace only",
46
+ }),
47
+ apiVersion: z.string().default("v18.0").optional(),
48
+ baseURL: z.string().url().default("https://graph.facebook.com").optional(),
49
+ timeout: z.number().positive().optional(),
50
+ });
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for debug token response
5
+ * Matches Graph API debug_token endpoint response structure
6
+ */
7
+ export const debugTokenResponseSchema = z.object({
8
+ data: z.object({
9
+ app_id: z.string().optional(),
10
+ type: z.string().optional(),
11
+ application: z.string().optional(),
12
+ data_access_expires_at: z.number().optional(),
13
+ expires_at: z.number().optional(),
14
+ is_valid: z.boolean().optional(),
15
+ issued_at: z.number().optional(),
16
+ metadata: z
17
+ .object({
18
+ auth_type: z.string().optional(),
19
+ sso: z.string().optional(),
20
+ })
21
+ .optional(),
22
+ scopes: z.array(z.string()).optional(),
23
+ user_id: z.string().optional(),
24
+ }),
25
+ });
@@ -0,0 +1,6 @@
1
+ export * from "./client";
2
+ export * from "./messages/index";
3
+ export * from "./accounts/index";
4
+ export * from "./business/index";
5
+ export * from "./templates/index";
6
+ export * from "./debug";
@@ -0,0 +1,2 @@
1
+ export * from "./request";
2
+ export * from "./response";
@@ -0,0 +1,82 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Base schema for all message requests
5
+ * phoneNumberId is handled at the client level, not in the request object
6
+ */
7
+ const baseMessageRequestSchema = z.object({
8
+ to: z.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format"),
9
+ });
10
+
11
+ /**
12
+ * Schema for image object in image messages
13
+ * Matches WhatsApp API structure
14
+ */
15
+ const imageSchema = z
16
+ .object({
17
+ id: z.string().optional(),
18
+ link: z.string().url().optional(),
19
+ caption: z.string().max(1024).optional(),
20
+ })
21
+ .refine((data) => data.link || data.id, "Either link or id must be provided");
22
+
23
+ /**
24
+ * Schema for sending an image message
25
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
26
+ */
27
+ export const sendImageRequestSchema = baseMessageRequestSchema.extend({
28
+ image: imageSchema,
29
+ });
30
+
31
+ /**
32
+ * Schema for text object in text messages
33
+ * Matches WhatsApp API structure
34
+ */
35
+ const textSchema = z.object({
36
+ body: z.string().min(1).max(4096),
37
+ preview_url: z.boolean().optional(),
38
+ });
39
+
40
+ /**
41
+ * Schema for sending a text message
42
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
43
+ */
44
+ export const sendTextRequestSchema = baseMessageRequestSchema.extend({
45
+ text: textSchema,
46
+ });
47
+
48
+ /**
49
+ * Schema for location object in location messages
50
+ * Matches WhatsApp API structure
51
+ */
52
+ const locationSchema = z.object({
53
+ longitude: z.number().min(-180).max(180),
54
+ latitude: z.number().min(-90).max(90),
55
+ name: z.string().optional(),
56
+ address: z.string().optional(),
57
+ });
58
+
59
+ /**
60
+ * Schema for sending a location message
61
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
62
+ */
63
+ export const sendLocationRequestSchema = baseMessageRequestSchema.extend({
64
+ location: locationSchema,
65
+ });
66
+
67
+ /**
68
+ * Schema for reaction object in reaction messages
69
+ * Matches WhatsApp API structure
70
+ */
71
+ const reactionSchema = z.object({
72
+ message_id: z.string().min(1),
73
+ emoji: z.string().min(1).max(1), // Single emoji character
74
+ });
75
+
76
+ /**
77
+ * Schema for sending a reaction message
78
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
79
+ */
80
+ export const sendReactionRequestSchema = baseMessageRequestSchema.extend({
81
+ reaction: reactionSchema,
82
+ });