@adatechnology/meta-whatsapp-module 0.2.0-rc.13 → 0.2.0-rc.15
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 +279 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +363 -3
- package/dist/index.d.ts +363 -3
- package/dist/index.js +274 -8
- package/dist/index.js.map +1 -1
- package/dist/migrations/0007_flow_media.sql +18 -0
- package/dist/migrations/meta/_journal.json +7 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ var __dirname = /* @__PURE__ */ getDirname();
|
|
|
10
10
|
|
|
11
11
|
// src/createMetaWhatsAppModule.ts
|
|
12
12
|
import { WhatsAppMessageProvider } from "@adatechnology/meta-whatsapp-provider";
|
|
13
|
+
import { FLOW_ACTION_KIND } from "@adatechnology/meta-whatsapp-contracts";
|
|
13
14
|
|
|
14
15
|
// src/repositories/SessionRepository.ts
|
|
15
16
|
import { and, desc, eq, sql as sql2 } from "drizzle-orm";
|
|
@@ -197,6 +198,46 @@ var flowGraphs = metaWhatsAppSchema.table("flow_graphs", {
|
|
|
197
198
|
}, (table) => [
|
|
198
199
|
uniqueIndex("idx_flow_graphs_company_key").on(table.companyId, table.key)
|
|
199
200
|
]);
|
|
201
|
+
var flowMedia = metaWhatsAppSchema.table("flow_media", {
|
|
202
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
203
|
+
companyId: uuid("company_id").notNull(),
|
|
204
|
+
flowKey: varchar("flow_key", {
|
|
205
|
+
length: 64
|
|
206
|
+
}).notNull(),
|
|
207
|
+
nodeId: varchar("node_id", {
|
|
208
|
+
length: 64
|
|
209
|
+
}).notNull(),
|
|
210
|
+
uploadId: varchar("upload_id", {
|
|
211
|
+
length: 256
|
|
212
|
+
}).notNull(),
|
|
213
|
+
filename: varchar("filename", {
|
|
214
|
+
length: 512
|
|
215
|
+
}).notNull(),
|
|
216
|
+
mimeType: varchar("mime_type", {
|
|
217
|
+
length: 128
|
|
218
|
+
}).notNull(),
|
|
219
|
+
sizeBytes: integer("size_bytes").notNull(),
|
|
220
|
+
// Legenda da mídia no WhatsApp. Por arquivo, não por nó: um nó que manda tabela de preços e
|
|
221
|
+
// um folder precisa de textos diferentes para cada um.
|
|
222
|
+
caption: text("caption"),
|
|
223
|
+
// Ordem de envio dentro do nó — o cliente recebe as mensagens em sequência, e "tabela antes
|
|
224
|
+
// do folder" é decisão de quem edita, não do banco.
|
|
225
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
226
|
+
// Desligar sem desanexar: trocar o material da campanha é o caso comum, e apagar a linha
|
|
227
|
+
// perderia a ordem e a legenda já ajustadas.
|
|
228
|
+
active: boolean("active").notNull().default(true),
|
|
229
|
+
createdAt: timestamp("created_at", {
|
|
230
|
+
withTimezone: true
|
|
231
|
+
}).notNull().defaultNow(),
|
|
232
|
+
updatedAt: timestamp("updated_at", {
|
|
233
|
+
withTimezone: true
|
|
234
|
+
}).notNull().defaultNow()
|
|
235
|
+
}, (table) => [
|
|
236
|
+
index("idx_flow_media_node").on(table.companyId, table.flowKey, table.nodeId, table.sortOrder),
|
|
237
|
+
// O mesmo arquivo anexado duas vezes ao MESMO nó é erro de clique no editor, e o cliente
|
|
238
|
+
// receberia o documento repetido.
|
|
239
|
+
uniqueIndex("idx_flow_media_node_upload").on(table.companyId, table.flowKey, table.nodeId, table.uploadId)
|
|
240
|
+
]);
|
|
200
241
|
var settings = metaWhatsAppSchema.table("settings", {
|
|
201
242
|
companyId: uuid("company_id").primaryKey(),
|
|
202
243
|
templateName: varchar("template_name", {
|
|
@@ -521,12 +562,21 @@ var FlowGraphRepository = class {
|
|
|
521
562
|
__name(this, "FlowGraphRepository");
|
|
522
563
|
}
|
|
523
564
|
db;
|
|
524
|
-
|
|
565
|
+
cache;
|
|
566
|
+
// Cache opcional: sem ele o repositório se comporta exatamente como antes, lendo sempre do
|
|
567
|
+
// banco. É o host que decide se quer cachear e com qual provedor (ver CacheInterface).
|
|
568
|
+
constructor(db, cache) {
|
|
525
569
|
this.db = db;
|
|
570
|
+
this.cache = cache;
|
|
526
571
|
}
|
|
527
572
|
async get(companyId, key) {
|
|
573
|
+
const cached = await this.cache?.read(companyId, key);
|
|
574
|
+
if (cached) return cached;
|
|
528
575
|
const [row] = await this.db.select().from(flowGraphs).where(and3(eq3(flowGraphs.companyId, companyId), eq3(flowGraphs.key, key))).limit(1);
|
|
529
|
-
|
|
576
|
+
if (!row) return void 0;
|
|
577
|
+
const graph = toContractGraph(row);
|
|
578
|
+
await this.cache?.write(companyId, graph);
|
|
579
|
+
return graph;
|
|
530
580
|
}
|
|
531
581
|
async list(companyId) {
|
|
532
582
|
const rows = await this.db.select().from(flowGraphs).where(eq3(flowGraphs.companyId, companyId));
|
|
@@ -556,7 +606,9 @@ var FlowGraphRepository = class {
|
|
|
556
606
|
showInMenu: graph.showInMenu ?? false,
|
|
557
607
|
menuOptionLabel: graph.menuOptionLabel
|
|
558
608
|
}).returning();
|
|
559
|
-
|
|
609
|
+
const createdGraph = toContractGraph(created);
|
|
610
|
+
await this.cache?.invalidate(companyId, createdGraph.key);
|
|
611
|
+
return createdGraph;
|
|
560
612
|
}
|
|
561
613
|
// Lock otimista: a escrita só aplica se `expectedVersion` ainda bater com o que está salvo —
|
|
562
614
|
// senão, alguém mais salvou entretanto e o editor precisa recarregar (ver comentário no schema).
|
|
@@ -570,10 +622,12 @@ var FlowGraphRepository = class {
|
|
|
570
622
|
updatedAt: /* @__PURE__ */ new Date()
|
|
571
623
|
}).where(and3(eq3(flowGraphs.companyId, companyId), eq3(flowGraphs.key, graph.key), eq3(flowGraphs.version, expectedVersion))).returning();
|
|
572
624
|
if (rows.length === 0) throw new OptimisticLockError(graph.key);
|
|
625
|
+
await this.cache?.invalidate(companyId, graph.key);
|
|
573
626
|
return toContractGraph(rows[0]);
|
|
574
627
|
}
|
|
575
628
|
async delete(companyId, key) {
|
|
576
629
|
await this.db.delete(flowGraphs).where(and3(eq3(flowGraphs.companyId, companyId), eq3(flowGraphs.key, key)));
|
|
630
|
+
await this.cache?.invalidate(companyId, key);
|
|
577
631
|
}
|
|
578
632
|
// T4.2 — GetLiveFlowPositions: agrega sessões ativas por (flowKey, currentNodeId), lendo as
|
|
579
633
|
// colunas dedicadas gravadas por SessionRepository.setFlowPosition. Agrega no banco (GROUP BY,
|
|
@@ -593,6 +647,48 @@ var FlowGraphRepository = class {
|
|
|
593
647
|
}
|
|
594
648
|
};
|
|
595
649
|
|
|
650
|
+
// src/repositories/FlowGraphCache.ts
|
|
651
|
+
var KEY_PREFIX = "meta-whatsapp:flow-graph";
|
|
652
|
+
var DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS = 300;
|
|
653
|
+
var FlowGraphCache = class {
|
|
654
|
+
static {
|
|
655
|
+
__name(this, "FlowGraphCache");
|
|
656
|
+
}
|
|
657
|
+
provider;
|
|
658
|
+
ttlSeconds;
|
|
659
|
+
constructor(provider, ttlSeconds = DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS) {
|
|
660
|
+
this.provider = provider;
|
|
661
|
+
this.ttlSeconds = ttlSeconds;
|
|
662
|
+
}
|
|
663
|
+
// companyId na chave, e não só a flowKey: a chave do fluxo é escolhida por quem edita e se
|
|
664
|
+
// repete entre empresas — 'consorcio' existe em todas — então uma chave sem tenant serviria o
|
|
665
|
+
// grafo de uma empresa para a conversa de outra.
|
|
666
|
+
keyFor(companyId, flowKey) {
|
|
667
|
+
return `${KEY_PREFIX}:${companyId}:${flowKey}`;
|
|
668
|
+
}
|
|
669
|
+
async read(companyId, flowKey) {
|
|
670
|
+
try {
|
|
671
|
+
const cached = await this.provider.get(this.keyFor(companyId, flowKey));
|
|
672
|
+
if (!cached) return void 0;
|
|
673
|
+
return JSON.parse(cached);
|
|
674
|
+
} catch {
|
|
675
|
+
return void 0;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async write(companyId, graph) {
|
|
679
|
+
try {
|
|
680
|
+
await this.provider.set(this.keyFor(companyId, graph.key), JSON.stringify(graph), this.ttlSeconds);
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async invalidate(companyId, flowKey) {
|
|
685
|
+
try {
|
|
686
|
+
await this.provider.delete(this.keyFor(companyId, flowKey));
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
596
692
|
// src/repositories/SettingsRepository.ts
|
|
597
693
|
import { eq as eq4 } from "drizzle-orm";
|
|
598
694
|
function toContractSettings(row) {
|
|
@@ -1489,6 +1585,151 @@ var FlowInterpreter = class {
|
|
|
1489
1585
|
}
|
|
1490
1586
|
};
|
|
1491
1587
|
|
|
1588
|
+
// src/flows/createSendMediaAction.ts
|
|
1589
|
+
function mediaTypeFor2(mimeType) {
|
|
1590
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
1591
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
1592
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
1593
|
+
return "document";
|
|
1594
|
+
}
|
|
1595
|
+
__name(mediaTypeFor2, "mediaTypeFor");
|
|
1596
|
+
function createSendMediaAction(params) {
|
|
1597
|
+
return async ({ node, session, channel }) => {
|
|
1598
|
+
if (!session.flowKey) return;
|
|
1599
|
+
const location = {
|
|
1600
|
+
companyId: session.companyId,
|
|
1601
|
+
flowKey: session.flowKey,
|
|
1602
|
+
nodeId: node.id
|
|
1603
|
+
};
|
|
1604
|
+
const attachments = await params.flowMediaRepository.listActive(location);
|
|
1605
|
+
for (const attachment of attachments) {
|
|
1606
|
+
try {
|
|
1607
|
+
const buffer = await params.objectStorage.getObject(attachment.uploadId);
|
|
1608
|
+
const { externalMessageId } = await channel.sendMedia({
|
|
1609
|
+
to: session.whatsappNumber,
|
|
1610
|
+
buffer,
|
|
1611
|
+
mimeType: attachment.mimeType,
|
|
1612
|
+
filename: attachment.filename,
|
|
1613
|
+
caption: attachment.caption ?? void 0
|
|
1614
|
+
});
|
|
1615
|
+
await params.logMessage.execute({
|
|
1616
|
+
companyId: session.companyId,
|
|
1617
|
+
whatsappNumber: session.whatsappNumber,
|
|
1618
|
+
direction: "outbound",
|
|
1619
|
+
sender: "bot",
|
|
1620
|
+
agentUserId: null,
|
|
1621
|
+
type: mediaTypeFor2(attachment.mimeType),
|
|
1622
|
+
content: attachment.caption ?? attachment.filename,
|
|
1623
|
+
payload: {
|
|
1624
|
+
filename: attachment.filename,
|
|
1625
|
+
mimeType: attachment.mimeType,
|
|
1626
|
+
uploadId: attachment.uploadId,
|
|
1627
|
+
flowMediaId: attachment.id
|
|
1628
|
+
},
|
|
1629
|
+
waMessageId: externalMessageId,
|
|
1630
|
+
status: "sent",
|
|
1631
|
+
startState: params.startState
|
|
1632
|
+
});
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
params.onError?.(error, {
|
|
1635
|
+
flowKey: location.flowKey,
|
|
1636
|
+
nodeId: node.id,
|
|
1637
|
+
uploadId: attachment.uploadId
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
__name(createSendMediaAction, "createSendMediaAction");
|
|
1644
|
+
|
|
1645
|
+
// src/repositories/FlowMediaRepository.ts
|
|
1646
|
+
import { and as and5, asc as asc2, eq as eq6, sql as sql4 } from "drizzle-orm";
|
|
1647
|
+
var FlowMediaRepository = class {
|
|
1648
|
+
static {
|
|
1649
|
+
__name(this, "FlowMediaRepository");
|
|
1650
|
+
}
|
|
1651
|
+
db;
|
|
1652
|
+
constructor(db) {
|
|
1653
|
+
this.db = db;
|
|
1654
|
+
}
|
|
1655
|
+
locationFilter(location) {
|
|
1656
|
+
return and5(eq6(flowMedia.companyId, location.companyId), eq6(flowMedia.flowKey, location.flowKey), eq6(flowMedia.nodeId, location.nodeId));
|
|
1657
|
+
}
|
|
1658
|
+
// O que o nó realmente envia, na ordem de envio. `active` filtrado aqui e não no chamador:
|
|
1659
|
+
// é a razão de a coluna existir, e um chamador que esquecesse do filtro mandaria ao cliente
|
|
1660
|
+
// justamente o material que alguém desligou.
|
|
1661
|
+
async listActive(location) {
|
|
1662
|
+
return this.db.select().from(flowMedia).where(and5(this.locationFilter(location), eq6(flowMedia.active, true))).orderBy(asc2(flowMedia.sortOrder), asc2(flowMedia.createdAt));
|
|
1663
|
+
}
|
|
1664
|
+
// Inclui os desligados — é a visão do editor, onde desligar precisa continuar visível para
|
|
1665
|
+
// poder ser religado.
|
|
1666
|
+
async listAll(location) {
|
|
1667
|
+
return this.db.select().from(flowMedia).where(this.locationFilter(location)).orderBy(asc2(flowMedia.sortOrder), asc2(flowMedia.createdAt));
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Anexa um arquivo já existente no storage ao nó.
|
|
1671
|
+
*
|
|
1672
|
+
* `onConflictDoUpdate` em vez de deixar estourar: reanexar o mesmo arquivo é clique repetido no
|
|
1673
|
+
* editor, e o esperado ali é atualizar a legenda/ordem — não um erro de índice único na cara de
|
|
1674
|
+
* quem está montando o fluxo.
|
|
1675
|
+
*/
|
|
1676
|
+
async attach(params) {
|
|
1677
|
+
const [row] = await this.db.insert(flowMedia).values({
|
|
1678
|
+
companyId: params.companyId,
|
|
1679
|
+
flowKey: params.flowKey,
|
|
1680
|
+
nodeId: params.nodeId,
|
|
1681
|
+
uploadId: params.uploadId,
|
|
1682
|
+
filename: params.filename,
|
|
1683
|
+
mimeType: params.mimeType,
|
|
1684
|
+
sizeBytes: params.sizeBytes,
|
|
1685
|
+
caption: params.caption ?? null,
|
|
1686
|
+
sortOrder: params.sortOrder ?? 0
|
|
1687
|
+
}).onConflictDoUpdate({
|
|
1688
|
+
target: [
|
|
1689
|
+
flowMedia.companyId,
|
|
1690
|
+
flowMedia.flowKey,
|
|
1691
|
+
flowMedia.nodeId,
|
|
1692
|
+
flowMedia.uploadId
|
|
1693
|
+
],
|
|
1694
|
+
set: {
|
|
1695
|
+
caption: params.caption ?? null,
|
|
1696
|
+
sortOrder: params.sortOrder ?? 0,
|
|
1697
|
+
active: true,
|
|
1698
|
+
updatedAt: sql4`now()`
|
|
1699
|
+
}
|
|
1700
|
+
}).returning();
|
|
1701
|
+
return row;
|
|
1702
|
+
}
|
|
1703
|
+
async update(params) {
|
|
1704
|
+
const [row] = await this.db.update(flowMedia).set({
|
|
1705
|
+
...params.caption !== void 0 ? {
|
|
1706
|
+
caption: params.caption
|
|
1707
|
+
} : {},
|
|
1708
|
+
...params.sortOrder !== void 0 ? {
|
|
1709
|
+
sortOrder: params.sortOrder
|
|
1710
|
+
} : {},
|
|
1711
|
+
...params.active !== void 0 ? {
|
|
1712
|
+
active: params.active
|
|
1713
|
+
} : {},
|
|
1714
|
+
updatedAt: sql4`now()`
|
|
1715
|
+
}).where(and5(eq6(flowMedia.companyId, params.companyId), eq6(flowMedia.id, params.id))).returning();
|
|
1716
|
+
return row;
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Desanexa do nó. Não toca no storage de propósito: o mesmo `uploadId` pode estar anexado a
|
|
1720
|
+
* outro nó ou a outro fluxo, e apagar o binário aqui quebraria os demais. Quem apaga objeto é o
|
|
1721
|
+
* host, que é dono da biblioteca de arquivos.
|
|
1722
|
+
*/
|
|
1723
|
+
async detach(params) {
|
|
1724
|
+
await this.db.delete(flowMedia).where(and5(eq6(flowMedia.companyId, params.companyId), eq6(flowMedia.id, params.id)));
|
|
1725
|
+
}
|
|
1726
|
+
// Chamado ao salvar o grafo: nós apagados no editor deixam linhas que nada mais alcança.
|
|
1727
|
+
async detachRemovedNodes(params) {
|
|
1728
|
+
const condition = params.existingNodeIds.length === 0 ? void 0 : sql4`${flowMedia.nodeId} NOT IN ${params.existingNodeIds}`;
|
|
1729
|
+
await this.db.delete(flowMedia).where(and5(eq6(flowMedia.companyId, params.companyId), eq6(flowMedia.flowKey, params.flowKey), condition));
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
|
|
1492
1733
|
// src/channel/WhatsAppChannelAdapter.ts
|
|
1493
1734
|
import { WhatsAppWindowExpiredError as ProviderWindowExpiredError } from "@adatechnology/meta-graph-core";
|
|
1494
1735
|
import { WindowExpiredError as WindowExpiredError2 } from "@adatechnology/meta-whatsapp-contracts";
|
|
@@ -1585,7 +1826,7 @@ async function claimWebhookDelivery(params) {
|
|
|
1585
1826
|
__name(claimWebhookDelivery, "claimWebhookDelivery");
|
|
1586
1827
|
|
|
1587
1828
|
// src/channel/IngestInboundMedia.use-case.ts
|
|
1588
|
-
import { eq as
|
|
1829
|
+
import { eq as eq7, and as and6 } from "drizzle-orm";
|
|
1589
1830
|
var IngestInboundMediaUseCase = class {
|
|
1590
1831
|
static {
|
|
1591
1832
|
__name(this, "IngestInboundMediaUseCase");
|
|
@@ -1601,7 +1842,7 @@ var IngestInboundMediaUseCase = class {
|
|
|
1601
1842
|
this.documentRepository = documentRepository;
|
|
1602
1843
|
}
|
|
1603
1844
|
async execute(params) {
|
|
1604
|
-
const [message] = await this.db.select().from(messages).where(
|
|
1845
|
+
const [message] = await this.db.select().from(messages).where(and6(eq7(messages.companyId, params.companyId), eq7(messages.id, params.messageId))).limit(1);
|
|
1605
1846
|
if (!message) throw new Error(`Mensagem ${params.messageId} n\xE3o encontrada para ingest\xE3o de m\xEDdia`);
|
|
1606
1847
|
const payload = message.payload ?? {};
|
|
1607
1848
|
if (payload["uploadId"] && payload["sourceMediaId"] === params.sourceMediaId) {
|
|
@@ -1622,13 +1863,17 @@ var IngestInboundMediaUseCase = class {
|
|
|
1622
1863
|
uploadId,
|
|
1623
1864
|
sourceMediaId: params.sourceMediaId,
|
|
1624
1865
|
mimeType: mimeType || params.mimeType,
|
|
1866
|
+
// O tamanho já está na mão (é o buffer que acabou de ser copiado) e a bolha de documento o
|
|
1867
|
+
// exibe ao lado do tipo. Sem gravar aqui, a UI mostraria "PDF" sem o "· 180 KB", e buscá-lo
|
|
1868
|
+
// depois custaria uma consulta à tabela de documentos por mensagem renderizada.
|
|
1869
|
+
sizeBytes: buffer.length,
|
|
1625
1870
|
...params.filename ? {
|
|
1626
1871
|
filename: params.filename
|
|
1627
1872
|
} : {}
|
|
1628
1873
|
};
|
|
1629
1874
|
await this.db.update(messages).set({
|
|
1630
1875
|
payload: updatedPayload
|
|
1631
|
-
}).where(
|
|
1876
|
+
}).where(and6(eq7(messages.companyId, params.companyId), eq7(messages.id, params.messageId)));
|
|
1632
1877
|
await this.documentRepository?.link({
|
|
1633
1878
|
companyId: params.companyId,
|
|
1634
1879
|
sessionId: message.sessionId,
|
|
@@ -1828,8 +2073,11 @@ function createMetaWhatsAppModule(params) {
|
|
|
1828
2073
|
const sessionRepository = new SessionRepository(db);
|
|
1829
2074
|
const messageRepository = new MessageRepository(db);
|
|
1830
2075
|
const settingsRepository = new SettingsRepository(db);
|
|
1831
|
-
const
|
|
2076
|
+
const flowGraphCacheFeature = params.features?.flowGraphCache ?? false;
|
|
2077
|
+
const flowGraphCache = flowGraphCacheFeature && providers.cache ? new FlowGraphCache(providers.cache, typeof flowGraphCacheFeature === "object" ? flowGraphCacheFeature.ttlSeconds ?? DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS : DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS) : void 0;
|
|
2078
|
+
const flowGraphRepository = new FlowGraphRepository(db, flowGraphCache);
|
|
1832
2079
|
const documentRepository = new DocumentRepository(db);
|
|
2080
|
+
const flowMediaRepository = new FlowMediaRepository(db);
|
|
1833
2081
|
const logMessage = new LogMessageUseCase(sessionRepository, messageRepository, providers.realtime, providers.moderator);
|
|
1834
2082
|
const sendMessage = new SendMessageUseCase(channel, sessionRepository, logMessage, providers.objectStorage, documentRepository);
|
|
1835
2083
|
const receiveWebhook = new ReceiveWebhookUseCase({
|
|
@@ -1843,6 +2091,15 @@ function createMetaWhatsAppModule(params) {
|
|
|
1843
2091
|
realtime: providers.realtime
|
|
1844
2092
|
});
|
|
1845
2093
|
const flowInterpreter = flowEngineEnabled ? new FlowInterpreter() : void 0;
|
|
2094
|
+
if (flowInterpreter && providers.objectStorage?.getObject) {
|
|
2095
|
+
flowInterpreter.registerFlowAction(FLOW_ACTION_KIND.SEND_MEDIA, createSendMediaAction({
|
|
2096
|
+
flowMediaRepository,
|
|
2097
|
+
objectStorage: providers.objectStorage,
|
|
2098
|
+
logMessage,
|
|
2099
|
+
startState,
|
|
2100
|
+
onError: hooks?.onFlowMediaError
|
|
2101
|
+
}));
|
|
2102
|
+
}
|
|
1846
2103
|
const ingestInboundMedia = providers.objectStorage ? new IngestInboundMediaUseCase(db, channel, providers.objectStorage, documentRepository) : void 0;
|
|
1847
2104
|
const listDocuments = new ListConversationDocumentsUseCase(sessionRepository, documentRepository);
|
|
1848
2105
|
return {
|
|
@@ -1886,7 +2143,11 @@ function createMetaWhatsAppModule(params) {
|
|
|
1886
2143
|
save: new SaveFlowGraphUseCase(flowGraphRepository),
|
|
1887
2144
|
delete: new DeleteFlowGraphUseCase(flowGraphRepository),
|
|
1888
2145
|
livePositions: new GetLiveFlowPositionsUseCase(flowGraphRepository),
|
|
1889
|
-
repository: flowGraphRepository
|
|
2146
|
+
repository: flowGraphRepository,
|
|
2147
|
+
// Biblioteca de mídia dos nós `send_media` — o host liga nas rotas do editor
|
|
2148
|
+
// (anexar/reordenar/desligar). Existe mesmo sem storage injetado: gerenciar anexos é
|
|
2149
|
+
// consultar a tabela, e só o ENVIO precisa dos bytes.
|
|
2150
|
+
mediaRepository: flowMediaRepository
|
|
1890
2151
|
} : void 0,
|
|
1891
2152
|
catalog: providers.catalog
|
|
1892
2153
|
};
|
|
@@ -1985,12 +2246,15 @@ async function redeemSseTicket(store, ticket) {
|
|
|
1985
2246
|
__name(redeemSseTicket, "redeemSseTicket");
|
|
1986
2247
|
export {
|
|
1987
2248
|
CreateFlowGraphUseCase,
|
|
2249
|
+
DEFAULT_FLOW_GRAPH_CACHE_TTL_SECONDS,
|
|
1988
2250
|
DeleteConversationUseCase,
|
|
1989
2251
|
DeleteFlowGraphUseCase,
|
|
1990
2252
|
DocumentRepository,
|
|
1991
2253
|
ExportConversationUseCase,
|
|
2254
|
+
FlowGraphCache,
|
|
1992
2255
|
FlowGraphRepository,
|
|
1993
2256
|
FlowInterpreter,
|
|
2257
|
+
FlowMediaRepository,
|
|
1994
2258
|
GetFlowGraphUseCase,
|
|
1995
2259
|
GetLiveFlowPositionsUseCase,
|
|
1996
2260
|
IngestInboundMediaUseCase,
|
|
@@ -2017,9 +2281,11 @@ export {
|
|
|
2017
2281
|
WhatsAppChannelAdapter,
|
|
2018
2282
|
claimWebhookDelivery,
|
|
2019
2283
|
createMetaWhatsAppModule,
|
|
2284
|
+
createSendMediaAction,
|
|
2020
2285
|
documents,
|
|
2021
2286
|
extractMediaDescriptor,
|
|
2022
2287
|
flowGraphs,
|
|
2288
|
+
flowMedia,
|
|
2023
2289
|
issueSseTicket,
|
|
2024
2290
|
messages,
|
|
2025
2291
|
metaWhatsAppMigrationsFolder,
|