@adatechnology/meta-whatsapp-module 0.2.0-rc.3 → 0.2.0-rc.30
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/dist/index.cjs +1613 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1685 -139
- package/dist/index.d.ts +1685 -139
- package/dist/index.js +1575 -132
- package/dist/index.js.map +1 -1
- package/dist/migrations/0004_widen_message_type.sql +1 -0
- package/dist/migrations/0005_message_moderation.sql +3 -0
- package/dist/migrations/0006_conversation_documents.sql +19 -0
- package/dist/migrations/0007_flow_media.sql +18 -0
- package/dist/migrations/0008_message_transcription.sql +5 -0
- package/dist/migrations/0009_settings_transcription_policy.sql +2 -0
- package/dist/migrations/meta/_journal.json +43 -1
- package/dist/testing/index.cjs +222 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +74 -0
- package/dist/testing/index.d.ts +74 -0
- package/dist/testing/index.js +186 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +16 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
2
2
|
import { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
|
|
3
3
|
import * as _adatechnology_meta_whatsapp_contracts from '@adatechnology/meta-whatsapp-contracts';
|
|
4
|
-
import { SessionState, SessionMode, ConversationSummary,
|
|
4
|
+
import { TranscriptionMode, SessionState, SessionMode, ConversationSummary, MessageDirection, MessageSender, MessageStatus, CacheInterface, FlowGraphData, FlowGraphSummary, LiveFlowPosition, WhatsAppSettings, RealtimeNotifierInterface, ChannelAdapterInterface, ObjectStorageInterface, FlowActionKind, FlowActionHandler, ConversationSession, WhatsAppMessage, WhatsAppStatus, MetaWhatsAppHooks, SubjectResolverInterface, CatalogPort } from '@adatechnology/meta-whatsapp-contracts';
|
|
5
|
+
export { PREVIEW_MEDIA_ID_PREFIX, TranscriptionMode, resolvePreviewUploadId, toPreviewMediaId } from '@adatechnology/meta-whatsapp-contracts';
|
|
5
6
|
import { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider';
|
|
7
|
+
export { WEBHOOK_CLAIM_TTL_SECONDS, WEBHOOK_NONCE_TTL_SECONDS } from '@adatechnology/meta-graph-core';
|
|
6
8
|
|
|
7
9
|
type MetaWhatsAppDatabase = PgDatabase<PgQueryResultHKT, any, any>;
|
|
8
10
|
type DrizzleMigrateFunction = (db: never, config: {
|
|
@@ -10,6 +12,72 @@ type DrizzleMigrateFunction = (db: never, config: {
|
|
|
10
12
|
migrationsTable?: string;
|
|
11
13
|
}) => Promise<void>;
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Vocabulário de transcrição de áudio do módulo.
|
|
17
|
+
*
|
|
18
|
+
* Fica em arquivo próprio porque o schema e os dois use-cases (ingestão automática e sob demanda)
|
|
19
|
+
* precisam dos mesmos tipos, e pendurá-los em qualquer um dos três faria os outros dois importarem
|
|
20
|
+
* de dentro de uma camada que não é a deles.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
declare const TRANSCRIPTION_STATUS: {
|
|
24
|
+
/** Falhou de forma retriável (cota, rede, 5xx) — vai sair quando alguém tentar de novo. */
|
|
25
|
+
readonly PENDING: "pending";
|
|
26
|
+
/** Processado. Texto vazio aqui é áudio em silêncio, e NÃO deve ser reprocessado. */
|
|
27
|
+
readonly DONE: "done";
|
|
28
|
+
/** Falha definitiva do engine (credencial, áudio corrompido, arquivo grande demais). */
|
|
29
|
+
readonly FAILED: "failed";
|
|
30
|
+
/** Nenhum engine da cadeia aceita o formato. Retentar não conserta codec. */
|
|
31
|
+
readonly UNSUPPORTED: "unsupported";
|
|
32
|
+
};
|
|
33
|
+
type TranscriptionStatus = (typeof TRANSCRIPTION_STATUS)[keyof typeof TRANSCRIPTION_STATUS];
|
|
34
|
+
/**
|
|
35
|
+
* Quando transcrever.
|
|
36
|
+
*
|
|
37
|
+
* `auto` transcreve durante a ingestão da mídia, onde o buffer do áudio JÁ está em memória — não
|
|
38
|
+
* custa um segundo download do storage. `onDemand` só transcreve quando o atendente pede, o que
|
|
39
|
+
* troca latência na interface por não gastar cota com áudio que ninguém vai ler.
|
|
40
|
+
*
|
|
41
|
+
* O tipo vem do contrato (o painel escolhe, a API transporta, o módulo obedece) e `satisfies`
|
|
42
|
+
* garante em tempo de compilação que estes valores continuam sendo exatamente os de lá — sem isso,
|
|
43
|
+
* um modo novo no contrato passaria despercebido aqui.
|
|
44
|
+
*/
|
|
45
|
+
declare const TRANSCRIPTION_MODE: {
|
|
46
|
+
readonly AUTO: "auto";
|
|
47
|
+
readonly ON_DEMAND: "onDemand";
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* O contrato mínimo de `@adatechnology/audio-transcription-provider`, declarado aqui em vez de
|
|
52
|
+
* importado — mesma decisão do `MessageModerator` em `LogMessage.use-case.ts`.
|
|
53
|
+
*
|
|
54
|
+
* Assim o módulo não ganha dependência de pacote por um recurso opcional, e transcrição não fica
|
|
55
|
+
* amarrada a WhatsApp: o que atravessa esta fronteira é um buffer de áudio e um mime.
|
|
56
|
+
*/
|
|
57
|
+
type AudioTranscriber = {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
transcribe: (input: {
|
|
60
|
+
buffer: Buffer;
|
|
61
|
+
mimeType: string;
|
|
62
|
+
languageHint?: string;
|
|
63
|
+
}) => Promise<{
|
|
64
|
+
text: string;
|
|
65
|
+
language?: string;
|
|
66
|
+
durationSeconds?: number;
|
|
67
|
+
engine: string;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Recorte do erro do provider que o módulo precisa ler para escolher entre `'pending'` e
|
|
72
|
+
* `'failed'`. Estrutural, não `instanceof`: o provider é opcional e pode nem estar instalado, e um
|
|
73
|
+
* `instanceof` contra classe ausente não compila.
|
|
74
|
+
*/
|
|
75
|
+
declare function isRetriableTranscriptionError(error: unknown): boolean;
|
|
76
|
+
declare function isUnsupportedTranscriptionError(error: unknown): boolean;
|
|
77
|
+
declare function transcriptionRetryAfterSeconds(error: unknown): number | undefined;
|
|
78
|
+
/** Mime de áudio? Só áudio é transcrito — vídeo, imagem e documento passam sem tocar no engine. */
|
|
79
|
+
declare function isAudioMimeType(mimeType: string | undefined | null): boolean;
|
|
80
|
+
|
|
13
81
|
declare const metaWhatsAppSchema: drizzle_orm_pg_core.PgSchema<"meta_whatsapp">;
|
|
14
82
|
declare const sessions: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
15
83
|
name: "sessions";
|
|
@@ -431,7 +499,7 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
431
499
|
identity: undefined;
|
|
432
500
|
generated: undefined;
|
|
433
501
|
}, {}, {
|
|
434
|
-
length:
|
|
502
|
+
length: 32;
|
|
435
503
|
}>;
|
|
436
504
|
content: drizzle_orm_pg_core.PgColumn<{
|
|
437
505
|
name: "content";
|
|
@@ -524,6 +592,117 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
524
592
|
identity: undefined;
|
|
525
593
|
generated: undefined;
|
|
526
594
|
}, {}, {}>;
|
|
595
|
+
moderationFlagged: drizzle_orm_pg_core.PgColumn<{
|
|
596
|
+
name: "moderation_flagged";
|
|
597
|
+
tableName: "messages";
|
|
598
|
+
dataType: "boolean";
|
|
599
|
+
columnType: "PgBoolean";
|
|
600
|
+
data: boolean;
|
|
601
|
+
driverParam: boolean;
|
|
602
|
+
notNull: false;
|
|
603
|
+
hasDefault: false;
|
|
604
|
+
isPrimaryKey: false;
|
|
605
|
+
isAutoincrement: false;
|
|
606
|
+
hasRuntimeDefault: false;
|
|
607
|
+
enumValues: undefined;
|
|
608
|
+
baseColumn: never;
|
|
609
|
+
identity: undefined;
|
|
610
|
+
generated: undefined;
|
|
611
|
+
}, {}, {}>;
|
|
612
|
+
moderationTerms: drizzle_orm_pg_core.PgColumn<{
|
|
613
|
+
name: "moderation_terms";
|
|
614
|
+
tableName: "messages";
|
|
615
|
+
dataType: "json";
|
|
616
|
+
columnType: "PgJsonb";
|
|
617
|
+
data: string[];
|
|
618
|
+
driverParam: unknown;
|
|
619
|
+
notNull: false;
|
|
620
|
+
hasDefault: false;
|
|
621
|
+
isPrimaryKey: false;
|
|
622
|
+
isAutoincrement: false;
|
|
623
|
+
hasRuntimeDefault: false;
|
|
624
|
+
enumValues: undefined;
|
|
625
|
+
baseColumn: never;
|
|
626
|
+
identity: undefined;
|
|
627
|
+
generated: undefined;
|
|
628
|
+
}, {}, {
|
|
629
|
+
$type: string[];
|
|
630
|
+
}>;
|
|
631
|
+
transcriptionStatus: drizzle_orm_pg_core.PgColumn<{
|
|
632
|
+
name: "transcription_status";
|
|
633
|
+
tableName: "messages";
|
|
634
|
+
dataType: "string";
|
|
635
|
+
columnType: "PgVarchar";
|
|
636
|
+
data: TranscriptionStatus;
|
|
637
|
+
driverParam: string;
|
|
638
|
+
notNull: false;
|
|
639
|
+
hasDefault: false;
|
|
640
|
+
isPrimaryKey: false;
|
|
641
|
+
isAutoincrement: false;
|
|
642
|
+
hasRuntimeDefault: false;
|
|
643
|
+
enumValues: [string, ...string[]];
|
|
644
|
+
baseColumn: never;
|
|
645
|
+
identity: undefined;
|
|
646
|
+
generated: undefined;
|
|
647
|
+
}, {}, {
|
|
648
|
+
length: 16;
|
|
649
|
+
$type: TranscriptionStatus;
|
|
650
|
+
}>;
|
|
651
|
+
transcriptionText: drizzle_orm_pg_core.PgColumn<{
|
|
652
|
+
name: "transcription_text";
|
|
653
|
+
tableName: "messages";
|
|
654
|
+
dataType: "string";
|
|
655
|
+
columnType: "PgText";
|
|
656
|
+
data: string;
|
|
657
|
+
driverParam: string;
|
|
658
|
+
notNull: false;
|
|
659
|
+
hasDefault: false;
|
|
660
|
+
isPrimaryKey: false;
|
|
661
|
+
isAutoincrement: false;
|
|
662
|
+
hasRuntimeDefault: false;
|
|
663
|
+
enumValues: [string, ...string[]];
|
|
664
|
+
baseColumn: never;
|
|
665
|
+
identity: undefined;
|
|
666
|
+
generated: undefined;
|
|
667
|
+
}, {}, {}>;
|
|
668
|
+
transcriptionLanguage: drizzle_orm_pg_core.PgColumn<{
|
|
669
|
+
name: "transcription_language";
|
|
670
|
+
tableName: "messages";
|
|
671
|
+
dataType: "string";
|
|
672
|
+
columnType: "PgVarchar";
|
|
673
|
+
data: string;
|
|
674
|
+
driverParam: string;
|
|
675
|
+
notNull: false;
|
|
676
|
+
hasDefault: false;
|
|
677
|
+
isPrimaryKey: false;
|
|
678
|
+
isAutoincrement: false;
|
|
679
|
+
hasRuntimeDefault: false;
|
|
680
|
+
enumValues: [string, ...string[]];
|
|
681
|
+
baseColumn: never;
|
|
682
|
+
identity: undefined;
|
|
683
|
+
generated: undefined;
|
|
684
|
+
}, {}, {
|
|
685
|
+
length: 32;
|
|
686
|
+
}>;
|
|
687
|
+
transcriptionEngine: drizzle_orm_pg_core.PgColumn<{
|
|
688
|
+
name: "transcription_engine";
|
|
689
|
+
tableName: "messages";
|
|
690
|
+
dataType: "string";
|
|
691
|
+
columnType: "PgVarchar";
|
|
692
|
+
data: string;
|
|
693
|
+
driverParam: string;
|
|
694
|
+
notNull: false;
|
|
695
|
+
hasDefault: false;
|
|
696
|
+
isPrimaryKey: false;
|
|
697
|
+
isAutoincrement: false;
|
|
698
|
+
hasRuntimeDefault: false;
|
|
699
|
+
enumValues: [string, ...string[]];
|
|
700
|
+
baseColumn: never;
|
|
701
|
+
identity: undefined;
|
|
702
|
+
generated: undefined;
|
|
703
|
+
}, {}, {
|
|
704
|
+
length: 32;
|
|
705
|
+
}>;
|
|
527
706
|
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
528
707
|
name: "created_at";
|
|
529
708
|
tableName: "messages";
|
|
@@ -544,13 +723,25 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
544
723
|
};
|
|
545
724
|
dialect: "pg";
|
|
546
725
|
}>;
|
|
547
|
-
|
|
548
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Biblioteca de arquivos da conversa.
|
|
728
|
+
*
|
|
729
|
+
* Tabela própria, e não derivada de `messages.payload`: o painel precisa de `source` e `linkedAt`,
|
|
730
|
+
* que não existem no payload; busca por nome de arquivo quer índice, não varredura de jsonb; e
|
|
731
|
+
* `messageId` anulável é o que permite o atendente anexar documento à conversa sem que exista uma
|
|
732
|
+
* mensagem correspondente.
|
|
733
|
+
*
|
|
734
|
+
* `sessionId` em cascata apaga a LINHA junto com a conversa, mas não o binário no storage — quem
|
|
735
|
+
* apaga objeto é passo de aplicação (listar `uploadId` → apagar no storage → apagar a sessão).
|
|
736
|
+
* Confiar só na FK deixaria objeto órfão sendo cobrado para sempre.
|
|
737
|
+
*/
|
|
738
|
+
declare const documents: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
739
|
+
name: "documents";
|
|
549
740
|
schema: "meta_whatsapp";
|
|
550
741
|
columns: {
|
|
551
742
|
id: drizzle_orm_pg_core.PgColumn<{
|
|
552
743
|
name: "id";
|
|
553
|
-
tableName: "
|
|
744
|
+
tableName: "documents";
|
|
554
745
|
dataType: "string";
|
|
555
746
|
columnType: "PgUUID";
|
|
556
747
|
data: string;
|
|
@@ -567,7 +758,7 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
567
758
|
}, {}, {}>;
|
|
568
759
|
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
569
760
|
name: "company_id";
|
|
570
|
-
tableName: "
|
|
761
|
+
tableName: "documents";
|
|
571
762
|
dataType: "string";
|
|
572
763
|
columnType: "PgUUID";
|
|
573
764
|
data: string;
|
|
@@ -582,11 +773,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
582
773
|
identity: undefined;
|
|
583
774
|
generated: undefined;
|
|
584
775
|
}, {}, {}>;
|
|
585
|
-
|
|
586
|
-
name: "
|
|
587
|
-
tableName: "
|
|
776
|
+
sessionId: drizzle_orm_pg_core.PgColumn<{
|
|
777
|
+
name: "session_id";
|
|
778
|
+
tableName: "documents";
|
|
588
779
|
dataType: "string";
|
|
589
|
-
columnType: "
|
|
780
|
+
columnType: "PgUUID";
|
|
590
781
|
data: string;
|
|
591
782
|
driverParam: string;
|
|
592
783
|
notNull: true;
|
|
@@ -594,16 +785,31 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
594
785
|
isPrimaryKey: false;
|
|
595
786
|
isAutoincrement: false;
|
|
596
787
|
hasRuntimeDefault: false;
|
|
597
|
-
enumValues:
|
|
788
|
+
enumValues: undefined;
|
|
598
789
|
baseColumn: never;
|
|
599
790
|
identity: undefined;
|
|
600
791
|
generated: undefined;
|
|
601
|
-
}, {}, {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
792
|
+
}, {}, {}>;
|
|
793
|
+
messageId: drizzle_orm_pg_core.PgColumn<{
|
|
794
|
+
name: "message_id";
|
|
795
|
+
tableName: "documents";
|
|
796
|
+
dataType: "string";
|
|
797
|
+
columnType: "PgUUID";
|
|
798
|
+
data: string;
|
|
799
|
+
driverParam: string;
|
|
800
|
+
notNull: false;
|
|
801
|
+
hasDefault: false;
|
|
802
|
+
isPrimaryKey: false;
|
|
803
|
+
isAutoincrement: false;
|
|
804
|
+
hasRuntimeDefault: false;
|
|
805
|
+
enumValues: undefined;
|
|
806
|
+
baseColumn: never;
|
|
807
|
+
identity: undefined;
|
|
808
|
+
generated: undefined;
|
|
809
|
+
}, {}, {}>;
|
|
810
|
+
uploadId: drizzle_orm_pg_core.PgColumn<{
|
|
811
|
+
name: "upload_id";
|
|
812
|
+
tableName: "documents";
|
|
607
813
|
dataType: "string";
|
|
608
814
|
columnType: "PgVarchar";
|
|
609
815
|
data: string;
|
|
@@ -618,11 +824,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
618
824
|
identity: undefined;
|
|
619
825
|
generated: undefined;
|
|
620
826
|
}, {}, {
|
|
621
|
-
length:
|
|
827
|
+
length: 256;
|
|
622
828
|
}>;
|
|
623
|
-
|
|
624
|
-
name: "
|
|
625
|
-
tableName: "
|
|
829
|
+
filename: drizzle_orm_pg_core.PgColumn<{
|
|
830
|
+
name: "filename";
|
|
831
|
+
tableName: "documents";
|
|
626
832
|
dataType: "string";
|
|
627
833
|
columnType: "PgVarchar";
|
|
628
834
|
data: string;
|
|
@@ -637,36 +843,36 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
637
843
|
identity: undefined;
|
|
638
844
|
generated: undefined;
|
|
639
845
|
}, {}, {
|
|
640
|
-
length:
|
|
846
|
+
length: 512;
|
|
641
847
|
}>;
|
|
642
|
-
|
|
643
|
-
name: "
|
|
644
|
-
tableName: "
|
|
645
|
-
dataType: "
|
|
646
|
-
columnType: "
|
|
647
|
-
data:
|
|
648
|
-
driverParam:
|
|
848
|
+
mimeType: drizzle_orm_pg_core.PgColumn<{
|
|
849
|
+
name: "mime_type";
|
|
850
|
+
tableName: "documents";
|
|
851
|
+
dataType: "string";
|
|
852
|
+
columnType: "PgVarchar";
|
|
853
|
+
data: string;
|
|
854
|
+
driverParam: string;
|
|
649
855
|
notNull: true;
|
|
650
|
-
hasDefault:
|
|
856
|
+
hasDefault: false;
|
|
651
857
|
isPrimaryKey: false;
|
|
652
858
|
isAutoincrement: false;
|
|
653
859
|
hasRuntimeDefault: false;
|
|
654
|
-
enumValues:
|
|
860
|
+
enumValues: [string, ...string[]];
|
|
655
861
|
baseColumn: never;
|
|
656
862
|
identity: undefined;
|
|
657
863
|
generated: undefined;
|
|
658
864
|
}, {}, {
|
|
659
|
-
|
|
865
|
+
length: 128;
|
|
660
866
|
}>;
|
|
661
|
-
|
|
662
|
-
name: "
|
|
663
|
-
tableName: "
|
|
867
|
+
sizeBytes: drizzle_orm_pg_core.PgColumn<{
|
|
868
|
+
name: "size_bytes";
|
|
869
|
+
tableName: "documents";
|
|
664
870
|
dataType: "number";
|
|
665
871
|
columnType: "PgInteger";
|
|
666
872
|
data: number;
|
|
667
873
|
driverParam: string | number;
|
|
668
874
|
notNull: true;
|
|
669
|
-
hasDefault:
|
|
875
|
+
hasDefault: false;
|
|
670
876
|
isPrimaryKey: false;
|
|
671
877
|
isAutoincrement: false;
|
|
672
878
|
hasRuntimeDefault: false;
|
|
@@ -675,31 +881,33 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
675
881
|
identity: undefined;
|
|
676
882
|
generated: undefined;
|
|
677
883
|
}, {}, {}>;
|
|
678
|
-
|
|
679
|
-
name: "
|
|
680
|
-
tableName: "
|
|
681
|
-
dataType: "
|
|
682
|
-
columnType: "
|
|
683
|
-
data:
|
|
684
|
-
driverParam:
|
|
685
|
-
notNull:
|
|
686
|
-
hasDefault:
|
|
884
|
+
sha256: drizzle_orm_pg_core.PgColumn<{
|
|
885
|
+
name: "sha256";
|
|
886
|
+
tableName: "documents";
|
|
887
|
+
dataType: "string";
|
|
888
|
+
columnType: "PgVarchar";
|
|
889
|
+
data: string;
|
|
890
|
+
driverParam: string;
|
|
891
|
+
notNull: false;
|
|
892
|
+
hasDefault: false;
|
|
687
893
|
isPrimaryKey: false;
|
|
688
894
|
isAutoincrement: false;
|
|
689
895
|
hasRuntimeDefault: false;
|
|
690
|
-
enumValues:
|
|
896
|
+
enumValues: [string, ...string[]];
|
|
691
897
|
baseColumn: never;
|
|
692
898
|
identity: undefined;
|
|
693
899
|
generated: undefined;
|
|
694
|
-
}, {}, {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
900
|
+
}, {}, {
|
|
901
|
+
length: 64;
|
|
902
|
+
}>;
|
|
903
|
+
source: drizzle_orm_pg_core.PgColumn<{
|
|
904
|
+
name: "source";
|
|
905
|
+
tableName: "documents";
|
|
698
906
|
dataType: "string";
|
|
699
907
|
columnType: "PgVarchar";
|
|
700
908
|
data: string;
|
|
701
909
|
driverParam: string;
|
|
702
|
-
notNull:
|
|
910
|
+
notNull: true;
|
|
703
911
|
hasDefault: false;
|
|
704
912
|
isPrimaryKey: false;
|
|
705
913
|
isAutoincrement: false;
|
|
@@ -709,11 +917,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
709
917
|
identity: undefined;
|
|
710
918
|
generated: undefined;
|
|
711
919
|
}, {}, {
|
|
712
|
-
length:
|
|
920
|
+
length: 12;
|
|
713
921
|
}>;
|
|
714
|
-
|
|
715
|
-
name: "
|
|
716
|
-
tableName: "
|
|
922
|
+
linkedAt: drizzle_orm_pg_core.PgColumn<{
|
|
923
|
+
name: "linked_at";
|
|
924
|
+
tableName: "documents";
|
|
717
925
|
dataType: "date";
|
|
718
926
|
columnType: "PgTimestamp";
|
|
719
927
|
data: Date;
|
|
@@ -728,16 +936,23 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
728
936
|
identity: undefined;
|
|
729
937
|
generated: undefined;
|
|
730
938
|
}, {}, {}>;
|
|
731
|
-
|
|
732
|
-
|
|
939
|
+
};
|
|
940
|
+
dialect: "pg";
|
|
941
|
+
}>;
|
|
942
|
+
declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
943
|
+
name: "flow_graphs";
|
|
944
|
+
schema: "meta_whatsapp";
|
|
945
|
+
columns: {
|
|
946
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
947
|
+
name: "id";
|
|
733
948
|
tableName: "flow_graphs";
|
|
734
|
-
dataType: "
|
|
735
|
-
columnType: "
|
|
736
|
-
data:
|
|
949
|
+
dataType: "string";
|
|
950
|
+
columnType: "PgUUID";
|
|
951
|
+
data: string;
|
|
737
952
|
driverParam: string;
|
|
738
953
|
notNull: true;
|
|
739
954
|
hasDefault: true;
|
|
740
|
-
isPrimaryKey:
|
|
955
|
+
isPrimaryKey: true;
|
|
741
956
|
isAutoincrement: false;
|
|
742
957
|
hasRuntimeDefault: false;
|
|
743
958
|
enumValues: undefined;
|
|
@@ -745,23 +960,16 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
745
960
|
identity: undefined;
|
|
746
961
|
generated: undefined;
|
|
747
962
|
}, {}, {}>;
|
|
748
|
-
};
|
|
749
|
-
dialect: "pg";
|
|
750
|
-
}>;
|
|
751
|
-
declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
752
|
-
name: "settings";
|
|
753
|
-
schema: "meta_whatsapp";
|
|
754
|
-
columns: {
|
|
755
963
|
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
756
964
|
name: "company_id";
|
|
757
|
-
tableName: "
|
|
965
|
+
tableName: "flow_graphs";
|
|
758
966
|
dataType: "string";
|
|
759
967
|
columnType: "PgUUID";
|
|
760
968
|
data: string;
|
|
761
969
|
driverParam: string;
|
|
762
970
|
notNull: true;
|
|
763
971
|
hasDefault: false;
|
|
764
|
-
isPrimaryKey:
|
|
972
|
+
isPrimaryKey: false;
|
|
765
973
|
isAutoincrement: false;
|
|
766
974
|
hasRuntimeDefault: false;
|
|
767
975
|
enumValues: undefined;
|
|
@@ -769,14 +977,14 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
769
977
|
identity: undefined;
|
|
770
978
|
generated: undefined;
|
|
771
979
|
}, {}, {}>;
|
|
772
|
-
|
|
773
|
-
name: "
|
|
774
|
-
tableName: "
|
|
980
|
+
key: drizzle_orm_pg_core.PgColumn<{
|
|
981
|
+
name: "key";
|
|
982
|
+
tableName: "flow_graphs";
|
|
775
983
|
dataType: "string";
|
|
776
984
|
columnType: "PgVarchar";
|
|
777
985
|
data: string;
|
|
778
986
|
driverParam: string;
|
|
779
|
-
notNull:
|
|
987
|
+
notNull: true;
|
|
780
988
|
hasDefault: false;
|
|
781
989
|
isPrimaryKey: false;
|
|
782
990
|
isAutoincrement: false;
|
|
@@ -786,17 +994,17 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
786
994
|
identity: undefined;
|
|
787
995
|
generated: undefined;
|
|
788
996
|
}, {}, {
|
|
789
|
-
length:
|
|
997
|
+
length: 64;
|
|
790
998
|
}>;
|
|
791
|
-
|
|
792
|
-
name: "
|
|
793
|
-
tableName: "
|
|
999
|
+
label: drizzle_orm_pg_core.PgColumn<{
|
|
1000
|
+
name: "label";
|
|
1001
|
+
tableName: "flow_graphs";
|
|
794
1002
|
dataType: "string";
|
|
795
1003
|
columnType: "PgVarchar";
|
|
796
1004
|
data: string;
|
|
797
1005
|
driverParam: string;
|
|
798
1006
|
notNull: true;
|
|
799
|
-
hasDefault:
|
|
1007
|
+
hasDefault: false;
|
|
800
1008
|
isPrimaryKey: false;
|
|
801
1009
|
isAutoincrement: false;
|
|
802
1010
|
hasRuntimeDefault: false;
|
|
@@ -805,50 +1013,522 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
805
1013
|
identity: undefined;
|
|
806
1014
|
generated: undefined;
|
|
807
1015
|
}, {}, {
|
|
808
|
-
length:
|
|
1016
|
+
length: 120;
|
|
809
1017
|
}>;
|
|
810
|
-
|
|
811
|
-
name: "
|
|
812
|
-
tableName: "
|
|
813
|
-
dataType: "
|
|
814
|
-
columnType: "
|
|
815
|
-
data: string
|
|
816
|
-
driverParam:
|
|
817
|
-
notNull: true;
|
|
818
|
-
hasDefault:
|
|
1018
|
+
startNodeId: drizzle_orm_pg_core.PgColumn<{
|
|
1019
|
+
name: "start_node_id";
|
|
1020
|
+
tableName: "flow_graphs";
|
|
1021
|
+
dataType: "string";
|
|
1022
|
+
columnType: "PgVarchar";
|
|
1023
|
+
data: string;
|
|
1024
|
+
driverParam: string;
|
|
1025
|
+
notNull: true;
|
|
1026
|
+
hasDefault: false;
|
|
1027
|
+
isPrimaryKey: false;
|
|
1028
|
+
isAutoincrement: false;
|
|
1029
|
+
hasRuntimeDefault: false;
|
|
1030
|
+
enumValues: [string, ...string[]];
|
|
1031
|
+
baseColumn: never;
|
|
1032
|
+
identity: undefined;
|
|
1033
|
+
generated: undefined;
|
|
1034
|
+
}, {}, {
|
|
1035
|
+
length: 64;
|
|
1036
|
+
}>;
|
|
1037
|
+
nodes: drizzle_orm_pg_core.PgColumn<{
|
|
1038
|
+
name: "nodes";
|
|
1039
|
+
tableName: "flow_graphs";
|
|
1040
|
+
dataType: "json";
|
|
1041
|
+
columnType: "PgJsonb";
|
|
1042
|
+
data: Record<string, unknown>;
|
|
1043
|
+
driverParam: unknown;
|
|
1044
|
+
notNull: true;
|
|
1045
|
+
hasDefault: true;
|
|
1046
|
+
isPrimaryKey: false;
|
|
1047
|
+
isAutoincrement: false;
|
|
1048
|
+
hasRuntimeDefault: false;
|
|
1049
|
+
enumValues: undefined;
|
|
1050
|
+
baseColumn: never;
|
|
1051
|
+
identity: undefined;
|
|
1052
|
+
generated: undefined;
|
|
1053
|
+
}, {}, {
|
|
1054
|
+
$type: Record<string, unknown>;
|
|
1055
|
+
}>;
|
|
1056
|
+
version: drizzle_orm_pg_core.PgColumn<{
|
|
1057
|
+
name: "version";
|
|
1058
|
+
tableName: "flow_graphs";
|
|
1059
|
+
dataType: "number";
|
|
1060
|
+
columnType: "PgInteger";
|
|
1061
|
+
data: number;
|
|
1062
|
+
driverParam: string | number;
|
|
1063
|
+
notNull: true;
|
|
1064
|
+
hasDefault: true;
|
|
1065
|
+
isPrimaryKey: false;
|
|
1066
|
+
isAutoincrement: false;
|
|
1067
|
+
hasRuntimeDefault: false;
|
|
1068
|
+
enumValues: undefined;
|
|
1069
|
+
baseColumn: never;
|
|
1070
|
+
identity: undefined;
|
|
1071
|
+
generated: undefined;
|
|
1072
|
+
}, {}, {}>;
|
|
1073
|
+
showInMenu: drizzle_orm_pg_core.PgColumn<{
|
|
1074
|
+
name: "show_in_menu";
|
|
1075
|
+
tableName: "flow_graphs";
|
|
1076
|
+
dataType: "boolean";
|
|
1077
|
+
columnType: "PgBoolean";
|
|
1078
|
+
data: boolean;
|
|
1079
|
+
driverParam: boolean;
|
|
1080
|
+
notNull: true;
|
|
1081
|
+
hasDefault: true;
|
|
1082
|
+
isPrimaryKey: false;
|
|
1083
|
+
isAutoincrement: false;
|
|
1084
|
+
hasRuntimeDefault: false;
|
|
1085
|
+
enumValues: undefined;
|
|
1086
|
+
baseColumn: never;
|
|
1087
|
+
identity: undefined;
|
|
1088
|
+
generated: undefined;
|
|
1089
|
+
}, {}, {}>;
|
|
1090
|
+
menuOptionLabel: drizzle_orm_pg_core.PgColumn<{
|
|
1091
|
+
name: "menu_option_label";
|
|
1092
|
+
tableName: "flow_graphs";
|
|
1093
|
+
dataType: "string";
|
|
1094
|
+
columnType: "PgVarchar";
|
|
1095
|
+
data: string;
|
|
1096
|
+
driverParam: string;
|
|
1097
|
+
notNull: false;
|
|
1098
|
+
hasDefault: false;
|
|
1099
|
+
isPrimaryKey: false;
|
|
1100
|
+
isAutoincrement: false;
|
|
1101
|
+
hasRuntimeDefault: false;
|
|
1102
|
+
enumValues: [string, ...string[]];
|
|
1103
|
+
baseColumn: never;
|
|
1104
|
+
identity: undefined;
|
|
1105
|
+
generated: undefined;
|
|
1106
|
+
}, {}, {
|
|
1107
|
+
length: 64;
|
|
1108
|
+
}>;
|
|
1109
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
1110
|
+
name: "created_at";
|
|
1111
|
+
tableName: "flow_graphs";
|
|
1112
|
+
dataType: "date";
|
|
1113
|
+
columnType: "PgTimestamp";
|
|
1114
|
+
data: Date;
|
|
1115
|
+
driverParam: string;
|
|
1116
|
+
notNull: true;
|
|
1117
|
+
hasDefault: true;
|
|
1118
|
+
isPrimaryKey: false;
|
|
1119
|
+
isAutoincrement: false;
|
|
1120
|
+
hasRuntimeDefault: false;
|
|
1121
|
+
enumValues: undefined;
|
|
1122
|
+
baseColumn: never;
|
|
1123
|
+
identity: undefined;
|
|
1124
|
+
generated: undefined;
|
|
1125
|
+
}, {}, {}>;
|
|
1126
|
+
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
1127
|
+
name: "updated_at";
|
|
1128
|
+
tableName: "flow_graphs";
|
|
1129
|
+
dataType: "date";
|
|
1130
|
+
columnType: "PgTimestamp";
|
|
1131
|
+
data: Date;
|
|
1132
|
+
driverParam: string;
|
|
1133
|
+
notNull: true;
|
|
1134
|
+
hasDefault: true;
|
|
1135
|
+
isPrimaryKey: false;
|
|
1136
|
+
isAutoincrement: false;
|
|
1137
|
+
hasRuntimeDefault: false;
|
|
1138
|
+
enumValues: undefined;
|
|
1139
|
+
baseColumn: never;
|
|
1140
|
+
identity: undefined;
|
|
1141
|
+
generated: undefined;
|
|
1142
|
+
}, {}, {}>;
|
|
1143
|
+
};
|
|
1144
|
+
dialect: "pg";
|
|
1145
|
+
}>;
|
|
1146
|
+
/**
|
|
1147
|
+
* Biblioteca de mídia do bot: arquivos que um nó `action` de `send_media` dispara ao chegar nele.
|
|
1148
|
+
*
|
|
1149
|
+
* Tabela separada de `documents` de propósito, e não por simetria: `documents` é o acervo DE UMA
|
|
1150
|
+
* CONVERSA (tem `sessionId` obrigatório) e tem índice único em (companyId, uploadId), porque lá o
|
|
1151
|
+
* mesmo binário chegando duas vezes é reentrega de job. Aqui é o oposto — o mesmo arquivo é
|
|
1152
|
+
* enviado para todo cliente que passar pelo nó, então aquele único bloquearia o segundo envio.
|
|
1153
|
+
*
|
|
1154
|
+
* Sem FK para `flow_graphs.id`: o vínculo natural é (companyId, flowKey), que é justamente o
|
|
1155
|
+
* índice único de lá. `nodeId` não tem como ser FK — nós vivem dentro do jsonb `nodes` — então
|
|
1156
|
+
* apagar um nó no editor deixa a linha órfã; quem lista sempre parte de um nó existente, e a
|
|
1157
|
+
* limpeza é passo de aplicação ao salvar o grafo.
|
|
1158
|
+
*/
|
|
1159
|
+
declare const flowMedia: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
1160
|
+
name: "flow_media";
|
|
1161
|
+
schema: "meta_whatsapp";
|
|
1162
|
+
columns: {
|
|
1163
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
1164
|
+
name: "id";
|
|
1165
|
+
tableName: "flow_media";
|
|
1166
|
+
dataType: "string";
|
|
1167
|
+
columnType: "PgUUID";
|
|
1168
|
+
data: string;
|
|
1169
|
+
driverParam: string;
|
|
1170
|
+
notNull: true;
|
|
1171
|
+
hasDefault: true;
|
|
1172
|
+
isPrimaryKey: true;
|
|
1173
|
+
isAutoincrement: false;
|
|
1174
|
+
hasRuntimeDefault: false;
|
|
1175
|
+
enumValues: undefined;
|
|
1176
|
+
baseColumn: never;
|
|
1177
|
+
identity: undefined;
|
|
1178
|
+
generated: undefined;
|
|
1179
|
+
}, {}, {}>;
|
|
1180
|
+
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
1181
|
+
name: "company_id";
|
|
1182
|
+
tableName: "flow_media";
|
|
1183
|
+
dataType: "string";
|
|
1184
|
+
columnType: "PgUUID";
|
|
1185
|
+
data: string;
|
|
1186
|
+
driverParam: string;
|
|
1187
|
+
notNull: true;
|
|
1188
|
+
hasDefault: false;
|
|
1189
|
+
isPrimaryKey: false;
|
|
1190
|
+
isAutoincrement: false;
|
|
1191
|
+
hasRuntimeDefault: false;
|
|
1192
|
+
enumValues: undefined;
|
|
1193
|
+
baseColumn: never;
|
|
1194
|
+
identity: undefined;
|
|
1195
|
+
generated: undefined;
|
|
1196
|
+
}, {}, {}>;
|
|
1197
|
+
flowKey: drizzle_orm_pg_core.PgColumn<{
|
|
1198
|
+
name: "flow_key";
|
|
1199
|
+
tableName: "flow_media";
|
|
1200
|
+
dataType: "string";
|
|
1201
|
+
columnType: "PgVarchar";
|
|
1202
|
+
data: string;
|
|
1203
|
+
driverParam: string;
|
|
1204
|
+
notNull: true;
|
|
1205
|
+
hasDefault: false;
|
|
1206
|
+
isPrimaryKey: false;
|
|
1207
|
+
isAutoincrement: false;
|
|
1208
|
+
hasRuntimeDefault: false;
|
|
1209
|
+
enumValues: [string, ...string[]];
|
|
1210
|
+
baseColumn: never;
|
|
1211
|
+
identity: undefined;
|
|
1212
|
+
generated: undefined;
|
|
1213
|
+
}, {}, {
|
|
1214
|
+
length: 64;
|
|
1215
|
+
}>;
|
|
1216
|
+
nodeId: drizzle_orm_pg_core.PgColumn<{
|
|
1217
|
+
name: "node_id";
|
|
1218
|
+
tableName: "flow_media";
|
|
1219
|
+
dataType: "string";
|
|
1220
|
+
columnType: "PgVarchar";
|
|
1221
|
+
data: string;
|
|
1222
|
+
driverParam: string;
|
|
1223
|
+
notNull: true;
|
|
1224
|
+
hasDefault: false;
|
|
1225
|
+
isPrimaryKey: false;
|
|
1226
|
+
isAutoincrement: false;
|
|
1227
|
+
hasRuntimeDefault: false;
|
|
1228
|
+
enumValues: [string, ...string[]];
|
|
1229
|
+
baseColumn: never;
|
|
1230
|
+
identity: undefined;
|
|
1231
|
+
generated: undefined;
|
|
1232
|
+
}, {}, {
|
|
1233
|
+
length: 64;
|
|
1234
|
+
}>;
|
|
1235
|
+
uploadId: drizzle_orm_pg_core.PgColumn<{
|
|
1236
|
+
name: "upload_id";
|
|
1237
|
+
tableName: "flow_media";
|
|
1238
|
+
dataType: "string";
|
|
1239
|
+
columnType: "PgVarchar";
|
|
1240
|
+
data: string;
|
|
1241
|
+
driverParam: string;
|
|
1242
|
+
notNull: true;
|
|
1243
|
+
hasDefault: false;
|
|
1244
|
+
isPrimaryKey: false;
|
|
1245
|
+
isAutoincrement: false;
|
|
1246
|
+
hasRuntimeDefault: false;
|
|
1247
|
+
enumValues: [string, ...string[]];
|
|
1248
|
+
baseColumn: never;
|
|
1249
|
+
identity: undefined;
|
|
1250
|
+
generated: undefined;
|
|
1251
|
+
}, {}, {
|
|
1252
|
+
length: 256;
|
|
1253
|
+
}>;
|
|
1254
|
+
filename: drizzle_orm_pg_core.PgColumn<{
|
|
1255
|
+
name: "filename";
|
|
1256
|
+
tableName: "flow_media";
|
|
1257
|
+
dataType: "string";
|
|
1258
|
+
columnType: "PgVarchar";
|
|
1259
|
+
data: string;
|
|
1260
|
+
driverParam: string;
|
|
1261
|
+
notNull: true;
|
|
1262
|
+
hasDefault: false;
|
|
1263
|
+
isPrimaryKey: false;
|
|
1264
|
+
isAutoincrement: false;
|
|
1265
|
+
hasRuntimeDefault: false;
|
|
1266
|
+
enumValues: [string, ...string[]];
|
|
1267
|
+
baseColumn: never;
|
|
1268
|
+
identity: undefined;
|
|
1269
|
+
generated: undefined;
|
|
1270
|
+
}, {}, {
|
|
1271
|
+
length: 512;
|
|
1272
|
+
}>;
|
|
1273
|
+
mimeType: drizzle_orm_pg_core.PgColumn<{
|
|
1274
|
+
name: "mime_type";
|
|
1275
|
+
tableName: "flow_media";
|
|
1276
|
+
dataType: "string";
|
|
1277
|
+
columnType: "PgVarchar";
|
|
1278
|
+
data: string;
|
|
1279
|
+
driverParam: string;
|
|
1280
|
+
notNull: true;
|
|
1281
|
+
hasDefault: false;
|
|
1282
|
+
isPrimaryKey: false;
|
|
1283
|
+
isAutoincrement: false;
|
|
1284
|
+
hasRuntimeDefault: false;
|
|
1285
|
+
enumValues: [string, ...string[]];
|
|
1286
|
+
baseColumn: never;
|
|
1287
|
+
identity: undefined;
|
|
1288
|
+
generated: undefined;
|
|
1289
|
+
}, {}, {
|
|
1290
|
+
length: 128;
|
|
1291
|
+
}>;
|
|
1292
|
+
sizeBytes: drizzle_orm_pg_core.PgColumn<{
|
|
1293
|
+
name: "size_bytes";
|
|
1294
|
+
tableName: "flow_media";
|
|
1295
|
+
dataType: "number";
|
|
1296
|
+
columnType: "PgInteger";
|
|
1297
|
+
data: number;
|
|
1298
|
+
driverParam: string | number;
|
|
1299
|
+
notNull: true;
|
|
1300
|
+
hasDefault: false;
|
|
1301
|
+
isPrimaryKey: false;
|
|
1302
|
+
isAutoincrement: false;
|
|
1303
|
+
hasRuntimeDefault: false;
|
|
1304
|
+
enumValues: undefined;
|
|
1305
|
+
baseColumn: never;
|
|
1306
|
+
identity: undefined;
|
|
1307
|
+
generated: undefined;
|
|
1308
|
+
}, {}, {}>;
|
|
1309
|
+
caption: drizzle_orm_pg_core.PgColumn<{
|
|
1310
|
+
name: "caption";
|
|
1311
|
+
tableName: "flow_media";
|
|
1312
|
+
dataType: "string";
|
|
1313
|
+
columnType: "PgText";
|
|
1314
|
+
data: string;
|
|
1315
|
+
driverParam: string;
|
|
1316
|
+
notNull: false;
|
|
1317
|
+
hasDefault: false;
|
|
1318
|
+
isPrimaryKey: false;
|
|
1319
|
+
isAutoincrement: false;
|
|
1320
|
+
hasRuntimeDefault: false;
|
|
1321
|
+
enumValues: [string, ...string[]];
|
|
1322
|
+
baseColumn: never;
|
|
1323
|
+
identity: undefined;
|
|
1324
|
+
generated: undefined;
|
|
1325
|
+
}, {}, {}>;
|
|
1326
|
+
sortOrder: drizzle_orm_pg_core.PgColumn<{
|
|
1327
|
+
name: "sort_order";
|
|
1328
|
+
tableName: "flow_media";
|
|
1329
|
+
dataType: "number";
|
|
1330
|
+
columnType: "PgInteger";
|
|
1331
|
+
data: number;
|
|
1332
|
+
driverParam: string | number;
|
|
1333
|
+
notNull: true;
|
|
1334
|
+
hasDefault: true;
|
|
1335
|
+
isPrimaryKey: false;
|
|
1336
|
+
isAutoincrement: false;
|
|
1337
|
+
hasRuntimeDefault: false;
|
|
1338
|
+
enumValues: undefined;
|
|
1339
|
+
baseColumn: never;
|
|
1340
|
+
identity: undefined;
|
|
1341
|
+
generated: undefined;
|
|
1342
|
+
}, {}, {}>;
|
|
1343
|
+
active: drizzle_orm_pg_core.PgColumn<{
|
|
1344
|
+
name: "active";
|
|
1345
|
+
tableName: "flow_media";
|
|
1346
|
+
dataType: "boolean";
|
|
1347
|
+
columnType: "PgBoolean";
|
|
1348
|
+
data: boolean;
|
|
1349
|
+
driverParam: boolean;
|
|
1350
|
+
notNull: true;
|
|
1351
|
+
hasDefault: true;
|
|
1352
|
+
isPrimaryKey: false;
|
|
1353
|
+
isAutoincrement: false;
|
|
1354
|
+
hasRuntimeDefault: false;
|
|
1355
|
+
enumValues: undefined;
|
|
1356
|
+
baseColumn: never;
|
|
1357
|
+
identity: undefined;
|
|
1358
|
+
generated: undefined;
|
|
1359
|
+
}, {}, {}>;
|
|
1360
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
1361
|
+
name: "created_at";
|
|
1362
|
+
tableName: "flow_media";
|
|
1363
|
+
dataType: "date";
|
|
1364
|
+
columnType: "PgTimestamp";
|
|
1365
|
+
data: Date;
|
|
1366
|
+
driverParam: string;
|
|
1367
|
+
notNull: true;
|
|
1368
|
+
hasDefault: true;
|
|
1369
|
+
isPrimaryKey: false;
|
|
1370
|
+
isAutoincrement: false;
|
|
1371
|
+
hasRuntimeDefault: false;
|
|
1372
|
+
enumValues: undefined;
|
|
1373
|
+
baseColumn: never;
|
|
1374
|
+
identity: undefined;
|
|
1375
|
+
generated: undefined;
|
|
1376
|
+
}, {}, {}>;
|
|
1377
|
+
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
1378
|
+
name: "updated_at";
|
|
1379
|
+
tableName: "flow_media";
|
|
1380
|
+
dataType: "date";
|
|
1381
|
+
columnType: "PgTimestamp";
|
|
1382
|
+
data: Date;
|
|
1383
|
+
driverParam: string;
|
|
1384
|
+
notNull: true;
|
|
1385
|
+
hasDefault: true;
|
|
1386
|
+
isPrimaryKey: false;
|
|
1387
|
+
isAutoincrement: false;
|
|
1388
|
+
hasRuntimeDefault: false;
|
|
1389
|
+
enumValues: undefined;
|
|
1390
|
+
baseColumn: never;
|
|
1391
|
+
identity: undefined;
|
|
1392
|
+
generated: undefined;
|
|
1393
|
+
}, {}, {}>;
|
|
1394
|
+
};
|
|
1395
|
+
dialect: "pg";
|
|
1396
|
+
}>;
|
|
1397
|
+
declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
1398
|
+
name: "settings";
|
|
1399
|
+
schema: "meta_whatsapp";
|
|
1400
|
+
columns: {
|
|
1401
|
+
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
1402
|
+
name: "company_id";
|
|
1403
|
+
tableName: "settings";
|
|
1404
|
+
dataType: "string";
|
|
1405
|
+
columnType: "PgUUID";
|
|
1406
|
+
data: string;
|
|
1407
|
+
driverParam: string;
|
|
1408
|
+
notNull: true;
|
|
1409
|
+
hasDefault: false;
|
|
1410
|
+
isPrimaryKey: true;
|
|
1411
|
+
isAutoincrement: false;
|
|
1412
|
+
hasRuntimeDefault: false;
|
|
1413
|
+
enumValues: undefined;
|
|
1414
|
+
baseColumn: never;
|
|
1415
|
+
identity: undefined;
|
|
1416
|
+
generated: undefined;
|
|
1417
|
+
}, {}, {}>;
|
|
1418
|
+
templateName: drizzle_orm_pg_core.PgColumn<{
|
|
1419
|
+
name: "template_name";
|
|
1420
|
+
tableName: "settings";
|
|
1421
|
+
dataType: "string";
|
|
1422
|
+
columnType: "PgVarchar";
|
|
1423
|
+
data: string;
|
|
1424
|
+
driverParam: string;
|
|
1425
|
+
notNull: false;
|
|
1426
|
+
hasDefault: false;
|
|
1427
|
+
isPrimaryKey: false;
|
|
1428
|
+
isAutoincrement: false;
|
|
1429
|
+
hasRuntimeDefault: false;
|
|
1430
|
+
enumValues: [string, ...string[]];
|
|
1431
|
+
baseColumn: never;
|
|
1432
|
+
identity: undefined;
|
|
1433
|
+
generated: undefined;
|
|
1434
|
+
}, {}, {
|
|
1435
|
+
length: 128;
|
|
1436
|
+
}>;
|
|
1437
|
+
templateLanguage: drizzle_orm_pg_core.PgColumn<{
|
|
1438
|
+
name: "template_language";
|
|
1439
|
+
tableName: "settings";
|
|
1440
|
+
dataType: "string";
|
|
1441
|
+
columnType: "PgVarchar";
|
|
1442
|
+
data: string;
|
|
1443
|
+
driverParam: string;
|
|
1444
|
+
notNull: true;
|
|
1445
|
+
hasDefault: true;
|
|
1446
|
+
isPrimaryKey: false;
|
|
1447
|
+
isAutoincrement: false;
|
|
1448
|
+
hasRuntimeDefault: false;
|
|
1449
|
+
enumValues: [string, ...string[]];
|
|
1450
|
+
baseColumn: never;
|
|
1451
|
+
identity: undefined;
|
|
1452
|
+
generated: undefined;
|
|
1453
|
+
}, {}, {
|
|
1454
|
+
length: 16;
|
|
1455
|
+
}>;
|
|
1456
|
+
templateVariables: drizzle_orm_pg_core.PgColumn<{
|
|
1457
|
+
name: "template_variables";
|
|
1458
|
+
tableName: "settings";
|
|
1459
|
+
dataType: "json";
|
|
1460
|
+
columnType: "PgJsonb";
|
|
1461
|
+
data: string[];
|
|
1462
|
+
driverParam: unknown;
|
|
1463
|
+
notNull: true;
|
|
1464
|
+
hasDefault: true;
|
|
1465
|
+
isPrimaryKey: false;
|
|
1466
|
+
isAutoincrement: false;
|
|
1467
|
+
hasRuntimeDefault: false;
|
|
1468
|
+
enumValues: undefined;
|
|
1469
|
+
baseColumn: never;
|
|
1470
|
+
identity: undefined;
|
|
1471
|
+
generated: undefined;
|
|
1472
|
+
}, {}, {
|
|
1473
|
+
$type: string[];
|
|
1474
|
+
}>;
|
|
1475
|
+
welcomeMessage: drizzle_orm_pg_core.PgColumn<{
|
|
1476
|
+
name: "welcome_message";
|
|
1477
|
+
tableName: "settings";
|
|
1478
|
+
dataType: "string";
|
|
1479
|
+
columnType: "PgText";
|
|
1480
|
+
data: string;
|
|
1481
|
+
driverParam: string;
|
|
1482
|
+
notNull: false;
|
|
1483
|
+
hasDefault: false;
|
|
1484
|
+
isPrimaryKey: false;
|
|
1485
|
+
isAutoincrement: false;
|
|
1486
|
+
hasRuntimeDefault: false;
|
|
1487
|
+
enumValues: [string, ...string[]];
|
|
1488
|
+
baseColumn: never;
|
|
1489
|
+
identity: undefined;
|
|
1490
|
+
generated: undefined;
|
|
1491
|
+
}, {}, {}>;
|
|
1492
|
+
farewellMessage: drizzle_orm_pg_core.PgColumn<{
|
|
1493
|
+
name: "farewell_message";
|
|
1494
|
+
tableName: "settings";
|
|
1495
|
+
dataType: "string";
|
|
1496
|
+
columnType: "PgText";
|
|
1497
|
+
data: string;
|
|
1498
|
+
driverParam: string;
|
|
1499
|
+
notNull: false;
|
|
1500
|
+
hasDefault: false;
|
|
819
1501
|
isPrimaryKey: false;
|
|
820
1502
|
isAutoincrement: false;
|
|
821
1503
|
hasRuntimeDefault: false;
|
|
822
|
-
enumValues:
|
|
1504
|
+
enumValues: [string, ...string[]];
|
|
823
1505
|
baseColumn: never;
|
|
824
1506
|
identity: undefined;
|
|
825
1507
|
generated: undefined;
|
|
826
|
-
}, {}, {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
welcomeMessage: drizzle_orm_pg_core.PgColumn<{
|
|
830
|
-
name: "welcome_message";
|
|
1508
|
+
}, {}, {}>;
|
|
1509
|
+
transcriptionEnabled: drizzle_orm_pg_core.PgColumn<{
|
|
1510
|
+
name: "transcription_enabled";
|
|
831
1511
|
tableName: "settings";
|
|
832
|
-
dataType: "
|
|
833
|
-
columnType: "
|
|
834
|
-
data:
|
|
835
|
-
driverParam:
|
|
1512
|
+
dataType: "boolean";
|
|
1513
|
+
columnType: "PgBoolean";
|
|
1514
|
+
data: boolean;
|
|
1515
|
+
driverParam: boolean;
|
|
836
1516
|
notNull: false;
|
|
837
1517
|
hasDefault: false;
|
|
838
1518
|
isPrimaryKey: false;
|
|
839
1519
|
isAutoincrement: false;
|
|
840
1520
|
hasRuntimeDefault: false;
|
|
841
|
-
enumValues:
|
|
1521
|
+
enumValues: undefined;
|
|
842
1522
|
baseColumn: never;
|
|
843
1523
|
identity: undefined;
|
|
844
1524
|
generated: undefined;
|
|
845
1525
|
}, {}, {}>;
|
|
846
|
-
|
|
847
|
-
name: "
|
|
1526
|
+
transcriptionMode: drizzle_orm_pg_core.PgColumn<{
|
|
1527
|
+
name: "transcription_mode";
|
|
848
1528
|
tableName: "settings";
|
|
849
1529
|
dataType: "string";
|
|
850
|
-
columnType: "
|
|
851
|
-
data:
|
|
1530
|
+
columnType: "PgVarchar";
|
|
1531
|
+
data: TranscriptionMode;
|
|
852
1532
|
driverParam: string;
|
|
853
1533
|
notNull: false;
|
|
854
1534
|
hasDefault: false;
|
|
@@ -859,7 +1539,10 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
859
1539
|
baseColumn: never;
|
|
860
1540
|
identity: undefined;
|
|
861
1541
|
generated: undefined;
|
|
862
|
-
}, {}, {
|
|
1542
|
+
}, {}, {
|
|
1543
|
+
length: 16;
|
|
1544
|
+
$type: TranscriptionMode;
|
|
1545
|
+
}>;
|
|
863
1546
|
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
864
1547
|
name: "created_at";
|
|
865
1548
|
tableName: "settings";
|
|
@@ -905,6 +1588,10 @@ type MessageRow = typeof messages.$inferSelect;
|
|
|
905
1588
|
type NewMessageRow = typeof messages.$inferInsert;
|
|
906
1589
|
type FlowGraphRow = typeof flowGraphs.$inferSelect;
|
|
907
1590
|
type NewFlowGraphRow = typeof flowGraphs.$inferInsert;
|
|
1591
|
+
type DocumentRow = typeof documents.$inferSelect;
|
|
1592
|
+
type NewDocumentRow = typeof documents.$inferInsert;
|
|
1593
|
+
type FlowMediaRow = typeof flowMedia.$inferSelect;
|
|
1594
|
+
type NewFlowMediaRow = typeof flowMedia.$inferInsert;
|
|
908
1595
|
|
|
909
1596
|
interface ListConversationsFilters {
|
|
910
1597
|
page?: number;
|
|
@@ -917,13 +1604,23 @@ declare class SessionRepository {
|
|
|
917
1604
|
constructor(db: MetaWhatsAppDatabase);
|
|
918
1605
|
getContext(companyId: string, whatsappNumber: string): Promise<SessionRow | undefined>;
|
|
919
1606
|
getOrCreate(companyId: string, whatsappNumber: string, startState: SessionState): Promise<SessionRow>;
|
|
920
|
-
setState(companyId: string, whatsappNumber: string, state: SessionState, context?:
|
|
1607
|
+
setState<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string, state: SessionState, context?: TSessionContext): Promise<void>;
|
|
1608
|
+
patchContext<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string, patch: Partial<TSessionContext>): Promise<void>;
|
|
1609
|
+
readContext<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string): Promise<TSessionContext | undefined>;
|
|
921
1610
|
setFlowPosition(companyId: string, whatsappNumber: string, flowKey: string | null, currentNodeId: string | null): Promise<void>;
|
|
922
1611
|
touchInbound(companyId: string, whatsappNumber: string): Promise<void>;
|
|
923
1612
|
hoursSinceLastInbound(companyId: string, whatsappNumber: string): Promise<number | undefined>;
|
|
924
1613
|
setMode(companyId: string, whatsappNumber: string, mode: SessionMode, assignedUserId?: string | null): Promise<void>;
|
|
925
1614
|
takeover(companyId: string, whatsappNumber: string, agentUserId: string): Promise<void>;
|
|
926
1615
|
release(companyId: string, whatsappNumber: string): Promise<void>;
|
|
1616
|
+
/**
|
|
1617
|
+
* Apaga a sessão; a cascata das FKs leva mensagens e documentos.
|
|
1618
|
+
*
|
|
1619
|
+
* Não apaga o binário no storage — isso é passo de aplicação, e é por isso que este método é
|
|
1620
|
+
* chamado por `DeleteConversationUseCase` e não diretamente pelo host. Chamar daqui sem apagar os
|
|
1621
|
+
* objetos antes deixa mídia órfã sendo cobrada para sempre.
|
|
1622
|
+
*/
|
|
1623
|
+
deleteByNumber(companyId: string, whatsappNumber: string): Promise<void>;
|
|
927
1624
|
requestHuman(companyId: string, whatsappNumber: string): Promise<void>;
|
|
928
1625
|
markRead(companyId: string, whatsappNumber: string): Promise<void>;
|
|
929
1626
|
markAllRead(companyId: string, userId: string): Promise<number>;
|
|
@@ -960,11 +1657,108 @@ declare class SessionRepository {
|
|
|
960
1657
|
waMessageId: string | null;
|
|
961
1658
|
status: string | null;
|
|
962
1659
|
readAt: Date | null;
|
|
1660
|
+
moderationFlagged: boolean | null;
|
|
1661
|
+
moderationTerms: string[] | null;
|
|
1662
|
+
transcriptionStatus: TranscriptionStatus | null;
|
|
1663
|
+
transcriptionText: string | null;
|
|
1664
|
+
transcriptionLanguage: string | null;
|
|
1665
|
+
transcriptionEngine: string | null;
|
|
963
1666
|
createdAt: Date;
|
|
964
1667
|
}[];
|
|
965
1668
|
} | null>;
|
|
966
1669
|
}
|
|
967
1670
|
|
|
1671
|
+
interface InsertMessageParams {
|
|
1672
|
+
companyId: string;
|
|
1673
|
+
sessionId: string;
|
|
1674
|
+
whatsappNumber: string;
|
|
1675
|
+
direction: MessageDirection;
|
|
1676
|
+
sender: MessageSender;
|
|
1677
|
+
agentUserId?: string | null;
|
|
1678
|
+
type: string;
|
|
1679
|
+
content?: string | null;
|
|
1680
|
+
payload?: Record<string, unknown> | null;
|
|
1681
|
+
waMessageId?: string | null;
|
|
1682
|
+
status?: MessageStatus | null;
|
|
1683
|
+
/** `undefined` deixa a coluna nula: não avaliado, distinto de avaliado e limpo. */
|
|
1684
|
+
moderationFlagged?: boolean | null;
|
|
1685
|
+
moderationTerms?: string[] | null;
|
|
1686
|
+
}
|
|
1687
|
+
interface ListMessagesParams$1 {
|
|
1688
|
+
companyId: string;
|
|
1689
|
+
sessionId: string;
|
|
1690
|
+
limit?: number;
|
|
1691
|
+
before?: string;
|
|
1692
|
+
}
|
|
1693
|
+
interface SaveTranscriptionByWaMessageIdParams extends Omit<SaveTranscriptionParams, 'messageId'> {
|
|
1694
|
+
/**
|
|
1695
|
+
* Id da mensagem na Meta. É o único que quem processa o webhook conhece — o id do módulo só
|
|
1696
|
+
* existe depois da gravação, e obrigar o host a descobri-lo faria cada um escrever a própria
|
|
1697
|
+
* consulta por `wa_message_id`.
|
|
1698
|
+
*/
|
|
1699
|
+
waMessageId: string;
|
|
1700
|
+
}
|
|
1701
|
+
interface SaveTranscriptionParams {
|
|
1702
|
+
companyId: string;
|
|
1703
|
+
messageId: string;
|
|
1704
|
+
status: TranscriptionStatus;
|
|
1705
|
+
/** Ausente em `pending`/`failed`/`unsupported`; vazio em `done` é silêncio já processado. */
|
|
1706
|
+
text?: string | null;
|
|
1707
|
+
language?: string | null;
|
|
1708
|
+
engine?: string | null;
|
|
1709
|
+
}
|
|
1710
|
+
declare class MessageRepository {
|
|
1711
|
+
private readonly db;
|
|
1712
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
1713
|
+
insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
|
|
1714
|
+
updateMessageStatus(companyId: string, waMessageId: string, status: MessageStatus): Promise<MessageRow | undefined>;
|
|
1715
|
+
/**
|
|
1716
|
+
* Grava a transcrição endereçando pelo id da Meta, para quem só tem esse.
|
|
1717
|
+
*
|
|
1718
|
+
* Serve ao caso em que a transcrição acontece no próprio webhook — o grafo precisa do texto para
|
|
1719
|
+
* responder ao cliente, e jogar fora o que ele já pagou para transcrever significaria transcrever
|
|
1720
|
+
* o mesmo áudio uma segunda vez só para o painel ver.
|
|
1721
|
+
*
|
|
1722
|
+
* Devolve `undefined` quando não achou a mensagem: entrega duplicada e mensagem apagada são
|
|
1723
|
+
* corridas normais, não erro.
|
|
1724
|
+
*/
|
|
1725
|
+
saveTranscriptionByWaMessageId(params: SaveTranscriptionByWaMessageIdParams): Promise<MessageRow | undefined>;
|
|
1726
|
+
findById(companyId: string, messageId: string): Promise<MessageRow | undefined>;
|
|
1727
|
+
/**
|
|
1728
|
+
* Grava o resultado da transcrição. Devolve `undefined` quando a mensagem não existe (apagada
|
|
1729
|
+
* entre o enfileiramento e a execução do job) — não é erro, é corrida normal.
|
|
1730
|
+
*
|
|
1731
|
+
* `text`/`language`/`engine` só são tocados quando informados: uma retentativa que volta a falhar
|
|
1732
|
+
* atualiza o status sem apagar a transcrição parcial de uma tentativa anterior que tenha vindo de
|
|
1733
|
+
* outro engine da cadeia.
|
|
1734
|
+
*/
|
|
1735
|
+
saveTranscription(params: SaveTranscriptionParams): Promise<MessageRow | undefined>;
|
|
1736
|
+
listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
declare const DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS = 300;
|
|
1740
|
+
/**
|
|
1741
|
+
* Cache de leitura dos grafos de fluxo, com invalidação na publicação.
|
|
1742
|
+
*
|
|
1743
|
+
* TTL **e** invalidação explícita, e não um dos dois: só TTL deixaria o cliente andando no grafo
|
|
1744
|
+
* antigo até a chave expirar, logo depois de alguém corrigir o fluxo no editor; só invalidação
|
|
1745
|
+
* deixaria cache envenenado para sempre se um `delete` se perdesse (Redis reiniciando, deploy no
|
|
1746
|
+
* meio da escrita).
|
|
1747
|
+
*
|
|
1748
|
+
* Toda operação é tolerante a falha por decisão de projeto: cache é aceleração, não dependência.
|
|
1749
|
+
* Redis fora do ar tem que degradar para leitura no banco — que é exatamente o comportamento de
|
|
1750
|
+
* quem não configura cache nenhum — em vez de derrubar a conversa do cliente.
|
|
1751
|
+
*/
|
|
1752
|
+
declare class FlowGraphCache {
|
|
1753
|
+
private readonly provider;
|
|
1754
|
+
private readonly ttlSeconds;
|
|
1755
|
+
constructor(provider: CacheInterface, ttlSeconds?: number);
|
|
1756
|
+
private keyFor;
|
|
1757
|
+
read(companyId: string, flowKey: string): Promise<FlowGraphData | undefined>;
|
|
1758
|
+
write(companyId: string, graph: FlowGraphData): Promise<void>;
|
|
1759
|
+
invalidate(companyId: string, flowKey: string): Promise<void>;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
968
1762
|
declare class InvalidFlowGraphError extends Error {
|
|
969
1763
|
readonly validationMessage: string;
|
|
970
1764
|
constructor(key: string, validationMessage: string);
|
|
@@ -974,7 +1768,8 @@ declare class OptimisticLockError extends Error {
|
|
|
974
1768
|
}
|
|
975
1769
|
declare class FlowGraphRepository {
|
|
976
1770
|
private readonly db;
|
|
977
|
-
|
|
1771
|
+
private readonly cache?;
|
|
1772
|
+
constructor(db: MetaWhatsAppDatabase, cache?: FlowGraphCache | undefined);
|
|
978
1773
|
get(companyId: string, key: string): Promise<FlowGraphData | undefined>;
|
|
979
1774
|
list(companyId: string): Promise<FlowGraphSummary[]>;
|
|
980
1775
|
private assertValidNodes;
|
|
@@ -995,42 +1790,124 @@ declare class SettingsRepository {
|
|
|
995
1790
|
resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]>;
|
|
996
1791
|
}
|
|
997
1792
|
|
|
998
|
-
|
|
1793
|
+
type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {
|
|
1794
|
+
startState: SessionState;
|
|
1795
|
+
};
|
|
1796
|
+
/**
|
|
1797
|
+
* O contrato mínimo de `@adatechnology/text-moderation`, declarado aqui em vez de importado.
|
|
1798
|
+
*
|
|
1799
|
+
* Assim o módulo não ganha dependência de pacote para um recurso opcional, e — mais importante —
|
|
1800
|
+
* moderação não fica amarrada a WhatsApp: o que atravessa esta fronteira é texto, e qualquer canal
|
|
1801
|
+
* que passe a escrever no transcript por este use case herda a marcação sem tocar nada aqui.
|
|
1802
|
+
*/
|
|
1803
|
+
type MessageModerator = {
|
|
1804
|
+
inspect: (text: string) => {
|
|
1805
|
+
isOffensive: boolean;
|
|
1806
|
+
matchedTerms: readonly string[];
|
|
1807
|
+
};
|
|
1808
|
+
};
|
|
1809
|
+
declare class LogMessageUseCase {
|
|
1810
|
+
private readonly sessionRepository;
|
|
1811
|
+
private readonly messageRepository;
|
|
1812
|
+
private readonly realtime?;
|
|
1813
|
+
private readonly moderator?;
|
|
1814
|
+
constructor(sessionRepository: SessionRepository, messageRepository: MessageRepository, realtime?: RealtimeNotifierInterface | undefined, moderator?: MessageModerator | undefined);
|
|
1815
|
+
execute(params: LogMessageParams): Promise<MessageRow | undefined>;
|
|
1816
|
+
private moderationOf;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
interface LinkDocumentParams {
|
|
999
1820
|
companyId: string;
|
|
1000
1821
|
sessionId: string;
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
waMessageId?: string | null;
|
|
1009
|
-
status?: MessageStatus | null;
|
|
1822
|
+
messageId?: string | null;
|
|
1823
|
+
uploadId: string;
|
|
1824
|
+
filename: string;
|
|
1825
|
+
mimeType: string;
|
|
1826
|
+
sizeBytes: number;
|
|
1827
|
+
sha256?: string | null;
|
|
1828
|
+
source: string;
|
|
1010
1829
|
}
|
|
1011
|
-
interface
|
|
1830
|
+
interface ListDocumentsParams {
|
|
1012
1831
|
companyId: string;
|
|
1013
1832
|
sessionId: string;
|
|
1833
|
+
search?: string;
|
|
1834
|
+
/**
|
|
1835
|
+
* Origens aceitas, como lista explícita (`['agent', 'bot']`) e não como apelido de UI.
|
|
1836
|
+
*
|
|
1837
|
+
* Agrupamento tipo "Equipe" é vocabulário de tela e muda por produto; traduzir aqui obrigaria o
|
|
1838
|
+
* módulo a conhecer o rótulo de cada host. Quem recebe o apelido na borda é a rota.
|
|
1839
|
+
*/
|
|
1840
|
+
sources?: readonly string[];
|
|
1841
|
+
sortDirection?: 'asc' | 'desc';
|
|
1842
|
+
page?: number;
|
|
1014
1843
|
limit?: number;
|
|
1015
|
-
before?: string;
|
|
1016
1844
|
}
|
|
1017
|
-
|
|
1845
|
+
interface ListCompanyDocumentsParams$1 {
|
|
1846
|
+
companyId: string;
|
|
1847
|
+
search?: string;
|
|
1848
|
+
sources?: readonly string[];
|
|
1849
|
+
sortDirection?: 'asc' | 'desc';
|
|
1850
|
+
page?: number;
|
|
1851
|
+
limit?: number;
|
|
1852
|
+
}
|
|
1853
|
+
interface CompanyDocumentRow {
|
|
1854
|
+
id: string;
|
|
1855
|
+
uploadId: string;
|
|
1856
|
+
filename: string;
|
|
1857
|
+
mimeType: string;
|
|
1858
|
+
sizeBytes: number;
|
|
1859
|
+
source: string;
|
|
1860
|
+
linkedAt: Date;
|
|
1861
|
+
whatsappNumber: string;
|
|
1862
|
+
}
|
|
1863
|
+
interface ListCompanyDocumentsResult {
|
|
1864
|
+
rows: CompanyDocumentRow[];
|
|
1865
|
+
total: number;
|
|
1866
|
+
}
|
|
1867
|
+
interface ListDocumentsResult {
|
|
1868
|
+
rows: DocumentRow[];
|
|
1869
|
+
/** Total no servidor, ANTES do corte de página — é o que permite calcular a última página. */
|
|
1870
|
+
total: number;
|
|
1871
|
+
}
|
|
1872
|
+
declare class DocumentRepository {
|
|
1018
1873
|
private readonly db;
|
|
1019
1874
|
constructor(db: MetaWhatsAppDatabase);
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1875
|
+
/**
|
|
1876
|
+
* Idempotente por (companyId, uploadId), garantido pelo índice único e não por SELECT prévio: o
|
|
1877
|
+
* job de ingestão é reentregue por retry e duas tentativas concorrentes passariam as duas por uma
|
|
1878
|
+
* checagem, duplicando a linha no painel.
|
|
1879
|
+
*
|
|
1880
|
+
* Devolve `undefined` quando o documento já estava linkado.
|
|
1881
|
+
*/
|
|
1882
|
+
link(params: LinkDocumentParams): Promise<DocumentRow | undefined>;
|
|
1883
|
+
listByConversation(params: ListDocumentsParams): Promise<ListDocumentsResult>;
|
|
1884
|
+
/**
|
|
1885
|
+
* A biblioteca da EMPRESA inteira, não de uma conversa.
|
|
1886
|
+
*
|
|
1887
|
+
* A busca casa nome do arquivo OU telefone da conversa — ver `companyDocumentSearch`.
|
|
1888
|
+
*
|
|
1889
|
+
* Faz join com `sessions` para carregar de qual conversa cada arquivo veio — numa lista global,
|
|
1890
|
+
* arquivo sem essa referência é inútil: o atendente vê "comprovante.pdf" e não sabe de quem.
|
|
1891
|
+
*
|
|
1892
|
+
* Ordena por `linkedAt` apoiada no índice `idx_documents_company_linked`, que já existia para a
|
|
1893
|
+
* varredura de retenção.
|
|
1894
|
+
*/
|
|
1895
|
+
listByCompany(params: ListCompanyDocumentsParams$1): Promise<ListCompanyDocumentsResult>;
|
|
1896
|
+
/**
|
|
1897
|
+
* Um documento pela key do objeto. Serve para recuperar o nome original na hora de assinar o
|
|
1898
|
+
* download: a key é caminho no bucket e salvaria o arquivo com o id da Meta.
|
|
1899
|
+
*/
|
|
1900
|
+
findByUploadId(companyId: string, uploadId: string): Promise<DocumentRow | undefined>;
|
|
1901
|
+
/**
|
|
1902
|
+
* Os objetos a apagar no storage antes de a linha sumir.
|
|
1903
|
+
*
|
|
1904
|
+
* Existe porque a cascata da FK apaga a linha e deixa o binário órfão: quem for apagar a conversa
|
|
1905
|
+
* precisa desta lista primeiro, senão paga armazenamento para sempre por arquivo inalcançável.
|
|
1906
|
+
*/
|
|
1907
|
+
listUploadIdsBySession(companyId: string, sessionId: string): Promise<string[]>;
|
|
1908
|
+
/** Varredura de retenção por idade — o par é o mesmo cuidado com o objeto no storage. */
|
|
1909
|
+
listExpired(companyId: string, olderThan: Date, limit?: number): Promise<DocumentRow[]>;
|
|
1910
|
+
deleteById(companyId: string, id: string): Promise<void>;
|
|
1034
1911
|
}
|
|
1035
1912
|
|
|
1036
1913
|
type SendTextParams = {
|
|
@@ -1067,7 +1944,8 @@ declare class SendMessageUseCase {
|
|
|
1067
1944
|
private readonly sessionRepository;
|
|
1068
1945
|
private readonly logMessage;
|
|
1069
1946
|
private readonly objectStorage?;
|
|
1070
|
-
|
|
1947
|
+
private readonly documentRepository?;
|
|
1948
|
+
constructor(channel: ChannelAdapterInterface, sessionRepository: SessionRepository, logMessage: LogMessageUseCase, objectStorage?: ObjectStorageInterface | undefined, documentRepository?: DocumentRepository | undefined);
|
|
1071
1949
|
private assertWithinWindow;
|
|
1072
1950
|
sendText(params: SendTextParams): Promise<MessageRow | undefined>;
|
|
1073
1951
|
sendMedia(params: SendMediaParams): Promise<MessageRow | undefined>;
|
|
@@ -1120,6 +1998,143 @@ declare class ListMessagesUseCase {
|
|
|
1120
1998
|
execute(params: ListMessagesParams): Promise<MessageRow[]>;
|
|
1121
1999
|
}
|
|
1122
2000
|
|
|
2001
|
+
type ListConversationDocumentsParams = {
|
|
2002
|
+
companyId: string;
|
|
2003
|
+
whatsappNumber: string;
|
|
2004
|
+
search?: string;
|
|
2005
|
+
/** Origens explícitas. O apelido de UI ("Equipe") é traduzido na borda HTTP, não aqui. */
|
|
2006
|
+
sources?: readonly string[];
|
|
2007
|
+
sortDirection?: 'asc' | 'desc';
|
|
2008
|
+
page?: number;
|
|
2009
|
+
limit?: number;
|
|
2010
|
+
};
|
|
2011
|
+
/**
|
|
2012
|
+
* O que o `ConversationDocumentsPanel` do conversations-ui consome. O shape é o do pacote de UI
|
|
2013
|
+
* (`ConversationDocument`), com `linkedAt` já em ISO para não obrigar cada host a serializar.
|
|
2014
|
+
*/
|
|
2015
|
+
type ConversationDocumentView = {
|
|
2016
|
+
/**
|
|
2017
|
+
* O `uploadId` (key no storage), e NÃO o id da linha.
|
|
2018
|
+
*
|
|
2019
|
+
* É este valor que o consumidor devolve para pedir a URL assinada ou montar o zip, então expor o
|
|
2020
|
+
* UUID da tabela aqui fazia o download apontar para um objeto inexistente — falha que só aparece
|
|
2021
|
+
* no clique, porque a assinatura é gerada sem consultar o bucket.
|
|
2022
|
+
*/
|
|
2023
|
+
id: string;
|
|
2024
|
+
filename: string;
|
|
2025
|
+
mimeType: string;
|
|
2026
|
+
sizeBytes: number;
|
|
2027
|
+
source: string;
|
|
2028
|
+
linkedAt: string;
|
|
2029
|
+
};
|
|
2030
|
+
/** Espelha o `ConversationDocumentPage` do conversations-ui: lista da página + total no servidor. */
|
|
2031
|
+
type ConversationDocumentsPage = {
|
|
2032
|
+
documents: ConversationDocumentView[];
|
|
2033
|
+
total: number;
|
|
2034
|
+
};
|
|
2035
|
+
declare class ListConversationDocumentsUseCase {
|
|
2036
|
+
private readonly sessionRepository;
|
|
2037
|
+
private readonly documentRepository;
|
|
2038
|
+
constructor(sessionRepository: SessionRepository, documentRepository: DocumentRepository);
|
|
2039
|
+
execute(params: ListConversationDocumentsParams): Promise<ConversationDocumentsPage>;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
type ListCompanyDocumentsParams = {
|
|
2043
|
+
companyId: string;
|
|
2044
|
+
search?: string;
|
|
2045
|
+
/** Origens explícitas. O apelido de UI ("Equipe") é traduzido na borda HTTP, não aqui. */
|
|
2046
|
+
sources?: readonly string[];
|
|
2047
|
+
sortDirection?: 'asc' | 'desc';
|
|
2048
|
+
page?: number;
|
|
2049
|
+
limit?: number;
|
|
2050
|
+
};
|
|
2051
|
+
/**
|
|
2052
|
+
* Um arquivo na biblioteca da empresa. Igual ao da conversa, mais `conversationId` — sem saber de
|
|
2053
|
+
* quem veio, uma lista global de anexos não responde nenhuma pergunta útil.
|
|
2054
|
+
*/
|
|
2055
|
+
type CompanyDocumentView = {
|
|
2056
|
+
/** O `uploadId` (key no storage), pelo mesmo motivo do `ConversationDocumentView`. */
|
|
2057
|
+
id: string;
|
|
2058
|
+
conversationId: string;
|
|
2059
|
+
filename: string;
|
|
2060
|
+
mimeType: string;
|
|
2061
|
+
sizeBytes: number;
|
|
2062
|
+
source: string;
|
|
2063
|
+
linkedAt: string;
|
|
2064
|
+
};
|
|
2065
|
+
type CompanyDocumentsPage = {
|
|
2066
|
+
documents: CompanyDocumentView[];
|
|
2067
|
+
total: number;
|
|
2068
|
+
};
|
|
2069
|
+
/**
|
|
2070
|
+
* Biblioteca de arquivos de todas as conversas da empresa.
|
|
2071
|
+
*
|
|
2072
|
+
* Existe separada de `ListConversationDocumentsUseCase` porque a pergunta é outra: aquela parte de
|
|
2073
|
+
* uma conversa conhecida, esta varre a empresa e por isso precisa dizer de qual conversa cada
|
|
2074
|
+
* arquivo veio. Reaproveitar a primeira exigiria um `sessionId` opcional que muda o significado do
|
|
2075
|
+
* retorno — dois nomes claros custam menos que um parâmetro que dobra o comportamento.
|
|
2076
|
+
*/
|
|
2077
|
+
declare class ListCompanyDocumentsUseCase {
|
|
2078
|
+
private readonly documentRepository;
|
|
2079
|
+
constructor(documentRepository: DocumentRepository);
|
|
2080
|
+
execute(params: ListCompanyDocumentsParams): Promise<CompanyDocumentsPage>;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
type DeleteConversationParams = {
|
|
2084
|
+
companyId: string;
|
|
2085
|
+
whatsappNumber: string;
|
|
2086
|
+
};
|
|
2087
|
+
type DeleteConversationResult = {
|
|
2088
|
+
/** Objetos efetivamente apagados no storage. */
|
|
2089
|
+
deletedObjects: number;
|
|
2090
|
+
/** Objetos que o storage recusou. A conversa NÃO é apagada quando isto é maior que zero. */
|
|
2091
|
+
failedObjects: readonly string[];
|
|
2092
|
+
};
|
|
2093
|
+
/**
|
|
2094
|
+
* Apaga a conversa e a mídia dela.
|
|
2095
|
+
*
|
|
2096
|
+
* A ordem é o ponto: **storage primeiro, banco depois**. A FK de `documents.session_id` é
|
|
2097
|
+
* `on delete cascade`, então apagar a sessão primeiro derrubaria as linhas e levaria embora a única
|
|
2098
|
+
* lista de `uploadId` existente — os binários ficariam órfãos, cobrados para sempre e inalcançáveis.
|
|
2099
|
+
*
|
|
2100
|
+
* Se algum objeto falhar, a conversa é preservada e o chamador recebe a lista. Apagar as linhas
|
|
2101
|
+
* "mesmo assim" transformaria uma falha visível e reexecutável em lixo silencioso no storage.
|
|
2102
|
+
*/
|
|
2103
|
+
declare class DeleteConversationUseCase {
|
|
2104
|
+
private readonly sessionRepository;
|
|
2105
|
+
private readonly documentRepository;
|
|
2106
|
+
private readonly objectStorage?;
|
|
2107
|
+
constructor(sessionRepository: SessionRepository, documentRepository: DocumentRepository, objectStorage?: ObjectStorageInterface | undefined);
|
|
2108
|
+
execute(params: DeleteConversationParams): Promise<DeleteConversationResult>;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
type PurgeExpiredDocumentsParams = {
|
|
2112
|
+
companyId: string;
|
|
2113
|
+
/** Dias de retenção. O produto configura; o módulo não escolhe política de dado pessoal. */
|
|
2114
|
+
retentionDays: number;
|
|
2115
|
+
/** Teto por execução, para o job não segurar conexão nem storage por tempo indefinido. */
|
|
2116
|
+
batchSize?: number;
|
|
2117
|
+
/** Instante de referência — injetado para o teste não depender do relógio. */
|
|
2118
|
+
now?: Date;
|
|
2119
|
+
};
|
|
2120
|
+
type PurgeExpiredDocumentsResult = {
|
|
2121
|
+
purged: number;
|
|
2122
|
+
failed: readonly string[];
|
|
2123
|
+
};
|
|
2124
|
+
/**
|
|
2125
|
+
* Apaga documento vencido: objeto no storage primeiro, linha depois — a mesma ordem do
|
|
2126
|
+
* `DeleteConversationUseCase`, e pelo mesmo motivo.
|
|
2127
|
+
*
|
|
2128
|
+
* A linha só cai quando o objeto caiu. Contar como purgado sem ter apagado o binário deixaria lixo
|
|
2129
|
+
* pago no storage sem nenhum registro que o alcance depois: a linha era o único ponteiro.
|
|
2130
|
+
*/
|
|
2131
|
+
declare class PurgeExpiredDocumentsUseCase {
|
|
2132
|
+
private readonly documentRepository;
|
|
2133
|
+
private readonly objectStorage?;
|
|
2134
|
+
constructor(documentRepository: DocumentRepository, objectStorage?: ObjectStorageInterface | undefined);
|
|
2135
|
+
execute(params: PurgeExpiredDocumentsParams): Promise<PurgeExpiredDocumentsResult>;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
1123
2138
|
type ExportConversationParams = {
|
|
1124
2139
|
companyId: string;
|
|
1125
2140
|
whatsappNumber: string;
|
|
@@ -1241,10 +2256,77 @@ declare class FlowInterpreter {
|
|
|
1241
2256
|
}): Promise<FlowRunResult>;
|
|
1242
2257
|
}
|
|
1243
2258
|
|
|
2259
|
+
type FlowMediaLocation = {
|
|
2260
|
+
companyId: string;
|
|
2261
|
+
flowKey: string;
|
|
2262
|
+
nodeId: string;
|
|
2263
|
+
};
|
|
2264
|
+
type AttachFlowMediaParams = FlowMediaLocation & {
|
|
2265
|
+
uploadId: string;
|
|
2266
|
+
filename: string;
|
|
2267
|
+
mimeType: string;
|
|
2268
|
+
sizeBytes: number;
|
|
2269
|
+
caption?: string;
|
|
2270
|
+
sortOrder?: number;
|
|
2271
|
+
};
|
|
2272
|
+
type UpdateFlowMediaParams = {
|
|
2273
|
+
companyId: string;
|
|
2274
|
+
id: string;
|
|
2275
|
+
caption?: string | null;
|
|
2276
|
+
sortOrder?: number;
|
|
2277
|
+
active?: boolean;
|
|
2278
|
+
};
|
|
2279
|
+
declare class FlowMediaRepository {
|
|
2280
|
+
private readonly db;
|
|
2281
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
2282
|
+
private locationFilter;
|
|
2283
|
+
listActive(location: FlowMediaLocation): Promise<FlowMediaRow[]>;
|
|
2284
|
+
listAll(location: FlowMediaLocation): Promise<FlowMediaRow[]>;
|
|
2285
|
+
/**
|
|
2286
|
+
* Anexa um arquivo já existente no storage ao nó.
|
|
2287
|
+
*
|
|
2288
|
+
* `onConflictDoUpdate` em vez de deixar estourar: reanexar o mesmo arquivo é clique repetido no
|
|
2289
|
+
* editor, e o esperado ali é atualizar a legenda/ordem — não um erro de índice único na cara de
|
|
2290
|
+
* quem está montando o fluxo.
|
|
2291
|
+
*/
|
|
2292
|
+
attach(params: AttachFlowMediaParams): Promise<FlowMediaRow>;
|
|
2293
|
+
update(params: UpdateFlowMediaParams): Promise<FlowMediaRow | undefined>;
|
|
2294
|
+
/**
|
|
2295
|
+
* Desanexa do nó. Não toca no storage de propósito: o mesmo `uploadId` pode estar anexado a
|
|
2296
|
+
* outro nó ou a outro fluxo, e apagar o binário aqui quebraria os demais. Quem apaga objeto é o
|
|
2297
|
+
* host, que é dono da biblioteca de arquivos.
|
|
2298
|
+
*/
|
|
2299
|
+
detach(params: {
|
|
2300
|
+
companyId: string;
|
|
2301
|
+
id: string;
|
|
2302
|
+
}): Promise<void>;
|
|
2303
|
+
detachRemovedNodes(params: {
|
|
2304
|
+
companyId: string;
|
|
2305
|
+
flowKey: string;
|
|
2306
|
+
existingNodeIds: string[];
|
|
2307
|
+
}): Promise<void>;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* Copyright (c) 2026 Ada Technology. MIT License.
|
|
2312
|
+
*
|
|
2313
|
+
* O algoritmo mora em `meta-graph-core`, compartilhado com os outros objetos que a Meta assina.
|
|
2314
|
+
* Aqui fica só a tradução para o vocabulário deste domínio: o erro lançado e o namespace do nonce.
|
|
2315
|
+
*/
|
|
2316
|
+
|
|
1244
2317
|
interface NonceStoreInterface {
|
|
1245
2318
|
setIfAbsent(key: string, ttlSeconds: number): Promise<boolean>;
|
|
2319
|
+
/**
|
|
2320
|
+
* Estende a chave já reivindicada para a janela cheia. Um SET simples, sem NX.
|
|
2321
|
+
*
|
|
2322
|
+
* Opcional só por compatibilidade com hosts que ainda não o implementam: sem ele o claim curto
|
|
2323
|
+
* expira, e a Meta pode reentregar uma entrega já processada — trabalho repetido, que a dedupe
|
|
2324
|
+
* por `waMessageId` ainda segura antes de virar efeito visível ao cliente. Implementar é o
|
|
2325
|
+
* caminho correto.
|
|
2326
|
+
*/
|
|
2327
|
+
confirm?(key: string, ttlSeconds: number): Promise<void>;
|
|
1246
2328
|
}
|
|
1247
|
-
|
|
2329
|
+
|
|
1248
2330
|
declare function verifyWebhookChallenge(params: {
|
|
1249
2331
|
mode: string | null;
|
|
1250
2332
|
token: string | null;
|
|
@@ -1256,11 +2338,101 @@ declare function verifyWebhookSignature(params: {
|
|
|
1256
2338
|
signatureHeader: string | null | undefined;
|
|
1257
2339
|
appSecret: string;
|
|
1258
2340
|
}): void;
|
|
2341
|
+
/**
|
|
2342
|
+
* Reivindica a entrega por pouco tempo. O par obrigatório é `confirmWebhookDelivery` ao fim do
|
|
2343
|
+
* processamento — ver `WEBHOOK_CLAIM_TTL_SECONDS` para o porquê dos dois tempos.
|
|
2344
|
+
*/
|
|
1259
2345
|
declare function claimWebhookDelivery(params: {
|
|
1260
2346
|
nonceStore: NonceStoreInterface;
|
|
1261
2347
|
signatureHeader: string;
|
|
1262
2348
|
ttlSeconds?: number;
|
|
1263
2349
|
}): Promise<boolean>;
|
|
2350
|
+
/** Só depois da entrega processada por inteiro: é isto que fecha a janela anti-replay. */
|
|
2351
|
+
declare function confirmWebhookDelivery(params: {
|
|
2352
|
+
nonceStore: NonceStoreInterface;
|
|
2353
|
+
signatureHeader: string;
|
|
2354
|
+
ttlSeconds?: number;
|
|
2355
|
+
}): Promise<void>;
|
|
2356
|
+
|
|
2357
|
+
/**
|
|
2358
|
+
* Copyright (c) 2026 Ada Technology. MIT License.
|
|
2359
|
+
*
|
|
2360
|
+
* Separa a entrega do webhook em duas metades: o que precisa acontecer antes de responder à Meta
|
|
2361
|
+
* (persistir a mensagem, que é escrita local e rápida) e o que pode esperar alguns milissegundos
|
|
2362
|
+
* (os hooks do host, que costumam falar com rede — motor de fluxo, IA, integrações).
|
|
2363
|
+
*
|
|
2364
|
+
* A segunda metade rodava dentro da requisição do webhook. Um deploy no meio de uma conversa
|
|
2365
|
+
* matava a chamada em voo, e o cliente ficava sem resposta sem que nada registrasse a perda. Com
|
|
2366
|
+
* uma fila no host, derrubar o processo vira atraso: o job espera e é retomado.
|
|
2367
|
+
*/
|
|
2368
|
+
|
|
2369
|
+
type InboundMediaDescriptor = {
|
|
2370
|
+
sourceMediaId: string;
|
|
2371
|
+
mimeType: string;
|
|
2372
|
+
filename?: string;
|
|
2373
|
+
};
|
|
2374
|
+
type InboundMessageEffectsJob = {
|
|
2375
|
+
kind: 'message';
|
|
2376
|
+
companyId: string;
|
|
2377
|
+
message: WhatsAppMessage;
|
|
2378
|
+
savedMessageId: string;
|
|
2379
|
+
media?: InboundMediaDescriptor;
|
|
2380
|
+
receivedAt: number;
|
|
2381
|
+
};
|
|
2382
|
+
type InboundStatusEffectsJob = {
|
|
2383
|
+
kind: 'status';
|
|
2384
|
+
companyId: string;
|
|
2385
|
+
status: WhatsAppStatus;
|
|
2386
|
+
whatsappNumber: string;
|
|
2387
|
+
receivedAt: number;
|
|
2388
|
+
};
|
|
2389
|
+
type InboundDispatchJob = InboundMessageEffectsJob | InboundStatusEffectsJob;
|
|
2390
|
+
/**
|
|
2391
|
+
* Porta de fila. O módulo não escolhe a tecnologia — BullMQ, SQS, o que o host já tiver — mas
|
|
2392
|
+
* exige dela duas garantias, e sem as duas o padrão não entrega o que promete:
|
|
2393
|
+
*
|
|
2394
|
+
* 1. **Durabilidade**: o job sobrevive à morte do processo que o enfileirou. Uma fila em memória
|
|
2395
|
+
* reintroduz exatamente a perda que este desenho existe para evitar.
|
|
2396
|
+
* 2. **Retentativa com backoff**: o destino dos hooks (n8n, IA, API de terceiro) também cai em
|
|
2397
|
+
* deploy. Sem retry, o job falha uma vez e a conversa morre do mesmo jeito.
|
|
2398
|
+
*
|
|
2399
|
+
* `jobId` é estável e derivado da mensagem: reentrega da Meta e re-enfileiramento produzem o mesmo
|
|
2400
|
+
* id, e a fila descarta o segundo em vez de rodar o efeito duas vezes.
|
|
2401
|
+
*/
|
|
2402
|
+
interface InboundDispatchQueueInterface {
|
|
2403
|
+
enqueue(job: InboundDispatchJob, options: {
|
|
2404
|
+
jobId: string;
|
|
2405
|
+
}): Promise<void>;
|
|
2406
|
+
}
|
|
2407
|
+
declare function buildInboundJobId(job: InboundDispatchJob): string;
|
|
2408
|
+
declare function toSessionContract(row: SessionRow): ConversationSession;
|
|
2409
|
+
/**
|
|
2410
|
+
* Os efeitos de host de uma entrega já persistida. Vive fora do `ReceiveWebhookUseCase` porque
|
|
2411
|
+
* roda nos dois lados: inline, quando o host não configurou fila, e dentro do worker, quando
|
|
2412
|
+
* configurou. Um só corpo de regra para os dois caminhos — duas cópias divergiriam.
|
|
2413
|
+
*/
|
|
2414
|
+
declare class InboundEffectsDispatcher {
|
|
2415
|
+
private readonly params;
|
|
2416
|
+
constructor(params: {
|
|
2417
|
+
sessionRepository: SessionRepository;
|
|
2418
|
+
hooks?: MetaWhatsAppHooks;
|
|
2419
|
+
realtime?: RealtimeNotifierInterface;
|
|
2420
|
+
});
|
|
2421
|
+
run(job: InboundDispatchJob): Promise<void>;
|
|
2422
|
+
runMessageEffects(job: InboundMessageEffectsJob): Promise<void>;
|
|
2423
|
+
runStatusEffects(job: InboundStatusEffectsJob): Promise<void>;
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Ponto de entrada do worker do host: recebe o job que a fila devolveu e roda os efeitos.
|
|
2427
|
+
*
|
|
2428
|
+
* Deixe a exceção propagar. É ela que faz a fila contar a tentativa e reagendar com backoff;
|
|
2429
|
+
* capturar aqui para logar transforma falha recuperável em mensagem perdida em silêncio.
|
|
2430
|
+
*/
|
|
2431
|
+
declare class ProcessInboundDispatchUseCase {
|
|
2432
|
+
private readonly dispatcher;
|
|
2433
|
+
constructor(dispatcher: InboundEffectsDispatcher);
|
|
2434
|
+
execute(job: InboundDispatchJob): Promise<void>;
|
|
2435
|
+
}
|
|
1264
2436
|
|
|
1265
2437
|
type ReceiveWebhookParams = {
|
|
1266
2438
|
companyId: string;
|
|
@@ -1271,11 +2443,13 @@ type ReceiveWebhookResult = {
|
|
|
1271
2443
|
duplicate: boolean;
|
|
1272
2444
|
messagesProcessed: number;
|
|
1273
2445
|
statusesProcessed: number;
|
|
2446
|
+
ignoredForeignNumber: number;
|
|
1274
2447
|
};
|
|
1275
2448
|
declare class ReceiveWebhookUseCase {
|
|
1276
2449
|
private readonly params;
|
|
1277
2450
|
constructor(params: {
|
|
1278
2451
|
appSecret: string;
|
|
2452
|
+
phoneNumberId: string;
|
|
1279
2453
|
nonceStore: NonceStoreInterface;
|
|
1280
2454
|
sessionRepository: SessionRepository;
|
|
1281
2455
|
messageRepository: MessageRepository;
|
|
@@ -1283,12 +2457,52 @@ declare class ReceiveWebhookUseCase {
|
|
|
1283
2457
|
startState: SessionState;
|
|
1284
2458
|
hooks?: MetaWhatsAppHooks;
|
|
1285
2459
|
realtime?: RealtimeNotifierInterface;
|
|
2460
|
+
/**
|
|
2461
|
+
* Sem fila o módulo se comporta como sempre: os hooks rodam dentro da requisição do webhook.
|
|
2462
|
+
* Configurá-la é o que faz a conversa sobreviver a um deploy no meio do atendimento.
|
|
2463
|
+
*/
|
|
2464
|
+
inboundQueue?: InboundDispatchQueueInterface;
|
|
1286
2465
|
});
|
|
2466
|
+
private readonly dispatcher;
|
|
1287
2467
|
execute(input: ReceiveWebhookParams): Promise<ReceiveWebhookResult>;
|
|
1288
2468
|
private handleMessage;
|
|
1289
2469
|
private handleStatus;
|
|
2470
|
+
private dispatch;
|
|
1290
2471
|
}
|
|
1291
2472
|
|
|
2473
|
+
/**
|
|
2474
|
+
* Política efetiva de transcrição de uma empresa.
|
|
2475
|
+
*
|
|
2476
|
+
* A separação que este arquivo existe para manter: **ambiente decide se é POSSÍVEL, settings decide
|
|
2477
|
+
* se é para FAZER.** A capacidade (engine, chave, storage que sabe reler) é injetada pelo host e é
|
|
2478
|
+
* por deploy — chave de API não vai para tabela de configuração de tenant. Já "transcrever ou não" e
|
|
2479
|
+
* "automático ou sob demanda" são decisão de operação de cada empresa, e pedir deploy para mudar
|
|
2480
|
+
* isso é o que transforma um interruptor em ticket.
|
|
2481
|
+
*/
|
|
2482
|
+
type TranscriptionPolicy = {
|
|
2483
|
+
readonly isEnabled: boolean;
|
|
2484
|
+
readonly mode: TranscriptionMode;
|
|
2485
|
+
};
|
|
2486
|
+
type TranscriptionPolicyDefaults = {
|
|
2487
|
+
/** O que vale quando o painel não decidiu. Tipicamente vem do ambiente do host. */
|
|
2488
|
+
readonly isEnabled: boolean;
|
|
2489
|
+
readonly mode: TranscriptionMode;
|
|
2490
|
+
};
|
|
2491
|
+
type ResolveTranscriptionPolicyDependencies = {
|
|
2492
|
+
readonly settingsRepository: SettingsRepository;
|
|
2493
|
+
readonly defaults: TranscriptionPolicyDefaults;
|
|
2494
|
+
};
|
|
2495
|
+
/**
|
|
2496
|
+
* Resolve a política por empresa, com o padrão do host como base.
|
|
2497
|
+
*
|
|
2498
|
+
* Uma consulta a `settings` por áudio transcrito. Não é cacheado de propósito: é uma leitura por
|
|
2499
|
+
* chave primária, acontece uma vez por nota de voz (não por mensagem), e cachear introduziria a
|
|
2500
|
+
* pergunta "por quanto tempo o operador continua vendo o interruptor antigo depois de mexer nele" —
|
|
2501
|
+
* custo real, para economizar um índice único.
|
|
2502
|
+
*/
|
|
2503
|
+
declare function createTranscriptionPolicyResolver(dependencies: ResolveTranscriptionPolicyDependencies): (companyId: string) => Promise<TranscriptionPolicy>;
|
|
2504
|
+
type TranscriptionPolicyResolver = ReturnType<typeof createTranscriptionPolicyResolver>;
|
|
2505
|
+
|
|
1292
2506
|
type IngestInboundMediaParams = {
|
|
1293
2507
|
companyId: string;
|
|
1294
2508
|
messageId: string;
|
|
@@ -1299,13 +2513,52 @@ type IngestInboundMediaParams = {
|
|
|
1299
2513
|
type IngestInboundMediaResult = {
|
|
1300
2514
|
uploadId: string;
|
|
1301
2515
|
alreadyIngested: boolean;
|
|
2516
|
+
/**
|
|
2517
|
+
* Só presente quando a transcrição automática rodou nesta execução. Ausente é o normal: mídia que
|
|
2518
|
+
* não é áudio, transcrição desligada, modo sob demanda, ou mídia já ingerida antes.
|
|
2519
|
+
*/
|
|
2520
|
+
transcription?: {
|
|
2521
|
+
status: TranscriptionStatus;
|
|
2522
|
+
};
|
|
2523
|
+
};
|
|
2524
|
+
/**
|
|
2525
|
+
* Transcrição durante a ingestão — o modo `auto`.
|
|
2526
|
+
*
|
|
2527
|
+
* Entra aqui, e não em use-case separado, por um motivo só: neste ponto o buffer do áudio ACABOU de
|
|
2528
|
+
* ser baixado e está em memória. Transcrever fora daqui custaria um segundo download do storage por
|
|
2529
|
+
* áudio, e o `TranscribeAudioUseCase` existe justamente para esse caso (sob demanda e retomada).
|
|
2530
|
+
*/
|
|
2531
|
+
type IngestTranscriptionOptions = {
|
|
2532
|
+
transcriber: AudioTranscriber;
|
|
2533
|
+
messageRepository: MessageRepository;
|
|
2534
|
+
/**
|
|
2535
|
+
* Política POR EMPRESA, resolvida a cada áudio. Não é um `mode` fixo porque o interruptor mora nas
|
|
2536
|
+
* configurações da empresa: um valor capturado na construção do use-case congelaria a escolha até
|
|
2537
|
+
* o próximo deploy, e o worker é um processo longo — o operador mexeria no painel e nada mudaria.
|
|
2538
|
+
*/
|
|
2539
|
+
resolvePolicy: TranscriptionPolicyResolver;
|
|
2540
|
+
languageHint?: string;
|
|
2541
|
+
hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
|
|
1302
2542
|
};
|
|
1303
2543
|
declare class IngestInboundMediaUseCase {
|
|
1304
2544
|
private readonly db;
|
|
1305
2545
|
private readonly channel;
|
|
1306
2546
|
private readonly objectStorage;
|
|
1307
|
-
|
|
2547
|
+
private readonly documentRepository?;
|
|
2548
|
+
private readonly transcription?;
|
|
2549
|
+
constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface, documentRepository?: DocumentRepository | undefined, transcription?: IngestTranscriptionOptions | undefined);
|
|
1308
2550
|
execute(params: IngestInboundMediaParams): Promise<IngestInboundMediaResult>;
|
|
2551
|
+
/**
|
|
2552
|
+
* Transcreve o áudio recém-baixado, quando o modo é `auto`.
|
|
2553
|
+
*
|
|
2554
|
+
* **Nunca propaga erro.** Neste ponto o binário já está no storage e já entrou na biblioteca da
|
|
2555
|
+
* conversa: deixar uma falha de transcrição subir marcaria a ingestão inteira como falha, e o
|
|
2556
|
+
* retry do host baixaria de novo da Meta um arquivo que está salvo — gastando banda para reproduzir
|
|
2557
|
+
* um efeito que já aconteceu. O status fica gravado na mensagem e o `onTranscriptionDeferred`
|
|
2558
|
+
* avisa quem sabe reenfileirar.
|
|
2559
|
+
*/
|
|
2560
|
+
private transcribeIfAuto;
|
|
2561
|
+
private recordTranscriptionFailure;
|
|
1309
2562
|
}
|
|
1310
2563
|
declare function extractMediaDescriptor(message: MessageRow): {
|
|
1311
2564
|
sourceMediaId: string;
|
|
@@ -1313,6 +2566,105 @@ declare function extractMediaDescriptor(message: MessageRow): {
|
|
|
1313
2566
|
filename?: string;
|
|
1314
2567
|
} | undefined;
|
|
1315
2568
|
|
|
2569
|
+
/**
|
|
2570
|
+
* Storage com leitura garantida. `getObject` é opcional no contrato, então quem monta este use-case
|
|
2571
|
+
* precisa provar que o método existe — sem os bytes não há o que transcrever, e um use-case que
|
|
2572
|
+
* sempre falha é pior do que a ausência ser visível no tipo.
|
|
2573
|
+
*/
|
|
2574
|
+
type ReadableObjectStorage = ObjectStorageInterface & {
|
|
2575
|
+
getObject: NonNullable<ObjectStorageInterface['getObject']>;
|
|
2576
|
+
};
|
|
2577
|
+
type TranscribeAudioParams = {
|
|
2578
|
+
companyId: string;
|
|
2579
|
+
messageId: string;
|
|
2580
|
+
/**
|
|
2581
|
+
* Refaz mesmo com transcrição já salva. Serve ao "transcrever de novo" depois de trocar de engine
|
|
2582
|
+
* — sem isto, um resultado ruim do engine antigo ficaria congelado para sempre.
|
|
2583
|
+
*/
|
|
2584
|
+
force?: boolean;
|
|
2585
|
+
};
|
|
2586
|
+
type TranscribeAudioResult = {
|
|
2587
|
+
status: TranscriptionStatus;
|
|
2588
|
+
text: string | null;
|
|
2589
|
+
language: string | null;
|
|
2590
|
+
engine: string | null;
|
|
2591
|
+
/** `true` quando devolveu o que já estava salvo, sem gastar cota. */
|
|
2592
|
+
alreadyTranscribed: boolean;
|
|
2593
|
+
};
|
|
2594
|
+
type TranscribeAudioDependencies = {
|
|
2595
|
+
messageRepository: MessageRepository;
|
|
2596
|
+
objectStorage: ReadableObjectStorage;
|
|
2597
|
+
transcriber: AudioTranscriber;
|
|
2598
|
+
/**
|
|
2599
|
+
* Política por empresa. Ausente, a transcrição sob demanda não consulta configuração nenhuma e
|
|
2600
|
+
* atende sempre — que é o comportamento de quem controla o liga/desliga só por ambiente.
|
|
2601
|
+
*/
|
|
2602
|
+
resolvePolicy?: TranscriptionPolicyResolver;
|
|
2603
|
+
/** ISO 639-1 do produto. Informar corta a detecção do Whisper e evita pt-BR curto virar espanhol. */
|
|
2604
|
+
languageHint?: string;
|
|
2605
|
+
hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
|
|
2606
|
+
};
|
|
2607
|
+
/**
|
|
2608
|
+
* Transcreve o áudio de UMA mensagem já persistida e ingerida.
|
|
2609
|
+
*
|
|
2610
|
+
* Serve aos dois modos: é o que o painel chama no botão "transcrever" (`onDemand`) e é o que o host
|
|
2611
|
+
* chama ao retomar um `pending` reenfileirado. O modo `auto` não passa por aqui — ele transcreve
|
|
2612
|
+
* dentro da ingestão, onde o buffer já está em memória e não custa um segundo download.
|
|
2613
|
+
*
|
|
2614
|
+
* Idempotente por `transcription_status`: chamar de novo num `'done'` devolve o que está salvo em
|
|
2615
|
+
* vez de gastar cota transcrevendo o mesmo áudio.
|
|
2616
|
+
*/
|
|
2617
|
+
declare class TranscribeAudioUseCase {
|
|
2618
|
+
private readonly dependencies;
|
|
2619
|
+
constructor(dependencies: TranscribeAudioDependencies);
|
|
2620
|
+
execute(params: TranscribeAudioParams): Promise<TranscribeAudioResult>;
|
|
2621
|
+
private transcribeBuffer;
|
|
2622
|
+
/**
|
|
2623
|
+
* Carimba o motivo antes de propagar. O status é o que impede os dois desperdícios simétricos:
|
|
2624
|
+
* reprocessar para sempre um codec impossível, e desistir de um áudio que só esbarrou na cota.
|
|
2625
|
+
*/
|
|
2626
|
+
private persistFailure;
|
|
2627
|
+
}
|
|
2628
|
+
declare function resolveFailureStatus(error: unknown): TranscriptionStatus;
|
|
2629
|
+
|
|
2630
|
+
/**
|
|
2631
|
+
* Guarda um arquivo gravado no simulador e devolve o id que o webhook vai referenciar.
|
|
2632
|
+
*
|
|
2633
|
+
* Existe como use-case para o host só precisar da ROTA: receber o corpo, chamar isto, devolver o
|
|
2634
|
+
* `mediaId`. A parte que erra — onde gravar, com que chave, como marcar o id para o adaptador
|
|
2635
|
+
* reconhecer depois — fica aqui, num lugar só, e não em cada produto.
|
|
2636
|
+
*
|
|
2637
|
+
* O SDK para exatamente na porta do HTTP: registrar endpoint é do host, e um pacote que abrisse rota
|
|
2638
|
+
* no servidor de quem o instala decidiria caminho, autenticação e versionamento no lugar dele.
|
|
2639
|
+
*/
|
|
2640
|
+
type StorePreviewMediaParams = {
|
|
2641
|
+
companyId: string;
|
|
2642
|
+
/** Bytes do arquivo. Quem converte de base64 é a rota — o use-case não conhece transporte. */
|
|
2643
|
+
buffer: Buffer;
|
|
2644
|
+
mimeType: string;
|
|
2645
|
+
filename?: string;
|
|
2646
|
+
};
|
|
2647
|
+
type StorePreviewMediaResult = {
|
|
2648
|
+
/** Já com o prefixo: é isto que o simulador manda no webhook. */
|
|
2649
|
+
mediaId: string;
|
|
2650
|
+
uploadId: string;
|
|
2651
|
+
};
|
|
2652
|
+
declare class StorePreviewMediaUseCase {
|
|
2653
|
+
private readonly objectStorage;
|
|
2654
|
+
/**
|
|
2655
|
+
* Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
|
|
2656
|
+
* um teste precisa de chave previsível.
|
|
2657
|
+
*/
|
|
2658
|
+
private readonly generateKeySuffix;
|
|
2659
|
+
constructor(objectStorage: ObjectStorageInterface,
|
|
2660
|
+
/**
|
|
2661
|
+
* Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
|
|
2662
|
+
* um teste precisa de chave previsível.
|
|
2663
|
+
*/
|
|
2664
|
+
generateKeySuffix: () => string);
|
|
2665
|
+
execute(params: StorePreviewMediaParams): Promise<StorePreviewMediaResult>;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
1316
2668
|
interface MetaWhatsAppModuleConfig {
|
|
1317
2669
|
phoneNumberId: string;
|
|
1318
2670
|
accessToken: string;
|
|
@@ -1321,15 +2673,66 @@ interface MetaWhatsAppModuleConfig {
|
|
|
1321
2673
|
wabaId?: string;
|
|
1322
2674
|
apiVersion?: string;
|
|
1323
2675
|
baseUrl?: string;
|
|
2676
|
+
/**
|
|
2677
|
+
* Catálogo do Meta Commerce que a vitrine no chat oferece. Sem ele — ou sem `providers.catalog`
|
|
2678
|
+
* — a action `send_product_list` não é registrada: nó que o editor oferece e que em silêncio não
|
|
2679
|
+
* faz nada é pior do que nó que não existe.
|
|
2680
|
+
*/
|
|
2681
|
+
catalogId?: string;
|
|
1324
2682
|
}
|
|
1325
2683
|
interface MetaWhatsAppModuleFeatures {
|
|
1326
2684
|
flowEngine?: boolean;
|
|
2685
|
+
flowGraphCache?: boolean | {
|
|
2686
|
+
ttlSeconds?: number;
|
|
2687
|
+
};
|
|
2688
|
+
/**
|
|
2689
|
+
* Aceita mídia do simulador de conversa — o que faz o microfone aparecer no preview do cliente.
|
|
2690
|
+
*
|
|
2691
|
+
* **Desligado por omissão, e a decisão é consciente.** Ligado, o canal passa a aceitar id que não
|
|
2692
|
+
* veio da Meta: `preview-upload:<chave>` faz o servidor ler aquele objeto do storage. Em ambiente
|
|
2693
|
+
* de simulação isso é o recurso; em produção é leitura arbitrária do bucket por webhook forjado.
|
|
2694
|
+
*
|
|
2695
|
+
* Exige `providers.objectStorage` com `getObject` — sem os bytes não há o que devolver, e o flag é
|
|
2696
|
+
* ignorado em vez de produzir um canal que falha na primeira nota de voz.
|
|
2697
|
+
*/
|
|
2698
|
+
previewMedia?: boolean;
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Transcrição de áudio. Ausente = desligada, e as colunas ficam nulas ("não avaliado").
|
|
2702
|
+
*
|
|
2703
|
+
* `mode` é a decisão de produto que este objeto existe para carregar: `auto` transcreve toda nota de
|
|
2704
|
+
* voz recebida, na ingestão, onde o buffer já está em memória; `onDemand` só transcreve quando o
|
|
2705
|
+
* atendente pede, gastando cota apenas com áudio que alguém vai ler de fato.
|
|
2706
|
+
*/
|
|
2707
|
+
interface MetaWhatsAppTranscriptionConfig {
|
|
2708
|
+
transcriber: AudioTranscriber;
|
|
2709
|
+
/**
|
|
2710
|
+
* PADRÃO, não decisão final: vale para as empresas que não mexeram no interruptor do painel. As
|
|
2711
|
+
* configurações por empresa (`settings.transcriptionMode`) têm precedência.
|
|
2712
|
+
*
|
|
2713
|
+
* Padrão `onDemand` — o modo que não gasta cota sem alguém pedir.
|
|
2714
|
+
*/
|
|
2715
|
+
mode?: TranscriptionMode;
|
|
2716
|
+
/**
|
|
2717
|
+
* PADRÃO de ligado/desligado para empresas sem escolha registrada. `true` — injetar o transcritor
|
|
2718
|
+
* já é a declaração de que o host quer o recurso; quem controla por empresa usa o painel.
|
|
2719
|
+
*/
|
|
2720
|
+
isEnabledByDefault?: boolean;
|
|
2721
|
+
/** ISO 639-1 do produto (ex.: `'pt'`). Corta a detecção de idioma do engine. */
|
|
2722
|
+
languageHint?: string;
|
|
1327
2723
|
}
|
|
1328
2724
|
interface MetaWhatsAppModuleProviders {
|
|
1329
2725
|
objectStorage?: ObjectStorageInterface;
|
|
2726
|
+
cache?: CacheInterface;
|
|
1330
2727
|
realtime?: RealtimeNotifierInterface;
|
|
1331
2728
|
subjectResolver?: SubjectResolverInterface;
|
|
1332
2729
|
catalog?: CatalogPort;
|
|
2730
|
+
moderator?: MessageModerator;
|
|
2731
|
+
/**
|
|
2732
|
+
* Converte nota de voz em texto. Exige `objectStorage` com `getObject` para o modo sob demanda —
|
|
2733
|
+
* transcrever um áudio já salvo significa ler os bytes de volta.
|
|
2734
|
+
*/
|
|
2735
|
+
transcription?: MetaWhatsAppTranscriptionConfig;
|
|
1333
2736
|
}
|
|
1334
2737
|
interface CreateMetaWhatsAppModuleParams {
|
|
1335
2738
|
db: MetaWhatsAppDatabase;
|
|
@@ -1350,10 +2753,31 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
|
|
|
1350
2753
|
release: ReleaseConversationUseCase;
|
|
1351
2754
|
list: ListConversationsUseCase;
|
|
1352
2755
|
listMessages: ListMessagesUseCase;
|
|
2756
|
+
listDocuments: ListConversationDocumentsUseCase;
|
|
2757
|
+
listCompanyDocuments: ListCompanyDocumentsUseCase;
|
|
2758
|
+
delete: DeleteConversationUseCase;
|
|
2759
|
+
purgeExpiredDocuments: PurgeExpiredDocumentsUseCase;
|
|
1353
2760
|
export: ExportConversationUseCase;
|
|
2761
|
+
transcribeAudio: TranscribeAudioUseCase | undefined;
|
|
1354
2762
|
repository: SessionRepository;
|
|
2763
|
+
messageRepository: MessageRepository;
|
|
2764
|
+
documentRepository: DocumentRepository;
|
|
1355
2765
|
};
|
|
2766
|
+
/**
|
|
2767
|
+
* `undefined` = o host não injetou transcritor, e nenhuma configuração de empresa muda isso: a
|
|
2768
|
+
* capacidade não existe. Presente, `resolvePolicy` responde o que vale para uma empresa —
|
|
2769
|
+
* é o que a rota de configurações usa para dizer ao painel se desenha o interruptor.
|
|
2770
|
+
*/
|
|
2771
|
+
transcription: {
|
|
2772
|
+
defaultMode: TranscriptionMode;
|
|
2773
|
+
resolvePolicy: (companyId: string) => Promise<TranscriptionPolicy>;
|
|
2774
|
+
} | undefined;
|
|
1356
2775
|
settings: SettingsRepository;
|
|
2776
|
+
/**
|
|
2777
|
+
* `undefined` quando o recurso não está ligado (ou falta storage legível). O host consulta a
|
|
2778
|
+
* ausência para não registrar a rota de upload — e o preview, sem a rota, esconde o microfone.
|
|
2779
|
+
*/
|
|
2780
|
+
previewMedia: StorePreviewMediaUseCase | undefined;
|
|
1357
2781
|
webhook: {
|
|
1358
2782
|
receive: ReceiveWebhookUseCase;
|
|
1359
2783
|
verifyChallenge: (query: {
|
|
@@ -1372,14 +2796,42 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
|
|
|
1372
2796
|
delete: DeleteFlowGraphUseCase;
|
|
1373
2797
|
livePositions: GetLiveFlowPositionsUseCase;
|
|
1374
2798
|
repository: FlowGraphRepository;
|
|
2799
|
+
mediaRepository: FlowMediaRepository;
|
|
1375
2800
|
} | undefined;
|
|
1376
2801
|
catalog: CatalogPort | undefined;
|
|
1377
2802
|
};
|
|
1378
2803
|
type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>;
|
|
1379
2804
|
|
|
2805
|
+
/**
|
|
2806
|
+
* Leitura de mídia do simulador de conversa.
|
|
2807
|
+
*
|
|
2808
|
+
* **Atrás de flag de propósito, e não ligado por omissão.** Aceitar id que não veio da Meta é
|
|
2809
|
+
* exatamente o que um webhook forjado exploraria: bastaria mandar `preview-upload:<chave>` para
|
|
2810
|
+
* fazer o servidor ler um objeto arbitrário do storage e devolvê-lo. Num ambiente de simulação isso
|
|
2811
|
+
* é o recurso; em produção é leitura arbitrária. Quem liga assume, e a decisão fica visível no
|
|
2812
|
+
* lugar onde o módulo é montado.
|
|
2813
|
+
*/
|
|
2814
|
+
type PreviewMediaSupport = {
|
|
2815
|
+
readonly isEnabled: boolean;
|
|
2816
|
+
readonly objectStorage: ObjectStorageInterface & {
|
|
2817
|
+
getObject: NonNullable<ObjectStorageInterface['getObject']>;
|
|
2818
|
+
};
|
|
2819
|
+
/** Mime a devolver, já que o storage guarda bytes e não o tipo. Padrão `audio/ogg`. */
|
|
2820
|
+
readonly defaultMimeType?: string;
|
|
2821
|
+
};
|
|
1380
2822
|
declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
|
|
1381
2823
|
private readonly messages;
|
|
1382
|
-
|
|
2824
|
+
/**
|
|
2825
|
+
* Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
|
|
2826
|
+
* que atualizar o pacote não abre nada em quem não pediu.
|
|
2827
|
+
*/
|
|
2828
|
+
private readonly previewMedia?;
|
|
2829
|
+
constructor(messages: WhatsAppMessageProvider,
|
|
2830
|
+
/**
|
|
2831
|
+
* Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
|
|
2832
|
+
* que atualizar o pacote não abre nada em quem não pediu.
|
|
2833
|
+
*/
|
|
2834
|
+
previewMedia?: PreviewMediaSupport | undefined);
|
|
1383
2835
|
private translateErrors;
|
|
1384
2836
|
sendText(to: string, body: string): Promise<{
|
|
1385
2837
|
externalMessageId: string | null;
|
|
@@ -1412,6 +2864,34 @@ declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
|
|
|
1412
2864
|
}): Promise<{
|
|
1413
2865
|
externalMessageId: string | null;
|
|
1414
2866
|
}>;
|
|
2867
|
+
sendInteractiveButtons(params: {
|
|
2868
|
+
to: string;
|
|
2869
|
+
body: string;
|
|
2870
|
+
buttons: {
|
|
2871
|
+
id: string;
|
|
2872
|
+
title: string;
|
|
2873
|
+
}[];
|
|
2874
|
+
}): Promise<{
|
|
2875
|
+
externalMessageId: string | null;
|
|
2876
|
+
}>;
|
|
2877
|
+
sendProductList(params: {
|
|
2878
|
+
to: string;
|
|
2879
|
+
headerText: string;
|
|
2880
|
+
body: string;
|
|
2881
|
+
footerText?: string;
|
|
2882
|
+
sections: {
|
|
2883
|
+
title: string;
|
|
2884
|
+
retailerIds: string[];
|
|
2885
|
+
}[];
|
|
2886
|
+
}): Promise<{
|
|
2887
|
+
externalMessageId: string | null;
|
|
2888
|
+
}>;
|
|
2889
|
+
/**
|
|
2890
|
+
* Busca o binário da mídia — da Meta, ou do storage quando o id é do simulador.
|
|
2891
|
+
*
|
|
2892
|
+
* O desvio acontece ANTES de qualquer chamada de rede: id do simulador não existe na Meta, e
|
|
2893
|
+
* tentar buscá-lo lá renderia um 404 confuso em vez do áudio que o operador acabou de gravar.
|
|
2894
|
+
*/
|
|
1415
2895
|
fetchMediaAsBase64(mediaId: string): Promise<{
|
|
1416
2896
|
data: string;
|
|
1417
2897
|
mimeType: string;
|
|
@@ -1455,4 +2935,70 @@ declare function redeemSseTicket(store: TicketStoreInterface, ticket: string): P
|
|
|
1455
2935
|
whatsappNumber: string;
|
|
1456
2936
|
} | null>;
|
|
1457
2937
|
|
|
1458
|
-
|
|
2938
|
+
/**
|
|
2939
|
+
* Porta de escrita no transcript, e não a classe `LogMessageUseCase`.
|
|
2940
|
+
*
|
|
2941
|
+
* Exigir a classe amarraria a action a quem já guarda as mensagens nas tabelas do módulo — um host
|
|
2942
|
+
* em migração, com o transcript ainda no schema dele, não conseguiria usar a única action built-in
|
|
2943
|
+
* sem gravar o envio numa tabela que o painel dele não lê. O que a action precisa é só de um lugar
|
|
2944
|
+
* para registrar o que saiu.
|
|
2945
|
+
*/
|
|
2946
|
+
type FlowMediaTranscriptLogger = {
|
|
2947
|
+
execute(params: LogMessageParams): Promise<unknown>;
|
|
2948
|
+
};
|
|
2949
|
+
type CreateSendMediaActionParams = {
|
|
2950
|
+
flowMediaRepository: FlowMediaRepository;
|
|
2951
|
+
objectStorage: ObjectStorageInterface & {
|
|
2952
|
+
getObject: NonNullable<ObjectStorageInterface['getObject']>;
|
|
2953
|
+
};
|
|
2954
|
+
logMessage: FlowMediaTranscriptLogger;
|
|
2955
|
+
startState: SessionState;
|
|
2956
|
+
onError?: (error: unknown, details: {
|
|
2957
|
+
flowKey: string;
|
|
2958
|
+
nodeId: string;
|
|
2959
|
+
uploadId: string;
|
|
2960
|
+
}) => void;
|
|
2961
|
+
};
|
|
2962
|
+
/**
|
|
2963
|
+
* Action `send_media`: ao passar pelo nó, envia os arquivos que a biblioteca tem anexados a ele.
|
|
2964
|
+
*
|
|
2965
|
+
* É a única action built-in que o módulo implementa de ponta a ponta — as outras
|
|
2966
|
+
* (`handoff`, `send_product_list`) dependem de regra de negócio do produto. Esta não: "mandar
|
|
2967
|
+
* estes arquivos ao chegar aqui" é comportamento de canal, e replicá-la em cada host seria
|
|
2968
|
+
* copiar o mesmo código com os mesmos bugs.
|
|
2969
|
+
*
|
|
2970
|
+
* O nó não guarda os arquivos em `actionParams` de propósito. Se guardasse, trocar o material
|
|
2971
|
+
* exigiria editar e republicar o grafo — e o ponto todo é o contrário: quem cuida do conteúdo
|
|
2972
|
+
* troca o arquivo na biblioteca e o fluxo continua igual.
|
|
2973
|
+
*/
|
|
2974
|
+
declare function createSendMediaAction(params: CreateSendMediaActionParams): FlowActionHandler;
|
|
2975
|
+
|
|
2976
|
+
/** Tetos da Meta para `interactive.product_list`. Acima disso a mensagem inteira é recusada. */
|
|
2977
|
+
declare const PRODUCT_LIST_LIMIT: {
|
|
2978
|
+
readonly ITEMS: 30;
|
|
2979
|
+
readonly SECTIONS: 10;
|
|
2980
|
+
};
|
|
2981
|
+
type CreateSendProductListActionParams = {
|
|
2982
|
+
catalog: CatalogPort;
|
|
2983
|
+
catalogId: string;
|
|
2984
|
+
logMessage: FlowMediaTranscriptLogger;
|
|
2985
|
+
startState: SessionState;
|
|
2986
|
+
/** Falha de UMA vitrine não pode travar a conversa; o host observa por aqui. */
|
|
2987
|
+
onError?: (error: unknown, details: {
|
|
2988
|
+
flowKey: string;
|
|
2989
|
+
nodeId: string;
|
|
2990
|
+
}) => void;
|
|
2991
|
+
};
|
|
2992
|
+
/**
|
|
2993
|
+
* Action `send_product_list`: ao passar pelo nó, envia a vitrine do catálogo publicado na Meta.
|
|
2994
|
+
*
|
|
2995
|
+
* O nó guarda **critério**, não a lista de produtos. Congelar `retailerId` em `actionParams` faria
|
|
2996
|
+
* o fluxo continuar oferecendo o item esgotado — ou o item excluído, que a Meta recusa junto com a
|
|
2997
|
+
* mensagem inteira. O que o operador edita é o texto e o filtro; o estoque manda no resto.
|
|
2998
|
+
*
|
|
2999
|
+
* Só produto em estoque entra. Vitrine que mostra o que não tem produz a pior conversa possível:
|
|
3000
|
+
* o cliente escolhe, responde, e recebe "acabou".
|
|
3001
|
+
*/
|
|
3002
|
+
declare function createSendProductListAction(params: CreateSendProductListActionParams): FlowActionHandler;
|
|
3003
|
+
|
|
3004
|
+
export { type AttachFlowMediaParams, type AudioTranscriber, type CompanyDocumentView, type CompanyDocumentsPage, type ConversationDocumentView, type ConversationDocumentsPage, type CreateFlowGraphParams, CreateFlowGraphUseCase, type CreateMetaWhatsAppModuleParams, type CreateSendMediaActionParams, type CreateSendProductListActionParams, DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS, type DeleteConversationParams, type DeleteConversationResult, DeleteConversationUseCase, DeleteFlowGraphUseCase, DocumentRepository, type DocumentRow, type DrizzleMigrateFunction, type ExportConversationParams, type ExportConversationResult, ExportConversationUseCase, FlowGraphCache, FlowGraphRepository, type FlowGraphRow, FlowInterpreter, type FlowMediaLocation, FlowMediaRepository, type FlowMediaRow, type FlowMediaTranscriptLogger, type FlowRunResult, type FlowStepInput, type FlowStepResult, GetFlowGraphUseCase, GetLiveFlowPositionsUseCase, type InboundDispatchJob, type InboundDispatchQueueInterface, InboundEffectsDispatcher, type InboundMediaDescriptor, type InboundMessageEffectsJob, type InboundStatusEffectsJob, type IngestInboundMediaParams, type IngestInboundMediaResult, IngestInboundMediaUseCase, type IngestTranscriptionOptions, type InsertMessageParams, InvalidFlowGraphError, type LinkDocumentParams, type ListCompanyDocumentsParams, ListCompanyDocumentsUseCase, type ListConversationDocumentsParams, ListConversationDocumentsUseCase, type ListConversationsFilters, type ListConversationsParams, ListConversationsUseCase, type ListDocumentsParams, type ListDocumentsResult, ListFlowGraphsUseCase, type ListMessagesParams, ListMessagesUseCase, type LogMessageParams, LogMessageUseCase, META_WHATSAPP_MIGRATIONS_TABLE, type MessageModerator, MessageRepository, type MessageRow, type MetaWhatsAppDatabase, type MetaWhatsAppModule, type MetaWhatsAppModuleConfig, type MetaWhatsAppModuleFeatures, type MetaWhatsAppModuleProviders, type MetaWhatsAppTranscriptionConfig, type NewDocumentRow, type NewFlowGraphRow, type NewFlowMediaRow, type NewMessageRow, type NewSessionRow, type NewSettingsRow, type NonceStoreInterface, OptimisticLockError, PRODUCT_LIST_LIMIT, type PreviewMediaSupport, ProcessInboundDispatchUseCase, type PurgeExpiredDocumentsParams, type PurgeExpiredDocumentsResult, PurgeExpiredDocumentsUseCase, type RealtimeRelay, type ReceiveWebhookParams, type ReceiveWebhookResult, ReceiveWebhookUseCase, type ReleaseConversationParams, ReleaseConversationUseCase, type ListMessagesParams$1 as RepositoryListMessagesParams, type ResolveTranscriptionPolicyDependencies, type RunMetaWhatsAppMigrationsParams, type SaveFlowGraphParams, SaveFlowGraphUseCase, type SaveTranscriptionByWaMessageIdParams, type SaveTranscriptionParams, type SendMediaParams, SendMessageUseCase, type SendTemplateParams, type SendTextParams, SessionRepository, type SessionRow, SettingsRepository, type SettingsRow, SseHub, type SseListener, type StorePreviewMediaParams, type StorePreviewMediaResult, StorePreviewMediaUseCase, TRANSCRIPTION_MODE, TRANSCRIPTION_STATUS, type TakeoverConversationParams, TakeoverConversationUseCase, type TicketStoreInterface, type TranscribeAudioDependencies, type TranscribeAudioParams, type TranscribeAudioResult, TranscribeAudioUseCase, type TranscriptionPolicy, type TranscriptionPolicyDefaults, type TranscriptionPolicyResolver, type TranscriptionStatus, type UpdateFlowMediaParams, WhatsAppChannelAdapter, buildInboundJobId, claimWebhookDelivery, confirmWebhookDelivery, createMetaWhatsAppModule, createSendMediaAction, createSendProductListAction, createTranscriptionPolicyResolver, documents, extractMediaDescriptor, flowGraphs, flowMedia, isAudioMimeType, isRetriableTranscriptionError, isUnsupportedTranscriptionError, issueSseTicket, messages, metaWhatsAppMigrationsFolder, metaWhatsAppSchema, redeemSseTicket, resolveFailureStatus, runMetaWhatsAppMigrations, sessions, settings, toSessionContract, transcriptionRetryAfterSeconds, verifyWebhookChallenge, verifyWebhookSignature };
|