@natyapp/meta 1.6.7 → 1.7.0

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 (89) hide show
  1. package/.github/copilot-instructions.md +1540 -0
  2. package/README.md +122 -1
  3. package/dist/index.d.ts +7 -2
  4. package/dist/index.js +11 -2
  5. package/dist/interfaces/IConnection.d.ts +2 -2
  6. package/dist/interfaces/ILog.d.ts +2 -2
  7. package/dist/interfaces/ILogger.d.ts +62 -0
  8. package/dist/interfaces/ILogger.js +2 -0
  9. package/dist/interfaces/ISdk.d.ts +4 -2
  10. package/dist/interfaces/IWebhook.d.ts +2 -2
  11. package/dist/interfaces/index.d.ts +1 -0
  12. package/dist/interfaces/index.js +1 -0
  13. package/dist/queue/messageQueue.d.ts +1 -1
  14. package/dist/queue/messageQueue.js +45 -0
  15. package/dist/routes/webhooks/methods/connection.js +78 -11
  16. package/dist/routes/webhooks/methods/messages.js +18 -3
  17. package/dist/services/axiosInstances.d.ts +14 -5
  18. package/dist/services/axiosInstances.js +111 -23
  19. package/dist/services/middlewares/validations.d.ts +2 -2
  20. package/dist/services/middlewares/validations.js +1 -2
  21. package/dist/services/mutations/connection.js +1 -1
  22. package/dist/services/mutations/logs.js +1 -1
  23. package/dist/services/mutations/messages.js +1 -1
  24. package/dist/services/mutations/validation.d.ts +1 -1
  25. package/dist/services/mutations/validation.js +1 -1
  26. package/dist/services/mutations/webhooks.js +1 -1
  27. package/dist/types/logs.d.ts +1 -1
  28. package/dist/types/requestTypes.d.ts +2 -0
  29. package/dist/useCases/connection/index.d.ts +9 -9
  30. package/dist/useCases/connection/index.js +156 -7
  31. package/dist/useCases/events/NatyEvents.d.ts +4 -2
  32. package/dist/useCases/events/NatyEvents.js +13 -1
  33. package/dist/useCases/log/index.d.ts +5 -5
  34. package/dist/useCases/log/index.js +65 -3
  35. package/dist/useCases/message/whatsappResponse.d.ts +33 -23
  36. package/dist/useCases/message/whatsappResponse.js +655 -109
  37. package/dist/useCases/messages/index.d.ts +5 -5
  38. package/dist/useCases/messages/index.js +66 -3
  39. package/dist/useCases/sdk/index.d.ts +8 -4
  40. package/dist/useCases/sdk/index.js +38 -6
  41. package/dist/useCases/webhook/index.d.ts +9 -9
  42. package/dist/useCases/webhook/index.js +154 -7
  43. package/dist/utils/consoleLogger.d.ts +20 -0
  44. package/dist/utils/consoleLogger.js +51 -0
  45. package/dist/utils/index.d.ts +6 -0
  46. package/dist/utils/index.js +6 -0
  47. package/dist/utils/loggerContext.d.ts +57 -0
  48. package/dist/utils/loggerContext.js +90 -0
  49. package/dist/utils/methodContext.d.ts +34 -0
  50. package/dist/utils/methodContext.js +48 -0
  51. package/dist/utils/parseError.d.ts +12 -0
  52. package/dist/utils/parseError.js +27 -3
  53. package/dist/utils/pinoAdapter.d.ts +30 -0
  54. package/dist/utils/pinoAdapter.js +68 -0
  55. package/dist/utils/sanitize.d.ts +42 -0
  56. package/dist/utils/sanitize.js +120 -0
  57. package/dist/utils/tryCatch.d.ts +10 -1
  58. package/dist/utils/tryCatch.js +40 -5
  59. package/docs/01-visao-geral.md +355 -0
  60. package/docs/02-contexto-negocio.md +596 -0
  61. package/docs/03-arquitetura.md +925 -0
  62. package/docs/04-fluxos-funcionais.md +887 -0
  63. package/docs/05-integracoes.md +960 -0
  64. package/docs/06-entidades.md +849 -0
  65. package/docs/07-guia-pratico.md +1133 -0
  66. package/docs/08-troubleshooting.md +816 -0
  67. package/docs/README.md +125 -0
  68. package/examples/logger-example.ts +279 -0
  69. package/package.json +2 -2
  70. /package/dist/{Entities → entities}/Logs.d.ts +0 -0
  71. /package/dist/{Entities → entities}/Logs.js +0 -0
  72. /package/dist/{Entities → entities}/connection.d.ts +0 -0
  73. /package/dist/{Entities → entities}/connection.js +0 -0
  74. /package/dist/{Entities → entities}/errorLogs.d.ts +0 -0
  75. /package/dist/{Entities → entities}/errorLogs.js +0 -0
  76. /package/dist/{Entities → entities}/index.d.ts +0 -0
  77. /package/dist/{Entities → entities}/index.js +0 -0
  78. /package/dist/{Entities → entities}/messages.d.ts +0 -0
  79. /package/dist/{Entities → entities}/messages.js +0 -0
  80. /package/dist/{Entities → entities}/webhooks.d.ts +0 -0
  81. /package/dist/{Entities → entities}/webhooks.js +0 -0
  82. /package/dist/{Entities → entities}/whatsappMessage.d.ts +0 -0
  83. /package/dist/{Entities → entities}/whatsappMessage.js +0 -0
  84. /package/dist/{Errors → errors}/Either.d.ts +0 -0
  85. /package/dist/{Errors → errors}/Either.js +0 -0
  86. /package/dist/{Errors → errors}/ErrorHandling.d.ts +0 -0
  87. /package/dist/{Errors → errors}/ErrorHandling.js +0 -0
  88. /package/dist/{Errors → errors}/index.d.ts +0 -0
  89. /package/dist/{Errors → errors}/index.js +0 -0
