@adatechnology/meta-whatsapp-module 0.2.0-rc.8 → 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 +1762 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1759 -134
- package/dist/index.d.ts +1759 -134
- package/dist/index.js +1724 -155
- package/dist/index.js.map +1 -1
- package/dist/migrations/0005_message_moderation.sql +3 -0
- package/dist/migrations/0006_conversation_documents.sql +19 -0
- package/dist/migrations/0007_flow_media.sql +18 -0
- package/dist/migrations/0008_message_transcription.sql +5 -0
- package/dist/migrations/0009_settings_transcription_policy.sql +2 -0
- package/dist/migrations/0010_flow_media_meta_ids.sql +10 -0
- package/dist/migrations/meta/_journal.json +36 -1
- package/dist/testing/index.cjs +222 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +74 -0
- package/dist/testing/index.d.ts +74 -0
- package/dist/testing/index.js +186 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +16 -4
package/dist/index.d.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";
|
|
@@ -524,6 +592,117 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
524
592
|
identity: undefined;
|
|
525
593
|
generated: undefined;
|
|
526
594
|
}, {}, {}>;
|
|
595
|
+
moderationFlagged: drizzle_orm_pg_core.PgColumn<{
|
|
596
|
+
name: "moderation_flagged";
|
|
597
|
+
tableName: "messages";
|
|
598
|
+
dataType: "boolean";
|
|
599
|
+
columnType: "PgBoolean";
|
|
600
|
+
data: boolean;
|
|
601
|
+
driverParam: boolean;
|
|
602
|
+
notNull: false;
|
|
603
|
+
hasDefault: false;
|
|
604
|
+
isPrimaryKey: false;
|
|
605
|
+
isAutoincrement: false;
|
|
606
|
+
hasRuntimeDefault: false;
|
|
607
|
+
enumValues: undefined;
|
|
608
|
+
baseColumn: never;
|
|
609
|
+
identity: undefined;
|
|
610
|
+
generated: undefined;
|
|
611
|
+
}, {}, {}>;
|
|
612
|
+
moderationTerms: drizzle_orm_pg_core.PgColumn<{
|
|
613
|
+
name: "moderation_terms";
|
|
614
|
+
tableName: "messages";
|
|
615
|
+
dataType: "json";
|
|
616
|
+
columnType: "PgJsonb";
|
|
617
|
+
data: string[];
|
|
618
|
+
driverParam: unknown;
|
|
619
|
+
notNull: false;
|
|
620
|
+
hasDefault: false;
|
|
621
|
+
isPrimaryKey: false;
|
|
622
|
+
isAutoincrement: false;
|
|
623
|
+
hasRuntimeDefault: false;
|
|
624
|
+
enumValues: undefined;
|
|
625
|
+
baseColumn: never;
|
|
626
|
+
identity: undefined;
|
|
627
|
+
generated: undefined;
|
|
628
|
+
}, {}, {
|
|
629
|
+
$type: string[];
|
|
630
|
+
}>;
|
|
631
|
+
transcriptionStatus: drizzle_orm_pg_core.PgColumn<{
|
|
632
|
+
name: "transcription_status";
|
|
633
|
+
tableName: "messages";
|
|
634
|
+
dataType: "string";
|
|
635
|
+
columnType: "PgVarchar";
|
|
636
|
+
data: TranscriptionStatus;
|
|
637
|
+
driverParam: string;
|
|
638
|
+
notNull: false;
|
|
639
|
+
hasDefault: false;
|
|
640
|
+
isPrimaryKey: false;
|
|
641
|
+
isAutoincrement: false;
|
|
642
|
+
hasRuntimeDefault: false;
|
|
643
|
+
enumValues: [string, ...string[]];
|
|
644
|
+
baseColumn: never;
|
|
645
|
+
identity: undefined;
|
|
646
|
+
generated: undefined;
|
|
647
|
+
}, {}, {
|
|
648
|
+
length: 16;
|
|
649
|
+
$type: TranscriptionStatus;
|
|
650
|
+
}>;
|
|
651
|
+
transcriptionText: drizzle_orm_pg_core.PgColumn<{
|
|
652
|
+
name: "transcription_text";
|
|
653
|
+
tableName: "messages";
|
|
654
|
+
dataType: "string";
|
|
655
|
+
columnType: "PgText";
|
|
656
|
+
data: string;
|
|
657
|
+
driverParam: string;
|
|
658
|
+
notNull: false;
|
|
659
|
+
hasDefault: false;
|
|
660
|
+
isPrimaryKey: false;
|
|
661
|
+
isAutoincrement: false;
|
|
662
|
+
hasRuntimeDefault: false;
|
|
663
|
+
enumValues: [string, ...string[]];
|
|
664
|
+
baseColumn: never;
|
|
665
|
+
identity: undefined;
|
|
666
|
+
generated: undefined;
|
|
667
|
+
}, {}, {}>;
|
|
668
|
+
transcriptionLanguage: drizzle_orm_pg_core.PgColumn<{
|
|
669
|
+
name: "transcription_language";
|
|
670
|
+
tableName: "messages";
|
|
671
|
+
dataType: "string";
|
|
672
|
+
columnType: "PgVarchar";
|
|
673
|
+
data: string;
|
|
674
|
+
driverParam: string;
|
|
675
|
+
notNull: false;
|
|
676
|
+
hasDefault: false;
|
|
677
|
+
isPrimaryKey: false;
|
|
678
|
+
isAutoincrement: false;
|
|
679
|
+
hasRuntimeDefault: false;
|
|
680
|
+
enumValues: [string, ...string[]];
|
|
681
|
+
baseColumn: never;
|
|
682
|
+
identity: undefined;
|
|
683
|
+
generated: undefined;
|
|
684
|
+
}, {}, {
|
|
685
|
+
length: 32;
|
|
686
|
+
}>;
|
|
687
|
+
transcriptionEngine: drizzle_orm_pg_core.PgColumn<{
|
|
688
|
+
name: "transcription_engine";
|
|
689
|
+
tableName: "messages";
|
|
690
|
+
dataType: "string";
|
|
691
|
+
columnType: "PgVarchar";
|
|
692
|
+
data: string;
|
|
693
|
+
driverParam: string;
|
|
694
|
+
notNull: false;
|
|
695
|
+
hasDefault: false;
|
|
696
|
+
isPrimaryKey: false;
|
|
697
|
+
isAutoincrement: false;
|
|
698
|
+
hasRuntimeDefault: false;
|
|
699
|
+
enumValues: [string, ...string[]];
|
|
700
|
+
baseColumn: never;
|
|
701
|
+
identity: undefined;
|
|
702
|
+
generated: undefined;
|
|
703
|
+
}, {}, {
|
|
704
|
+
length: 32;
|
|
705
|
+
}>;
|
|
527
706
|
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
528
707
|
name: "created_at";
|
|
529
708
|
tableName: "messages";
|
|
@@ -544,13 +723,25 @@ declare const messages: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
544
723
|
};
|
|
545
724
|
dialect: "pg";
|
|
546
725
|
}>;
|
|
547
|
-
|
|
548
|
-
|
|
726
|
+
/**
|
|
727
|
+
* Biblioteca de arquivos da conversa.
|
|
728
|
+
*
|
|
729
|
+
* Tabela própria, e não derivada de `messages.payload`: o painel precisa de `source` e `linkedAt`,
|
|
730
|
+
* que não existem no payload; busca por nome de arquivo quer índice, não varredura de jsonb; e
|
|
731
|
+
* `messageId` anulável é o que permite o atendente anexar documento à conversa sem que exista uma
|
|
732
|
+
* mensagem correspondente.
|
|
733
|
+
*
|
|
734
|
+
* `sessionId` em cascata apaga a LINHA junto com a conversa, mas não o binário no storage — quem
|
|
735
|
+
* apaga objeto é passo de aplicação (listar `uploadId` → apagar no storage → apagar a sessão).
|
|
736
|
+
* Confiar só na FK deixaria objeto órfão sendo cobrado para sempre.
|
|
737
|
+
*/
|
|
738
|
+
declare const documents: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
739
|
+
name: "documents";
|
|
549
740
|
schema: "meta_whatsapp";
|
|
550
741
|
columns: {
|
|
551
742
|
id: drizzle_orm_pg_core.PgColumn<{
|
|
552
743
|
name: "id";
|
|
553
|
-
tableName: "
|
|
744
|
+
tableName: "documents";
|
|
554
745
|
dataType: "string";
|
|
555
746
|
columnType: "PgUUID";
|
|
556
747
|
data: string;
|
|
@@ -567,7 +758,7 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
567
758
|
}, {}, {}>;
|
|
568
759
|
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
569
760
|
name: "company_id";
|
|
570
|
-
tableName: "
|
|
761
|
+
tableName: "documents";
|
|
571
762
|
dataType: "string";
|
|
572
763
|
columnType: "PgUUID";
|
|
573
764
|
data: string;
|
|
@@ -582,11 +773,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
582
773
|
identity: undefined;
|
|
583
774
|
generated: undefined;
|
|
584
775
|
}, {}, {}>;
|
|
585
|
-
|
|
586
|
-
name: "
|
|
587
|
-
tableName: "
|
|
776
|
+
sessionId: drizzle_orm_pg_core.PgColumn<{
|
|
777
|
+
name: "session_id";
|
|
778
|
+
tableName: "documents";
|
|
588
779
|
dataType: "string";
|
|
589
|
-
columnType: "
|
|
780
|
+
columnType: "PgUUID";
|
|
590
781
|
data: string;
|
|
591
782
|
driverParam: string;
|
|
592
783
|
notNull: true;
|
|
@@ -594,16 +785,31 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
594
785
|
isPrimaryKey: false;
|
|
595
786
|
isAutoincrement: false;
|
|
596
787
|
hasRuntimeDefault: false;
|
|
597
|
-
enumValues:
|
|
788
|
+
enumValues: undefined;
|
|
598
789
|
baseColumn: never;
|
|
599
790
|
identity: undefined;
|
|
600
791
|
generated: undefined;
|
|
601
|
-
}, {}, {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
792
|
+
}, {}, {}>;
|
|
793
|
+
messageId: drizzle_orm_pg_core.PgColumn<{
|
|
794
|
+
name: "message_id";
|
|
795
|
+
tableName: "documents";
|
|
796
|
+
dataType: "string";
|
|
797
|
+
columnType: "PgUUID";
|
|
798
|
+
data: string;
|
|
799
|
+
driverParam: string;
|
|
800
|
+
notNull: false;
|
|
801
|
+
hasDefault: false;
|
|
802
|
+
isPrimaryKey: false;
|
|
803
|
+
isAutoincrement: false;
|
|
804
|
+
hasRuntimeDefault: false;
|
|
805
|
+
enumValues: undefined;
|
|
806
|
+
baseColumn: never;
|
|
807
|
+
identity: undefined;
|
|
808
|
+
generated: undefined;
|
|
809
|
+
}, {}, {}>;
|
|
810
|
+
uploadId: drizzle_orm_pg_core.PgColumn<{
|
|
811
|
+
name: "upload_id";
|
|
812
|
+
tableName: "documents";
|
|
607
813
|
dataType: "string";
|
|
608
814
|
columnType: "PgVarchar";
|
|
609
815
|
data: string;
|
|
@@ -618,11 +824,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
618
824
|
identity: undefined;
|
|
619
825
|
generated: undefined;
|
|
620
826
|
}, {}, {
|
|
621
|
-
length:
|
|
827
|
+
length: 256;
|
|
622
828
|
}>;
|
|
623
|
-
|
|
624
|
-
name: "
|
|
625
|
-
tableName: "
|
|
829
|
+
filename: drizzle_orm_pg_core.PgColumn<{
|
|
830
|
+
name: "filename";
|
|
831
|
+
tableName: "documents";
|
|
626
832
|
dataType: "string";
|
|
627
833
|
columnType: "PgVarchar";
|
|
628
834
|
data: string;
|
|
@@ -637,36 +843,36 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
637
843
|
identity: undefined;
|
|
638
844
|
generated: undefined;
|
|
639
845
|
}, {}, {
|
|
640
|
-
length:
|
|
846
|
+
length: 512;
|
|
641
847
|
}>;
|
|
642
|
-
|
|
643
|
-
name: "
|
|
644
|
-
tableName: "
|
|
645
|
-
dataType: "
|
|
646
|
-
columnType: "
|
|
647
|
-
data:
|
|
648
|
-
driverParam:
|
|
848
|
+
mimeType: drizzle_orm_pg_core.PgColumn<{
|
|
849
|
+
name: "mime_type";
|
|
850
|
+
tableName: "documents";
|
|
851
|
+
dataType: "string";
|
|
852
|
+
columnType: "PgVarchar";
|
|
853
|
+
data: string;
|
|
854
|
+
driverParam: string;
|
|
649
855
|
notNull: true;
|
|
650
|
-
hasDefault:
|
|
856
|
+
hasDefault: false;
|
|
651
857
|
isPrimaryKey: false;
|
|
652
858
|
isAutoincrement: false;
|
|
653
859
|
hasRuntimeDefault: false;
|
|
654
|
-
enumValues:
|
|
860
|
+
enumValues: [string, ...string[]];
|
|
655
861
|
baseColumn: never;
|
|
656
862
|
identity: undefined;
|
|
657
863
|
generated: undefined;
|
|
658
864
|
}, {}, {
|
|
659
|
-
|
|
865
|
+
length: 128;
|
|
660
866
|
}>;
|
|
661
|
-
|
|
662
|
-
name: "
|
|
663
|
-
tableName: "
|
|
867
|
+
sizeBytes: drizzle_orm_pg_core.PgColumn<{
|
|
868
|
+
name: "size_bytes";
|
|
869
|
+
tableName: "documents";
|
|
664
870
|
dataType: "number";
|
|
665
871
|
columnType: "PgInteger";
|
|
666
872
|
data: number;
|
|
667
873
|
driverParam: string | number;
|
|
668
874
|
notNull: true;
|
|
669
|
-
hasDefault:
|
|
875
|
+
hasDefault: false;
|
|
670
876
|
isPrimaryKey: false;
|
|
671
877
|
isAutoincrement: false;
|
|
672
878
|
hasRuntimeDefault: false;
|
|
@@ -675,31 +881,33 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
675
881
|
identity: undefined;
|
|
676
882
|
generated: undefined;
|
|
677
883
|
}, {}, {}>;
|
|
678
|
-
|
|
679
|
-
name: "
|
|
680
|
-
tableName: "
|
|
681
|
-
dataType: "
|
|
682
|
-
columnType: "
|
|
683
|
-
data:
|
|
684
|
-
driverParam:
|
|
685
|
-
notNull:
|
|
686
|
-
hasDefault:
|
|
884
|
+
sha256: drizzle_orm_pg_core.PgColumn<{
|
|
885
|
+
name: "sha256";
|
|
886
|
+
tableName: "documents";
|
|
887
|
+
dataType: "string";
|
|
888
|
+
columnType: "PgVarchar";
|
|
889
|
+
data: string;
|
|
890
|
+
driverParam: string;
|
|
891
|
+
notNull: false;
|
|
892
|
+
hasDefault: false;
|
|
687
893
|
isPrimaryKey: false;
|
|
688
894
|
isAutoincrement: false;
|
|
689
895
|
hasRuntimeDefault: false;
|
|
690
|
-
enumValues:
|
|
896
|
+
enumValues: [string, ...string[]];
|
|
691
897
|
baseColumn: never;
|
|
692
898
|
identity: undefined;
|
|
693
899
|
generated: undefined;
|
|
694
|
-
}, {}, {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
900
|
+
}, {}, {
|
|
901
|
+
length: 64;
|
|
902
|
+
}>;
|
|
903
|
+
source: drizzle_orm_pg_core.PgColumn<{
|
|
904
|
+
name: "source";
|
|
905
|
+
tableName: "documents";
|
|
698
906
|
dataType: "string";
|
|
699
907
|
columnType: "PgVarchar";
|
|
700
908
|
data: string;
|
|
701
909
|
driverParam: string;
|
|
702
|
-
notNull:
|
|
910
|
+
notNull: true;
|
|
703
911
|
hasDefault: false;
|
|
704
912
|
isPrimaryKey: false;
|
|
705
913
|
isAutoincrement: false;
|
|
@@ -709,11 +917,11 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
709
917
|
identity: undefined;
|
|
710
918
|
generated: undefined;
|
|
711
919
|
}, {}, {
|
|
712
|
-
length:
|
|
920
|
+
length: 12;
|
|
713
921
|
}>;
|
|
714
|
-
|
|
715
|
-
name: "
|
|
716
|
-
tableName: "
|
|
922
|
+
linkedAt: drizzle_orm_pg_core.PgColumn<{
|
|
923
|
+
name: "linked_at";
|
|
924
|
+
tableName: "documents";
|
|
717
925
|
dataType: "date";
|
|
718
926
|
columnType: "PgTimestamp";
|
|
719
927
|
data: Date;
|
|
@@ -728,16 +936,23 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
728
936
|
identity: undefined;
|
|
729
937
|
generated: undefined;
|
|
730
938
|
}, {}, {}>;
|
|
731
|
-
|
|
732
|
-
|
|
939
|
+
};
|
|
940
|
+
dialect: "pg";
|
|
941
|
+
}>;
|
|
942
|
+
declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
943
|
+
name: "flow_graphs";
|
|
944
|
+
schema: "meta_whatsapp";
|
|
945
|
+
columns: {
|
|
946
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
947
|
+
name: "id";
|
|
733
948
|
tableName: "flow_graphs";
|
|
734
|
-
dataType: "
|
|
735
|
-
columnType: "
|
|
736
|
-
data:
|
|
949
|
+
dataType: "string";
|
|
950
|
+
columnType: "PgUUID";
|
|
951
|
+
data: string;
|
|
737
952
|
driverParam: string;
|
|
738
953
|
notNull: true;
|
|
739
954
|
hasDefault: true;
|
|
740
|
-
isPrimaryKey:
|
|
955
|
+
isPrimaryKey: true;
|
|
741
956
|
isAutoincrement: false;
|
|
742
957
|
hasRuntimeDefault: false;
|
|
743
958
|
enumValues: undefined;
|
|
@@ -745,23 +960,16 @@ declare const flowGraphs: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
745
960
|
identity: undefined;
|
|
746
961
|
generated: undefined;
|
|
747
962
|
}, {}, {}>;
|
|
748
|
-
};
|
|
749
|
-
dialect: "pg";
|
|
750
|
-
}>;
|
|
751
|
-
declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
752
|
-
name: "settings";
|
|
753
|
-
schema: "meta_whatsapp";
|
|
754
|
-
columns: {
|
|
755
963
|
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
756
964
|
name: "company_id";
|
|
757
|
-
tableName: "
|
|
965
|
+
tableName: "flow_graphs";
|
|
758
966
|
dataType: "string";
|
|
759
967
|
columnType: "PgUUID";
|
|
760
968
|
data: string;
|
|
761
969
|
driverParam: string;
|
|
762
970
|
notNull: true;
|
|
763
971
|
hasDefault: false;
|
|
764
|
-
isPrimaryKey:
|
|
972
|
+
isPrimaryKey: false;
|
|
765
973
|
isAutoincrement: false;
|
|
766
974
|
hasRuntimeDefault: false;
|
|
767
975
|
enumValues: undefined;
|
|
@@ -769,14 +977,14 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
769
977
|
identity: undefined;
|
|
770
978
|
generated: undefined;
|
|
771
979
|
}, {}, {}>;
|
|
772
|
-
|
|
773
|
-
name: "
|
|
774
|
-
tableName: "
|
|
980
|
+
key: drizzle_orm_pg_core.PgColumn<{
|
|
981
|
+
name: "key";
|
|
982
|
+
tableName: "flow_graphs";
|
|
775
983
|
dataType: "string";
|
|
776
984
|
columnType: "PgVarchar";
|
|
777
985
|
data: string;
|
|
778
986
|
driverParam: string;
|
|
779
|
-
notNull:
|
|
987
|
+
notNull: true;
|
|
780
988
|
hasDefault: false;
|
|
781
989
|
isPrimaryKey: false;
|
|
782
990
|
isAutoincrement: false;
|
|
@@ -786,17 +994,17 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
786
994
|
identity: undefined;
|
|
787
995
|
generated: undefined;
|
|
788
996
|
}, {}, {
|
|
789
|
-
length:
|
|
997
|
+
length: 64;
|
|
790
998
|
}>;
|
|
791
|
-
|
|
792
|
-
name: "
|
|
793
|
-
tableName: "
|
|
999
|
+
label: drizzle_orm_pg_core.PgColumn<{
|
|
1000
|
+
name: "label";
|
|
1001
|
+
tableName: "flow_graphs";
|
|
794
1002
|
dataType: "string";
|
|
795
1003
|
columnType: "PgVarchar";
|
|
796
1004
|
data: string;
|
|
797
1005
|
driverParam: string;
|
|
798
1006
|
notNull: true;
|
|
799
|
-
hasDefault:
|
|
1007
|
+
hasDefault: false;
|
|
800
1008
|
isPrimaryKey: false;
|
|
801
1009
|
isAutoincrement: false;
|
|
802
1010
|
hasRuntimeDefault: false;
|
|
@@ -805,16 +1013,473 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
805
1013
|
identity: undefined;
|
|
806
1014
|
generated: undefined;
|
|
807
1015
|
}, {}, {
|
|
808
|
-
length:
|
|
1016
|
+
length: 120;
|
|
809
1017
|
}>;
|
|
810
|
-
|
|
811
|
-
name: "
|
|
812
|
-
tableName: "
|
|
813
|
-
dataType: "
|
|
814
|
-
columnType: "
|
|
815
|
-
data: string
|
|
816
|
-
driverParam:
|
|
817
|
-
notNull: true;
|
|
1018
|
+
startNodeId: drizzle_orm_pg_core.PgColumn<{
|
|
1019
|
+
name: "start_node_id";
|
|
1020
|
+
tableName: "flow_graphs";
|
|
1021
|
+
dataType: "string";
|
|
1022
|
+
columnType: "PgVarchar";
|
|
1023
|
+
data: string;
|
|
1024
|
+
driverParam: string;
|
|
1025
|
+
notNull: true;
|
|
1026
|
+
hasDefault: false;
|
|
1027
|
+
isPrimaryKey: false;
|
|
1028
|
+
isAutoincrement: false;
|
|
1029
|
+
hasRuntimeDefault: false;
|
|
1030
|
+
enumValues: [string, ...string[]];
|
|
1031
|
+
baseColumn: never;
|
|
1032
|
+
identity: undefined;
|
|
1033
|
+
generated: undefined;
|
|
1034
|
+
}, {}, {
|
|
1035
|
+
length: 64;
|
|
1036
|
+
}>;
|
|
1037
|
+
nodes: drizzle_orm_pg_core.PgColumn<{
|
|
1038
|
+
name: "nodes";
|
|
1039
|
+
tableName: "flow_graphs";
|
|
1040
|
+
dataType: "json";
|
|
1041
|
+
columnType: "PgJsonb";
|
|
1042
|
+
data: Record<string, unknown>;
|
|
1043
|
+
driverParam: unknown;
|
|
1044
|
+
notNull: true;
|
|
1045
|
+
hasDefault: true;
|
|
1046
|
+
isPrimaryKey: false;
|
|
1047
|
+
isAutoincrement: false;
|
|
1048
|
+
hasRuntimeDefault: false;
|
|
1049
|
+
enumValues: undefined;
|
|
1050
|
+
baseColumn: never;
|
|
1051
|
+
identity: undefined;
|
|
1052
|
+
generated: undefined;
|
|
1053
|
+
}, {}, {
|
|
1054
|
+
$type: Record<string, unknown>;
|
|
1055
|
+
}>;
|
|
1056
|
+
version: drizzle_orm_pg_core.PgColumn<{
|
|
1057
|
+
name: "version";
|
|
1058
|
+
tableName: "flow_graphs";
|
|
1059
|
+
dataType: "number";
|
|
1060
|
+
columnType: "PgInteger";
|
|
1061
|
+
data: number;
|
|
1062
|
+
driverParam: string | number;
|
|
1063
|
+
notNull: true;
|
|
1064
|
+
hasDefault: true;
|
|
1065
|
+
isPrimaryKey: false;
|
|
1066
|
+
isAutoincrement: false;
|
|
1067
|
+
hasRuntimeDefault: false;
|
|
1068
|
+
enumValues: undefined;
|
|
1069
|
+
baseColumn: never;
|
|
1070
|
+
identity: undefined;
|
|
1071
|
+
generated: undefined;
|
|
1072
|
+
}, {}, {}>;
|
|
1073
|
+
showInMenu: drizzle_orm_pg_core.PgColumn<{
|
|
1074
|
+
name: "show_in_menu";
|
|
1075
|
+
tableName: "flow_graphs";
|
|
1076
|
+
dataType: "boolean";
|
|
1077
|
+
columnType: "PgBoolean";
|
|
1078
|
+
data: boolean;
|
|
1079
|
+
driverParam: boolean;
|
|
1080
|
+
notNull: true;
|
|
1081
|
+
hasDefault: true;
|
|
1082
|
+
isPrimaryKey: false;
|
|
1083
|
+
isAutoincrement: false;
|
|
1084
|
+
hasRuntimeDefault: false;
|
|
1085
|
+
enumValues: undefined;
|
|
1086
|
+
baseColumn: never;
|
|
1087
|
+
identity: undefined;
|
|
1088
|
+
generated: undefined;
|
|
1089
|
+
}, {}, {}>;
|
|
1090
|
+
menuOptionLabel: drizzle_orm_pg_core.PgColumn<{
|
|
1091
|
+
name: "menu_option_label";
|
|
1092
|
+
tableName: "flow_graphs";
|
|
1093
|
+
dataType: "string";
|
|
1094
|
+
columnType: "PgVarchar";
|
|
1095
|
+
data: string;
|
|
1096
|
+
driverParam: string;
|
|
1097
|
+
notNull: false;
|
|
1098
|
+
hasDefault: false;
|
|
1099
|
+
isPrimaryKey: false;
|
|
1100
|
+
isAutoincrement: false;
|
|
1101
|
+
hasRuntimeDefault: false;
|
|
1102
|
+
enumValues: [string, ...string[]];
|
|
1103
|
+
baseColumn: never;
|
|
1104
|
+
identity: undefined;
|
|
1105
|
+
generated: undefined;
|
|
1106
|
+
}, {}, {
|
|
1107
|
+
length: 64;
|
|
1108
|
+
}>;
|
|
1109
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
1110
|
+
name: "created_at";
|
|
1111
|
+
tableName: "flow_graphs";
|
|
1112
|
+
dataType: "date";
|
|
1113
|
+
columnType: "PgTimestamp";
|
|
1114
|
+
data: Date;
|
|
1115
|
+
driverParam: string;
|
|
1116
|
+
notNull: true;
|
|
1117
|
+
hasDefault: true;
|
|
1118
|
+
isPrimaryKey: false;
|
|
1119
|
+
isAutoincrement: false;
|
|
1120
|
+
hasRuntimeDefault: false;
|
|
1121
|
+
enumValues: undefined;
|
|
1122
|
+
baseColumn: never;
|
|
1123
|
+
identity: undefined;
|
|
1124
|
+
generated: undefined;
|
|
1125
|
+
}, {}, {}>;
|
|
1126
|
+
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
1127
|
+
name: "updated_at";
|
|
1128
|
+
tableName: "flow_graphs";
|
|
1129
|
+
dataType: "date";
|
|
1130
|
+
columnType: "PgTimestamp";
|
|
1131
|
+
data: Date;
|
|
1132
|
+
driverParam: string;
|
|
1133
|
+
notNull: true;
|
|
1134
|
+
hasDefault: true;
|
|
1135
|
+
isPrimaryKey: false;
|
|
1136
|
+
isAutoincrement: false;
|
|
1137
|
+
hasRuntimeDefault: false;
|
|
1138
|
+
enumValues: undefined;
|
|
1139
|
+
baseColumn: never;
|
|
1140
|
+
identity: undefined;
|
|
1141
|
+
generated: undefined;
|
|
1142
|
+
}, {}, {}>;
|
|
1143
|
+
};
|
|
1144
|
+
dialect: "pg";
|
|
1145
|
+
}>;
|
|
1146
|
+
/**
|
|
1147
|
+
* Biblioteca de mídia do bot: arquivos que um nó `action` de `send_media` dispara ao chegar nele.
|
|
1148
|
+
*
|
|
1149
|
+
* Tabela separada de `documents` de propósito, e não por simetria: `documents` é o acervo DE UMA
|
|
1150
|
+
* CONVERSA (tem `sessionId` obrigatório) e tem índice único em (companyId, uploadId), porque lá o
|
|
1151
|
+
* mesmo binário chegando duas vezes é reentrega de job. Aqui é o oposto — o mesmo arquivo é
|
|
1152
|
+
* enviado para todo cliente que passar pelo nó, então aquele único bloquearia o segundo envio.
|
|
1153
|
+
*
|
|
1154
|
+
* Sem FK para `flow_graphs.id`: o vínculo natural é (companyId, flowKey), que é justamente o
|
|
1155
|
+
* índice único de lá. `nodeId` não tem como ser FK — nós vivem dentro do jsonb `nodes` — então
|
|
1156
|
+
* apagar um nó no editor deixa a linha órfã; quem lista sempre parte de um nó existente, e a
|
|
1157
|
+
* limpeza é passo de aplicação ao salvar o grafo.
|
|
1158
|
+
*/
|
|
1159
|
+
declare const flowMedia: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
1160
|
+
name: "flow_media";
|
|
1161
|
+
schema: "meta_whatsapp";
|
|
1162
|
+
columns: {
|
|
1163
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
1164
|
+
name: "id";
|
|
1165
|
+
tableName: "flow_media";
|
|
1166
|
+
dataType: "string";
|
|
1167
|
+
columnType: "PgUUID";
|
|
1168
|
+
data: string;
|
|
1169
|
+
driverParam: string;
|
|
1170
|
+
notNull: true;
|
|
1171
|
+
hasDefault: true;
|
|
1172
|
+
isPrimaryKey: true;
|
|
1173
|
+
isAutoincrement: false;
|
|
1174
|
+
hasRuntimeDefault: false;
|
|
1175
|
+
enumValues: undefined;
|
|
1176
|
+
baseColumn: never;
|
|
1177
|
+
identity: undefined;
|
|
1178
|
+
generated: undefined;
|
|
1179
|
+
}, {}, {}>;
|
|
1180
|
+
companyId: drizzle_orm_pg_core.PgColumn<{
|
|
1181
|
+
name: "company_id";
|
|
1182
|
+
tableName: "flow_media";
|
|
1183
|
+
dataType: "string";
|
|
1184
|
+
columnType: "PgUUID";
|
|
1185
|
+
data: string;
|
|
1186
|
+
driverParam: string;
|
|
1187
|
+
notNull: true;
|
|
1188
|
+
hasDefault: false;
|
|
1189
|
+
isPrimaryKey: false;
|
|
1190
|
+
isAutoincrement: false;
|
|
1191
|
+
hasRuntimeDefault: false;
|
|
1192
|
+
enumValues: undefined;
|
|
1193
|
+
baseColumn: never;
|
|
1194
|
+
identity: undefined;
|
|
1195
|
+
generated: undefined;
|
|
1196
|
+
}, {}, {}>;
|
|
1197
|
+
flowKey: drizzle_orm_pg_core.PgColumn<{
|
|
1198
|
+
name: "flow_key";
|
|
1199
|
+
tableName: "flow_media";
|
|
1200
|
+
dataType: "string";
|
|
1201
|
+
columnType: "PgVarchar";
|
|
1202
|
+
data: string;
|
|
1203
|
+
driverParam: string;
|
|
1204
|
+
notNull: true;
|
|
1205
|
+
hasDefault: false;
|
|
1206
|
+
isPrimaryKey: false;
|
|
1207
|
+
isAutoincrement: false;
|
|
1208
|
+
hasRuntimeDefault: false;
|
|
1209
|
+
enumValues: [string, ...string[]];
|
|
1210
|
+
baseColumn: never;
|
|
1211
|
+
identity: undefined;
|
|
1212
|
+
generated: undefined;
|
|
1213
|
+
}, {}, {
|
|
1214
|
+
length: 64;
|
|
1215
|
+
}>;
|
|
1216
|
+
nodeId: drizzle_orm_pg_core.PgColumn<{
|
|
1217
|
+
name: "node_id";
|
|
1218
|
+
tableName: "flow_media";
|
|
1219
|
+
dataType: "string";
|
|
1220
|
+
columnType: "PgVarchar";
|
|
1221
|
+
data: string;
|
|
1222
|
+
driverParam: string;
|
|
1223
|
+
notNull: true;
|
|
1224
|
+
hasDefault: false;
|
|
1225
|
+
isPrimaryKey: false;
|
|
1226
|
+
isAutoincrement: false;
|
|
1227
|
+
hasRuntimeDefault: false;
|
|
1228
|
+
enumValues: [string, ...string[]];
|
|
1229
|
+
baseColumn: never;
|
|
1230
|
+
identity: undefined;
|
|
1231
|
+
generated: undefined;
|
|
1232
|
+
}, {}, {
|
|
1233
|
+
length: 64;
|
|
1234
|
+
}>;
|
|
1235
|
+
uploadId: drizzle_orm_pg_core.PgColumn<{
|
|
1236
|
+
name: "upload_id";
|
|
1237
|
+
tableName: "flow_media";
|
|
1238
|
+
dataType: "string";
|
|
1239
|
+
columnType: "PgVarchar";
|
|
1240
|
+
data: string;
|
|
1241
|
+
driverParam: string;
|
|
1242
|
+
notNull: true;
|
|
1243
|
+
hasDefault: false;
|
|
1244
|
+
isPrimaryKey: false;
|
|
1245
|
+
isAutoincrement: false;
|
|
1246
|
+
hasRuntimeDefault: false;
|
|
1247
|
+
enumValues: [string, ...string[]];
|
|
1248
|
+
baseColumn: never;
|
|
1249
|
+
identity: undefined;
|
|
1250
|
+
generated: undefined;
|
|
1251
|
+
}, {}, {
|
|
1252
|
+
length: 256;
|
|
1253
|
+
}>;
|
|
1254
|
+
filename: drizzle_orm_pg_core.PgColumn<{
|
|
1255
|
+
name: "filename";
|
|
1256
|
+
tableName: "flow_media";
|
|
1257
|
+
dataType: "string";
|
|
1258
|
+
columnType: "PgVarchar";
|
|
1259
|
+
data: string;
|
|
1260
|
+
driverParam: string;
|
|
1261
|
+
notNull: true;
|
|
1262
|
+
hasDefault: false;
|
|
1263
|
+
isPrimaryKey: false;
|
|
1264
|
+
isAutoincrement: false;
|
|
1265
|
+
hasRuntimeDefault: false;
|
|
1266
|
+
enumValues: [string, ...string[]];
|
|
1267
|
+
baseColumn: never;
|
|
1268
|
+
identity: undefined;
|
|
1269
|
+
generated: undefined;
|
|
1270
|
+
}, {}, {
|
|
1271
|
+
length: 512;
|
|
1272
|
+
}>;
|
|
1273
|
+
mimeType: drizzle_orm_pg_core.PgColumn<{
|
|
1274
|
+
name: "mime_type";
|
|
1275
|
+
tableName: "flow_media";
|
|
1276
|
+
dataType: "string";
|
|
1277
|
+
columnType: "PgVarchar";
|
|
1278
|
+
data: string;
|
|
1279
|
+
driverParam: string;
|
|
1280
|
+
notNull: true;
|
|
1281
|
+
hasDefault: false;
|
|
1282
|
+
isPrimaryKey: false;
|
|
1283
|
+
isAutoincrement: false;
|
|
1284
|
+
hasRuntimeDefault: false;
|
|
1285
|
+
enumValues: [string, ...string[]];
|
|
1286
|
+
baseColumn: never;
|
|
1287
|
+
identity: undefined;
|
|
1288
|
+
generated: undefined;
|
|
1289
|
+
}, {}, {
|
|
1290
|
+
length: 128;
|
|
1291
|
+
}>;
|
|
1292
|
+
sizeBytes: drizzle_orm_pg_core.PgColumn<{
|
|
1293
|
+
name: "size_bytes";
|
|
1294
|
+
tableName: "flow_media";
|
|
1295
|
+
dataType: "number";
|
|
1296
|
+
columnType: "PgInteger";
|
|
1297
|
+
data: number;
|
|
1298
|
+
driverParam: string | number;
|
|
1299
|
+
notNull: true;
|
|
1300
|
+
hasDefault: false;
|
|
1301
|
+
isPrimaryKey: false;
|
|
1302
|
+
isAutoincrement: false;
|
|
1303
|
+
hasRuntimeDefault: false;
|
|
1304
|
+
enumValues: undefined;
|
|
1305
|
+
baseColumn: never;
|
|
1306
|
+
identity: undefined;
|
|
1307
|
+
generated: undefined;
|
|
1308
|
+
}, {}, {}>;
|
|
1309
|
+
caption: drizzle_orm_pg_core.PgColumn<{
|
|
1310
|
+
name: "caption";
|
|
1311
|
+
tableName: "flow_media";
|
|
1312
|
+
dataType: "string";
|
|
1313
|
+
columnType: "PgText";
|
|
1314
|
+
data: string;
|
|
1315
|
+
driverParam: string;
|
|
1316
|
+
notNull: false;
|
|
1317
|
+
hasDefault: false;
|
|
1318
|
+
isPrimaryKey: false;
|
|
1319
|
+
isAutoincrement: false;
|
|
1320
|
+
hasRuntimeDefault: false;
|
|
1321
|
+
enumValues: [string, ...string[]];
|
|
1322
|
+
baseColumn: never;
|
|
1323
|
+
identity: undefined;
|
|
1324
|
+
generated: undefined;
|
|
1325
|
+
}, {}, {}>;
|
|
1326
|
+
sortOrder: drizzle_orm_pg_core.PgColumn<{
|
|
1327
|
+
name: "sort_order";
|
|
1328
|
+
tableName: "flow_media";
|
|
1329
|
+
dataType: "number";
|
|
1330
|
+
columnType: "PgInteger";
|
|
1331
|
+
data: number;
|
|
1332
|
+
driverParam: string | number;
|
|
1333
|
+
notNull: true;
|
|
1334
|
+
hasDefault: true;
|
|
1335
|
+
isPrimaryKey: false;
|
|
1336
|
+
isAutoincrement: false;
|
|
1337
|
+
hasRuntimeDefault: false;
|
|
1338
|
+
enumValues: undefined;
|
|
1339
|
+
baseColumn: never;
|
|
1340
|
+
identity: undefined;
|
|
1341
|
+
generated: undefined;
|
|
1342
|
+
}, {}, {}>;
|
|
1343
|
+
active: drizzle_orm_pg_core.PgColumn<{
|
|
1344
|
+
name: "active";
|
|
1345
|
+
tableName: "flow_media";
|
|
1346
|
+
dataType: "boolean";
|
|
1347
|
+
columnType: "PgBoolean";
|
|
1348
|
+
data: boolean;
|
|
1349
|
+
driverParam: boolean;
|
|
1350
|
+
notNull: true;
|
|
1351
|
+
hasDefault: true;
|
|
1352
|
+
isPrimaryKey: false;
|
|
1353
|
+
isAutoincrement: false;
|
|
1354
|
+
hasRuntimeDefault: false;
|
|
1355
|
+
enumValues: undefined;
|
|
1356
|
+
baseColumn: never;
|
|
1357
|
+
identity: undefined;
|
|
1358
|
+
generated: undefined;
|
|
1359
|
+
}, {}, {}>;
|
|
1360
|
+
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;
|
|
818
1483
|
hasDefault: true;
|
|
819
1484
|
isPrimaryKey: false;
|
|
820
1485
|
isAutoincrement: false;
|
|
@@ -838,17 +1503,51 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
838
1503
|
isPrimaryKey: false;
|
|
839
1504
|
isAutoincrement: false;
|
|
840
1505
|
hasRuntimeDefault: false;
|
|
841
|
-
enumValues: [string, ...string[]];
|
|
1506
|
+
enumValues: [string, ...string[]];
|
|
1507
|
+
baseColumn: never;
|
|
1508
|
+
identity: undefined;
|
|
1509
|
+
generated: undefined;
|
|
1510
|
+
}, {}, {}>;
|
|
1511
|
+
farewellMessage: drizzle_orm_pg_core.PgColumn<{
|
|
1512
|
+
name: "farewell_message";
|
|
1513
|
+
tableName: "settings";
|
|
1514
|
+
dataType: "string";
|
|
1515
|
+
columnType: "PgText";
|
|
1516
|
+
data: string;
|
|
1517
|
+
driverParam: string;
|
|
1518
|
+
notNull: false;
|
|
1519
|
+
hasDefault: false;
|
|
1520
|
+
isPrimaryKey: false;
|
|
1521
|
+
isAutoincrement: false;
|
|
1522
|
+
hasRuntimeDefault: false;
|
|
1523
|
+
enumValues: [string, ...string[]];
|
|
1524
|
+
baseColumn: never;
|
|
1525
|
+
identity: undefined;
|
|
1526
|
+
generated: undefined;
|
|
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;
|
|
842
1541
|
baseColumn: never;
|
|
843
1542
|
identity: undefined;
|
|
844
1543
|
generated: undefined;
|
|
845
1544
|
}, {}, {}>;
|
|
846
|
-
|
|
847
|
-
name: "
|
|
1545
|
+
transcriptionMode: drizzle_orm_pg_core.PgColumn<{
|
|
1546
|
+
name: "transcription_mode";
|
|
848
1547
|
tableName: "settings";
|
|
849
1548
|
dataType: "string";
|
|
850
|
-
columnType: "
|
|
851
|
-
data:
|
|
1549
|
+
columnType: "PgVarchar";
|
|
1550
|
+
data: TranscriptionMode;
|
|
852
1551
|
driverParam: string;
|
|
853
1552
|
notNull: false;
|
|
854
1553
|
hasDefault: false;
|
|
@@ -859,7 +1558,10 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
859
1558
|
baseColumn: never;
|
|
860
1559
|
identity: undefined;
|
|
861
1560
|
generated: undefined;
|
|
862
|
-
}, {}, {
|
|
1561
|
+
}, {}, {
|
|
1562
|
+
length: 16;
|
|
1563
|
+
$type: TranscriptionMode;
|
|
1564
|
+
}>;
|
|
863
1565
|
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
864
1566
|
name: "created_at";
|
|
865
1567
|
tableName: "settings";
|
|
@@ -905,6 +1607,10 @@ type MessageRow = typeof messages.$inferSelect;
|
|
|
905
1607
|
type NewMessageRow = typeof messages.$inferInsert;
|
|
906
1608
|
type FlowGraphRow = typeof flowGraphs.$inferSelect;
|
|
907
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;
|
|
908
1614
|
|
|
909
1615
|
interface ListConversationsFilters {
|
|
910
1616
|
page?: number;
|
|
@@ -917,13 +1623,23 @@ declare class SessionRepository {
|
|
|
917
1623
|
constructor(db: MetaWhatsAppDatabase);
|
|
918
1624
|
getContext(companyId: string, whatsappNumber: string): Promise<SessionRow | undefined>;
|
|
919
1625
|
getOrCreate(companyId: string, whatsappNumber: string, startState: SessionState): Promise<SessionRow>;
|
|
920
|
-
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>;
|
|
921
1629
|
setFlowPosition(companyId: string, whatsappNumber: string, flowKey: string | null, currentNodeId: string | null): Promise<void>;
|
|
922
1630
|
touchInbound(companyId: string, whatsappNumber: string): Promise<void>;
|
|
923
1631
|
hoursSinceLastInbound(companyId: string, whatsappNumber: string): Promise<number | undefined>;
|
|
924
1632
|
setMode(companyId: string, whatsappNumber: string, mode: SessionMode, assignedUserId?: string | null): Promise<void>;
|
|
925
1633
|
takeover(companyId: string, whatsappNumber: string, agentUserId: string): Promise<void>;
|
|
926
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>;
|
|
927
1643
|
requestHuman(companyId: string, whatsappNumber: string): Promise<void>;
|
|
928
1644
|
markRead(companyId: string, whatsappNumber: string): Promise<void>;
|
|
929
1645
|
markAllRead(companyId: string, userId: string): Promise<number>;
|
|
@@ -960,11 +1676,108 @@ declare class SessionRepository {
|
|
|
960
1676
|
waMessageId: string | null;
|
|
961
1677
|
status: string | null;
|
|
962
1678
|
readAt: Date | null;
|
|
1679
|
+
moderationFlagged: boolean | null;
|
|
1680
|
+
moderationTerms: string[] | null;
|
|
1681
|
+
transcriptionStatus: TranscriptionStatus | null;
|
|
1682
|
+
transcriptionText: string | null;
|
|
1683
|
+
transcriptionLanguage: string | null;
|
|
1684
|
+
transcriptionEngine: string | null;
|
|
963
1685
|
createdAt: Date;
|
|
964
1686
|
}[];
|
|
965
1687
|
} | null>;
|
|
966
1688
|
}
|
|
967
1689
|
|
|
1690
|
+
interface InsertMessageParams {
|
|
1691
|
+
companyId: string;
|
|
1692
|
+
sessionId: string;
|
|
1693
|
+
whatsappNumber: string;
|
|
1694
|
+
direction: MessageDirection;
|
|
1695
|
+
sender: MessageSender;
|
|
1696
|
+
agentUserId?: string | null;
|
|
1697
|
+
type: string;
|
|
1698
|
+
content?: string | null;
|
|
1699
|
+
payload?: Record<string, unknown> | null;
|
|
1700
|
+
waMessageId?: string | null;
|
|
1701
|
+
status?: MessageStatus | null;
|
|
1702
|
+
/** `undefined` deixa a coluna nula: não avaliado, distinto de avaliado e limpo. */
|
|
1703
|
+
moderationFlagged?: boolean | null;
|
|
1704
|
+
moderationTerms?: string[] | null;
|
|
1705
|
+
}
|
|
1706
|
+
interface ListMessagesParams$1 {
|
|
1707
|
+
companyId: string;
|
|
1708
|
+
sessionId: string;
|
|
1709
|
+
limit?: number;
|
|
1710
|
+
before?: string;
|
|
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
|
+
}
|
|
1729
|
+
declare class MessageRepository {
|
|
1730
|
+
private readonly db;
|
|
1731
|
+
constructor(db: MetaWhatsAppDatabase);
|
|
1732
|
+
insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined>;
|
|
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>;
|
|
1755
|
+
listByConversation(params: ListMessagesParams$1): Promise<MessageRow[]>;
|
|
1756
|
+
}
|
|
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
|
+
|
|
968
1781
|
declare class InvalidFlowGraphError extends Error {
|
|
969
1782
|
readonly validationMessage: string;
|
|
970
1783
|
constructor(key: string, validationMessage: string);
|
|
@@ -974,7 +1787,8 @@ declare class OptimisticLockError extends Error {
|
|
|
974
1787
|
}
|
|
975
1788
|
declare class FlowGraphRepository {
|
|
976
1789
|
private readonly db;
|
|
977
|
-
|
|
1790
|
+
private readonly cache?;
|
|
1791
|
+
constructor(db: MetaWhatsAppDatabase, cache?: FlowGraphCache | undefined);
|
|
978
1792
|
get(companyId: string, key: string): Promise<FlowGraphData | undefined>;
|
|
979
1793
|
list(companyId: string): Promise<FlowGraphSummary[]>;
|
|
980
1794
|
private assertValidNodes;
|
|
@@ -995,42 +1809,124 @@ declare class SettingsRepository {
|
|
|
995
1809
|
resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]>;
|
|
996
1810
|
}
|
|
997
1811
|
|
|
998
|
-
|
|
1812
|
+
type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {
|
|
1813
|
+
startState: SessionState;
|
|
1814
|
+
};
|
|
1815
|
+
/**
|
|
1816
|
+
* O contrato mínimo de `@adatechnology/text-moderation`, declarado aqui em vez de importado.
|
|
1817
|
+
*
|
|
1818
|
+
* Assim o módulo não ganha dependência de pacote para um recurso opcional, e — mais importante —
|
|
1819
|
+
* moderação não fica amarrada a WhatsApp: o que atravessa esta fronteira é texto, e qualquer canal
|
|
1820
|
+
* que passe a escrever no transcript por este use case herda a marcação sem tocar nada aqui.
|
|
1821
|
+
*/
|
|
1822
|
+
type MessageModerator = {
|
|
1823
|
+
inspect: (text: string) => {
|
|
1824
|
+
isOffensive: boolean;
|
|
1825
|
+
matchedTerms: readonly string[];
|
|
1826
|
+
};
|
|
1827
|
+
};
|
|
1828
|
+
declare class LogMessageUseCase {
|
|
1829
|
+
private readonly sessionRepository;
|
|
1830
|
+
private readonly messageRepository;
|
|
1831
|
+
private readonly realtime?;
|
|
1832
|
+
private readonly moderator?;
|
|
1833
|
+
constructor(sessionRepository: SessionRepository, messageRepository: MessageRepository, realtime?: RealtimeNotifierInterface | undefined, moderator?: MessageModerator | undefined);
|
|
1834
|
+
execute(params: LogMessageParams): Promise<MessageRow | undefined>;
|
|
1835
|
+
private moderationOf;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
interface LinkDocumentParams {
|
|
999
1839
|
companyId: string;
|
|
1000
1840
|
sessionId: string;
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
waMessageId?: string | null;
|
|
1009
|
-
status?: MessageStatus | null;
|
|
1841
|
+
messageId?: string | null;
|
|
1842
|
+
uploadId: string;
|
|
1843
|
+
filename: string;
|
|
1844
|
+
mimeType: string;
|
|
1845
|
+
sizeBytes: number;
|
|
1846
|
+
sha256?: string | null;
|
|
1847
|
+
source: string;
|
|
1010
1848
|
}
|
|
1011
|
-
interface
|
|
1849
|
+
interface ListDocumentsParams {
|
|
1012
1850
|
companyId: string;
|
|
1013
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;
|
|
1014
1862
|
limit?: number;
|
|
1015
|
-
before?: string;
|
|
1016
1863
|
}
|
|
1017
|
-
|
|
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 {
|
|
1018
1892
|
private readonly db;
|
|
1019
1893
|
constructor(db: MetaWhatsAppDatabase);
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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>;
|
|
1034
1930
|
}
|
|
1035
1931
|
|
|
1036
1932
|
type SendTextParams = {
|
|
@@ -1067,7 +1963,8 @@ declare class SendMessageUseCase {
|
|
|
1067
1963
|
private readonly sessionRepository;
|
|
1068
1964
|
private readonly logMessage;
|
|
1069
1965
|
private readonly objectStorage?;
|
|
1070
|
-
|
|
1966
|
+
private readonly documentRepository?;
|
|
1967
|
+
constructor(channel: ChannelAdapterInterface, sessionRepository: SessionRepository, logMessage: LogMessageUseCase, objectStorage?: ObjectStorageInterface | undefined, documentRepository?: DocumentRepository | undefined);
|
|
1071
1968
|
private assertWithinWindow;
|
|
1072
1969
|
sendText(params: SendTextParams): Promise<MessageRow | undefined>;
|
|
1073
1970
|
sendMedia(params: SendMediaParams): Promise<MessageRow | undefined>;
|
|
@@ -1120,6 +2017,143 @@ declare class ListMessagesUseCase {
|
|
|
1120
2017
|
execute(params: ListMessagesParams): Promise<MessageRow[]>;
|
|
1121
2018
|
}
|
|
1122
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
|
+
|
|
1123
2157
|
type ExportConversationParams = {
|
|
1124
2158
|
companyId: string;
|
|
1125
2159
|
whatsappNumber: string;
|
|
@@ -1241,10 +2275,77 @@ declare class FlowInterpreter {
|
|
|
1241
2275
|
}): Promise<FlowRunResult>;
|
|
1242
2276
|
}
|
|
1243
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
|
+
|
|
1244
2336
|
interface NonceStoreInterface {
|
|
1245
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>;
|
|
1246
2347
|
}
|
|
1247
|
-
|
|
2348
|
+
|
|
1248
2349
|
declare function verifyWebhookChallenge(params: {
|
|
1249
2350
|
mode: string | null;
|
|
1250
2351
|
token: string | null;
|
|
@@ -1256,11 +2357,101 @@ declare function verifyWebhookSignature(params: {
|
|
|
1256
2357
|
signatureHeader: string | null | undefined;
|
|
1257
2358
|
appSecret: string;
|
|
1258
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
|
+
*/
|
|
1259
2364
|
declare function claimWebhookDelivery(params: {
|
|
1260
2365
|
nonceStore: NonceStoreInterface;
|
|
1261
2366
|
signatureHeader: string;
|
|
1262
2367
|
ttlSeconds?: number;
|
|
1263
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
|
+
}
|
|
1264
2455
|
|
|
1265
2456
|
type ReceiveWebhookParams = {
|
|
1266
2457
|
companyId: string;
|
|
@@ -1271,11 +2462,15 @@ type ReceiveWebhookResult = {
|
|
|
1271
2462
|
duplicate: boolean;
|
|
1272
2463
|
messagesProcessed: number;
|
|
1273
2464
|
statusesProcessed: number;
|
|
2465
|
+
ignoredForeignNumber: number;
|
|
2466
|
+
accountEventsProcessed: number;
|
|
2467
|
+
unhandledEvents: number;
|
|
1274
2468
|
};
|
|
1275
2469
|
declare class ReceiveWebhookUseCase {
|
|
1276
2470
|
private readonly params;
|
|
1277
2471
|
constructor(params: {
|
|
1278
2472
|
appSecret: string;
|
|
2473
|
+
phoneNumberId: string;
|
|
1279
2474
|
nonceStore: NonceStoreInterface;
|
|
1280
2475
|
sessionRepository: SessionRepository;
|
|
1281
2476
|
messageRepository: MessageRepository;
|
|
@@ -1283,12 +2478,53 @@ declare class ReceiveWebhookUseCase {
|
|
|
1283
2478
|
startState: SessionState;
|
|
1284
2479
|
hooks?: MetaWhatsAppHooks;
|
|
1285
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;
|
|
1286
2486
|
});
|
|
2487
|
+
private readonly dispatcher;
|
|
1287
2488
|
execute(input: ReceiveWebhookParams): Promise<ReceiveWebhookResult>;
|
|
2489
|
+
private handleAccountEvent;
|
|
1288
2490
|
private handleMessage;
|
|
1289
2491
|
private handleStatus;
|
|
2492
|
+
private dispatch;
|
|
1290
2493
|
}
|
|
1291
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
|
+
|
|
1292
2528
|
type IngestInboundMediaParams = {
|
|
1293
2529
|
companyId: string;
|
|
1294
2530
|
messageId: string;
|
|
@@ -1299,13 +2535,52 @@ type IngestInboundMediaParams = {
|
|
|
1299
2535
|
type IngestInboundMediaResult = {
|
|
1300
2536
|
uploadId: string;
|
|
1301
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'>;
|
|
1302
2564
|
};
|
|
1303
2565
|
declare class IngestInboundMediaUseCase {
|
|
1304
2566
|
private readonly db;
|
|
1305
2567
|
private readonly channel;
|
|
1306
2568
|
private readonly objectStorage;
|
|
1307
|
-
|
|
2569
|
+
private readonly documentRepository?;
|
|
2570
|
+
private readonly transcription?;
|
|
2571
|
+
constructor(db: MetaWhatsAppDatabase, channel: ChannelAdapterInterface, objectStorage: ObjectStorageInterface, documentRepository?: DocumentRepository | undefined, transcription?: IngestTranscriptionOptions | undefined);
|
|
1308
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;
|
|
1309
2584
|
}
|
|
1310
2585
|
declare function extractMediaDescriptor(message: MessageRow): {
|
|
1311
2586
|
sourceMediaId: string;
|
|
@@ -1313,6 +2588,105 @@ declare function extractMediaDescriptor(message: MessageRow): {
|
|
|
1313
2588
|
filename?: string;
|
|
1314
2589
|
} | undefined;
|
|
1315
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
|
+
|
|
1316
2690
|
interface MetaWhatsAppModuleConfig {
|
|
1317
2691
|
phoneNumberId: string;
|
|
1318
2692
|
accessToken: string;
|
|
@@ -1321,15 +2695,66 @@ interface MetaWhatsAppModuleConfig {
|
|
|
1321
2695
|
wabaId?: string;
|
|
1322
2696
|
apiVersion?: string;
|
|
1323
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;
|
|
1324
2704
|
}
|
|
1325
2705
|
interface MetaWhatsAppModuleFeatures {
|
|
1326
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;
|
|
1327
2745
|
}
|
|
1328
2746
|
interface MetaWhatsAppModuleProviders {
|
|
1329
2747
|
objectStorage?: ObjectStorageInterface;
|
|
2748
|
+
cache?: CacheInterface;
|
|
1330
2749
|
realtime?: RealtimeNotifierInterface;
|
|
1331
2750
|
subjectResolver?: SubjectResolverInterface;
|
|
1332
2751
|
catalog?: CatalogPort;
|
|
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;
|
|
1333
2758
|
}
|
|
1334
2759
|
interface CreateMetaWhatsAppModuleParams {
|
|
1335
2760
|
db: MetaWhatsAppDatabase;
|
|
@@ -1350,10 +2775,31 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
|
|
|
1350
2775
|
release: ReleaseConversationUseCase;
|
|
1351
2776
|
list: ListConversationsUseCase;
|
|
1352
2777
|
listMessages: ListMessagesUseCase;
|
|
2778
|
+
listDocuments: ListConversationDocumentsUseCase;
|
|
2779
|
+
listCompanyDocuments: ListCompanyDocumentsUseCase;
|
|
2780
|
+
delete: DeleteConversationUseCase;
|
|
2781
|
+
purgeExpiredDocuments: PurgeExpiredDocumentsUseCase;
|
|
1353
2782
|
export: ExportConversationUseCase;
|
|
2783
|
+
transcribeAudio: TranscribeAudioUseCase | undefined;
|
|
1354
2784
|
repository: SessionRepository;
|
|
2785
|
+
messageRepository: MessageRepository;
|
|
2786
|
+
documentRepository: DocumentRepository;
|
|
1355
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;
|
|
1356
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;
|
|
1357
2803
|
webhook: {
|
|
1358
2804
|
receive: ReceiveWebhookUseCase;
|
|
1359
2805
|
verifyChallenge: (query: {
|
|
@@ -1372,26 +2818,49 @@ declare function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams
|
|
|
1372
2818
|
delete: DeleteFlowGraphUseCase;
|
|
1373
2819
|
livePositions: GetLiveFlowPositionsUseCase;
|
|
1374
2820
|
repository: FlowGraphRepository;
|
|
2821
|
+
mediaRepository: FlowMediaRepository;
|
|
1375
2822
|
} | undefined;
|
|
1376
2823
|
catalog: CatalogPort | undefined;
|
|
1377
2824
|
};
|
|
1378
2825
|
type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>;
|
|
1379
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
|
+
};
|
|
1380
2844
|
declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
|
|
1381
2845
|
private readonly messages;
|
|
1382
|
-
|
|
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);
|
|
1383
2857
|
private translateErrors;
|
|
1384
2858
|
sendText(to: string, body: string): Promise<{
|
|
1385
2859
|
externalMessageId: string | null;
|
|
1386
2860
|
}>;
|
|
1387
|
-
sendMedia(params: {
|
|
1388
|
-
to: string;
|
|
1389
|
-
buffer: Buffer;
|
|
1390
|
-
mimeType: string;
|
|
1391
|
-
filename: string;
|
|
1392
|
-
caption?: string;
|
|
1393
|
-
}): Promise<{
|
|
2861
|
+
sendMedia(params: SendChannelMediaParams): Promise<{
|
|
1394
2862
|
externalMessageId: string | null;
|
|
2863
|
+
mediaId?: string | undefined;
|
|
1395
2864
|
}>;
|
|
1396
2865
|
sendTemplate(params: {
|
|
1397
2866
|
to: string;
|
|
@@ -1412,6 +2881,34 @@ declare class WhatsAppChannelAdapter implements ChannelAdapterInterface {
|
|
|
1412
2881
|
}): Promise<{
|
|
1413
2882
|
externalMessageId: string | null;
|
|
1414
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
|
+
*/
|
|
1415
2912
|
fetchMediaAsBase64(mediaId: string): Promise<{
|
|
1416
2913
|
data: string;
|
|
1417
2914
|
mimeType: string;
|
|
@@ -1455,4 +2952,132 @@ declare function redeemSseTicket(store: TicketStoreInterface, ticket: string): P
|
|
|
1455
2952
|
whatsappNumber: string;
|
|
1456
2953
|
} | null>;
|
|
1457
2954
|
|
|
1458
|
-
|
|
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 };
|