@adatechnology/meta-whatsapp-module 0.2.0-rc.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1734 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1704 -136
- package/dist/index.d.ts +1704 -136
- package/dist/index.js +1686 -145
- package/dist/index.js.map +1 -1
- 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/0010_flow_media_meta_ids.sql +10 -0
- package/dist/migrations/meta/_journal.json +28 -0
- package/dist/testing/index.cjs +150 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +42 -2
- package/dist/testing/index.d.ts +42 -2
- package/dist/testing/index.js +147 -1
- package/dist/testing/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
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, SendChannelMediaParams } 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";
|
|
@@ -560,6 +628,81 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
560
628
|
}, {}, {
|
|
561
629
|
$type: string[];
|
|
562
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
|
+
}>;
|
|
563
706
|
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
564
707
|
name: "created_at";
|
|
565
708
|
tableName: "messages";
|
|
@@ -580,13 +723,25 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
580
723
|
};
|
|
581
724
|
dialect: "pg";
|
|
582
725
|
}>;
|
|
583
|
-
|
|
584
|
-
|
|
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";
|
|
585
740
|
schema: "meta_whatsapp";
|
|
586
741
|
columns: {
|
|
587
742
|
id: drizzle_orm_pg_core.PgColumn<{
|
|
588
743
|
name: "id";
|
|
589
|
-
tableName: "
|
|
744
|
+
tableName: "documents";
|
|
590
745
|
dataType: "string";
|
|
591
746
|
columnType: "PgUUID";
|
|
592
747
|
data: string;
|
|
@@ -603,7 +758,7 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
603
758
|
}, {}, {}>;
|
|
604
759
|
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
605
760
|
name: "company_id";
|
|
606
|
-
tableName: "
|
|
761
|
+
tableName: "documents";
|
|
607
762
|
dataType: "string";
|
|
608
763
|
columnType: "PgUUID";
|
|
609
764
|
data: string;
|
|
@@ -618,11 +773,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
618
773
|
identity: undefined;
|
|
619
774
|
generated: undefined;
|
|
620
775
|
}, {}, {}>;
|
|
621
|
-
|
|
622
|
-
name: "
|
|
623
|
-
tableName: "
|
|
776
|
+
sessionId: drizzle_orm_pg_core.PgColumn<{
|
|
777
|
+
name: "session_id";
|
|
778
|
+
tableName: "documents";
|
|
624
779
|
dataType: "string";
|
|
625
|
-
columnType: "
|
|
780
|
+
columnType: "PgUUID";
|
|
626
781
|
data: string;
|
|
627
782
|
driverParam: string;
|
|
628
783
|
notNull: true;
|
|
@@ -630,16 +785,31 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
630
785
|
isPrimaryKey: false;
|
|
631
786
|
isAutoincrement: false;
|
|
632
787
|
hasRuntimeDefault: false;
|
|
633
|
-
enumValues:
|
|
788
|
+
enumValues: undefined;
|
|
634
789
|
baseColumn: never;
|
|
635
790
|
identity: undefined;
|
|
636
791
|
generated: undefined;
|
|
637
|
-
}, {}, {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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";
|
|
643
813
|
dataType: "string";
|
|
644
814
|
columnType: "PgVarchar";
|
|
645
815
|
data: string;
|
|
@@ -654,11 +824,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
654
824
|
identity: undefined;
|
|
655
825
|
generated: undefined;
|
|
656
826
|
}, {}, {
|
|
657
|
-
length:
|
|
827
|
+
length: 256;
|
|
658
828
|
}>;
|
|
659
|
-
|
|
660
|
-
name: "
|
|
661
|
-
tableName: "
|
|
829
|
+
filename: drizzle_orm_pg_core.PgColumn<{
|
|
830
|
+
name: "filename";
|
|
831
|
+
tableName: "documents";
|
|
662
832
|
dataType: "string";
|
|
663
833
|
columnType: "PgVarchar";
|
|
664
834
|
data: string;
|
|
@@ -673,36 +843,36 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
673
843
|
identity: undefined;
|
|
674
844
|
generated: undefined;
|
|
675
845
|
}, {}, {
|
|
676
|
-
length:
|
|
846
|
+
length: 512;
|
|
677
847
|
}>;
|
|
678
|
-
|
|
679
|
-
name: "
|
|
680
|
-
tableName: "
|
|
681
|
-
dataType: "
|
|
682
|
-
columnType: "
|
|
683
|
-
data:
|
|
684
|
-
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;
|
|
685
855
|
notNull: true;
|
|
686
|
-
hasDefault:
|
|
856
|
+
hasDefault: false;
|
|
687
857
|
isPrimaryKey: false;
|
|
688
858
|
isAutoincrement: false;
|
|
689
859
|
hasRuntimeDefault: false;
|
|
690
|
-
enumValues:
|
|
860
|
+
enumValues: [string, ...string[]];
|
|
691
861
|
baseColumn: never;
|
|
692
862
|
identity: undefined;
|
|
693
863
|
generated: undefined;
|
|
694
864
|
}, {}, {
|
|
695
|
-
|
|
865
|
+
length: 128;
|
|
696
866
|
}>;
|
|
697
|
-
|
|
698
|
-
name: "
|
|
699
|
-
tableName: "
|
|
867
|
+
sizeBytes: drizzle_orm_pg_core.PgColumn<{
|
|
868
|
+
name: "size_bytes";
|
|
869
|
+
tableName: "documents";
|
|
700
870
|
dataType: "number";
|
|
701
871
|
columnType: "PgInteger";
|
|
702
872
|
data: number;
|
|
703
873
|
driverParam: string | number;
|
|
704
874
|
notNull: true;
|
|
705
|
-
hasDefault:
|
|
875
|
+
hasDefault: false;
|
|
706
876
|
isPrimaryKey: false;
|
|
707
877
|
isAutoincrement: false;
|
|
708
878
|
hasRuntimeDefault: false;
|
|
@@ -711,31 +881,33 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
711
881
|
identity: undefined;
|
|
712
882
|
generated: undefined;
|
|
713
883
|
}, {}, {}>;
|
|
714
|
-
|
|
715
|
-
name: "
|
|
716
|
-
tableName: "
|
|
717
|
-
dataType: "
|
|
718
|
-
columnType: "
|
|
719
|
-
data:
|
|
720
|
-
driverParam:
|
|
721
|
-
notNull:
|
|
722
|
-
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;
|
|
723
893
|
isPrimaryKey: false;
|
|
724
894
|
isAutoincrement: false;
|
|
725
895
|
hasRuntimeDefault: false;
|
|
726
|
-
enumValues:
|
|
896
|
+
enumValues: [string, ...string[]];
|
|
727
897
|
baseColumn: never;
|
|
728
898
|
identity: undefined;
|
|
729
899
|
generated: undefined;
|
|
730
|
-
}, {}, {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
900
|
+
}, {}, {
|
|
901
|
+
length: 64;
|
|
902
|
+
}>;
|
|
903
|
+
source: drizzle_orm_pg_core.PgColumn<{
|
|
904
|
+
name: "source";
|
|
905
|
+
tableName: "documents";
|
|
734
906
|
dataType: "string";
|
|
735
907
|
columnType: "PgVarchar";
|
|
736
908
|
data: string;
|
|
737
909
|
driverParam: string;
|
|
738
|
-
notNull:
|
|
910
|
+
notNull: true;
|
|
739
911
|
hasDefault: false;
|
|
740
912
|
isPrimaryKey: false;
|
|
741
913
|
isAutoincrement: false;
|
|
@@ -745,11 +917,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
745
917
|
identity: undefined;
|
|
746
918
|
generated: undefined;
|
|
747
919
|
}, {}, {
|
|
748
|
-
length:
|
|
920
|
+
length: 12;
|
|
749
921
|
}>;
|
|
750
|
-
|
|
751
|
-
name: "
|
|
752
|
-
tableName: "
|
|
922
|
+
linkedAt: drizzle_orm_pg_core.PgColumn<{
|
|
923
|
+
name: "linked_at";
|
|
924
|
+
tableName: "documents";
|
|
753
925
|
dataType: "date";
|
|
754
926
|
columnType: "PgTimestamp";
|
|
755
927
|
data: Date;
|
|
@@ -764,16 +936,23 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
764
936
|
identity: undefined;
|
|
765
937
|
generated: undefined;
|
|
766
938
|
}, {}, {}>;
|
|
767
|
-
|
|
768
|
-
|
|
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";
|
|
769
948
|
tableName: "flow_graphs";
|
|
770
|
-
dataType: "
|
|
771
|
-
columnType: "
|
|
772
|
-
data:
|
|
949
|
+
dataType: "string";
|
|
950
|
+
columnType: "PgUUID";
|
|
951
|
+
data: string;
|
|
773
952
|
driverParam: string;
|
|
774
953
|
notNull: true;
|
|
775
954
|
hasDefault: true;
|
|
776
|
-
isPrimaryKey:
|
|
955
|
+
isPrimaryKey: true;
|
|
777
956
|
isAutoincrement: false;
|
|
778
957
|
hasRuntimeDefault: false;
|
|
779
958
|
enumValues: undefined;
|
|
@@ -781,23 +960,16 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
781
960
|
identity: undefined;
|
|
782
961
|
generated: undefined;
|
|
783
962
|
}, {}, {}>;
|
|
784
|
-
};
|
|
785
|
-
dialect: "pg";
|
|
786
|
-
}>;
|
|
787
|
-
declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
788
|
-
name: "settings";
|
|
789
|
-
schema: "meta_whatsapp";
|
|
790
|
-
columns: {
|
|
791
963
|
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
792
964
|
name: "company_id";
|
|
793
|
-
tableName: "
|
|
965
|
+
tableName: "flow_graphs";
|
|
794
966
|
dataType: "string";
|
|
795
967
|
columnType: "PgUUID";
|
|
796
968
|
data: string;
|
|
797
969
|
driverParam: string;
|
|
798
970
|
notNull: true;
|
|
799
971
|
hasDefault: false;
|
|
800
|
-
isPrimaryKey:
|
|
972
|
+
isPrimaryKey: false;
|
|
801
973
|
isAutoincrement: false;
|
|
802
974
|
hasRuntimeDefault: false;
|
|
803
975
|
enumValues: undefined;
|
|
@@ -805,14 +977,14 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
805
977
|
identity: undefined;
|
|
806
978
|
generated: undefined;
|
|
807
979
|
}, {}, {}>;
|
|
808
|
-
|
|
809
|
-
name: "
|
|
810
|
-
tableName: "
|
|
980
|
+
key: drizzle_orm_pg_core.PgColumn<{
|
|
981
|
+
name: "key";
|
|
982
|
+
tableName: "flow_graphs";
|
|
811
983
|
dataType: "string";
|
|
812
984
|
columnType: "PgVarchar";
|
|
813
985
|
data: string;
|
|
814
986
|
driverParam: string;
|
|
815
|
-
notNull:
|
|
987
|
+
notNull: true;
|
|
816
988
|
hasDefault: false;
|
|
817
989
|
isPrimaryKey: false;
|
|
818
990
|
isAutoincrement: false;
|
|
@@ -822,17 +994,17 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
822
994
|
identity: undefined;
|
|
823
995
|
generated: undefined;
|
|
824
996
|
}, {}, {
|
|
825
|
-
length:
|
|
997
|
+
length: 64;
|
|
826
998
|
}>;
|
|
827
|
-
|
|
828
|
-
name: "
|
|
829
|
-
tableName: "
|
|
999
|
+
label: drizzle_orm_pg_core.PgColumn<{
|
|
1000
|
+
name: "label";
|
|
1001
|
+
tableName: "flow_graphs";
|
|
830
1002
|
dataType: "string";
|
|
831
1003
|
columnType: "PgVarchar";
|
|
832
1004
|
data: string;
|
|
833
1005
|
driverParam: string;
|
|
834
1006
|
notNull: true;
|
|
835
|
-
hasDefault:
|
|
1007
|
+
hasDefault: false;
|
|
836
1008
|
isPrimaryKey: false;
|
|
837
1009
|
isAutoincrement: false;
|
|
838
1010
|
hasRuntimeDefault: false;
|
|
@@ -841,31 +1013,488 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
841
1013
|
identity: undefined;
|
|
842
1014
|
generated: undefined;
|
|
843
1015
|
}, {}, {
|
|
844
|
-
length:
|
|
1016
|
+
length: 120;
|
|
845
1017
|
}>;
|
|
846
|
-
|
|
847
|
-
name: "
|
|
848
|
-
tableName: "
|
|
849
|
-
dataType: "
|
|
850
|
-
columnType: "
|
|
851
|
-
data: string
|
|
852
|
-
driverParam:
|
|
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;
|
|
853
1025
|
notNull: true;
|
|
854
|
-
hasDefault:
|
|
1026
|
+
hasDefault: false;
|
|
855
1027
|
isPrimaryKey: false;
|
|
856
1028
|
isAutoincrement: false;
|
|
857
1029
|
hasRuntimeDefault: false;
|
|
858
|
-
enumValues:
|
|
1030
|
+
enumValues: [string, ...string[]];
|
|
859
1031
|
baseColumn: never;
|
|
860
1032
|
identity: undefined;
|
|
861
1033
|
generated: undefined;
|
|
862
1034
|
}, {}, {
|
|
863
|
-
|
|
1035
|
+
length: 64;
|
|
864
1036
|
}>;
|
|
865
|
-
|
|
866
|
-
name: "
|
|
867
|
-
tableName: "
|
|
868
|
-
dataType: "
|
|
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
|
+
metaMediaIds: drizzle_orm_pg_core.PgColumn<{
|
|
1361
|
+
name: "meta_media_ids";
|
|
1362
|
+
tableName: "flow_media";
|
|
1363
|
+
dataType: "json";
|
|
1364
|
+
columnType: "PgJsonb";
|
|
1365
|
+
data: Record<string, string>;
|
|
1366
|
+
driverParam: unknown;
|
|
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
|
+
$type: Record<string, string>;
|
|
1378
|
+
}>;
|
|
1379
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
1380
|
+
name: "created_at";
|
|
1381
|
+
tableName: "flow_media";
|
|
1382
|
+
dataType: "date";
|
|
1383
|
+
columnType: "PgTimestamp";
|
|
1384
|
+
data: Date;
|
|
1385
|
+
driverParam: string;
|
|
1386
|
+
notNull: true;
|
|
1387
|
+
hasDefault: true;
|
|
1388
|
+
isPrimaryKey: false;
|
|
1389
|
+
isAutoincrement: false;
|
|
1390
|
+
hasRuntimeDefault: false;
|
|
1391
|
+
enumValues: undefined;
|
|
1392
|
+
baseColumn: never;
|
|
1393
|
+
identity: undefined;
|
|
1394
|
+
generated: undefined;
|
|
1395
|
+
}, {}, {}>;
|
|
1396
|
+
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
1397
|
+
name: "updated_at";
|
|
1398
|
+
tableName: "flow_media";
|
|
1399
|
+
dataType: "date";
|
|
1400
|
+
columnType: "PgTimestamp";
|
|
1401
|
+
data: Date;
|
|
1402
|
+
driverParam: string;
|
|
1403
|
+
notNull: true;
|
|
1404
|
+
hasDefault: true;
|
|
1405
|
+
isPrimaryKey: false;
|
|
1406
|
+
isAutoincrement: false;
|
|
1407
|
+
hasRuntimeDefault: false;
|
|
1408
|
+
enumValues: undefined;
|
|
1409
|
+
baseColumn: never;
|
|
1410
|
+
identity: undefined;
|
|
1411
|
+
generated: undefined;
|
|
1412
|
+
}, {}, {}>;
|
|
1413
|
+
};
|
|
1414
|
+
dialect: "pg";
|
|
1415
|
+
}>;
|
|
1416
|
+
declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
1417
|
+
name: "settings";
|
|
1418
|
+
schema: "meta_whatsapp";
|
|
1419
|
+
columns: {
|
|
1420
|
+
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
1421
|
+
name: "company_id";
|
|
1422
|
+
tableName: "settings";
|
|
1423
|
+
dataType: "string";
|
|
1424
|
+
columnType: "PgUUID";
|
|
1425
|
+
data: string;
|
|
1426
|
+
driverParam: string;
|
|
1427
|
+
notNull: true;
|
|
1428
|
+
hasDefault: false;
|
|
1429
|
+
isPrimaryKey: true;
|
|
1430
|
+
isAutoincrement: false;
|
|
1431
|
+
hasRuntimeDefault: false;
|
|
1432
|
+
enumValues: undefined;
|
|
1433
|
+
baseColumn: never;
|
|
1434
|
+
identity: undefined;
|
|
1435
|
+
generated: undefined;
|
|
1436
|
+
}, {}, {}>;
|
|
1437
|
+
templateName: drizzle_orm_pg_core.PgColumn<{
|
|
1438
|
+
name: "template_name";
|
|
1439
|
+
tableName: "settings";
|
|
1440
|
+
dataType: "string";
|
|
1441
|
+
columnType: "PgVarchar";
|
|
1442
|
+
data: string;
|
|
1443
|
+
driverParam: string;
|
|
1444
|
+
notNull: false;
|
|
1445
|
+
hasDefault: false;
|
|
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: 128;
|
|
1455
|
+
}>;
|
|
1456
|
+
templateLanguage: drizzle_orm_pg_core.PgColumn<{
|
|
1457
|
+
name: "template_language";
|
|
1458
|
+
tableName: "settings";
|
|
1459
|
+
dataType: "string";
|
|
1460
|
+
columnType: "PgVarchar";
|
|
1461
|
+
data: string;
|
|
1462
|
+
driverParam: string;
|
|
1463
|
+
notNull: true;
|
|
1464
|
+
hasDefault: true;
|
|
1465
|
+
isPrimaryKey: false;
|
|
1466
|
+
isAutoincrement: false;
|
|
1467
|
+
hasRuntimeDefault: false;
|
|
1468
|
+
enumValues: [string, ...string[]];
|
|
1469
|
+
baseColumn: never;
|
|
1470
|
+
identity: undefined;
|
|
1471
|
+
generated: undefined;
|
|
1472
|
+
}, {}, {
|
|
1473
|
+
length: 16;
|
|
1474
|
+
}>;
|
|
1475
|
+
templateVariables: drizzle_orm_pg_core.PgColumn<{
|
|
1476
|
+
name: "template_variables";
|
|
1477
|
+
tableName: "settings";
|
|
1478
|
+
dataType: "json";
|
|
1479
|
+
columnType: "PgJsonb";
|
|
1480
|
+
data: string[];
|
|
1481
|
+
driverParam: unknown;
|
|
1482
|
+
notNull: true;
|
|
1483
|
+
hasDefault: true;
|
|
1484
|
+
isPrimaryKey: false;
|
|
1485
|
+
isAutoincrement: false;
|
|
1486
|
+
hasRuntimeDefault: false;
|
|
1487
|
+
enumValues: undefined;
|
|
1488
|
+
baseColumn: never;
|
|
1489
|
+
identity: undefined;
|
|
1490
|
+
generated: undefined;
|
|
1491
|
+
}, {}, {
|
|
1492
|
+
$type: string[];
|
|
1493
|
+
}>;
|
|
1494
|
+
welcomeMessage: drizzle_orm_pg_core.PgColumn<{
|
|
1495
|
+
name: "welcome_message";
|
|
1496
|
+
tableName: "settings";
|
|
1497
|
+
dataType: "string";
|
|
869
1498
|
columnType: "PgText";
|
|
870
1499
|
data: string;
|
|
871
1500
|
driverParam: string;
|
|
@@ -896,6 +1525,43 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
896
1525
|
identity: undefined;
|
|
897
1526
|
generated: undefined;
|
|
898
1527
|
}, {}, {}>;
|
|
1528
|
+
transcriptionEnabled: drizzle_orm_pg_core.PgColumn<{
|
|
1529
|
+
name: "transcription_enabled";
|
|
1530
|
+
tableName: "settings";
|
|
1531
|
+
dataType: "boolean";
|
|
1532
|
+
columnType: "PgBoolean";
|
|
1533
|
+
data: boolean;
|
|
1534
|
+
driverParam: boolean;
|
|
1535
|
+
notNull: false;
|
|
1536
|
+
hasDefault: false;
|
|
1537
|
+
isPrimaryKey: false;
|
|
1538
|
+
isAutoincrement: false;
|
|
1539
|
+
hasRuntimeDefault: false;
|
|
1540
|
+
enumValues: undefined;
|
|
1541
|
+
baseColumn: never;
|
|
1542
|
+
identity: undefined;
|
|
1543
|
+
generated: undefined;
|
|
1544
|
+
}, {}, {}>;
|
|
1545
|
+
transcriptionMode: drizzle_orm_pg_core.PgColumn<{
|
|
1546
|
+
name: "transcription_mode";
|
|
1547
|
+
tableName: "settings";
|
|
1548
|
+
dataType: "string";
|
|
1549
|
+
columnType: "PgVarchar";
|
|
1550
|
+
data: TranscriptionMode;
|
|
1551
|
+
driverParam: string;
|
|
1552
|
+
notNull: false;
|
|
1553
|
+
hasDefault: false;
|
|
1554
|
+
isPrimaryKey: false;
|
|
1555
|
+
isAutoincrement: false;
|
|
1556
|
+
hasRuntimeDefault: false;
|
|
1557
|
+
enumValues: [string, ...string[]];
|
|
1558
|
+
baseColumn: never;
|
|
1559
|
+
identity: undefined;
|
|
1560
|
+
generated: undefined;
|
|
1561
|
+
}, {}, {
|
|
1562
|
+
length: 16;
|
|
1563
|
+
$type: TranscriptionMode;
|
|
1564
|
+
}>;
|
|
899
1565
|
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
900
1566
|
name: "created_at";
|
|
901
1567
|
tableName: "settings";
|
|
@@ -941,6 +1607,10 @@ type MessageRow = typeof messages.$inferSelect;
|
|
|
941
1607
|
type NewMessageRow = typeof messages.$inferInsert;
|
|
942
1608
|
type FlowGraphRow = typeof flowGraphs.$inferSelect;
|
|
943
1609
|
type NewFlowGraphRow = typeof flowGraphs.$inferInsert;
|
|
1610
|
+
type DocumentRow = typeof documents.$inferSelect;
|
|
1611
|
+
type NewDocumentRow = typeof documents.$inferInsert;
|
|
1612
|
+
type FlowMediaRow = typeof flowMedia.$inferSelect;
|
|
1613
|
+
type NewFlowMediaRow = typeof flowMedia.$inferInsert;
|
|
944
1614
|
|
|
945
1615
|
interface ListConversationsFilters {
|
|
946
1616
|
page?: number;
|
|
@@ -953,13 +1623,23 @@ declare class SessionRepository {
|
|
|
953
1623
|
constructor(db: MetaWhatsAppDatabase);
|
|
954
1624
|
getContext(companyId: string, whatsappNumber: string): Promise<SessionRow | undefined>;
|
|
955
1625
|
getOrCreate(companyId: string, whatsappNumber: string, startState: SessionState): Promise<SessionRow>;
|
|
956
|
-
setState(companyId: string, whatsappNumber: string, state: SessionState, context?:
|
|
1626
|
+
setState<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string, state: SessionState, context?: TSessionContext): Promise<void>;
|
|
1627
|
+
patchContext<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string, patch: Partial<TSessionContext>): Promise<void>;
|
|
1628
|
+
readContext<TSessionContext extends Record<string, unknown> = Record<string, unknown>>(companyId: string, whatsappNumber: string): Promise<TSessionContext | undefined>;
|
|
957
1629
|
setFlowPosition(companyId: string, whatsappNumber: string, flowKey: string | null, currentNodeId: string | null): Promise<void>;
|
|
958
1630
|
touchInbound(companyId: string, whatsappNumber: string): Promise<void>;
|
|
959
1631
|
hoursSinceLastInbound(companyId: string, whatsappNumber: string): Promise<number | undefined>;
|
|
960
1632
|
setMode(companyId: string, whatsappNumber: string, mode: SessionMode, assignedUserId?: string | null): Promise<void>;
|
|
961
1633
|
takeover(companyId: string, whatsappNumber: string, agentUserId: string): Promise<void>;
|
|
962
1634
|
release(companyId: string, whatsappNumber: string): Promise<void>;
|
|
1635
|
+
/**
|
|
1636
|
+
* Apaga a sessão; a cascata das FKs leva mensagens e documentos.
|
|
1637
|
+
*
|
|
1638
|
+
* Não apaga o binário no storage — isso é passo de aplicação, e é por isso que este método é
|
|
1639
|
+
* chamado por `DeleteConversationUseCase` e não diretamente pelo host. Chamar daqui sem apagar os
|
|
1640
|
+
* objetos antes deixa mídia órfã sendo cobrada para sempre.
|
|
1641
|
+
*/
|
|
1642
|
+
deleteByNumber(companyId: string, whatsappNumber: string): Promise<void>;
|
|
963
1643
|
requestHuman(companyId: string, whatsappNumber: string): Promise<void>;
|
|
964
1644
|
markRead(companyId: string, whatsappNumber: string): Promise<void>;
|
|
965
1645
|
markAllRead(companyId: string, userId: string): Promise<number>;
|
|
@@ -998,41 +1678,15 @@ declare class SessionRepository {
|
|
|
998
1678
|
readAt: Date | null;
|
|
999
1679
|
moderationFlagged: boolean | null;
|
|
1000
1680
|
moderationTerms: string[] | null;
|
|
1681
|
+
transcriptionStatus: TranscriptionStatus | null;
|
|
1682
|
+
transcriptionText: string | null;
|
|
1683
|
+
transcriptionLanguage: string | null;
|
|
1684
|
+
transcriptionEngine: string | null;
|
|
1001
1685
|
createdAt: Date;
|
|
1002
1686
|
}[];
|
|
1003
1687
|
} | null>;
|
|
1004
1688
|
}
|
|
1005
1689
|
|
|
1006
|
-
declare class InvalidFlowGraphError extends Error {
|
|
1007
|
-
readonly validationMessage: string;
|
|
1008
|
-
constructor(key: string, validationMessage: string);
|
|
1009
|
-
}
|
|
1010
|
-
declare class OptimisticLockError extends Error {
|
|
1011
|
-
constructor(key: string);
|
|
1012
|
-
}
|
|
1013
|
-
declare class FlowGraphRepository {
|
|
1014
|
-
private readonly db;
|
|
1015
|
-
constructor(db: MetaWhatsAppDatabase);
|
|
1016
|
-
get(companyId: string, key: string): Promise<FlowGraphData | undefined>;
|
|
1017
|
-
list(companyId: string): Promise<FlowGraphSummary[]>;
|
|
1018
|
-
private assertValidNodes;
|
|
1019
|
-
create(companyId: string, graph: Omit<FlowGraphData, 'version'> & {
|
|
1020
|
-
showInMenu?: boolean;
|
|
1021
|
-
menuOptionLabel?: string;
|
|
1022
|
-
}): Promise<FlowGraphData>;
|
|
1023
|
-
save(companyId: string, graph: FlowGraphData, expectedVersion: number): Promise<FlowGraphData>;
|
|
1024
|
-
delete(companyId: string, key: string): Promise<void>;
|
|
1025
|
-
getLiveFlowPositions(companyId: string): Promise<LiveFlowPosition[]>;
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
declare class SettingsRepository {
|
|
1029
|
-
private readonly db;
|
|
1030
|
-
constructor(db: MetaWhatsAppDatabase);
|
|
1031
|
-
get(companyId: string): Promise<WhatsAppSettings>;
|
|
1032
|
-
save(companyId: string, update: Partial<WhatsAppSettings>): Promise<WhatsAppSettings>;
|
|
1033
|
-
resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]>;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
1690
|
interface InsertMessageParams {
|
|
1037
1691
|
companyId: string;
|
|
1038
1692
|
sessionId: string;
|
|
@@ -1055,14 +1709,106 @@ interface ListMessagesParams$1 {
|
|
|
1055
1709
|
limit?: number;
|
|
1056
1710
|
before?: string;
|
|
1057
1711
|
}
|
|
1712
|
+
interface SaveTranscriptionByWaMessageIdParams extends Omit<SaveTranscriptionParams, 'messageId'> {
|
|
1713
|
+
/**
|
|
1714
|
+
* Id da mensagem na Meta. É o único que quem processa o webhook conhece — o id do módulo só
|
|
1715
|
+
* existe depois da gravação, e obrigar o host a descobri-lo faria cada um escrever a própria
|
|
1716
|
+
* consulta por `wa_message_id`.
|
|
1717
|
+
*/
|
|
1718
|
+
waMessageId: string;
|
|
1719
|
+
}
|
|
1720
|
+
interface SaveTranscriptionParams {
|
|
1721
|
+
companyId: string;
|
|
1722
|
+
messageId: string;
|
|
1723
|
+
status: TranscriptionStatus;
|
|
1724
|
+
/** Ausente em `pending`/`failed`/`unsupported`; vazio em `done` é silêncio já processado. */
|
|
1725
|
+
text?: string | null;
|
|
1726
|
+
language?: string | null;
|
|
1727
|
+
engine?: string | null;
|
|
1728
|
+
}
|
|
1058
1729
|
declare class MessageRepository {
|
|
1059
1730
|
private readonly db;
|
|
1060
1731
|
constructor(db: MetaWhatsAppDatabase);
|
|
1061
1732
|
insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
|
|
1062
1733
|
updateMessageStatus(companyId: string, waMessageId: string, status: MessageStatus): Promise<MessageRow | undefined>;
|
|
1734
|
+
/**
|
|
1735
|
+
* Grava a transcrição endereçando pelo id da Meta, para quem só tem esse.
|
|
1736
|
+
*
|
|
1737
|
+
* Serve ao caso em que a transcrição acontece no próprio webhook — o grafo precisa do texto para
|
|
1738
|
+
* responder ao cliente, e jogar fora o que ele já pagou para transcrever significaria transcrever
|
|
1739
|
+
* o mesmo áudio uma segunda vez só para o painel ver.
|
|
1740
|
+
*
|
|
1741
|
+
* Devolve `undefined` quando não achou a mensagem: entrega duplicada e mensagem apagada são
|
|
1742
|
+
* corridas normais, não erro.
|
|
1743
|
+
*/
|
|
1744
|
+
saveTranscriptionByWaMessageId(params: SaveTranscriptionByWaMessageIdParams): Promise<MessageRow | undefined>;
|
|
1745
|
+
findById(companyId: string, messageId: string): Promise<MessageRow | undefined>;
|
|
1746
|
+
/**
|
|
1747
|
+
* Grava o resultado da transcrição. Devolve `undefined` quando a mensagem não existe (apagada
|
|
1748
|
+
* entre o enfileiramento e a execução do job) — não é erro, é corrida normal.
|
|
1749
|
+
*
|
|
1750
|
+
* `text`/`language`/`engine` só são tocados quando informados: uma retentativa que volta a falhar
|
|
1751
|
+
* atualiza o status sem apagar a transcrição parcial de uma tentativa anterior que tenha vindo de
|
|
1752
|
+
* outro engine da cadeia.
|
|
1753
|
+
*/
|
|
1754
|
+
saveTranscription(params: SaveTranscriptionParams): Promise<MessageRow | undefined>;
|
|
1063
1755
|
listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
|
|
1064
1756
|
}
|
|
1065
1757
|
|
|
1758
|
+
declare const DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS = 300;
|
|
1759
|
+
/**
|
|
1760
|
+
* Cache de leitura dos grafos de fluxo, com invalidação na publicação.
|
|
1761
|
+
*
|
|
1762
|
+
* TTL **e** invalidação explícita, e não um dos dois: só TTL deixaria o cliente andando no grafo
|
|
1763
|
+
* antigo até a chave expirar, logo depois de alguém corrigir o fluxo no editor; só invalidação
|
|
1764
|
+
* deixaria cache envenenado para sempre se um `delete` se perdesse (Redis reiniciando, deploy no
|
|
1765
|
+
* meio da escrita).
|
|
1766
|
+
*
|
|
1767
|
+
* Toda operação é tolerante a falha por decisão de projeto: cache é aceleração, não dependência.
|
|
1768
|
+
* Redis fora do ar tem que degradar para leitura no banco — que é exatamente o comportamento de
|
|
1769
|
+
* quem não configura cache nenhum — em vez de derrubar a conversa do cliente.
|
|
1770
|
+
*/
|
|
1771
|
+
declare class FlowGraphCache {
|
|
1772
|
+
private readonly provider;
|
|
1773
|
+
private readonly ttlSeconds;
|
|
1774
|
+
constructor(provider: CacheInterface, ttlSeconds?: number);
|
|
1775
|
+
private keyFor;
|
|
1776
|
+
read(companyId: string, flowKey: string): Promise<FlowGraphData | undefined>;
|
|
1777
|
+
write(companyId: string, graph: FlowGraphData): Promise<void>;
|
|
1778
|
+
invalidate(companyId: string, flowKey: string): Promise<void>;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
declare class InvalidFlowGraphError extends Error {
|
|
1782
|
+
readonly validationMessage: string;
|
|
1783
|
+
constructor(key: string, validationMessage: string);
|
|
1784
|
+
}
|
|
1785
|
+
declare class OptimisticLockError extends Error {
|
|
1786
|
+
constructor(key: string);
|
|
1787
|
+
}
|
|
1788
|
+
declare class FlowGraphRepository {
|
|
1789
|
+
private readonly db;
|
|
1790
|
+
private readonly cache?;
|
|
1791
|
+
constructor(db: MetaWhatsAppDatabase, cache?: FlowGraphCache | undefined);
|
|
1792
|
+
get(companyId: string, key: string): Promise<FlowGraphData | undefined>;
|
|
1793
|
+
list(companyId: string): Promise<FlowGraphSummary[]>;
|
|
1794
|
+
private assertValidNodes;
|
|
1795
|
+
create(companyId: string, graph: Omit<FlowGraphData, 'version'> & {
|
|
1796
|
+
showInMenu?: boolean;
|
|
1797
|
+
menuOptionLabel?: string;
|
|
1798
|
+
}): Promise<FlowGraphData>;
|
|
1799
|
+
save(companyId: string, graph: FlowGraphData, expectedVersion: number): Promise<FlowGraphData>;
|
|
1800
|
+
delete(companyId: string, key: string): Promise<void>;
|
|
1801
|
+
getLiveFlowPositions(companyId: string): Promise<LiveFlowPosition[]>;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
declare class SettingsRepository {
|
|
1805
|
+
private readonly db;
|
|
1806
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
1807
|
+
get(companyId: string): Promise<WhatsAppSettings>;
|
|
1808
|
+
save(companyId: string, update: Partial<WhatsAppSettings>): Promise<WhatsAppSettings>;
|
|
1809
|
+
resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]>;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1066
1812
|
type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {
|
|
1067
1813
|
startState: SessionState;
|
|
1068
1814
|
};
|
|
@@ -1089,6 +1835,100 @@ declare class LogMessageUseCase {
|
|
|
1089
1835
|
private moderationOf;
|
|
1090
1836
|
}
|
|
1091
1837
|
|
|
1838
|
+
interface LinkDocumentParams {
|
|
1839
|
+
companyId: string;
|
|
1840
|
+
sessionId: string;
|
|
1841
|
+
messageId?: string | null;
|
|
1842
|
+
uploadId: string;
|
|
1843
|
+
filename: string;
|
|
1844
|
+
mimeType: string;
|
|
1845
|
+
sizeBytes: number;
|
|
1846
|
+
sha256?: string | null;
|
|
1847
|
+
source: string;
|
|
1848
|
+
}
|
|
1849
|
+
interface ListDocumentsParams {
|
|
1850
|
+
companyId: string;
|
|
1851
|
+
sessionId: string;
|
|
1852
|
+
search?: string;
|
|
1853
|
+
/**
|
|
1854
|
+
* Origens aceitas, como lista explícita (`['agent', 'bot']`) e não como apelido de UI.
|
|
1855
|
+
*
|
|
1856
|
+
* Agrupamento tipo "Equipe" é vocabulário de tela e muda por produto; traduzir aqui obrigaria o
|
|
1857
|
+
* módulo a conhecer o rótulo de cada host. Quem recebe o apelido na borda é a rota.
|
|
1858
|
+
*/
|
|
1859
|
+
sources?: readonly string[];
|
|
1860
|
+
sortDirection?: 'asc' | 'desc';
|
|
1861
|
+
page?: number;
|
|
1862
|
+
limit?: number;
|
|
1863
|
+
}
|
|
1864
|
+
interface ListCompanyDocumentsParams$1 {
|
|
1865
|
+
companyId: string;
|
|
1866
|
+
search?: string;
|
|
1867
|
+
sources?: readonly string[];
|
|
1868
|
+
sortDirection?: 'asc' | 'desc';
|
|
1869
|
+
page?: number;
|
|
1870
|
+
limit?: number;
|
|
1871
|
+
}
|
|
1872
|
+
interface CompanyDocumentRow {
|
|
1873
|
+
id: string;
|
|
1874
|
+
uploadId: string;
|
|
1875
|
+
filename: string;
|
|
1876
|
+
mimeType: string;
|
|
1877
|
+
sizeBytes: number;
|
|
1878
|
+
source: string;
|
|
1879
|
+
linkedAt: Date;
|
|
1880
|
+
whatsappNumber: string;
|
|
1881
|
+
}
|
|
1882
|
+
interface ListCompanyDocumentsResult {
|
|
1883
|
+
rows: CompanyDocumentRow[];
|
|
1884
|
+
total: number;
|
|
1885
|
+
}
|
|
1886
|
+
interface ListDocumentsResult {
|
|
1887
|
+
rows: DocumentRow[];
|
|
1888
|
+
/** Total no servidor, ANTES do corte de página — é o que permite calcular a última página. */
|
|
1889
|
+
total: number;
|
|
1890
|
+
}
|
|
1891
|
+
declare class DocumentRepository {
|
|
1892
|
+
private readonly db;
|
|
1893
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
1894
|
+
/**
|
|
1895
|
+
* Idempotente por (companyId, uploadId), garantido pelo índice único e não por SELECT prévio: o
|
|
1896
|
+
* job de ingestão é reentregue por retry e duas tentativas concorrentes passariam as duas por uma
|
|
1897
|
+
* checagem, duplicando a linha no painel.
|
|
1898
|
+
*
|
|
1899
|
+
* Devolve `undefined` quando o documento já estava linkado.
|
|
1900
|
+
*/
|
|
1901
|
+
link(params: LinkDocumentParams): Promise<DocumentRow | undefined>;
|
|
1902
|
+
listByConversation(params: ListDocumentsParams): Promise<ListDocumentsResult>;
|
|
1903
|
+
/**
|
|
1904
|
+
* A biblioteca da EMPRESA inteira, não de uma conversa.
|
|
1905
|
+
*
|
|
1906
|
+
* A busca casa nome do arquivo OU telefone da conversa — ver `companyDocumentSearch`.
|
|
1907
|
+
*
|
|
1908
|
+
* Faz join com `sessions` para carregar de qual conversa cada arquivo veio — numa lista global,
|
|
1909
|
+
* arquivo sem essa referência é inútil: o atendente vê "comprovante.pdf" e não sabe de quem.
|
|
1910
|
+
*
|
|
1911
|
+
* Ordena por `linkedAt` apoiada no índice `idx_documents_company_linked`, que já existia para a
|
|
1912
|
+
* varredura de retenção.
|
|
1913
|
+
*/
|
|
1914
|
+
listByCompany(params: ListCompanyDocumentsParams$1): Promise<ListCompanyDocumentsResult>;
|
|
1915
|
+
/**
|
|
1916
|
+
* Um documento pela key do objeto. Serve para recuperar o nome original na hora de assinar o
|
|
1917
|
+
* download: a key é caminho no bucket e salvaria o arquivo com o id da Meta.
|
|
1918
|
+
*/
|
|
1919
|
+
findByUploadId(companyId: string, uploadId: string): Promise<DocumentRow | undefined>;
|
|
1920
|
+
/**
|
|
1921
|
+
* Os objetos a apagar no storage antes de a linha sumir.
|
|
1922
|
+
*
|
|
1923
|
+
* Existe porque a cascata da FK apaga a linha e deixa o binário órfão: quem for apagar a conversa
|
|
1924
|
+
* precisa desta lista primeiro, senão paga armazenamento para sempre por arquivo inalcançável.
|
|
1925
|
+
*/
|
|
1926
|
+
listUploadIdsBySession(companyId: string, sessionId: string): Promise<string[]>;
|
|
1927
|
+
/** Varredura de retenção por idade — o par é o mesmo cuidado com o objeto no storage. */
|
|
1928
|
+
listExpired(companyId: string, olderThan: Date, limit?: number): Promise<DocumentRow[]>;
|
|
1929
|
+
deleteById(companyId: string, id: string): Promise<void>;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1092
1932
|
type SendTextParams = {
|
|
1093
1933
|
companyId: string;
|
|
1094
1934
|
whatsappNumber: string;
|
|
@@ -1123,7 +1963,8 @@ declare class SendMessageUseCase {
|
|
|
1123
1963
|
private readonly sessionRepository;
|
|
1124
1964
|
private readonly logMessage;
|
|
1125
1965
|
private readonly objectStorage?;
|
|
1126
|
-
|
|
1966
|
+
private readonly documentRepository?;
|
|
1967
|
+
constructor(channel: ChannelAdapterInterface, sessionRepository: SessionRepository, logMessage: LogMessageUseCase, objectStorage?: ObjectStorageInterface | undefined, documentRepository?: DocumentRepository | undefined);
|
|
1127
1968
|
private assertWithinWindow;
|
|
1128
1969
|
sendText(params: SendTextParams): Promise<MessageRow | undefined>;
|
|
1129
1970
|
sendMedia(params: SendMediaParams): Promise<MessageRow | undefined>;
|
|
@@ -1176,6 +2017,143 @@ declare class ListMessagesUseCase {
|
|
|
1176
2017
|
execute(params: ListMessagesParams): Promise<MessageRow[]>;
|
|
1177
2018
|
}
|
|
1178
2019
|
|
|
2020
|
+
type ListConversationDocumentsParams = {
|
|
2021
|
+
companyId: string;
|
|
2022
|
+
whatsappNumber: string;
|
|
2023
|
+
search?: string;
|
|
2024
|
+
/** Origens explícitas. O apelido de UI ("Equipe") é traduzido na borda HTTP, não aqui. */
|
|
2025
|
+
sources?: readonly string[];
|
|
2026
|
+
sortDirection?: 'asc' | 'desc';
|
|
2027
|
+
page?: number;
|
|
2028
|
+
limit?: number;
|
|
2029
|
+
};
|
|
2030
|
+
/**
|
|
2031
|
+
* O que o `ConversationDocumentsPanel` do conversations-ui consome. O shape é o do pacote de UI
|
|
2032
|
+
* (`ConversationDocument`), com `linkedAt` já em ISO para não obrigar cada host a serializar.
|
|
2033
|
+
*/
|
|
2034
|
+
type ConversationDocumentView = {
|
|
2035
|
+
/**
|
|
2036
|
+
* O `uploadId` (key no storage), e NÃO o id da linha.
|
|
2037
|
+
*
|
|
2038
|
+
* É este valor que o consumidor devolve para pedir a URL assinada ou montar o zip, então expor o
|
|
2039
|
+
* UUID da tabela aqui fazia o download apontar para um objeto inexistente — falha que só aparece
|
|
2040
|
+
* no clique, porque a assinatura é gerada sem consultar o bucket.
|
|
2041
|
+
*/
|
|
2042
|
+
id: string;
|
|
2043
|
+
filename: string;
|
|
2044
|
+
mimeType: string;
|
|
2045
|
+
sizeBytes: number;
|
|
2046
|
+
source: string;
|
|
2047
|
+
linkedAt: string;
|
|
2048
|
+
};
|
|
2049
|
+
/** Espelha o `ConversationDocumentPage` do conversations-ui: lista da página + total no servidor. */
|
|
2050
|
+
type ConversationDocumentsPage = {
|
|
2051
|
+
documents: ConversationDocumentView[];
|
|
2052
|
+
total: number;
|
|
2053
|
+
};
|
|
2054
|
+
declare class ListConversationDocumentsUseCase {
|
|
2055
|
+
private readonly sessionRepository;
|
|
2056
|
+
private readonly documentRepository;
|
|
2057
|
+
constructor(sessionRepository: SessionRepository, documentRepository: DocumentRepository);
|
|
2058
|
+
execute(params: ListConversationDocumentsParams): Promise<ConversationDocumentsPage>;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
type ListCompanyDocumentsParams = {
|
|
2062
|
+
companyId: string;
|
|
2063
|
+
search?: string;
|
|
2064
|
+
/** Origens explícitas. O apelido de UI ("Equipe") é traduzido na borda HTTP, não aqui. */
|
|
2065
|
+
sources?: readonly string[];
|
|
2066
|
+
sortDirection?: 'asc' | 'desc';
|
|
2067
|
+
page?: number;
|
|
2068
|
+
limit?: number;
|
|
2069
|
+
};
|
|
2070
|
+
/**
|
|
2071
|
+
* Um arquivo na biblioteca da empresa. Igual ao da conversa, mais `conversationId` — sem saber de
|
|
2072
|
+
* quem veio, uma lista global de anexos não responde nenhuma pergunta útil.
|
|
2073
|
+
*/
|
|
2074
|
+
type CompanyDocumentView = {
|
|
2075
|
+
/** O `uploadId` (key no storage), pelo mesmo motivo do `ConversationDocumentView`. */
|
|
2076
|
+
id: string;
|
|
2077
|
+
conversationId: string;
|
|
2078
|
+
filename: string;
|
|
2079
|
+
mimeType: string;
|
|
2080
|
+
sizeBytes: number;
|
|
2081
|
+
source: string;
|
|
2082
|
+
linkedAt: string;
|
|
2083
|
+
};
|
|
2084
|
+
type CompanyDocumentsPage = {
|
|
2085
|
+
documents: CompanyDocumentView[];
|
|
2086
|
+
total: number;
|
|
2087
|
+
};
|
|
2088
|
+
/**
|
|
2089
|
+
* Biblioteca de arquivos de todas as conversas da empresa.
|
|
2090
|
+
*
|
|
2091
|
+
* Existe separada de `ListConversationDocumentsUseCase` porque a pergunta é outra: aquela parte de
|
|
2092
|
+
* uma conversa conhecida, esta varre a empresa e por isso precisa dizer de qual conversa cada
|
|
2093
|
+
* arquivo veio. Reaproveitar a primeira exigiria um `sessionId` opcional que muda o significado do
|
|
2094
|
+
* retorno — dois nomes claros custam menos que um parâmetro que dobra o comportamento.
|
|
2095
|
+
*/
|
|
2096
|
+
declare class ListCompanyDocumentsUseCase {
|
|
2097
|
+
private readonly documentRepository;
|
|
2098
|
+
constructor(documentRepository: DocumentRepository);
|
|
2099
|
+
execute(params: ListCompanyDocumentsParams): Promise<CompanyDocumentsPage>;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
type DeleteConversationParams = {
|
|
2103
|
+
companyId: string;
|
|
2104
|
+
whatsappNumber: string;
|
|
2105
|
+
};
|
|
2106
|
+
type DeleteConversationResult = {
|
|
2107
|
+
/** Objetos efetivamente apagados no storage. */
|
|
2108
|
+
deletedObjects: number;
|
|
2109
|
+
/** Objetos que o storage recusou. A conversa NÃO é apagada quando isto é maior que zero. */
|
|
2110
|
+
failedObjects: readonly string[];
|
|
2111
|
+
};
|
|
2112
|
+
/**
|
|
2113
|
+
* Apaga a conversa e a mídia dela.
|
|
2114
|
+
*
|
|
2115
|
+
* A ordem é o ponto: **storage primeiro, banco depois**. A FK de `documents.session_id` é
|
|
2116
|
+
* `on delete cascade`, então apagar a sessão primeiro derrubaria as linhas e levaria embora a única
|
|
2117
|
+
* lista de `uploadId` existente — os binários ficariam órfãos, cobrados para sempre e inalcançáveis.
|
|
2118
|
+
*
|
|
2119
|
+
* Se algum objeto falhar, a conversa é preservada e o chamador recebe a lista. Apagar as linhas
|
|
2120
|
+
* "mesmo assim" transformaria uma falha visível e reexecutável em lixo silencioso no storage.
|
|
2121
|
+
*/
|
|
2122
|
+
declare class DeleteConversationUseCase {
|
|
2123
|
+
private readonly sessionRepository;
|
|
2124
|
+
private readonly documentRepository;
|
|
2125
|
+
private readonly objectStorage?;
|
|
2126
|
+
constructor(sessionRepository: SessionRepository, documentRepository: DocumentRepository, objectStorage?: ObjectStorageInterface | undefined);
|
|
2127
|
+
execute(params: DeleteConversationParams): Promise<DeleteConversationResult>;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
type PurgeExpiredDocumentsParams = {
|
|
2131
|
+
companyId: string;
|
|
2132
|
+
/** Dias de retenção. O produto configura; o módulo não escolhe política de dado pessoal. */
|
|
2133
|
+
retentionDays: number;
|
|
2134
|
+
/** Teto por execução, para o job não segurar conexão nem storage por tempo indefinido. */
|
|
2135
|
+
batchSize?: number;
|
|
2136
|
+
/** Instante de referência — injetado para o teste não depender do relógio. */
|
|
2137
|
+
now?: Date;
|
|
2138
|
+
};
|
|
2139
|
+
type PurgeExpiredDocumentsResult = {
|
|
2140
|
+
purged: number;
|
|
2141
|
+
failed: readonly string[];
|
|
2142
|
+
};
|
|
2143
|
+
/**
|
|
2144
|
+
* Apaga documento vencido: objeto no storage primeiro, linha depois — a mesma ordem do
|
|
2145
|
+
* `DeleteConversationUseCase`, e pelo mesmo motivo.
|
|
2146
|
+
*
|
|
2147
|
+
* A linha só cai quando o objeto caiu. Contar como purgado sem ter apagado o binário deixaria lixo
|
|
2148
|
+
* pago no storage sem nenhum registro que o alcance depois: a linha era o único ponteiro.
|
|
2149
|
+
*/
|
|
2150
|
+
declare class PurgeExpiredDocumentsUseCase {
|
|
2151
|
+
private readonly documentRepository;
|
|
2152
|
+
private readonly objectStorage?;
|
|
2153
|
+
constructor(documentRepository: DocumentRepository, objectStorage?: ObjectStorageInterface | undefined);
|
|
2154
|
+
execute(params: PurgeExpiredDocumentsParams): Promise<PurgeExpiredDocumentsResult>;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
1179
2157
|
type ExportConversationParams = {
|
|
1180
2158
|
companyId: string;
|
|
1181
2159
|
whatsappNumber: string;
|
|
@@ -1297,10 +2275,77 @@ declare class FlowInterpreter {
|
|
|
1297
2275
|
}): Promise<FlowRunResult>;
|
|
1298
2276
|
}
|
|
1299
2277
|
|
|
2278
|
+
type FlowMediaLocation = {
|
|
2279
|
+
companyId: string;
|
|
2280
|
+
flowKey: string;
|
|
2281
|
+
nodeId: string;
|
|
2282
|
+
};
|
|
2283
|
+
type AttachFlowMediaParams = FlowMediaLocation & {
|
|
2284
|
+
uploadId: string;
|
|
2285
|
+
filename: string;
|
|
2286
|
+
mimeType: string;
|
|
2287
|
+
sizeBytes: number;
|
|
2288
|
+
caption?: string;
|
|
2289
|
+
sortOrder?: number;
|
|
2290
|
+
};
|
|
2291
|
+
type UpdateFlowMediaParams = {
|
|
2292
|
+
companyId: string;
|
|
2293
|
+
id: string;
|
|
2294
|
+
caption?: string | null;
|
|
2295
|
+
sortOrder?: number;
|
|
2296
|
+
active?: boolean;
|
|
2297
|
+
};
|
|
2298
|
+
declare class FlowMediaRepository {
|
|
2299
|
+
private readonly db;
|
|
2300
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
2301
|
+
private locationFilter;
|
|
2302
|
+
listActive(location: FlowMediaLocation): Promise<FlowMediaRow[]>;
|
|
2303
|
+
listAll(location: FlowMediaLocation): Promise<FlowMediaRow[]>;
|
|
2304
|
+
/**
|
|
2305
|
+
* Anexa um arquivo já existente no storage ao nó.
|
|
2306
|
+
*
|
|
2307
|
+
* `onConflictDoUpdate` em vez de deixar estourar: reanexar o mesmo arquivo é clique repetido no
|
|
2308
|
+
* editor, e o esperado ali é atualizar a legenda/ordem — não um erro de índice único na cara de
|
|
2309
|
+
* quem está montando o fluxo.
|
|
2310
|
+
*/
|
|
2311
|
+
attach(params: AttachFlowMediaParams): Promise<FlowMediaRow>;
|
|
2312
|
+
update(params: UpdateFlowMediaParams): Promise<FlowMediaRow | undefined>;
|
|
2313
|
+
/**
|
|
2314
|
+
* Desanexa do nó. Não toca no storage de propósito: o mesmo `uploadId` pode estar anexado a
|
|
2315
|
+
* outro nó ou a outro fluxo, e apagar o binário aqui quebraria os demais. Quem apaga objeto é o
|
|
2316
|
+
* host, que é dono da biblioteca de arquivos.
|
|
2317
|
+
*/
|
|
2318
|
+
detach(params: {
|
|
2319
|
+
companyId: string;
|
|
2320
|
+
id: string;
|
|
2321
|
+
}): Promise<void>;
|
|
2322
|
+
detachRemovedNodes(params: {
|
|
2323
|
+
companyId: string;
|
|
2324
|
+
flowKey: string;
|
|
2325
|
+
existingNodeIds: string[];
|
|
2326
|
+
}): Promise<void>;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
/**
|
|
2330
|
+
* Copyright (c) 2026 Ada Technology. MIT License.
|
|
2331
|
+
*
|
|
2332
|
+
* O algoritmo mora em `meta-graph-core`, compartilhado com os outros objetos que a Meta assina.
|
|
2333
|
+
* Aqui fica só a tradução para o vocabulário deste domínio: o erro lançado e o namespace do nonce.
|
|
2334
|
+
*/
|
|
2335
|
+
|
|
1300
2336
|
interface NonceStoreInterface {
|
|
1301
2337
|
setIfAbsent(key: string, ttlSeconds: number): Promise<boolean>;
|
|
2338
|
+
/**
|
|
2339
|
+
* Estende a chave já reivindicada para a janela cheia. Um SET simples, sem NX.
|
|
2340
|
+
*
|
|
2341
|
+
* Opcional só por compatibilidade com hosts que ainda não o implementam: sem ele o claim curto
|
|
2342
|
+
* expira, e a Meta pode reentregar uma entrega já processada — trabalho repetido, que a dedupe
|
|
2343
|
+
* por `waMessageId` ainda segura antes de virar efeito visível ao cliente. Implementar é o
|
|
2344
|
+
* caminho correto.
|
|
2345
|
+
*/
|
|
2346
|
+
confirm?(key: string, ttlSeconds: number): Promise<void>;
|
|
1302
2347
|
}
|
|
1303
|
-
|
|
2348
|
+
|
|
1304
2349
|
declare function verifyWebhookChallenge(params: {
|
|
1305
2350
|
mode: string | null;
|
|
1306
2351
|
token: string | null;
|
|
@@ -1312,11 +2357,101 @@ declare function verifyWebhookSignature(params: {
|
|
|
1312
2357
|
signatureHeader: string | null | undefined;
|
|
1313
2358
|
appSecret: string;
|
|
1314
2359
|
}): void;
|
|
2360
|
+
/**
|
|
2361
|
+
* Reivindica a entrega por pouco tempo. O par obrigatório é `confirmWebhookDelivery` ao fim do
|
|
2362
|
+
* processamento — ver `WEBHOOK_CLAIM_TTL_SECONDS` para o porquê dos dois tempos.
|
|
2363
|
+
*/
|
|
1315
2364
|
declare function claimWebhookDelivery(params: {
|
|
1316
2365
|
nonceStore: NonceStoreInterface;
|
|
1317
2366
|
signatureHeader: string;
|
|
1318
2367
|
ttlSeconds?: number;
|
|
1319
2368
|
}): Promise<boolean>;
|
|
2369
|
+
/** Só depois da entrega processada por inteiro: é isto que fecha a janela anti-replay. */
|
|
2370
|
+
declare function confirmWebhookDelivery(params: {
|
|
2371
|
+
nonceStore: NonceStoreInterface;
|
|
2372
|
+
signatureHeader: string;
|
|
2373
|
+
ttlSeconds?: number;
|
|
2374
|
+
}): Promise<void>;
|
|
2375
|
+
|
|
2376
|
+
/**
|
|
2377
|
+
* Copyright (c) 2026 Ada Technology. MIT License.
|
|
2378
|
+
*
|
|
2379
|
+
* Separa a entrega do webhook em duas metades: o que precisa acontecer antes de responder à Meta
|
|
2380
|
+
* (persistir a mensagem, que é escrita local e rápida) e o que pode esperar alguns milissegundos
|
|
2381
|
+
* (os hooks do host, que costumam falar com rede — motor de fluxo, IA, integrações).
|
|
2382
|
+
*
|
|
2383
|
+
* A segunda metade rodava dentro da requisição do webhook. Um deploy no meio de uma conversa
|
|
2384
|
+
* matava a chamada em voo, e o cliente ficava sem resposta sem que nada registrasse a perda. Com
|
|
2385
|
+
* uma fila no host, derrubar o processo vira atraso: o job espera e é retomado.
|
|
2386
|
+
*/
|
|
2387
|
+
|
|
2388
|
+
type InboundMediaDescriptor = {
|
|
2389
|
+
sourceMediaId: string;
|
|
2390
|
+
mimeType: string;
|
|
2391
|
+
filename?: string;
|
|
2392
|
+
};
|
|
2393
|
+
type InboundMessageEffectsJob = {
|
|
2394
|
+
kind: 'message';
|
|
2395
|
+
companyId: string;
|
|
2396
|
+
message: WhatsAppMessage;
|
|
2397
|
+
savedMessageId: string;
|
|
2398
|
+
media?: InboundMediaDescriptor;
|
|
2399
|
+
receivedAt: number;
|
|
2400
|
+
};
|
|
2401
|
+
type InboundStatusEffectsJob = {
|
|
2402
|
+
kind: 'status';
|
|
2403
|
+
companyId: string;
|
|
2404
|
+
status: WhatsAppStatus;
|
|
2405
|
+
whatsappNumber: string;
|
|
2406
|
+
receivedAt: number;
|
|
2407
|
+
};
|
|
2408
|
+
type InboundDispatchJob = InboundMessageEffectsJob | InboundStatusEffectsJob;
|
|
2409
|
+
/**
|
|
2410
|
+
* Porta de fila. O módulo não escolhe a tecnologia — BullMQ, SQS, o que o host já tiver — mas
|
|
2411
|
+
* exige dela duas garantias, e sem as duas o padrão não entrega o que promete:
|
|
2412
|
+
*
|
|
2413
|
+
* 1. **Durabilidade**: o job sobrevive à morte do processo que o enfileirou. Uma fila em memória
|
|
2414
|
+
* reintroduz exatamente a perda que este desenho existe para evitar.
|
|
2415
|
+
* 2. **Retentativa com backoff**: o destino dos hooks (n8n, IA, API de terceiro) também cai em
|
|
2416
|
+
* deploy. Sem retry, o job falha uma vez e a conversa morre do mesmo jeito.
|
|
2417
|
+
*
|
|
2418
|
+
* `jobId` é estável e derivado da mensagem: reentrega da Meta e re-enfileiramento produzem o mesmo
|
|
2419
|
+
* id, e a fila descarta o segundo em vez de rodar o efeito duas vezes.
|
|
2420
|
+
*/
|
|
2421
|
+
interface InboundDispatchQueueInterface {
|
|
2422
|
+
enqueue(job: InboundDispatchJob, options: {
|
|
2423
|
+
jobId: string;
|
|
2424
|
+
}): Promise<void>;
|
|
2425
|
+
}
|
|
2426
|
+
declare function buildInboundJobId(job: InboundDispatchJob): string;
|
|
2427
|
+
declare function toSessionContract(row: SessionRow): ConversationSession;
|
|
2428
|
+
/**
|
|
2429
|
+
* Os efeitos de host de uma entrega já persistida. Vive fora do `ReceiveWebhookUseCase` porque
|
|
2430
|
+
* roda nos dois lados: inline, quando o host não configurou fila, e dentro do worker, quando
|
|
2431
|
+
* configurou. Um só corpo de regra para os dois caminhos — duas cópias divergiriam.
|
|
2432
|
+
*/
|
|
2433
|
+
declare class InboundEffectsDispatcher {
|
|
2434
|
+
private readonly params;
|
|
2435
|
+
constructor(params: {
|
|
2436
|
+
sessionRepository: SessionRepository;
|
|
2437
|
+
hooks?: MetaWhatsAppHooks;
|
|
2438
|
+
realtime?: RealtimeNotifierInterface;
|
|
2439
|
+
});
|
|
2440
|
+
run(job: InboundDispatchJob): Promise<void>;
|
|
2441
|
+
runMessageEffects(job: InboundMessageEffectsJob): Promise<void>;
|
|
2442
|
+
runStatusEffects(job: InboundStatusEffectsJob): Promise<void>;
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Ponto de entrada do worker do host: recebe o job que a fila devolveu e roda os efeitos.
|
|
2446
|
+
*
|
|
2447
|
+
* Deixe a exceção propagar. É ela que faz a fila contar a tentativa e reagendar com backoff;
|
|
2448
|
+
* capturar aqui para logar transforma falha recuperável em mensagem perdida em silêncio.
|
|
2449
|
+
*/
|
|
2450
|
+
declare class ProcessInboundDispatchUseCase {
|
|
2451
|
+
private readonly dispatcher;
|
|
2452
|
+
constructor(dispatcher: InboundEffectsDispatcher);
|
|
2453
|
+
execute(job: InboundDispatchJob): Promise<void>;
|
|
2454
|
+
}
|
|
1320
2455
|
|
|
1321
2456
|
type ReceiveWebhookParams = {
|
|
1322
2457
|
companyId: string;
|
|
@@ -1327,11 +2462,15 @@ type ReceiveWebhookResult = {
|
|
|
1327
2462
|
duplicate: boolean;
|
|
1328
2463
|
messagesProcessed: number;
|
|
1329
2464
|
statusesProcessed: number;
|
|
2465
|
+
ignoredForeignNumber: number;
|
|
2466
|
+
accountEventsProcessed: number;
|
|
2467
|
+
unhandledEvents: number;
|
|
1330
2468
|
};
|
|
1331
2469
|
declare class ReceiveWebhookUseCase {
|
|
1332
2470
|
private readonly params;
|
|
1333
2471
|
constructor(params: {
|
|
1334
2472
|
appSecret: string;
|
|
2473
|
+
phoneNumberId: string;
|
|
1335
2474
|
nonceStore: NonceStoreInterface;
|
|
1336
2475
|
sessionRepository: SessionRepository;
|
|
1337
2476
|
messageRepository: MessageRepository;
|
|
@@ -1339,12 +2478,53 @@ declare class ReceiveWebhookUseCase {
|
|
|
1339
2478
|
startState: SessionState;
|
|
1340
2479
|
hooks?: MetaWhatsAppHooks;
|
|
1341
2480
|
realtime?: RealtimeNotifierInterface;
|
|
2481
|
+
/**
|
|
2482
|
+
* Sem fila o módulo se comporta como sempre: os hooks rodam dentro da requisição do webhook.
|
|
2483
|
+
* Configurá-la é o que faz a conversa sobreviver a um deploy no meio do atendimento.
|
|
2484
|
+
*/
|
|
2485
|
+
inboundQueue?: InboundDispatchQueueInterface;
|
|
1342
2486
|
});
|
|
2487
|
+
private readonly dispatcher;
|
|
1343
2488
|
execute(input: ReceiveWebhookParams): Promise<ReceiveWebhookResult>;
|
|
2489
|
+
private handleAccountEvent;
|
|
1344
2490
|
private handleMessage;
|
|
1345
2491
|
private handleStatus;
|
|
2492
|
+
private dispatch;
|
|
1346
2493
|
}
|
|
1347
2494
|
|
|
2495
|
+
/**
|
|
2496
|
+
* Política efetiva de transcrição de uma empresa.
|
|
2497
|
+
*
|
|
2498
|
+
* A separação que este arquivo existe para manter: **ambiente decide se é POSSÍVEL, settings decide
|
|
2499
|
+
* se é para FAZER.** A capacidade (engine, chave, storage que sabe reler) é injetada pelo host e é
|
|
2500
|
+
* por deploy — chave de API não vai para tabela de configuração de tenant. Já "transcrever ou não" e
|
|
2501
|
+
* "automático ou sob demanda" são decisão de operação de cada empresa, e pedir deploy para mudar
|
|
2502
|
+
* isso é o que transforma um interruptor em ticket.
|
|
2503
|
+
*/
|
|
2504
|
+
type TranscriptionPolicy = {
|
|
2505
|
+
readonly isEnabled: boolean;
|
|
2506
|
+
readonly mode: TranscriptionMode;
|
|
2507
|
+
};
|
|
2508
|
+
type TranscriptionPolicyDefaults = {
|
|
2509
|
+
/** O que vale quando o painel não decidiu. Tipicamente vem do ambiente do host. */
|
|
2510
|
+
readonly isEnabled: boolean;
|
|
2511
|
+
readonly mode: TranscriptionMode;
|
|
2512
|
+
};
|
|
2513
|
+
type ResolveTranscriptionPolicyDependencies = {
|
|
2514
|
+
readonly settingsRepository: SettingsRepository;
|
|
2515
|
+
readonly defaults: TranscriptionPolicyDefaults;
|
|
2516
|
+
};
|
|
2517
|
+
/**
|
|
2518
|
+
* Resolve a política por empresa, com o padrão do host como base.
|
|
2519
|
+
*
|
|
2520
|
+
* Uma consulta a `settings` por áudio transcrito. Não é cacheado de propósito: é uma leitura por
|
|
2521
|
+
* chave primária, acontece uma vez por nota de voz (não por mensagem), e cachear introduziria a
|
|
2522
|
+
* pergunta "por quanto tempo o operador continua vendo o interruptor antigo depois de mexer nele" —
|
|
2523
|
+
* custo real, para economizar um índice único.
|
|
2524
|
+
*/
|
|
2525
|
+
declare function createTranscriptionPolicyResolver(dependencies: ResolveTranscriptionPolicyDependencies): (companyId: string) => Promise<TranscriptionPolicy>;
|
|
2526
|
+
type TranscriptionPolicyResolver = ReturnType<typeof createTranscriptionPolicyResolver>;
|
|
2527
|
+
|
|
1348
2528
|
type IngestInboundMediaParams = {
|
|
1349
2529
|
companyId: string;
|
|
1350
2530
|
messageId: string;
|
|
@@ -1355,13 +2535,52 @@ type IngestInboundMediaParams = {
|
|
|
1355
2535
|
type IngestInboundMediaResult = {
|
|
1356
2536
|
uploadId: string;
|
|
1357
2537
|
alreadyIngested: boolean;
|
|
2538
|
+
/**
|
|
2539
|
+
* Só presente quando a transcrição automática rodou nesta execução. Ausente é o normal: mídia que
|
|
2540
|
+
* não é áudio, transcrição desligada, modo sob demanda, ou mídia já ingerida antes.
|
|
2541
|
+
*/
|
|
2542
|
+
transcription?: {
|
|
2543
|
+
status: TranscriptionStatus;
|
|
2544
|
+
};
|
|
2545
|
+
};
|
|
2546
|
+
/**
|
|
2547
|
+
* Transcrição durante a ingestão — o modo `auto`.
|
|
2548
|
+
*
|
|
2549
|
+
* Entra aqui, e não em use-case separado, por um motivo só: neste ponto o buffer do áudio ACABOU de
|
|
2550
|
+
* ser baixado e está em memória. Transcrever fora daqui custaria um segundo download do storage por
|
|
2551
|
+
* áudio, e o `TranscribeAudioUseCase` existe justamente para esse caso (sob demanda e retomada).
|
|
2552
|
+
*/
|
|
2553
|
+
type IngestTranscriptionOptions = {
|
|
2554
|
+
transcriber: AudioTranscriber;
|
|
2555
|
+
messageRepository: MessageRepository;
|
|
2556
|
+
/**
|
|
2557
|
+
* Política POR EMPRESA, resolvida a cada áudio. Não é um `mode` fixo porque o interruptor mora nas
|
|
2558
|
+
* configurações da empresa: um valor capturado na construção do use-case congelaria a escolha até
|
|
2559
|
+
* o próximo deploy, e o worker é um processo longo — o operador mexeria no painel e nada mudaria.
|
|
2560
|
+
*/
|
|
2561
|
+
resolvePolicy: TranscriptionPolicyResolver;
|
|
2562
|
+
languageHint?: string;
|
|
2563
|
+
hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
|
|
1358
2564
|
};
|
|
1359
2565
|
declare class IngestInboundMediaUseCase {
|
|
1360
2566
|
private readonly db;
|
|
1361
2567
|
private readonly channel;
|
|
1362
2568
|
private readonly objectStorage;
|
|
1363
|
-
|
|
2569
|
+
private readonly documentRepository?;
|
|
2570
|
+
private readonly transcription?;
|
|
2571
|
+
constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface, documentRepository?: DocumentRepository | undefined, transcription?: IngestTranscriptionOptions | undefined);
|
|
1364
2572
|
execute(params: IngestInboundMediaParams): Promise<IngestInboundMediaResult>;
|
|
2573
|
+
/**
|
|
2574
|
+
* Transcreve o áudio recém-baixado, quando o modo é `auto`.
|
|
2575
|
+
*
|
|
2576
|
+
* **Nunca propaga erro.** Neste ponto o binário já está no storage e já entrou na biblioteca da
|
|
2577
|
+
* conversa: deixar uma falha de transcrição subir marcaria a ingestão inteira como falha, e o
|
|
2578
|
+
* retry do host baixaria de novo da Meta um arquivo que está salvo — gastando banda para reproduzir
|
|
2579
|
+
* um efeito que já aconteceu. O status fica gravado na mensagem e o `onTranscriptionDeferred`
|
|
2580
|
+
* avisa quem sabe reenfileirar.
|
|
2581
|
+
*/
|
|
2582
|
+
private transcribeIfAuto;
|
|
2583
|
+
private recordTranscriptionFailure;
|
|
1365
2584
|
}
|
|
1366
2585
|
declare function extractMediaDescriptor(message: MessageRow): {
|
|
1367
2586
|
sourceMediaId: string;
|
|
@@ -1369,6 +2588,105 @@ declare function extractMediaDescriptor(message: MessageRow): {
|
|
|
1369
2588
|
filename?: string;
|
|
1370
2589
|
} | undefined;
|
|
1371
2590
|
|
|
2591
|
+
/**
|
|
2592
|
+
* Storage com leitura garantida. `getObject` é opcional no contrato, então quem monta este use-case
|
|
2593
|
+
* precisa provar que o método existe — sem os bytes não há o que transcrever, e um use-case que
|
|
2594
|
+
* sempre falha é pior do que a ausência ser visível no tipo.
|
|
2595
|
+
*/
|
|
2596
|
+
type ReadableObjectStorage = ObjectStorageInterface & {
|
|
2597
|
+
getObject: NonNullable<ObjectStorageInterface['getObject']>;
|
|
2598
|
+
};
|
|
2599
|
+
type TranscribeAudioParams = {
|
|
2600
|
+
companyId: string;
|
|
2601
|
+
messageId: string;
|
|
2602
|
+
/**
|
|
2603
|
+
* Refaz mesmo com transcrição já salva. Serve ao "transcrever de novo" depois de trocar de engine
|
|
2604
|
+
* — sem isto, um resultado ruim do engine antigo ficaria congelado para sempre.
|
|
2605
|
+
*/
|
|
2606
|
+
force?: boolean;
|
|
2607
|
+
};
|
|
2608
|
+
type TranscribeAudioResult = {
|
|
2609
|
+
status: TranscriptionStatus;
|
|
2610
|
+
text: string | null;
|
|
2611
|
+
language: string | null;
|
|
2612
|
+
engine: string | null;
|
|
2613
|
+
/** `true` quando devolveu o que já estava salvo, sem gastar cota. */
|
|
2614
|
+
alreadyTranscribed: boolean;
|
|
2615
|
+
};
|
|
2616
|
+
type TranscribeAudioDependencies = {
|
|
2617
|
+
messageRepository: MessageRepository;
|
|
2618
|
+
objectStorage: ReadableObjectStorage;
|
|
2619
|
+
transcriber: AudioTranscriber;
|
|
2620
|
+
/**
|
|
2621
|
+
* Política por empresa. Ausente, a transcrição sob demanda não consulta configuração nenhuma e
|
|
2622
|
+
* atende sempre — que é o comportamento de quem controla o liga/desliga só por ambiente.
|
|
2623
|
+
*/
|
|
2624
|
+
resolvePolicy?: TranscriptionPolicyResolver;
|
|
2625
|
+
/** ISO 639-1 do produto. Informar corta a detecção do Whisper e evita pt-BR curto virar espanhol. */
|
|
2626
|
+
languageHint?: string;
|
|
2627
|
+
hooks?: Pick<MetaWhatsAppHooks, 'onTranscriptionDeferred'>;
|
|
2628
|
+
};
|
|
2629
|
+
/**
|
|
2630
|
+
* Transcreve o áudio de UMA mensagem já persistida e ingerida.
|
|
2631
|
+
*
|
|
2632
|
+
* Serve aos dois modos: é o que o painel chama no botão "transcrever" (`onDemand`) e é o que o host
|
|
2633
|
+
* chama ao retomar um `pending` reenfileirado. O modo `auto` não passa por aqui — ele transcreve
|
|
2634
|
+
* dentro da ingestão, onde o buffer já está em memória e não custa um segundo download.
|
|
2635
|
+
*
|
|
2636
|
+
* Idempotente por `transcription_status`: chamar de novo num `'done'` devolve o que está salvo em
|
|
2637
|
+
* vez de gastar cota transcrevendo o mesmo áudio.
|
|
2638
|
+
*/
|
|
2639
|
+
declare class TranscribeAudioUseCase {
|
|
2640
|
+
private readonly dependencies;
|
|
2641
|
+
constructor(dependencies: TranscribeAudioDependencies);
|
|
2642
|
+
execute(params: TranscribeAudioParams): Promise<TranscribeAudioResult>;
|
|
2643
|
+
private transcribeBuffer;
|
|
2644
|
+
/**
|
|
2645
|
+
* Carimba o motivo antes de propagar. O status é o que impede os dois desperdícios simétricos:
|
|
2646
|
+
* reprocessar para sempre um codec impossível, e desistir de um áudio que só esbarrou na cota.
|
|
2647
|
+
*/
|
|
2648
|
+
private persistFailure;
|
|
2649
|
+
}
|
|
2650
|
+
declare function resolveFailureStatus(error: unknown): TranscriptionStatus;
|
|
2651
|
+
|
|
2652
|
+
/**
|
|
2653
|
+
* Guarda um arquivo gravado no simulador e devolve o id que o webhook vai referenciar.
|
|
2654
|
+
*
|
|
2655
|
+
* Existe como use-case para o host só precisar da ROTA: receber o corpo, chamar isto, devolver o
|
|
2656
|
+
* `mediaId`. A parte que erra — onde gravar, com que chave, como marcar o id para o adaptador
|
|
2657
|
+
* reconhecer depois — fica aqui, num lugar só, e não em cada produto.
|
|
2658
|
+
*
|
|
2659
|
+
* O SDK para exatamente na porta do HTTP: registrar endpoint é do host, e um pacote que abrisse rota
|
|
2660
|
+
* no servidor de quem o instala decidiria caminho, autenticação e versionamento no lugar dele.
|
|
2661
|
+
*/
|
|
2662
|
+
type StorePreviewMediaParams = {
|
|
2663
|
+
companyId: string;
|
|
2664
|
+
/** Bytes do arquivo. Quem converte de base64 é a rota — o use-case não conhece transporte. */
|
|
2665
|
+
buffer: Buffer;
|
|
2666
|
+
mimeType: string;
|
|
2667
|
+
filename?: string;
|
|
2668
|
+
};
|
|
2669
|
+
type StorePreviewMediaResult = {
|
|
2670
|
+
/** Já com o prefixo: é isto que o simulador manda no webhook. */
|
|
2671
|
+
mediaId: string;
|
|
2672
|
+
uploadId: string;
|
|
2673
|
+
};
|
|
2674
|
+
declare class StorePreviewMediaUseCase {
|
|
2675
|
+
private readonly objectStorage;
|
|
2676
|
+
/**
|
|
2677
|
+
* Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
|
|
2678
|
+
* um teste precisa de chave previsível.
|
|
2679
|
+
*/
|
|
2680
|
+
private readonly generateKeySuffix;
|
|
2681
|
+
constructor(objectStorage: ObjectStorageInterface,
|
|
2682
|
+
/**
|
|
2683
|
+
* Fonte do sufixo único da chave. Injetada porque o módulo não escolhe gerador de id — e porque
|
|
2684
|
+
* um teste precisa de chave previsível.
|
|
2685
|
+
*/
|
|
2686
|
+
generateKeySuffix: () => string);
|
|
2687
|
+
execute(params: StorePreviewMediaParams): Promise<StorePreviewMediaResult>;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
1372
2690
|
interface MetaWhatsAppModuleConfig {
|
|
1373
2691
|
phoneNumberId: string;
|
|
1374
2692
|
accessToken: string;
|
|
@@ -1377,16 +2695,66 @@ interface MetaWhatsAppModuleConfig {
|
|
|
1377
2695
|
wabaId?: string;
|
|
1378
2696
|
apiVersion?: string;
|
|
1379
2697
|
baseUrl?: string;
|
|
2698
|
+
/**
|
|
2699
|
+
* Catálogo do Meta Commerce que a vitrine no chat oferece. Sem ele — ou sem `providers.catalog`
|
|
2700
|
+
* — a action `send_product_list` não é registrada: nó que o editor oferece e que em silêncio não
|
|
2701
|
+
* faz nada é pior do que nó que não existe.
|
|
2702
|
+
*/
|
|
2703
|
+
catalogId?: string;
|
|
1380
2704
|
}
|
|
1381
2705
|
interface MetaWhatsAppModuleFeatures {
|
|
1382
2706
|
flowEngine?: boolean;
|
|
2707
|
+
flowGraphCache?: boolean | {
|
|
2708
|
+
ttlSeconds?: number;
|
|
2709
|
+
};
|
|
2710
|
+
/**
|
|
2711
|
+
* Aceita mídia do simulador de conversa — o que faz o microfone aparecer no preview do cliente.
|
|
2712
|
+
*
|
|
2713
|
+
* **Desligado por omissão, e a decisão é consciente.** Ligado, o canal passa a aceitar id que não
|
|
2714
|
+
* veio da Meta: `preview-upload:<chave>` faz o servidor ler aquele objeto do storage. Em ambiente
|
|
2715
|
+
* de simulação isso é o recurso; em produção é leitura arbitrária do bucket por webhook forjado.
|
|
2716
|
+
*
|
|
2717
|
+
* Exige `providers.objectStorage` com `getObject` — sem os bytes não há o que devolver, e o flag é
|
|
2718
|
+
* ignorado em vez de produzir um canal que falha na primeira nota de voz.
|
|
2719
|
+
*/
|
|
2720
|
+
previewMedia?: boolean;
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Transcrição de áudio. Ausente = desligada, e as colunas ficam nulas ("não avaliado").
|
|
2724
|
+
*
|
|
2725
|
+
* `mode` é a decisão de produto que este objeto existe para carregar: `auto` transcreve toda nota de
|
|
2726
|
+
* voz recebida, na ingestão, onde o buffer já está em memória; `onDemand` só transcreve quando o
|
|
2727
|
+
* atendente pede, gastando cota apenas com áudio que alguém vai ler de fato.
|
|
2728
|
+
*/
|
|
2729
|
+
interface MetaWhatsAppTranscriptionConfig {
|
|
2730
|
+
transcriber: AudioTranscriber;
|
|
2731
|
+
/**
|
|
2732
|
+
* PADRÃO, não decisão final: vale para as empresas que não mexeram no interruptor do painel. As
|
|
2733
|
+
* configurações por empresa (`settings.transcriptionMode`) têm precedência.
|
|
2734
|
+
*
|
|
2735
|
+
* Padrão `onDemand` — o modo que não gasta cota sem alguém pedir.
|
|
2736
|
+
*/
|
|
2737
|
+
mode?: TranscriptionMode;
|
|
2738
|
+
/**
|
|
2739
|
+
* PADRÃO de ligado/desligado para empresas sem escolha registrada. `true` — injetar o transcritor
|
|
2740
|
+
* já é a declaração de que o host quer o recurso; quem controla por empresa usa o painel.
|
|
2741
|
+
*/
|
|
2742
|
+
isEnabledByDefault?: boolean;
|
|
2743
|
+
/** ISO 639-1 do produto (ex.: `'pt'`). Corta a detecção de idioma do engine. */
|
|
2744
|
+
languageHint?: string;
|
|
1383
2745
|
}
|
|
1384
2746
|
interface MetaWhatsAppModuleProviders {
|
|
1385
2747
|
objectStorage?: ObjectStorageInterface;
|
|
2748
|
+
cache?: CacheInterface;
|
|
1386
2749
|
realtime?: RealtimeNotifierInterface;
|
|
1387
2750
|
subjectResolver?: SubjectResolverInterface;
|
|
1388
2751
|
catalog?: CatalogPort;
|
|
1389
2752
|
moderator?: MessageModerator;
|
|
2753
|
+
/**
|
|
2754
|
+
* Converte nota de voz em texto. Exige `objectStorage` com `getObject` para o modo sob demanda —
|
|
2755
|
+
* transcrever um áudio já salvo significa ler os bytes de volta.
|
|
2756
|
+
*/
|
|
2757
|
+
transcription?: MetaWhatsAppTranscriptionConfig;
|
|
1390
2758
|
}
|
|
1391
2759
|
interface CreateMetaWhatsAppModuleParams {
|
|
1392
2760
|
db: MetaWhatsAppDatabase;
|
|
@@ -1407,10 +2775,31 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
|
|
|
1407
2775
|
release: ReleaseConversationUseCase;
|
|
1408
2776
|
list: ListConversationsUseCase;
|
|
1409
2777
|
listMessages: ListMessagesUseCase;
|
|
2778
|
+
listDocuments: ListConversationDocumentsUseCase;
|
|
2779
|
+
listCompanyDocuments: ListCompanyDocumentsUseCase;
|
|
2780
|
+
delete: DeleteConversationUseCase;
|
|
2781
|
+
purgeExpiredDocuments: PurgeExpiredDocumentsUseCase;
|
|
1410
2782
|
export: ExportConversationUseCase;
|
|
2783
|
+
transcribeAudio: TranscribeAudioUseCase | undefined;
|
|
1411
2784
|
repository: SessionRepository;
|
|
2785
|
+
messageRepository: MessageRepository;
|
|
2786
|
+
documentRepository: DocumentRepository;
|
|
1412
2787
|
};
|
|
2788
|
+
/**
|
|
2789
|
+
* `undefined` = o host não injetou transcritor, e nenhuma configuração de empresa muda isso: a
|
|
2790
|
+
* capacidade não existe. Presente, `resolvePolicy` responde o que vale para uma empresa —
|
|
2791
|
+
* é o que a rota de configurações usa para dizer ao painel se desenha o interruptor.
|
|
2792
|
+
*/
|
|
2793
|
+
transcription: {
|
|
2794
|
+
defaultMode: TranscriptionMode;
|
|
2795
|
+
resolvePolicy: (companyId: string) => Promise<TranscriptionPolicy>;
|
|
2796
|
+
} | undefined;
|
|
1413
2797
|
settings: SettingsRepository;
|
|
2798
|
+
/**
|
|
2799
|
+
* `undefined` quando o recurso não está ligado (ou falta storage legível). O host consulta a
|
|
2800
|
+
* ausência para não registrar a rota de upload — e o preview, sem a rota, esconde o microfone.
|
|
2801
|
+
*/
|
|
2802
|
+
previewMedia: StorePreviewMediaUseCase | undefined;
|
|
1414
2803
|
webhook: {
|
|
1415
2804
|
receive: ReceiveWebhookUseCase;
|
|
1416
2805
|
verifyChallenge: (query: {
|
|
@@ -1429,26 +2818,49 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
|
|
|
1429
2818
|
delete: DeleteFlowGraphUseCase;
|
|
1430
2819
|
livePositions: GetLiveFlowPositionsUseCase;
|
|
1431
2820
|
repository: FlowGraphRepository;
|
|
2821
|
+
mediaRepository: FlowMediaRepository;
|
|
1432
2822
|
} | undefined;
|
|
1433
2823
|
catalog: CatalogPort | undefined;
|
|
1434
2824
|
};
|
|
1435
2825
|
type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>;
|
|
1436
2826
|
|
|
2827
|
+
/**
|
|
2828
|
+
* Leitura de mídia do simulador de conversa.
|
|
2829
|
+
*
|
|
2830
|
+
* **Atrás de flag de propósito, e não ligado por omissão.** Aceitar id que não veio da Meta é
|
|
2831
|
+
* exatamente o que um webhook forjado exploraria: bastaria mandar `preview-upload:<chave>` para
|
|
2832
|
+
* fazer o servidor ler um objeto arbitrário do storage e devolvê-lo. Num ambiente de simulação isso
|
|
2833
|
+
* é o recurso; em produção é leitura arbitrária. Quem liga assume, e a decisão fica visível no
|
|
2834
|
+
* lugar onde o módulo é montado.
|
|
2835
|
+
*/
|
|
2836
|
+
type PreviewMediaSupport = {
|
|
2837
|
+
readonly isEnabled: boolean;
|
|
2838
|
+
readonly objectStorage: ObjectStorageInterface & {
|
|
2839
|
+
getObject: NonNullable<ObjectStorageInterface['getObject']>;
|
|
2840
|
+
};
|
|
2841
|
+
/** Mime a devolver, já que o storage guarda bytes e não o tipo. Padrão `audio/ogg`. */
|
|
2842
|
+
readonly defaultMimeType?: string;
|
|
2843
|
+
};
|
|
1437
2844
|
declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
|
|
1438
2845
|
private readonly messages;
|
|
1439
|
-
|
|
2846
|
+
/**
|
|
2847
|
+
* Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
|
|
2848
|
+
* que atualizar o pacote não abre nada em quem não pediu.
|
|
2849
|
+
*/
|
|
2850
|
+
private readonly previewMedia?;
|
|
2851
|
+
constructor(messages: WhatsAppMessageProvider,
|
|
2852
|
+
/**
|
|
2853
|
+
* Ausente, o adaptador se comporta como sempre: todo id vai para a Graph API. É o que garante
|
|
2854
|
+
* que atualizar o pacote não abre nada em quem não pediu.
|
|
2855
|
+
*/
|
|
2856
|
+
previewMedia?: PreviewMediaSupport | undefined);
|
|
1440
2857
|
private translateErrors;
|
|
1441
2858
|
sendText(to: string, body: string): Promise<{
|
|
1442
2859
|
externalMessageId: string | null;
|
|
1443
2860
|
}>;
|
|
1444
|
-
sendMedia(params: {
|
|
1445
|
-
to: string;
|
|
1446
|
-
buffer: Buffer;
|
|
1447
|
-
mimeType: string;
|
|
1448
|
-
filename: string;
|
|
1449
|
-
caption?: string;
|
|
1450
|
-
}): Promise<{
|
|
2861
|
+
sendMedia(params: SendChannelMediaParams): Promise<{
|
|
1451
2862
|
externalMessageId: string | null;
|
|
2863
|
+
mediaId?: string | undefined;
|
|
1452
2864
|
}>;
|
|
1453
2865
|
sendTemplate(params: {
|
|
1454
2866
|
to: string;
|
|
@@ -1469,6 +2881,34 @@ declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
|
|
|
1469
2881
|
}): Promise<{
|
|
1470
2882
|
externalMessageId: string | null;
|
|
1471
2883
|
}>;
|
|
2884
|
+
sendInteractiveButtons(params: {
|
|
2885
|
+
to: string;
|
|
2886
|
+
body: string;
|
|
2887
|
+
buttons: {
|
|
2888
|
+
id: string;
|
|
2889
|
+
title: string;
|
|
2890
|
+
}[];
|
|
2891
|
+
}): Promise<{
|
|
2892
|
+
externalMessageId: string | null;
|
|
2893
|
+
}>;
|
|
2894
|
+
sendProductList(params: {
|
|
2895
|
+
to: string;
|
|
2896
|
+
headerText: string;
|
|
2897
|
+
body: string;
|
|
2898
|
+
footerText?: string;
|
|
2899
|
+
sections: {
|
|
2900
|
+
title: string;
|
|
2901
|
+
retailerIds: string[];
|
|
2902
|
+
}[];
|
|
2903
|
+
}): Promise<{
|
|
2904
|
+
externalMessageId: string | null;
|
|
2905
|
+
}>;
|
|
2906
|
+
/**
|
|
2907
|
+
* Busca o binário da mídia — da Meta, ou do storage quando o id é do simulador.
|
|
2908
|
+
*
|
|
2909
|
+
* O desvio acontece ANTES de qualquer chamada de rede: id do simulador não existe na Meta, e
|
|
2910
|
+
* tentar buscá-lo lá renderia um 404 confuso em vez do áudio que o operador acabou de gravar.
|
|
2911
|
+
*/
|
|
1472
2912
|
fetchMediaAsBase64(mediaId: string): Promise<{
|
|
1473
2913
|
data: string;
|
|
1474
2914
|
mimeType: string;
|
|
@@ -1512,4 +2952,132 @@ declare function redeemSseTicket(store: TicketStoreInterface, ticket: string): P
|
|
|
1512
2952
|
whatsappNumber: string;
|
|
1513
2953
|
} | null>;
|
|
1514
2954
|
|
|
1515
|
-
|
|
2955
|
+
/**
|
|
2956
|
+
* Porta de escrita no transcript, e não a classe `LogMessageUseCase`.
|
|
2957
|
+
*
|
|
2958
|
+
* Exigir a classe amarraria a action a quem já guarda as mensagens nas tabelas do módulo — um host
|
|
2959
|
+
* em migração, com o transcript ainda no schema dele, não conseguiria usar a única action built-in
|
|
2960
|
+
* sem gravar o envio numa tabela que o painel dele não lê. O que a action precisa é só de um lugar
|
|
2961
|
+
* para registrar o que saiu.
|
|
2962
|
+
*/
|
|
2963
|
+
/**
|
|
2964
|
+
* Onde guardar o `mediaId` que a Meta devolveu para cada arquivo da biblioteca.
|
|
2965
|
+
*
|
|
2966
|
+
* Porta, e não tabela: o módulo não sabe se o host guarda isso numa coluna, no Redis ou em memória.
|
|
2967
|
+
* O que ele precisa é reaproveitar o id em vez de ressubir o mesmo binário para cada cliente que
|
|
2968
|
+
* passa pelo nó — a Meta aceita reusar por 30 dias.
|
|
2969
|
+
*/
|
|
2970
|
+
type FlowMediaIdStore = {
|
|
2971
|
+
get(params: {
|
|
2972
|
+
flowMediaId: string;
|
|
2973
|
+
senderKey: string;
|
|
2974
|
+
}): Promise<string | undefined>;
|
|
2975
|
+
set(params: {
|
|
2976
|
+
flowMediaId: string;
|
|
2977
|
+
senderKey: string;
|
|
2978
|
+
mediaId: string;
|
|
2979
|
+
}): Promise<void>;
|
|
2980
|
+
clear(params: {
|
|
2981
|
+
flowMediaId: string;
|
|
2982
|
+
senderKey: string;
|
|
2983
|
+
}): Promise<void>;
|
|
2984
|
+
};
|
|
2985
|
+
/**
|
|
2986
|
+
* O `senderKey` vem junto do store, e não solto, para não existir a forma inválida.
|
|
2987
|
+
*
|
|
2988
|
+
* O `mediaId` é escopado ao número remetente (`phone_number_id`) na Meta: cachear sem separar por
|
|
2989
|
+
* número faz o id de um número ser mandado pelo outro, e o envio falha. Exigi-lo dentro do mesmo
|
|
2990
|
+
* objeto torna "liguei o cache e esqueci o número" impossível de escrever.
|
|
2991
|
+
*/
|
|
2992
|
+
type FlowMediaIdCache = {
|
|
2993
|
+
readonly store: FlowMediaIdStore;
|
|
2994
|
+
readonly senderKey: string;
|
|
2995
|
+
};
|
|
2996
|
+
type FlowMediaTranscriptLogger = {
|
|
2997
|
+
execute(params: LogMessageParams): Promise<unknown>;
|
|
2998
|
+
};
|
|
2999
|
+
type CreateSendMediaActionParams = {
|
|
3000
|
+
flowMediaRepository: FlowMediaRepository;
|
|
3001
|
+
objectStorage: ObjectStorageInterface & {
|
|
3002
|
+
getObject: NonNullable<ObjectStorageInterface['getObject']>;
|
|
3003
|
+
};
|
|
3004
|
+
logMessage: FlowMediaTranscriptLogger;
|
|
3005
|
+
/**
|
|
3006
|
+
* Reaproveitamento do arquivo já subido. Capacidade por ausência: sem o cache, cada envio ressobe
|
|
3007
|
+
* o binário — exatamente o comportamento anterior, sem flag para desligar.
|
|
3008
|
+
*/
|
|
3009
|
+
mediaIdCache?: FlowMediaIdCache;
|
|
3010
|
+
startState: SessionState;
|
|
3011
|
+
onError?: (error: unknown, details: {
|
|
3012
|
+
flowKey: string;
|
|
3013
|
+
nodeId: string;
|
|
3014
|
+
uploadId: string;
|
|
3015
|
+
}) => void;
|
|
3016
|
+
};
|
|
3017
|
+
declare function createSendMediaAction(params: CreateSendMediaActionParams): FlowActionHandler;
|
|
3018
|
+
|
|
3019
|
+
/** Tetos da Meta para `interactive.product_list`. Acima disso a mensagem inteira é recusada. */
|
|
3020
|
+
declare const PRODUCT_LIST_LIMIT: {
|
|
3021
|
+
readonly ITEMS: 30;
|
|
3022
|
+
readonly SECTIONS: 10;
|
|
3023
|
+
};
|
|
3024
|
+
type CreateSendProductListActionParams = {
|
|
3025
|
+
catalog: CatalogPort;
|
|
3026
|
+
catalogId: string;
|
|
3027
|
+
logMessage: FlowMediaTranscriptLogger;
|
|
3028
|
+
startState: SessionState;
|
|
3029
|
+
/** Falha de UMA vitrine não pode travar a conversa; o host observa por aqui. */
|
|
3030
|
+
onError?: (error: unknown, details: {
|
|
3031
|
+
flowKey: string;
|
|
3032
|
+
nodeId: string;
|
|
3033
|
+
}) => void;
|
|
3034
|
+
};
|
|
3035
|
+
/**
|
|
3036
|
+
* Action `send_product_list`: ao passar pelo nó, envia a vitrine do catálogo publicado na Meta.
|
|
3037
|
+
*
|
|
3038
|
+
* O nó guarda **critério**, não a lista de produtos. Congelar `retailerId` em `actionParams` faria
|
|
3039
|
+
* o fluxo continuar oferecendo o item esgotado — ou o item excluído, que a Meta recusa junto com a
|
|
3040
|
+
* mensagem inteira. O que o operador edita é o texto e o filtro; o estoque manda no resto.
|
|
3041
|
+
*
|
|
3042
|
+
* Só produto em estoque entra. Vitrine que mostra o que não tem produz a pior conversa possível:
|
|
3043
|
+
* o cliente escolhe, responde, e recebe "acabou".
|
|
3044
|
+
*/
|
|
3045
|
+
declare function createSendProductListAction(params: CreateSendProductListActionParams): FlowActionHandler;
|
|
3046
|
+
|
|
3047
|
+
/**
|
|
3048
|
+
* Store padrão do cache de `mediaId`, na própria linha da biblioteca de mídia.
|
|
3049
|
+
*
|
|
3050
|
+
* A porta `FlowMediaIdStore` existe para o host poder escolher Redis ou memória. Esta é a
|
|
3051
|
+
* implementação que o módulo já traz pronta, para o caso comum não exigir nada de quem instala: a
|
|
3052
|
+
* tabela é do módulo, a coluna é do módulo, e sem isso todo host reescreveria o mesmo repositório.
|
|
3053
|
+
*
|
|
3054
|
+
* Um mapa por `phone_number_id` na mesma linha, e não uma tabela à parte: o id não existe sem o
|
|
3055
|
+
* arquivo, morre com ele, e a linha é lida de qualquer forma no envio.
|
|
3056
|
+
*/
|
|
3057
|
+
declare class FlowMediaIdRepository implements FlowMediaIdStore {
|
|
3058
|
+
private readonly db;
|
|
3059
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
3060
|
+
get(params: {
|
|
3061
|
+
flowMediaId: string;
|
|
3062
|
+
senderKey: string;
|
|
3063
|
+
}): Promise<string | undefined>;
|
|
3064
|
+
/**
|
|
3065
|
+
* Grava só a chave deste número.
|
|
3066
|
+
*
|
|
3067
|
+
* `jsonb_set` no banco, e não ler-alterar-escrever na aplicação: dois clientes passando pelo nó
|
|
3068
|
+
* ao mesmo tempo com números diferentes leriam o mesmo mapa e o último gravaria por cima,
|
|
3069
|
+
* apagando o id do outro. A escrita atômica não tem essa janela.
|
|
3070
|
+
*/
|
|
3071
|
+
set(params: {
|
|
3072
|
+
flowMediaId: string;
|
|
3073
|
+
senderKey: string;
|
|
3074
|
+
mediaId: string;
|
|
3075
|
+
}): Promise<void>;
|
|
3076
|
+
/** Remove só a chave deste número — o id do outro número continua válido. */
|
|
3077
|
+
clear(params: {
|
|
3078
|
+
flowMediaId: string;
|
|
3079
|
+
senderKey: string;
|
|
3080
|
+
}): Promise<void>;
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
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 FlowMediaIdCache, FlowMediaIdRepository, type FlowMediaIdStore, 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 };
|