@@ -0,0 +1,1540 @@
1
+ # GitHub Copilot Custom Instructions - @natyapp/meta SDK
2
+
3
+ ## 📋 Visão Geral
4
+
5
+ **@natyapp/meta** é um SDK TypeScript que abstrai a integração com a WhatsApp Business API da Meta. Este documento define os padrões arquiteturais, convenções de código e práticas obrigatórias para manter consistência e qualidade no desenvolvimento.
6
+
7
+ ### Filosofia do Projeto
8
+
9
+ - **Type-Safe First**: TypeScript strict mode com interfaces explícitas
10
+ - **Functional Error Handling**: Either monad ao invés de exceptions
11
+ - **Imutabilidade**: Entities com campos readonly
12
+ - **Separation of Concerns**: Arquitetura em 4 camadas bem definidas
13
+ - **Multi-Tenancy Native**: `companyId` obrigatório em todas operações
14
+ - **Developer Experience**: APIs fluentes, builders, e abstrações intuitivas
15
+
16
+ ---
17
+
18
+ ## 🏗️ Arquitetura em Camadas
19
+
20
+ O SDK segue uma arquitetura em 4 camadas com responsabilidades claras:
21
+
22
+ ```
23
+ ┌─────────────────────────────────────────────────────────────────┐
24
+ │ Layer 1: PUBLIC API (src/index.ts, src/useCases/sdk/) │
25
+ │ • Exports públicos que consumidores do SDK usam │
26
+ │ • Classe NatyMeta como ponto de entrada principal │
27
+ │ • Tipos e interfaces exportadas │
28
+ ├─────────────────────────────────────────────────────────────────┤
29
+ │ Layer 2: USE CASES (src/useCases/) │
30
+ │ • Lógica de negócio e orquestração │
31
+ │ • Implementam interfaces (IConnection, IMessage, etc.) │
32
+ │ • Coordenam chamadas entre services e entities │
33
+ │ • Gerenciam eventos e estado │
34
+ ├─────────────────────────────────────────────────────────────────┤
35
+ │ Layer 3: SERVICES (src/services/) │
36
+ │ • Comunicação com APIs externas (Meta, Naty backend) │
37
+ │ • Mutations (CRUD operations) │
38
+ │ • Axios instances e interceptors │
39
+ │ • Middlewares e validações │
40
+ ├─────────────────────────────────────────────────────────────────┤
41
+ │ Layer 4: DATA & DOMAIN (src/entities/, src/interfaces/) │
42
+ │ • Domain models (ConnectionEntity, MessageEntity, etc.) │
43
+ │ • Interfaces que definem contratos │
44
+ │ • Types e constantes │
45
+ │ • Message builders (src/elements/) │
46
+ └─────────────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ### Onde cada tipo de código deve residir
50
+
51
+ | Tipo de Código | Localização | Exemplo |
52
+ | ----------------------- | -------------------------------- | -------------------------------------------- |
53
+ | **CRUD de API externa** | `src/services/mutations/` | `connectionMutations.getSingle()` |
54
+ | **Lógica de negócio** | `src/useCases/` | `Connections.getSingle()` com error handling |
55
+ | **Domain models** | `src/entities/` | `ConnectionEntity`, `MessageEntity` |
56
+ | **Type definitions** | `src/types/` | `WhatsappResponse`, `contactType` |
57
+ | **Interfaces** | `src/interfaces/` | `IConnection`, `IMessage` |
58
+ | **Message builders** | `src/elements/` | `Button`, `List`, `Text` |
59
+ | **HTTP clients** | `src/services/axiosInstances.ts` | Factory functions para axios |
60
+ | **Utility functions** | `src/utils/` | `genUuid`, `parseError`, `tryCatch` |
61
+ | **Routes/webhooks** | `src/routes/` | Express route handlers |
62
+ | **Error handling** | `src/errors/` | `Either`, `ErrorHandling` |
63
+
64
+ ### Fluxo de Dados Típico
65
+
66
+ ```
67
+ User Code
68
+
69
+ sdk.connection.getSingle(id) ← Layer 1: Public API
70
+
71
+ Connections.getSingle(id) ← Layer 2: Use Case (wraps with Try)
72
+
73
+ connectionMutations.getSingle ← Layer 3: Service (axios call)
74
+
75
+ api.get('/connections') ← External API
76
+
77
+ ConnectionEntity constructor ← Layer 4: Domain model
78
+
79
+ Either<Error, ConnectionEntity> ← Return to user
80
+ ```
81
+
82
+ ---
83
+
84
+ ## ⚡ Error Handling Funcional (OBRIGATÓRIO)
85
+
86
+ ### Either Monad Pattern
87
+
88
+ **REGRA FUNDAMENTAL**: Todas operações assíncronas DEVEM retornar `Either<Error, T>`. NUNCA use `throw`.
89
+
90
+ #### Definição do Either Type
91
+
92
+ ```typescript
93
+ // src/errors/Either.ts
94
+ export type Either<T, U> =
95
+ | { isError: T; isSuccess?: never }
96
+ | { isError?: never; isSuccess: U };
97
+
98
+ export const throwError = <T>(value: T): { isError: T } => ({ isError: value });
99
+ export const throwSuccess = <U>(value: U): { isSuccess: U } => ({
100
+ isSuccess: value,
101
+ });
102
+ ```
103
+
104
+ #### Try Wrapper (src/utils/tryCatch.ts)
105
+
106
+ Use `Try` para envolver qualquer operação que possa falhar:
107
+
108
+ ```typescript
109
+ import { Try } from "../utils/tryCatch";
110
+
111
+ // ✅ CORRETO
112
+ const result = await Try(connectionMutations.getSingle, connectionId);
113
+ if (result.isError) {
114
+ console.error("Failed:", result.isError);
115
+ return;
116
+ }
117
+ const connection = result.isSuccess; // Type-safe!
118
+ ```
119
+
120
+ #### Pattern Completo: Mutation → Use Case → Consumer
121
+
122
+ **1. Service Layer (Mutation)**
123
+
124
+ ```typescript
125
+ // src/services/mutations/connection.ts
126
+ import { throwError, throwSuccess } from "../../errors";
127
+ import { api } from "../axiosInstances";
128
+
129
+ export const connectionMutations = {
130
+ getSingle: async (id: string) => {
131
+ const { data } = await api.get(`/connections/${id}`);
132
+ return throwSuccess(new ConnectionEntity(data));
133
+ },
134
+ };
135
+ ```
136
+
137
+ **2. Use Case Layer**
138
+
139
+ ```typescript
140
+ // src/useCases/connection/index.ts
141
+ import { Try } from "../../utils/tryCatch";
142
+ import { IConnection } from "../../interfaces";
143
+
144
+ export class Connections implements IConnection {
145
+ getSingle = async (id: string) => {
146
+ return Try(connectionMutations.getSingle, id);
147
+ };
148
+ }
149
+ ```
150
+
151
+ **3. Consumer (User Code)**
152
+
153
+ ```typescript
154
+ const result = await sdk.connection.getSingle(connectionId);
155
+
156
+ if (result.isError) {
157
+ // Handle error - result.isError has error details
158
+ console.error("Failed to get connection:", result.isError);
159
+ return;
160
+ }
161
+
162
+ // Type-safe access to success value
163
+ const connection = result.isSuccess;
164
+ console.log("Phone:", connection.phone_number);
165
+ ```
166
+
167
+ ### Error Parsing
168
+
169
+ Use `parseError` para extrair mensagens consistentes:
170
+
171
+ ```typescript
172
+ // src/utils/parseError.ts
173
+ import { parseError } from "../utils";
174
+
175
+ const result = await someOperation();
176
+ if (result.isError) {
177
+ const { message, code } = parseError(result.isError, "Operation failed");
178
+ console.error(`[${code}] ${message}`);
179
+ }
180
+ ```
181
+
182
+ ### ❌ ANTI-PATTERNS de Error Handling
183
+
184
+ ```typescript
185
+ // ❌ NUNCA faça isso
186
+ async function badExample() {
187
+ throw new Error("Something failed"); // ❌ Não use throw
188
+ }
189
+
190
+ // ❌ NUNCA acesse .isSuccess sem checar .isError
191
+ const result = await operation();
192
+ console.log(result.isSuccess.id); // ❌ Pode crashear!
193
+
194
+ // ❌ NUNCA use try/catch diretamente (use Try wrapper)
195
+ try {
196
+ await externalCall();
197
+ } catch (err) {
198
+ // ❌ Pattern inconsistente
199
+ }
200
+
201
+ // ✅ CORRETO
202
+ async function goodExample() {
203
+ const result = await Try(externalOperation);
204
+ if (result.isError) return throwError(result.isError);
205
+ return throwSuccess(result.isSuccess);
206
+ }
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 📝 Convenções de Código (OBRIGATÓRIAS)
212
+
213
+ ### Naming Conventions
214
+
215
+ | Tipo | Pattern | Exemplos |
216
+ | --------------------- | --------------------------------------------- | -------------------------------------------- |
217
+ | **Entities** | PascalCase, campos `readonly` | `ConnectionEntity`, `MessageEntity` |
218
+ | **Interfaces** | `I` prefix + PascalCase | `IConnection`, `IMessage`, `IWebhook` |
219
+ | **Types** | camelCase + `Type` suffix | `contactType`, `flowMessageType`, `btnType` |
220
+ | **Enums** | PascalCase para enum, UPPER_CASE para valores | `enum Status { ACTIVE = 'ACTIVE' }` |
221
+ | **Files (classes)** | camelCase matching entity name | `connection.ts`, `messages.ts` |
222
+ | **Files (utilities)** | camelCase descriptive | `tryCatch.ts`, `parseError.ts`, `genUuid.ts` |
223
+ | **Folders** | camelCase or PascalCase by type | `useCases/`, `Entities/`, `elements/` |
224
+ | **Functions** | camelCase | `getSingle`, `sendMessage`, `updateToken` |
225
+ | **Constants** | UPPER_SNAKE_CASE | `API_META`, `VERIFY_TOKEN` |
226
+ | **Private fields** | camelCase with `_` prefix | `_connection`, `_axiosInstance` |
227
+ | **Props/Params** | camelCase | `phoneNumberId`, `accessToken`, `companyId` |
228
+
229
+ ### File Organization
230
+
231
+ **Regra: One Class Per File**
232
+
233
+ ```typescript
234
+ // ✅ CORRETO: src/entities/connection.ts
235
+ export class ConnectionEntity {
236
+ // Single class per file
237
+ }
238
+
239
+ // ❌ ERRADO: Múltiplas classes no mesmo arquivo
240
+ export class ConnectionEntity {}
241
+ export class MessageEntity {} // ❌ Separar em outro arquivo
242
+ ```
243
+
244
+ **Barrel Exports via index.ts**
245
+
246
+ ```typescript
247
+ // ✅ CORRETO: src/entities/index.ts
248
+ export * from "./connection";
249
+ export * from "./messages";
250
+ export * from "./webhooks";
251
+ export * from "./whatsappMessage";
252
+
253
+ // Permite imports limpos:
254
+ import { ConnectionEntity, MessageEntity } from "../entities";
255
+ ```
256
+
257
+ ### Import Organization
258
+
259
+ ```typescript
260
+ // 1. External dependencies
261
+ import axios from "axios";
262
+ import { EventEmitter } from "events";
263
+
264
+ // 2. Internal modules (absolute paths via tsconfig)
265
+ import { ConnectionEntity } from "../entities";
266
+ import { Try } from "../utils/tryCatch";
267
+
268
+ // 3. Types e interfaces
269
+ import type { IConnection, IMessage } from "../interfaces";
270
+ import type { WhatsappResponse } from "../types";
271
+ ```
272
+
273
+ ### Function Signatures
274
+
275
+ **Sempre inclua type annotations explícitas:**
276
+
277
+ ```typescript
278
+ // ✅ CORRETO
279
+ async function getSingle(id: string): Promise<Either<Error, ConnectionEntity>> {
280
+ return Try(connectionMutations.getSingle, id);
281
+ }
282
+
283
+ // ✅ CORRETO - Arrow function
284
+ const sendMessage = async (
285
+ params: SendMessageParams,
286
+ ): Promise<Either<Error, WhatsappResponse>> => {
287
+ return Try(messageMutations.send, params);
288
+ };
289
+
290
+ // ❌ ERRADO - Sem type annotations
291
+ async function getSingle(id) {
292
+ // ❌ Tipo inferido
293
+ return Try(connectionMutations.getSingle, id);
294
+ }
295
+ ```
296
+
297
+ ### Entity Fields (Imutabilidade)
298
+
299
+ **REGRA: Todos campos de entities são `readonly`**
300
+
301
+ ```typescript
302
+ // ✅ CORRETO
303
+ export class ConnectionEntity {
304
+ readonly _id: string;
305
+ readonly id: string;
306
+ readonly companyId: string;
307
+ readonly phone_number_id: string;
308
+ readonly waba_id: string;
309
+ readonly accessToken: string;
310
+ readonly systemUserAccessToken: string;
311
+ readonly businessAccountId: string;
312
+
313
+ constructor(data: any) {
314
+ Object.assign(this, data);
315
+ this.id = data.id || genId("connection");
316
+ }
317
+ }
318
+
319
+ // ❌ ERRADO - Campos mutáveis
320
+ export class ConnectionEntity {
321
+ _id: string; // ❌ Sem readonly
322
+ companyId: string; // ❌ Permite mutação acidental
323
+ }
324
+ ```
325
+
326
+ ### ID Generation Pattern
327
+
328
+ ```typescript
329
+ import { genId } from '../utils';
330
+
331
+ // ✅ CORRETO - Cada entity type tem seu prefix
332
+ const connectionId = genId("connection"); // → "CONNECTION_a1b2-34567"
333
+ const messageId = genId("message"); // → "MESSAGE_xyz9-12345"
334
+ const webhookId = genId("webhook"); // → "WEBHOOK_def3-98765"
335
+
336
+ // Entity constructor deve usar genId se ID não fornecido
337
+ constructor(data: any) {
338
+ Object.assign(this, data);
339
+ this.id = data.id || genId("connection"); // ✅ Fallback para novo ID
340
+ }
341
+ ```
342
+
343
+ ---
344
+
345
+ ## 🎨 Design Patterns do SDK
346
+
347
+ ### 1. Builder Pattern (Message Construction)
348
+
349
+ Todos os message builders em `src/elements/` usam fluent APIs:
350
+
351
+ ```typescript
352
+ import { List, Button, Text } from "@natyapp/meta/elements";
353
+
354
+ // ✅ Builder Pattern - Fluent API
355
+ const list = new List()
356
+ .setBody("Escolha uma opção:")
357
+ .setButtonText("Ver Menu")
358
+ .addSection("Produtos")
359
+ .addItem({
360
+ id: "prod1",
361
+ title: "Produto 1",
362
+ description: "Descrição do produto",
363
+ })
364
+ .addItem({ id: "prod2", title: "Produto 2" })
365
+ .addSection("Serviços")
366
+ .addItem({ id: "srv1", title: "Serviço A" })
367
+ .build();
368
+
369
+ // ✅ Button Builder
370
+ const buttons = new Button()
371
+ .setBodyText("Mensagem principal")
372
+ .addButton({ id: "btn1", title: "Opção 1" })
373
+ .addButton({ id: "btn2", title: "Opção 2" })
374
+ .build();
375
+ ```
376
+
377
+ **Pattern para criar novos builders:**
378
+
379
+ ```typescript
380
+ // src/elements/newType.ts
381
+ export class NewTypeBuilder {
382
+ private body: string = "";
383
+ private items: any[] = [];
384
+
385
+ setBody(text: string): this {
386
+ this.body = text;
387
+ return this; // ✅ Retorna 'this' para chaining
388
+ }
389
+
390
+ addItem(item: any): this {
391
+ this.items.push(item);
392
+ return this;
393
+ }
394
+
395
+ build(): object {
396
+ // Validação antes de retornar
397
+ if (!this.body) throw new Error("Body is required");
398
+
399
+ return {
400
+ type: "new_type",
401
+ body: { text: this.body },
402
+ items: this.items,
403
+ };
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### 2. Factory Pattern (Axios Instances)
409
+
410
+ Diferentes axios instances são criados por factory functions baseado no contexto:
411
+
412
+ ```typescript
413
+ // src/services/axiosInstances.ts
414
+
415
+ // ✅ Factory para mensagens WhatsApp
416
+ export const newAxiosInstance = ({
417
+ phone_number_id,
418
+ accessToken,
419
+ }: AxiosInstanceParams) => {
420
+ return axios.create({
421
+ baseURL: `https://graph.facebook.com/v18.0/${phone_number_id}`,
422
+ headers: {
423
+ "Content-Type": "application/json",
424
+ Authorization: `Bearer ${accessToken}`,
425
+ },
426
+ });
427
+ };
428
+
429
+ // ✅ Factory para download de media
430
+ export const axiosInstanceForDownloadMedia = ({ accessToken }: TokenParam) => {
431
+ return axios.create({
432
+ baseURL: "https://graph.facebook.com/v18.0",
433
+ headers: {
434
+ Authorization: `Bearer ${accessToken}`,
435
+ },
436
+ responseType: "arraybuffer",
437
+ });
438
+ };
439
+
440
+ // ✅ Factory para Facebook token refresh
441
+ export const newFacebookInstance = () => {
442
+ return axios.create({
443
+ baseURL: "https://graph.facebook.com/v18.0/oauth",
444
+ headers: { "Content-Type": "application/json" },
445
+ });
446
+ };
447
+ ```
448
+
449
+ **REGRA**: Nunca crie axios instances ad-hoc. Use factories existentes ou crie nova factory.
450
+
451
+ ### 3. Event Emitter Pattern (Webhooks)
452
+
453
+ ```typescript
454
+ // src/useCases/events/NatyEvents.ts
455
+ import { EventEmitter } from "events";
456
+
457
+ export class NatyEvents extends EventEmitter {
458
+ constructor() {
459
+ super();
460
+ }
461
+
462
+ emitMessage(data: WhatsappResponse): void {
463
+ this.emit("message", data);
464
+ }
465
+
466
+ emitConnection(event: connectionEvent): void {
467
+ this.emit("connection", event);
468
+ }
469
+
470
+ emitError(error: Error): void {
471
+ this.emit("error", error);
472
+ }
473
+ }
474
+
475
+ // ✅ Consumer usage
476
+ sdk.on("message", (data: WhatsappResponse) => {
477
+ console.log("New message:", data.id);
478
+ });
479
+
480
+ sdk.on("connection", (event) => {
481
+ console.log("Connection event:", event.type);
482
+ });
483
+
484
+ sdk.on("error", (error) => {
485
+ console.error("SDK error:", error.message);
486
+ });
487
+ ```
488
+
489
+ ### 4. Interface Implementation Pattern
490
+
491
+ **REGRA**: Interfaces definem contratos, classes implementam com Try wrapper
492
+
493
+ ```typescript
494
+ // 1. Definir interface
495
+ // src/interfaces/IConnection.ts
496
+ export interface IConnection {
497
+ getSingle(id: string): Promise<Either<Error, ConnectionEntity>>;
498
+ insert(data: ConnectionInsertData): Promise<Either<Error, ConnectionEntity>>;
499
+ update(
500
+ id: string,
501
+ data: ConnectionUpdateData,
502
+ ): Promise<Either<Error, ConnectionEntity>>;
503
+ }
504
+
505
+ // 2. Implementar com Try wrapper
506
+ // src/useCases/connection/index.ts
507
+ export class Connections implements IConnection {
508
+ getSingle = (id: string) => Try(connectionMutations.getSingle, id);
509
+
510
+ insert = (data: ConnectionInsertData) =>
511
+ Try(connectionMutations.insert, data);
512
+
513
+ update = (id: string, data: ConnectionUpdateData) =>
514
+ Try(connectionMutations.update, id, data);
515
+ }
516
+ ```
517
+
518
+ ---
519
+
520
+ ## 🌐 Regras de Negócio WhatsApp
521
+
522
+ ### 1. Janela de 24 Horas
523
+
524
+ **REGRA CRÍTICA**: Mensagens free-form só podem ser enviadas dentro de 24h da última mensagem do cliente.
525
+
526
+ ```typescript
527
+ // Fora da janela de 24h: OBRIGATÓRIO usar template
528
+ const result = await sdk.message.send_text_template({
529
+ companyId,
530
+ to: phoneNumber,
531
+ template: {
532
+ name: "hello_world",
533
+ language: { code: "pt_BR" },
534
+ },
535
+ });
536
+
537
+ // Dentro da janela: Pode usar mensagem livre
538
+ const result = await sdk.message.send_text_message({
539
+ companyId,
540
+ to: phoneNumber,
541
+ bodyText: "Mensagem personalizada",
542
+ });
543
+ ```
544
+
545
+ ### 2. Multi-Tenancy (companyId OBRIGATÓRIO)
546
+
547
+ **REGRA**: TODO método que acessa dados DEVE receber `companyId` para isolamento de tenant.
548
+
549
+ ```typescript
550
+ // ✅ CORRETO - companyId presente
551
+ await sdk.connection.getSingle(connectionId, { companyId });
552
+
553
+ await sdk.message.send_text_message({
554
+ companyId, // ✅ Obrigatório
555
+ to: phoneNumber,
556
+ bodyText: "Hello",
557
+ });
558
+
559
+ // ❌ ERRADO - Esqueceu companyId
560
+ await sdk.connection.getSingle(connectionId); // ❌ Faltando companyId
561
+ ```
562
+
563
+ **Pattern em mutations:**
564
+
565
+ ```typescript
566
+ // src/services/mutations/connection.ts
567
+ export const connectionMutations = {
568
+ getSingle: async (id: string, companyId?: string) => {
569
+ const params = companyId ? { companyId } : {};
570
+ const { data } = await api.get(`/connections/${id}`, { params });
571
+ return throwSuccess(new ConnectionEntity(data));
572
+ },
573
+ };
574
+ ```
575
+
576
+ ### 3. Token Refresh Automático
577
+
578
+ **REGRA**: SDK gerencia refresh automático. Ao criar `WhatsappResponse`, token é renovado se necessário.
579
+
580
+ ```typescript
581
+ // src/useCases/message/whatsappResponse.ts
582
+ export class WhatsappResponse {
583
+ private async getApiInstanceToken() {
584
+ // 1. Busca connection
585
+ const result = await this.getConnection({
586
+ companyId: this.companyId,
587
+ phoneNumberId: this.phone_number_id,
588
+ });
589
+
590
+ if (result.isError) return result.isError;
591
+
592
+ // 2. Exchange para long-lived token se necessário
593
+ const connection = result.isSuccess;
594
+ const tokenResult = await this.exchangeForLongLivedToken(connection);
595
+
596
+ // 3. Atualiza no banco
597
+ await this.updateConnectionToken(connection.id, tokenResult.access_token);
598
+
599
+ // 4. Cria axios instance com token atualizado
600
+ this._axiosInstance = newAxiosInstance({
601
+ phone_number_id: this.phone_number_id,
602
+ accessToken: tokenResult.access_token,
603
+ });
604
+ }
605
+ }
606
+ ```
607
+
608
+ ### 4. Formato de Phone Number
609
+
610
+ **REGRA**: Sempre use formato E.164 (`+5511999999999`)
611
+
612
+ ```typescript
613
+ // ✅ CORRETO - E.164 format
614
+ const phoneNumber = "+5511987654321";
615
+
616
+ // ❌ ERRADO - Formatos inválidos
617
+ const phoneNumber = "11987654321"; // ❌ Sem código do país
618
+ const phoneNumber = "(11) 98765-4321"; // ❌ Formatação
619
+ ```
620
+
621
+ **Nota**: SDK não valida formato. Aplicação consumidora é responsável pela validação.
622
+
623
+ ### 5. Webhook Deduplication
624
+
625
+ **REGRA**: Use `message.id` para evitar processar duplicatas (Meta pode enviar o mesmo webhook múltiplas vezes).
626
+
627
+ ```typescript
628
+ // ✅ CORRETO - Deduplicação com Set/Cache
629
+ const processedIds = new Set<string>();
630
+
631
+ sdk.on("message", async (data: WhatsappResponse) => {
632
+ if (processedIds.has(data.id)) {
633
+ console.log("Duplicate webhook ignored:", data.id);
634
+ return;
635
+ }
636
+
637
+ processedIds.add(data.id);
638
+
639
+ // Processar mensagem...
640
+ });
641
+ ```
642
+
643
+ ### 6. Rate Limits Meta (Informativo)
644
+
645
+ | Tier | Limite Diário | Obs |
646
+ | ------ | ------------- | -------------------- |
647
+ | Tier 1 | 1,000 msgs | Conta nova |
648
+ | Tier 2 | 10,000 msgs | Após verificação |
649
+ | Tier 3 | 100,000 msgs | Volume alto aprovado |
650
+
651
+ **SDK não implementa throttling**. Aplicação consumidora deve gerenciar rate limits se necessário.
652
+
653
+ ---
654
+
655
+ ## 🔧 Guidelines de Integração
656
+
657
+ ### Criar Nova Mutation
658
+
659
+ ```typescript
660
+ // 1. Adicionar em src/services/mutations/messages.ts
661
+ export const messageMutations = {
662
+ // ... existing mutations
663
+
664
+ deleteMessage: async (messageId: string, companyId: string) => {
665
+ const { data } = await api.delete(`/messages/${messageId}`, {
666
+ params: { companyId },
667
+ });
668
+ return throwSuccess(data);
669
+ },
670
+ };
671
+ ```
672
+
673
+ ### Criar Novo Use Case
674
+
675
+ ```typescript
676
+ // 2. Adicionar método na interface src/interfaces/IMessage.ts
677
+ export interface IMessage {
678
+ // ... existing methods
679
+ deleteMessage(
680
+ messageId: string,
681
+ companyId: string,
682
+ ): Promise<Either<Error, any>>;
683
+ }
684
+
685
+ // 3. Implementar em src/useCases/messages/index.ts
686
+ export class Messages implements IMessage {
687
+ // ... existing methods
688
+
689
+ deleteMessage = (messageId: string, companyId: string) =>
690
+ Try(messageMutations.deleteMessage, messageId, companyId);
691
+ }
692
+
693
+ // 4. Se for public API, adicionar no SDK principal
694
+ // src/useCases/sdk/index.ts
695
+ export class NatyMeta extends NatyEvents {
696
+ message: IMessage;
697
+
698
+ constructor() {
699
+ super();
700
+ this.message = new Messages();
701
+ }
702
+ }
703
+ ```
704
+
705
+ ### Estender Entity
706
+
707
+ ```typescript
708
+ // 1. Adicionar campo em src/entities/connection.ts
709
+ export class ConnectionEntity {
710
+ // ... existing readonly fields
711
+ readonly newField: string; // ✅ Sempre readonly
712
+ readonly createdAt?: Date;
713
+
714
+ constructor(data: any) {
715
+ Object.assign(this, data);
716
+ this.id = data.id || genId("connection");
717
+
718
+ // ✅ Transformações no constructor
719
+ if (data.createdAt) {
720
+ this.createdAt = new Date(data.createdAt);
721
+ }
722
+ }
723
+ }
724
+
725
+ // 2. Atualizar interface se necessário
726
+ // src/interfaces/IConnection.ts
727
+ export interface IConnectionData {
728
+ // ... existing fields
729
+ newField?: string;
730
+ createdAt?: string | Date;
731
+ }
732
+ ```
733
+
734
+ ### Criar Novo Message Builder
735
+
736
+ ```typescript
737
+ // src/elements/carousel.ts
738
+ export class Carousel {
739
+ private header: string = "";
740
+ private cards: any[] = [];
741
+
742
+ setHeader(text: string): this {
743
+ this.header = text;
744
+ return this;
745
+ }
746
+
747
+ addCard(card: { title: string; body: string; image?: string }): this {
748
+ this.cards.push(card);
749
+ return this;
750
+ }
751
+
752
+ build(): object {
753
+ if (!this.header) throw new Error("Header is required");
754
+ if (this.cards.length === 0) throw new Error("At least one card required");
755
+
756
+ return {
757
+ type: "carousel",
758
+ header: { text: this.header },
759
+ cards: this.cards,
760
+ };
761
+ }
762
+ }
763
+
764
+ // Exportar em src/elements/index.ts
765
+ export * from "./carousel";
766
+ ```
767
+
768
+ ### Configurar Axios Interceptor
769
+
770
+ ```typescript
771
+ // src/configs/axiosInterceptors.ts
772
+
773
+ // ✅ Adicionar novo header globalmente
774
+ export const updateInterceptor = (headers: Record<string, string>) => {
775
+ Object.assign(configInterceptors, headers);
776
+ };
777
+
778
+ // Usage:
779
+ updateInterceptor({
780
+ "x-access-token": appToken,
781
+ "x-custom-header": "value",
782
+ });
783
+ ```
784
+
785
+ ---
786
+
787
+ ## 🧪 Testing Guidelines
788
+
789
+ ### Configuração
790
+
791
+ ```javascript
792
+ // jest.config.js
793
+ module.exports = {
794
+ preset: "ts-jest",
795
+ testEnvironment: "node",
796
+ testMatch: ["**/*.spec.ts", "**/*.test.ts"],
797
+ collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
798
+ };
799
+ ```
800
+
801
+ ### Naming Conventions
802
+
803
+ - Test files: `*.spec.ts` ou `*.test.ts`
804
+ - Location: `Tests/` folder ou colocated com source
805
+ - Suite: `describe('EntityName ou functionName', () => {})`
806
+ - Cases: `it('should do something specific', () => {})`
807
+
808
+ ### Testes de Either Returns
809
+
810
+ ```typescript
811
+ // Tests/entities/connection.spec.ts
812
+ import { Connections } from "../../src/useCases/connection";
813
+ import { ConnectionEntity } from "../../src/Entities";
814
+
815
+ describe("Connections", () => {
816
+ let connections: Connections;
817
+
818
+ beforeEach(() => {
819
+ connections = new Connections();
820
+ });
821
+
822
+ it("should return ConnectionEntity on success", async () => {
823
+ const result = await connections.getSingle("CONNECTION_123");
824
+
825
+ expect(result.isError).toBeUndefined();
826
+ expect(result.isSuccess).toBeDefined();
827
+ expect(result.isSuccess).toBeInstanceOf(ConnectionEntity);
828
+ expect(result.isSuccess?.id).toBe("CONNECTION_123");
829
+ });
830
+
831
+ it("should return error on failure", async () => {
832
+ const result = await connections.getSingle("INVALID_ID");
833
+
834
+ expect(result.isSuccess).toBeUndefined();
835
+ expect(result.isError).toBeDefined();
836
+ expect(result.isError).toHaveProperty("message");
837
+ });
838
+ });
839
+ ```
840
+
841
+ ### Mocking External APIs
842
+
843
+ ```typescript
844
+ // ✅ Mock axios
845
+ jest.mock("../../src/services/axiosInstances", () => ({
846
+ api: {
847
+ get: jest.fn(),
848
+ post: jest.fn(),
849
+ put: jest.fn(),
850
+ delete: jest.fn(),
851
+ },
852
+ }));
853
+
854
+ import { api } from "../../src/services/axiosInstances";
855
+
856
+ describe("ConnectionMutations", () => {
857
+ it("should call API with correct params", async () => {
858
+ const mockData = { id: "CONNECTION_123", phone_number_id: "123456" };
859
+ (api.get as jest.Mock).mockResolvedValue({ data: mockData });
860
+
861
+ const result = await connectionMutations.getSingle("CONNECTION_123");
862
+
863
+ expect(api.get).toHaveBeenCalledWith("/connections/CONNECTION_123");
864
+ expect(result.isSuccess).toEqual(expect.objectContaining(mockData));
865
+ });
866
+ });
867
+ ```
868
+
869
+ ### Testing Builders
870
+
871
+ ```typescript
872
+ describe("List Builder", () => {
873
+ it("should build valid list structure", () => {
874
+ const list = new List()
875
+ .setBody("Choose option")
876
+ .setButtonText("Select")
877
+ .addSection("Section 1")
878
+ .addItem({ id: "item1", title: "Item 1" })
879
+ .build();
880
+
881
+ expect(list).toHaveProperty("type", "list");
882
+ expect(list).toHaveProperty("body");
883
+ expect(list).toHaveProperty("action.button", "Select");
884
+ expect(list).toHaveProperty("action.sections");
885
+ expect(list.action.sections).toHaveLength(1);
886
+ });
887
+
888
+ it("should throw error if required fields missing", () => {
889
+ expect(() => {
890
+ new List().build(); // Missing body
891
+ }).toThrow();
892
+ });
893
+ });
894
+ ```
895
+
896
+ ---
897
+
898
+ ## ❌ Anti-Patterns (EVITAR)
899
+
900
+ ### Error Handling
901
+
902
+ ```typescript
903
+ // ❌ NUNCA use throw diretamente
904
+ function badFunction() {
905
+ if (error) throw new Error("Failed");
906
+ }
907
+
908
+ // ✅ USE Either
909
+ function goodFunction() {
910
+ if (error) return throwError(new Error("Failed"));
911
+ return throwSuccess(data);
912
+ }
913
+
914
+ // ❌ NUNCA acesse .isSuccess sem checar .isError
915
+ const result = await operation();
916
+ console.log(result.isSuccess.id); // ❌ Crashea se houver erro!
917
+
918
+ // ✅ SEMPRE cheque primeiro
919
+ if (result.isError) return;
920
+ console.log(result.isSuccess.id); // ✅ Type-safe
921
+
922
+ // ❌ NUNCA use try/catch direto (use Try wrapper)
923
+ try {
924
+ await externalCall();
925
+ } catch (err) {
926
+ // ❌ Inconsistente
927
+ }
928
+
929
+ // ✅ USE Try wrapper
930
+ const result = await Try(externalCall);
931
+ if (result.isError) {
932
+ /* handle */
933
+ }
934
+ ```
935
+
936
+ ### Mutabilidade
937
+
938
+ ```typescript
939
+ // ❌ NUNCA mute entities
940
+ connection.phone_number_id = "new_value"; // ❌ Fields são readonly
941
+
942
+ // ✅ Crie nova instância
943
+ const updated = new ConnectionEntity({
944
+ ...connection,
945
+ phone_number_id: "new_value",
946
+ });
947
+ ```
948
+
949
+ ### Axios Instances
950
+
951
+ ```typescript
952
+ // ❌ NUNCA crie axios ad-hoc
953
+ const api = axios.create({ baseURL: "..." }); // ❌ Inconsistente
954
+
955
+ // ✅ USE factories
956
+ const api = newAxiosInstance({ phone_number_id, accessToken });
957
+ ```
958
+
959
+ ### Multi-Tenancy
960
+
961
+ ```typescript
962
+ // ❌ NUNCA esqueça companyId
963
+ await connection.getSingle(id); // ❌ Quebra isolamento
964
+
965
+ // ✅ SEMPRE inclua companyId
966
+ await connection.getSingle(id, { companyId });
967
+ ```
968
+
969
+ ### Imports
970
+
971
+ ```typescript
972
+ // ❌ NUNCA importe tudo
973
+ import * as everything from "./module"; // ❌ Poluição de namespace
974
+
975
+ // ✅ Seja específico
976
+ import { ConnectionEntity, MessageEntity } from "./entities";
977
+ ```
978
+
979
+ ### Hardcoding
980
+
981
+ ```typescript
982
+ // ❌ NUNCA hardcode URLs ou tokens
983
+ const url = "https://graph.facebook.com/v18.0"; // ❌
984
+
985
+ // ✅ USE variáveis de ambiente
986
+ const url = process.env.META_API_URL || "https://graph.facebook.com/v18.0";
987
+ ```
988
+
989
+ ---
990
+
991
+ ## 📚 Exemplos Completos End-to-End
992
+
993
+ ### Exemplo 1: Criar Método para Enviar Reação a Mensagem
994
+
995
+ **Objetivo**: Adicionar funcionalidade `send_reaction` para reagir a mensagens.
996
+
997
+ #### Passo 1: Criar Mutation
998
+
999
+ ```typescript
1000
+ // src/services/mutations/messages.ts
1001
+ export const messageMutations = {
1002
+ // ... existing methods
1003
+
1004
+ sendReaction: async (params: {
1005
+ phone_number_id: string;
1006
+ accessToken: string;
1007
+ to: string;
1008
+ message_id: string;
1009
+ emoji: string;
1010
+ }) => {
1011
+ const axiosInstance = newAxiosInstance({
1012
+ phone_number_id: params.phone_number_id,
1013
+ accessToken: params.accessToken,
1014
+ });
1015
+
1016
+ const payload = {
1017
+ messaging_product: "whatsapp",
1018
+ recipient_type: "individual",
1019
+ to: params.to,
1020
+ type: "reaction",
1021
+ reaction: {
1022
+ message_id: params.message_id,
1023
+ emoji: params.emoji,
1024
+ },
1025
+ };
1026
+
1027
+ const { data } = await axiosInstance.post("/messages", payload);
1028
+ return throwSuccess(data);
1029
+ },
1030
+ };
1031
+ ```
1032
+
1033
+ #### Passo 2: Adicionar Interface
1034
+
1035
+ ```typescript
1036
+ // src/interfaces/IMessage.ts
1037
+ export interface IMessage {
1038
+ // ... existing methods
1039
+
1040
+ send_reaction(params: {
1041
+ companyId: string;
1042
+ to: string;
1043
+ message_id: string;
1044
+ emoji: string;
1045
+ }): Promise<Either<Error, any>>;
1046
+ }
1047
+ ```
1048
+
1049
+ #### Passo 3: Implementar Use Case
1050
+
1051
+ ```typescript
1052
+ // src/useCases/messages/index.ts
1053
+ export class Messages implements IMessage {
1054
+ // ... existing methods
1055
+
1056
+ send_reaction = async (params: {
1057
+ companyId: string;
1058
+ to: string;
1059
+ message_id: string;
1060
+ emoji: string;
1061
+ }) => {
1062
+ // 1. Obter connection
1063
+ const connectionResult = await this.getConnection({
1064
+ companyId: params.companyId,
1065
+ to: params.to,
1066
+ });
1067
+
1068
+ if (connectionResult.isError) return connectionResult;
1069
+ const connection = connectionResult.isSuccess;
1070
+
1071
+ // 2. Chamar mutation com Try wrapper
1072
+ return Try(messageMutations.sendReaction, {
1073
+ phone_number_id: connection.phone_number_id,
1074
+ accessToken: connection.accessToken,
1075
+ to: params.to,
1076
+ message_id: params.message_id,
1077
+ emoji: params.emoji,
1078
+ });
1079
+ };
1080
+ }
1081
+ ```
1082
+
1083
+ #### Passo 4: Usar na Aplicação
1084
+
1085
+ ```typescript
1086
+ // Consumer code
1087
+ const result = await sdk.message.send_reaction({
1088
+ companyId: "COMPANY_123",
1089
+ to: "+5511987654321",
1090
+ message_id: "wamid.HBgLNT...",
1091
+ emoji: "👍",
1092
+ });
1093
+
1094
+ if (result.isError) {
1095
+ console.error("Failed to send reaction:", result.isError);
1096
+ return;
1097
+ }
1098
+
1099
+ console.log("Reaction sent:", result.isSuccess);
1100
+ ```
1101
+
1102
+ ### Exemplo 2: Criar Handler para Novo Tipo de Webhook
1103
+
1104
+ **Objetivo**: Adicionar handler para eventos de status de mensagem.
1105
+
1106
+ #### Passo 1: Criar Route Handler
1107
+
1108
+ ```typescript
1109
+ // src/routes/webhooks/methods/messageStatus.ts
1110
+ import { Request, Response } from "express";
1111
+ import { NatyEvents } from "../../../useCases/events/NatyEvents";
1112
+
1113
+ export const messageStatusHandler = (events: NatyEvents) => {
1114
+ return async (req: Request, res: Response) => {
1115
+ try {
1116
+ const { entry } = req.body;
1117
+
1118
+ for (const change of entry[0].changes) {
1119
+ const { statuses } = change.value;
1120
+
1121
+ if (!statuses) continue;
1122
+
1123
+ for (const status of statuses) {
1124
+ // Emitir evento
1125
+ events.emit("message_status", {
1126
+ id: status.id,
1127
+ status: status.status, // sent, delivered, read
1128
+ timestamp: status.timestamp,
1129
+ recipient_id: status.recipient_id,
1130
+ });
1131
+ }
1132
+ }
1133
+
1134
+ res.sendStatus(200);
1135
+ } catch (error) {
1136
+ console.error("Message status webhook error:", error);
1137
+ res.sendStatus(500);
1138
+ }
1139
+ };
1140
+ };
1141
+ ```
1142
+
1143
+ #### Passo 2: Registrar Route
1144
+
1145
+ ```typescript
1146
+ // src/routes/webhooks/index.ts
1147
+ import { messageStatusHandler } from "./methods/messageStatus";
1148
+
1149
+ export const setupWebhookRoutes = (app: Express, events: NatyEvents) => {
1150
+ // ... existing routes
1151
+
1152
+ app.post("/webhooks/message-status", messageStatusHandler(events));
1153
+ };
1154
+ ```
1155
+
1156
+ #### Passo 3: Adicionar Type
1157
+
1158
+ ```typescript
1159
+ // src/types/whatsappTypes.ts
1160
+ export type MessageStatusEvent = {
1161
+ id: string;
1162
+ status: "sent" | "delivered" | "read" | "failed";
1163
+ timestamp: string;
1164
+ recipient_id: string;
1165
+ };
1166
+ ```
1167
+
1168
+ #### Passo 4: Usar na Aplicação
1169
+
1170
+ ```typescript
1171
+ // Consumer code
1172
+ sdk.on("message_status", (event: MessageStatusEvent) => {
1173
+ console.log(`Message ${event.id} is now ${event.status}`);
1174
+
1175
+ // Atualizar status no banco de dados
1176
+ if (event.status === "read") {
1177
+ markAsRead(event.id);
1178
+ }
1179
+ });
1180
+ ```
1181
+
1182
+ ### Exemplo 3: Estender ConnectionEntity com Metadata
1183
+
1184
+ **Objetivo**: Adicionar campo `metadata` para dados customizados.
1185
+
1186
+ #### Passo 1: Atualizar Entity
1187
+
1188
+ ```typescript
1189
+ // src/entities/connection.ts
1190
+ export class ConnectionEntity {
1191
+ // ... existing readonly fields
1192
+ readonly metadata?: Record<string, any>; // ✅ Novo campo
1193
+
1194
+ constructor(data: any) {
1195
+ Object.assign(this, data);
1196
+ this.id = data.id || genId("connection");
1197
+
1198
+ // ✅ Parse metadata se for string
1199
+ if (typeof data.metadata === "string") {
1200
+ try {
1201
+ this.metadata = JSON.parse(data.metadata);
1202
+ } catch {
1203
+ this.metadata = {};
1204
+ }
1205
+ }
1206
+ }
1207
+ }
1208
+ ```
1209
+
1210
+ #### Passo 2: Atualizar Interface
1211
+
1212
+ ```typescript
1213
+ // src/interfaces/IConnection.ts
1214
+ export interface IConnectionData {
1215
+ // ... existing fields
1216
+ metadata?: Record<string, any>;
1217
+ }
1218
+
1219
+ export interface IConnection {
1220
+ // ... existing methods
1221
+
1222
+ updateMetadata(
1223
+ id: string,
1224
+ metadata: Record<string, any>,
1225
+ companyId: string,
1226
+ ): Promise<Either<Error, ConnectionEntity>>;
1227
+ }
1228
+ ```
1229
+
1230
+ #### Passo 3: Adicionar Mutation
1231
+
1232
+ ```typescript
1233
+ // src/services/mutations/connection.ts
1234
+ export const connectionMutations = {
1235
+ // ... existing methods
1236
+
1237
+ updateMetadata: async (
1238
+ id: string,
1239
+ metadata: Record<string, any>,
1240
+ companyId: string,
1241
+ ) => {
1242
+ const { data } = await api.put(
1243
+ `/connections/${id}`,
1244
+ { metadata },
1245
+ { params: { companyId } },
1246
+ );
1247
+ return throwSuccess(new ConnectionEntity(data));
1248
+ },
1249
+ };
1250
+ ```
1251
+
1252
+ #### Passo 4: Implementar Use Case
1253
+
1254
+ ```typescript
1255
+ // src/useCases/connection/index.ts
1256
+ export class Connections implements IConnection {
1257
+ // ... existing methods
1258
+
1259
+ updateMetadata = (
1260
+ id: string,
1261
+ metadata: Record<string, any>,
1262
+ companyId: string,
1263
+ ) => Try(connectionMutations.updateMetadata, id, metadata, companyId);
1264
+ }
1265
+ ```
1266
+
1267
+ #### Passo 5: Usar na Aplicação
1268
+
1269
+ ```typescript
1270
+ // Consumer code
1271
+ const result = await sdk.connection.updateMetadata(
1272
+ "CONNECTION_123",
1273
+ {
1274
+ department: "Sales",
1275
+ responsible: "john@company.com",
1276
+ custom_field: "value",
1277
+ },
1278
+ "COMPANY_456",
1279
+ );
1280
+
1281
+ if (result.isError) {
1282
+ console.error("Failed to update metadata:", result.isError);
1283
+ return;
1284
+ }
1285
+
1286
+ console.log("Metadata updated:", result.isSuccess.metadata);
1287
+ ```
1288
+
1289
+ ---
1290
+
1291
+ ## 📖 Referências à Documentação
1292
+
1293
+ ### Documentação Principal
1294
+
1295
+ - [Visão Geral](../docs/01-visao-geral.md) - Propósito e escopo do SDK
1296
+ - [Contexto de Negócio](../docs/02-contexto-negocio.md) - Domínio WhatsApp Business
1297
+ - [Arquitetura](../docs/03-arquitetura.md) - Detalhes das 4 camadas
1298
+ - [Fluxos Funcionais](../docs/04-fluxos-funcionais.md) - Sequências de operações
1299
+ - [Integrações](../docs/05-integracoes.md) - APIs externas (Meta, Naty)
1300
+ - [Entidades](../docs/06-entidades.md) - Domain models documentados
1301
+ - [Guia Prático](../docs/07-guia-pratico.md) - Exemplos de uso
1302
+ - [Troubleshooting](../docs/08-troubleshooting.md) - Problemas comuns
1303
+
1304
+ ### Arquivos Chave de Referência
1305
+
1306
+ - [src/index.ts](../src/index.ts) - API pública exportada
1307
+ - [src/interfaces/ISdk.ts](../src/interfaces/ISdk.ts) - Contrato principal do SDK
1308
+ - [src/errors/Either.ts](../src/errors/Either.ts) - Implementação do Either monad
1309
+ - [src/utils/tryCatch.ts](../src/utils/tryCatch.ts) - Try wrapper
1310
+ - [src/useCases/sdk/index.ts](../src/useCases/sdk/index.ts) - Classe NatyMeta
1311
+ - [tsconfig.json](../tsconfig.json) - Configuração TypeScript
1312
+ - [jest.config.js](../jest.config.js) - Configuração de testes
1313
+
1314
+ ---
1315
+
1316
+ ## 🎯 Checklist para Novos Desenvolvedores
1317
+
1318
+ Ao contribuir com o SDK, verifique:
1319
+
1320
+ - [ ] Todos métodos async retornam `Either<Error, T>`
1321
+ - [ ] Usou `Try` wrapper ao invés de try/catch
1322
+ - [ ] Entities têm campos `readonly`
1323
+ - [ ] Incluiu `companyId` em todas operações que acessam dados
1324
+ - [ ] Nomes seguem convenções (interfaces com `I`, types com `Type`, etc.)
1325
+ - [ ] Criou barrel export (`index.ts`) se adicionou novo módulo
1326
+ - [ ] Usou factory para axios instances (não criou ad-hoc)
1327
+ - [ ] Builders usam fluent API (retornam `this`)
1328
+ - [ ] Adicionou type annotations explícitas
1329
+ - [ ] Testou com Jest (coverage > 70%)
1330
+ - [ ] Documentou parâmetros complexos com JSDoc
1331
+ - [ ] Validou formato E.164 se aceita phone numbers (ou documentou)
1332
+ - [ ] Tratou casos de erro com parseError
1333
+ - [ ] Evitou hardcode de URLs/tokens (usou env vars)
1334
+
1335
+ ---
1336
+
1337
+ ## 🚀 Comandos Úteis
1338
+
1339
+ ```bash
1340
+ # Desenvolvimento
1341
+ npm run dev # Inicia com nodemon (hot reload)
1342
+
1343
+ # Build
1344
+ npm run build # Compila TypeScript para dist/
1345
+
1346
+ # Testes
1347
+ npm run test # Executa Jest em watch mode
1348
+ npm run test:ci # Executa testes uma vez (CI/CD)
1349
+
1350
+ # Linting
1351
+ npm run lint # ESLint check
1352
+ npm run lint:fix # ESLint auto-fix
1353
+
1354
+ # Type Check
1355
+ npm run type-check # TypeScript compiler check (sem emitir arquivos)
1356
+ ```
1357
+
1358
+ ---
1359
+
1360
+ ## � Logging Guidelines
1361
+
1362
+ ### Logger Integration
1363
+
1364
+ O SDK suporta logging configurável via injeção de dependência. Um logger customizado pode ser fornecido ao inicializar o SDK, caso contrário, um console logger padrão é usado.
1365
+
1366
+ #### Logger Interface
1367
+
1368
+ ```typescript
1369
+ // src/interfaces/ILogger.ts
1370
+ export interface ILogger {
1371
+ log(
1372
+ level: "debug" | "info" | "warn" | "error",
1373
+ message: string,
1374
+ meta?: Record<string, any>,
1375
+ ): void;
1376
+ }
1377
+ ```
1378
+
1379
+ #### Onde Usar Logger
1380
+
1381
+ | Contexto | Nível | Quando Usar |
1382
+ | ---------------------- | ----- | ------------------------------------------------- |
1383
+ | **SDK Initialization** | info | NatyMeta constructor, connect() |
1384
+ | **HTTP Requests** | debug | Axios interceptors (automático) |
1385
+ | **HTTP Responses** | debug | Axios interceptors (automático) |
1386
+ | **HTTP Errors** | error | Axios interceptors (automático) |
1387
+ | **Token Refresh** | info | WhatsappResponse.getApiInstanceToken() |
1388
+ | **Message Sent** | info | Após envio bem-sucedido (send_text_message, etc.) |
1389
+ | **Message Failed** | error | Catch blocks em métodos de envio |
1390
+ | **Webhook Received** | info | Webhook handlers (messages, connection) |
1391
+ | **Webhook Error** | error | Catch blocks em webhook handlers |
1392
+ | **Event Emitted** | debug | NatyEvents.emit() |
1393
+ | **Operation Failed** | error | Try wrapper catch block |
1394
+
1395
+ #### Padrões de Uso
1396
+
1397
+ ```typescript
1398
+ // ✅ CORRETO - Info para operações principais
1399
+ this.logger.log("info", "Sending text message", {
1400
+ to: this.clientNumber,
1401
+ textLength: text.length,
1402
+ });
1403
+
1404
+ // ✅ CORRETO - Debug para detalhes técnicos
1405
+ this.logger.log("debug", "HTTP Request", {
1406
+ method: "POST",
1407
+ url: "/messages",
1408
+ hasData: true,
1409
+ });
1410
+
1411
+ // ✅ CORRETO - Error com contexto útil
1412
+ this.logger.log("error", "Failed to send message", {
1413
+ to: this.clientNumber,
1414
+ error: err.message,
1415
+ code: err.code,
1416
+ });
1417
+
1418
+ // ❌ ERRADO - Logging sem contexto
1419
+ this.logger.log("error", "Error occurred");
1420
+
1421
+ // ❌ ERRADO - Expor dados sensíveis
1422
+ this.logger.log("debug", "Token", {
1423
+ accessToken: token, // ❌ Nunca logue tokens
1424
+ });
1425
+ ```
1426
+
1427
+ #### Segurança
1428
+
1429
+ **REGRA**: Nunca logue dados sensíveis diretamente. Use sanitização:
1430
+
1431
+ ```typescript
1432
+ // ✅ CORRETO - Headers sanitizados
1433
+ function sanitizeHeaders(headers: any): any {
1434
+ const sanitized = { ...headers };
1435
+ if (sanitized.Authorization) {
1436
+ sanitized.Authorization = "Bearer [REDACTED]";
1437
+ }
1438
+ if (sanitized["x-access-token"]) {
1439
+ sanitized["x-access-token"] = "[REDACTED]";
1440
+ }
1441
+ return sanitized;
1442
+ }
1443
+
1444
+ logger.log("debug", "HTTP Request", {
1445
+ headers: sanitizeHeaders(config.headers),
1446
+ });
1447
+ ```
1448
+
1449
+ #### Acesso ao Logger
1450
+
1451
+ **Context Global:**
1452
+
1453
+ ```typescript
1454
+ import { getGlobalLogger } from "../utils/loggerContext";
1455
+
1456
+ const logger = getGlobalLogger();
1457
+ logger.log("info", "Operation started");
1458
+ ```
1459
+
1460
+ **Injetado via Constructor:**
1461
+
1462
+ ```typescript
1463
+ export class WhatsappResponse {
1464
+ private logger: ILogger;
1465
+
1466
+ constructor(..., logger?: ILogger) {
1467
+ this.logger = logger || getGlobalLogger();
1468
+ }
1469
+ }
1470
+ ```
1471
+
1472
+ #### Criando Adapter para Logger Externo
1473
+
1474
+ ```typescript
1475
+ // Exemplo: Adapter para Pino
1476
+ export function createPinoAdapter(pinoLogger: any): ILogger {
1477
+ return {
1478
+ log(level, message, meta) {
1479
+ if (meta) {
1480
+ pinoLogger[level](meta, message);
1481
+ } else {
1482
+ pinoLogger[level](message);
1483
+ }
1484
+ },
1485
+ };
1486
+ }
1487
+ ```
1488
+
1489
+ #### Best Practices
1490
+
1491
+ 1. **Use níveis apropriados**:
1492
+ - `debug`: Detalhes técnicos (HTTP, tokens refresh, state changes)
1493
+ - `info`: Operações principais (mensagens enviadas, webhooks recebidos)
1494
+ - `warn`: Situações anormais mas não críticas
1495
+ - `error`: Falhas que impedem operação
1496
+
1497
+ 2. **Inclua contexto útil**:
1498
+ - IDs relevantes (`companyId`, `messageId`, `connectionId`)
1499
+ - Parâmetros de entrada (sanitizados)
1500
+ - Estado relevante antes/depois
1501
+ - Stack trace em errors (via meta)
1502
+
1503
+ 3. **Evite noise excessivo**:
1504
+ - Não logue em loops high-frequency
1505
+ - Use `debug` para logs verbose
1506
+ - Considere sampling para operações muito frequentes
1507
+
1508
+ 4. **Estruture metadata consistentemente**:
1509
+
1510
+ ```typescript
1511
+ // ✅ CORRETO - Estrutura consistente
1512
+ logger.log("info", "Message sent", {
1513
+ operation: "send_message",
1514
+ to: phoneNumber,
1515
+ messageType: "text",
1516
+ messageId: response.id,
1517
+ duration: Date.now() - startTime,
1518
+ });
1519
+ ```
1520
+
1521
+ 5. **Logger é opcional**: SDK deve funcionar sem logger customizado (fallback para console)
1522
+
1523
+ ---
1524
+
1525
+ ## �💡 Dicas Finais
1526
+
1527
+ 1. **Leia a documentação em docs/** antes de fazer mudanças significativas
1528
+ 2. **Consulte código existente** para entender patterns na prática
1529
+ 3. **Either é obrigatório** - não há exceções a essa regra
1530
+ 4. **Multi-tenancy é crítico** - nunca esqueça companyId
1531
+ 5. **Imutabilidade ajuda debug** - entities readonly previnem bugs sutis
1532
+ 6. **Type safety é seu amigo** - não use `any` sem justificativa forte
1533
+ 7. **Teste seus changes** - Jest deve passar em todos os casos
1534
+ 8. **Documente APIs públicas** - JSDoc para parâmetros não óbvios
1535
+
1536
+ ---
1537
+
1538
+ **Versão**: 1.0.0
1539
+ **Última Atualização**: Fevereiro 2026
1540
+ **Mantido por**: Time @natyapp/meta