@open-mercato/webhooks 0.6.6-develop.6460.1.f94c74174c → 0.6.6-develop.6471.1.9ab5bdd79a
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/modules/webhooks/lib/queue.js +3 -1
- package/dist/modules/webhooks/lib/queue.js.map +2 -2
- package/dist/modules/webhooks/subscribers/failed-delivery-notification.js +3 -1
- package/dist/modules/webhooks/subscribers/failed-delivery-notification.js.map +2 -2
- package/dist/modules/webhooks/subscribers/inbound-process.js +3 -5
- package/dist/modules/webhooks/subscribers/inbound-process.js.map +2 -2
- package/dist/modules/webhooks/subscribers/outbound-dispatch.js +4 -2
- package/dist/modules/webhooks/subscribers/outbound-dispatch.js.map +2 -2
- package/dist/modules/webhooks/workers/webhook-delivery.js +4 -2
- package/dist/modules/webhooks/workers/webhook-delivery.js.map +2 -2
- package/package.json +6 -6
- package/src/modules/webhooks/lib/queue.ts +4 -1
- package/src/modules/webhooks/subscribers/__tests__/outbound-dispatch.test.ts +20 -5
- package/src/modules/webhooks/subscribers/failed-delivery-notification.ts +4 -1
- package/src/modules/webhooks/subscribers/inbound-process.ts +4 -5
- package/src/modules/webhooks/subscribers/outbound-dispatch.ts +5 -2
- package/src/modules/webhooks/workers/webhook-delivery.ts +5 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createModuleQueue } from "@open-mercato/queue";
|
|
2
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
3
|
+
const logger = createLogger("webhooks");
|
|
2
4
|
const queues = /* @__PURE__ */ new Map();
|
|
3
5
|
const LOCAL_WORKER_PROMISE_KEY = "__openMercatoWebhookLocalWorkerPromise__";
|
|
4
6
|
const WEBHOOK_DELIVERIES_QUEUE = "webhook-deliveries";
|
|
@@ -30,7 +32,7 @@ async function ensureLocalWebhookQueueWorkerStarted() {
|
|
|
30
32
|
});
|
|
31
33
|
})().catch((error) => {
|
|
32
34
|
delete globalStore[LOCAL_WORKER_PROMISE_KEY];
|
|
33
|
-
|
|
35
|
+
logger.error("Failed to start local delivery worker", { err: error });
|
|
34
36
|
throw error;
|
|
35
37
|
});
|
|
36
38
|
await globalStore[LOCAL_WORKER_PROMISE_KEY];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/lib/queue.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createModuleQueue, type Queue } from '@open-mercato/queue'\nimport type { WebhookDeliveryJob } from './delivery'\n\nconst queues = new Map<string, Queue<WebhookDeliveryJob>>()\nconst LOCAL_WORKER_PROMISE_KEY = '__openMercatoWebhookLocalWorkerPromise__'\n\nexport const WEBHOOK_DELIVERIES_QUEUE = 'webhook-deliveries'\n\nexport function getWebhookQueue(queueName: string = WEBHOOK_DELIVERIES_QUEUE): Queue<WebhookDeliveryJob> {\n const existing = queues.get(queueName)\n if (existing) return existing\n\n const concurrency = Math.max(1, Number.parseInt(process.env.WEBHOOK_QUEUE_CONCURRENCY ?? '10', 10) || 10)\n const created = createModuleQueue<WebhookDeliveryJob>(queueName, { concurrency })\n\n queues.set(queueName, created)\n return created\n}\n\nasync function ensureLocalWebhookQueueWorkerStarted(): Promise<void> {\n if (process.env.QUEUE_STRATEGY === 'async') return\n\n const globalStore = globalThis as typeof globalThis & {\n [LOCAL_WORKER_PROMISE_KEY]?: Promise<void>\n }\n\n if (globalStore[LOCAL_WORKER_PROMISE_KEY]) {\n await globalStore[LOCAL_WORKER_PROMISE_KEY]\n return\n }\n\n globalStore[LOCAL_WORKER_PROMISE_KEY] = (async () => {\n const queue = getWebhookQueue()\n\n await queue.process(async (job) => {\n const [{ createRequestContainer }, { processWebhookDeliveryJob }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('./delivery'),\n ])\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager).fork()\n await processWebhookDeliveryJob(em, job.payload)\n })\n })().catch((error) => {\n delete globalStore[LOCAL_WORKER_PROMISE_KEY]\n
|
|
5
|
-
"mappings": "AACA,SAAS,yBAAqC;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createModuleQueue, type Queue } from '@open-mercato/queue'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { WebhookDeliveryJob } from './delivery'\n\nconst logger = createLogger('webhooks')\n\nconst queues = new Map<string, Queue<WebhookDeliveryJob>>()\nconst LOCAL_WORKER_PROMISE_KEY = '__openMercatoWebhookLocalWorkerPromise__'\n\nexport const WEBHOOK_DELIVERIES_QUEUE = 'webhook-deliveries'\n\nexport function getWebhookQueue(queueName: string = WEBHOOK_DELIVERIES_QUEUE): Queue<WebhookDeliveryJob> {\n const existing = queues.get(queueName)\n if (existing) return existing\n\n const concurrency = Math.max(1, Number.parseInt(process.env.WEBHOOK_QUEUE_CONCURRENCY ?? '10', 10) || 10)\n const created = createModuleQueue<WebhookDeliveryJob>(queueName, { concurrency })\n\n queues.set(queueName, created)\n return created\n}\n\nasync function ensureLocalWebhookQueueWorkerStarted(): Promise<void> {\n if (process.env.QUEUE_STRATEGY === 'async') return\n\n const globalStore = globalThis as typeof globalThis & {\n [LOCAL_WORKER_PROMISE_KEY]?: Promise<void>\n }\n\n if (globalStore[LOCAL_WORKER_PROMISE_KEY]) {\n await globalStore[LOCAL_WORKER_PROMISE_KEY]\n return\n }\n\n globalStore[LOCAL_WORKER_PROMISE_KEY] = (async () => {\n const queue = getWebhookQueue()\n\n await queue.process(async (job) => {\n const [{ createRequestContainer }, { processWebhookDeliveryJob }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('./delivery'),\n ])\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager).fork()\n await processWebhookDeliveryJob(em, job.payload)\n })\n })().catch((error) => {\n delete globalStore[LOCAL_WORKER_PROMISE_KEY]\n logger.error('Failed to start local delivery worker', { err: error })\n throw error\n })\n\n await globalStore[LOCAL_WORKER_PROMISE_KEY]\n}\n\nexport async function enqueueWebhookDelivery(job: WebhookDeliveryJob, delayMs?: number): Promise<string> {\n const queue = getWebhookQueue()\n const jobId = await queue.enqueue(job, delayMs && delayMs > 0 ? { delayMs } : undefined)\n await ensureLocalWebhookQueueWorkerStarted()\n return jobId\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,yBAAqC;AAC9C,SAAS,oBAAoB;AAG7B,MAAM,SAAS,aAAa,UAAU;AAEtC,MAAM,SAAS,oBAAI,IAAuC;AAC1D,MAAM,2BAA2B;AAE1B,MAAM,2BAA2B;AAEjC,SAAS,gBAAgB,YAAoB,0BAAqD;AACvG,QAAM,WAAW,OAAO,IAAI,SAAS;AACrC,MAAI,SAAU,QAAO;AAErB,QAAM,cAAc,KAAK,IAAI,GAAG,OAAO,SAAS,QAAQ,IAAI,6BAA6B,MAAM,EAAE,KAAK,EAAE;AACxG,QAAM,UAAU,kBAAsC,WAAW,EAAE,YAAY,CAAC;AAEhF,SAAO,IAAI,WAAW,OAAO;AAC7B,SAAO;AACT;AAEA,eAAe,uCAAsD;AACnE,MAAI,QAAQ,IAAI,mBAAmB,QAAS;AAE5C,QAAM,cAAc;AAIpB,MAAI,YAAY,wBAAwB,GAAG;AACzC,UAAM,YAAY,wBAAwB;AAC1C;AAAA,EACF;AAEA,cAAY,wBAAwB,KAAK,YAAY;AACnD,UAAM,QAAQ,gBAAgB;AAE9B,UAAM,MAAM,QAAQ,OAAO,QAAQ;AACjC,YAAM,CAAC,EAAE,uBAAuB,GAAG,EAAE,0BAA0B,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QACpF,OAAO,uCAAuC;AAAA,QAC9C,OAAO,YAAY;AAAA,MACrB,CAAC;AAED,YAAM,YAAY,MAAM,uBAAuB;AAC/C,YAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC3D,YAAM,0BAA0B,IAAI,IAAI,OAAO;AAAA,IACjD,CAAC;AAAA,EACH,GAAG,EAAE,MAAM,CAAC,UAAU;AACpB,WAAO,YAAY,wBAAwB;AAC3C,WAAO,MAAM,yCAAyC,EAAE,KAAK,MAAM,CAAC;AACpE,UAAM;AAAA,EACR,CAAC;AAED,QAAM,YAAY,wBAAwB;AAC5C;AAEA,eAAsB,uBAAuB,KAAyB,SAAmC;AACvG,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,QAAQ,MAAM,MAAM,QAAQ,KAAK,WAAW,UAAU,IAAI,EAAE,QAAQ,IAAI,MAAS;AACvF,QAAM,qCAAqC;AAC3C,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
2
|
import { resolveNotificationService } from "@open-mercato/core/modules/notifications/lib/notificationService";
|
|
3
3
|
import { buildFeatureNotificationFromType } from "@open-mercato/core/modules/notifications/lib/notificationBuilder";
|
|
4
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
4
5
|
import { WebhookEntity } from "../data/entities.js";
|
|
5
6
|
import { resolveWebhookIntegrationSettings } from "../lib/integration-settings.js";
|
|
6
7
|
import { notificationTypes } from "../notifications.js";
|
|
8
|
+
const logger = createLogger("webhooks").child({ component: "failed-delivery-notification" });
|
|
7
9
|
const metadata = {
|
|
8
10
|
event: "webhooks.delivery.exhausted",
|
|
9
11
|
persistent: true,
|
|
@@ -55,7 +57,7 @@ async function handle(payload, ctx) {
|
|
|
55
57
|
organizationId
|
|
56
58
|
});
|
|
57
59
|
} catch (err) {
|
|
58
|
-
|
|
60
|
+
logger.error("Failed to create notification", { err });
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/subscribers/failed-delivery-notification.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'\nimport { buildFeatureNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'\nimport { WebhookEntity } from '../data/entities'\nimport { resolveWebhookIntegrationSettings } from '../lib/integration-settings'\nimport { notificationTypes } from '../notifications'\n\nexport const metadata = {\n event: 'webhooks.delivery.exhausted',\n persistent: true,\n id: 'webhooks:failed-delivery-notification',\n}\n\ntype WebhookDeliveryExhaustedPayload = {\n deliveryId: string\n webhookId: string\n eventType: string\n errorMessage?: string | null\n tenantId: string\n organizationId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(payload: WebhookDeliveryExhaustedPayload, ctx: ResolverContext) {\n try {\n const organizationId = payload.organizationId ?? null\n const settings = await resolveWebhookIntegrationSettings(ctx, {\n tenantId: payload.tenantId,\n organizationId: organizationId ?? '',\n })\n if (!settings.notifyOnFailedDelivery) return\n\n const notificationService = resolveNotificationService(ctx)\n const typeDef = notificationTypes.find((type) => type.type === 'webhooks.delivery.failed')\n if (!typeDef) return\n\n const em = ctx.resolve<EntityManager>('em').fork()\n const webhook = await findOneWithDecryption(\n em,\n WebhookEntity,\n {\n id: payload.webhookId,\n tenantId: payload.tenantId,\n organizationId: organizationId ?? undefined,\n deletedAt: null,\n },\n {},\n { tenantId: payload.tenantId, organizationId: organizationId ?? '' },\n )\n\n const webhookName = webhook?.name?.trim() || payload.webhookId\n const errorMessage = payload.errorMessage?.trim() || 'Delivery retries exhausted'\n\n const notificationInput = buildFeatureNotificationFromType(typeDef, {\n requiredFeature: 'webhooks.manage',\n titleVariables: {\n webhookName,\n },\n bodyVariables: {\n webhookName,\n eventType: payload.eventType,\n errorMessage,\n },\n sourceEntityType: 'webhooks:webhook',\n sourceEntityId: payload.webhookId,\n linkHref: `/backend/webhooks/${payload.webhookId}`,\n groupKey: `delivery-failed:${payload.deliveryId}`,\n })\n\n await notificationService.createForFeature(notificationInput, {\n tenantId: payload.tenantId,\n organizationId,\n })\n } catch (err) {\n
|
|
5
|
-
"mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,kCAAkC;AAC3C,SAAS,wCAAwC;AACjD,SAAS,qBAAqB;AAC9B,SAAS,yCAAyC;AAClD,SAAS,yBAAyB;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'\nimport { buildFeatureNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { WebhookEntity } from '../data/entities'\nimport { resolveWebhookIntegrationSettings } from '../lib/integration-settings'\nimport { notificationTypes } from '../notifications'\n\nconst logger = createLogger('webhooks').child({ component: 'failed-delivery-notification' })\n\nexport const metadata = {\n event: 'webhooks.delivery.exhausted',\n persistent: true,\n id: 'webhooks:failed-delivery-notification',\n}\n\ntype WebhookDeliveryExhaustedPayload = {\n deliveryId: string\n webhookId: string\n eventType: string\n errorMessage?: string | null\n tenantId: string\n organizationId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(payload: WebhookDeliveryExhaustedPayload, ctx: ResolverContext) {\n try {\n const organizationId = payload.organizationId ?? null\n const settings = await resolveWebhookIntegrationSettings(ctx, {\n tenantId: payload.tenantId,\n organizationId: organizationId ?? '',\n })\n if (!settings.notifyOnFailedDelivery) return\n\n const notificationService = resolveNotificationService(ctx)\n const typeDef = notificationTypes.find((type) => type.type === 'webhooks.delivery.failed')\n if (!typeDef) return\n\n const em = ctx.resolve<EntityManager>('em').fork()\n const webhook = await findOneWithDecryption(\n em,\n WebhookEntity,\n {\n id: payload.webhookId,\n tenantId: payload.tenantId,\n organizationId: organizationId ?? undefined,\n deletedAt: null,\n },\n {},\n { tenantId: payload.tenantId, organizationId: organizationId ?? '' },\n )\n\n const webhookName = webhook?.name?.trim() || payload.webhookId\n const errorMessage = payload.errorMessage?.trim() || 'Delivery retries exhausted'\n\n const notificationInput = buildFeatureNotificationFromType(typeDef, {\n requiredFeature: 'webhooks.manage',\n titleVariables: {\n webhookName,\n },\n bodyVariables: {\n webhookName,\n eventType: payload.eventType,\n errorMessage,\n },\n sourceEntityType: 'webhooks:webhook',\n sourceEntityId: payload.webhookId,\n linkHref: `/backend/webhooks/${payload.webhookId}`,\n groupKey: `delivery-failed:${payload.deliveryId}`,\n })\n\n await notificationService.createForFeature(notificationInput, {\n tenantId: payload.tenantId,\n organizationId,\n })\n } catch (err) {\n logger.error('Failed to create notification', { err })\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AACtC,SAAS,kCAAkC;AAC3C,SAAS,wCAAwC;AACjD,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,yCAAyC;AAClD,SAAS,yBAAyB;AAElC,MAAM,SAAS,aAAa,UAAU,EAAE,MAAM,EAAE,WAAW,+BAA+B,CAAC;AAEpF,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAeA,eAAO,OAA8B,SAA0C,KAAsB;AACnG,MAAI;AACF,UAAM,iBAAiB,QAAQ,kBAAkB;AACjD,UAAM,WAAW,MAAM,kCAAkC,KAAK;AAAA,MAC5D,UAAU,QAAQ;AAAA,MAClB,gBAAgB,kBAAkB;AAAA,IACpC,CAAC;AACD,QAAI,CAAC,SAAS,uBAAwB;AAEtC,UAAM,sBAAsB,2BAA2B,GAAG;AAC1D,UAAM,UAAU,kBAAkB,KAAK,CAAC,SAAS,KAAK,SAAS,0BAA0B;AACzF,QAAI,CAAC,QAAS;AAEd,UAAM,KAAK,IAAI,QAAuB,IAAI,EAAE,KAAK;AACjD,UAAM,UAAU,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,MACA;AAAA,QACE,IAAI,QAAQ;AAAA,QACZ,UAAU,QAAQ;AAAA,QAClB,gBAAgB,kBAAkB;AAAA,QAClC,WAAW;AAAA,MACb;AAAA,MACA,CAAC;AAAA,MACD,EAAE,UAAU,QAAQ,UAAU,gBAAgB,kBAAkB,GAAG;AAAA,IACrE;AAEA,UAAM,cAAc,SAAS,MAAM,KAAK,KAAK,QAAQ;AACrD,UAAM,eAAe,QAAQ,cAAc,KAAK,KAAK;AAErD,UAAM,oBAAoB,iCAAiC,SAAS;AAAA,MAClE,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,QACd;AAAA,MACF;AAAA,MACA,eAAe;AAAA,QACb;AAAA,QACA,WAAW,QAAQ;AAAA,QACnB;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,UAAU,qBAAqB,QAAQ,SAAS;AAAA,MAChD,UAAU,mBAAmB,QAAQ,UAAU;AAAA,IACjD,CAAC;AAED,UAAM,oBAAoB,iBAAiB,mBAAmB;AAAA,MAC5D,UAAU,QAAQ;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,MAAM,iCAAiC,EAAE,IAAI,CAAC;AAAA,EACvD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { getWebhookEndpointAdapter } from "../lib/adapter-registry.js";
|
|
3
|
+
const logger = createLogger("webhooks").child({ component: "inbound" });
|
|
2
4
|
const metadata = {
|
|
3
5
|
event: "webhooks.inbound.received",
|
|
4
6
|
persistent: true,
|
|
@@ -24,11 +26,7 @@ async function handler(payload) {
|
|
|
24
26
|
organizationId: typeof payload.organizationId === "string" ? payload.organizationId : void 0
|
|
25
27
|
});
|
|
26
28
|
} catch (err) {
|
|
27
|
-
|
|
28
|
-
providerKey,
|
|
29
|
-
eventType,
|
|
30
|
-
error: err instanceof Error ? err.message : String(err)
|
|
31
|
-
});
|
|
29
|
+
logger.error("Processing failed", { providerKey, eventType, err });
|
|
32
30
|
throw err;
|
|
33
31
|
}
|
|
34
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/subscribers/inbound-process.ts"],
|
|
4
|
-
"sourcesContent": ["import { getWebhookEndpointAdapter } from '../lib/adapter-registry'\n\nexport const metadata = {\n event: 'webhooks.inbound.received',\n persistent: true,\n id: 'webhooks:inbound-process',\n}\n\nexport default async function handler(payload: Record<string, unknown>) {\n const providerKey = typeof payload.providerKey === 'string' ? payload.providerKey : null\n const eventType = typeof payload.eventType === 'string' ? payload.eventType : null\n const verifiedPayload = isRecord(payload.payload) ? payload.payload : null\n\n if (!providerKey || !eventType || !verifiedPayload) {\n return\n }\n\n const adapter = getWebhookEndpointAdapter(providerKey)\n if (!adapter) {\n throw new Error(`Webhook endpoint adapter \"${providerKey}\" is not registered`)\n }\n\n try {\n await adapter.processInbound({\n providerKey,\n eventType,\n payload: verifiedPayload,\n tenantId: typeof payload.tenantId === 'string' ? payload.tenantId : undefined,\n organizationId: typeof payload.organizationId === 'string' ? payload.organizationId : undefined,\n })\n } catch (err) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,iCAAiC;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getWebhookEndpointAdapter } from '../lib/adapter-registry'\n\nconst logger = createLogger('webhooks').child({ component: 'inbound' })\n\nexport const metadata = {\n event: 'webhooks.inbound.received',\n persistent: true,\n id: 'webhooks:inbound-process',\n}\n\nexport default async function handler(payload: Record<string, unknown>) {\n const providerKey = typeof payload.providerKey === 'string' ? payload.providerKey : null\n const eventType = typeof payload.eventType === 'string' ? payload.eventType : null\n const verifiedPayload = isRecord(payload.payload) ? payload.payload : null\n\n if (!providerKey || !eventType || !verifiedPayload) {\n return\n }\n\n const adapter = getWebhookEndpointAdapter(providerKey)\n if (!adapter) {\n throw new Error(`Webhook endpoint adapter \"${providerKey}\" is not registered`)\n }\n\n try {\n await adapter.processInbound({\n providerKey,\n eventType,\n payload: verifiedPayload,\n tenantId: typeof payload.tenantId === 'string' ? payload.tenantId : undefined,\n organizationId: typeof payload.organizationId === 'string' ? payload.organizationId : undefined,\n })\n } catch (err) {\n logger.error('Processing failed', { providerKey, eventType, err })\n throw err\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;AAE1C,MAAM,SAAS,aAAa,UAAU,EAAE,MAAM,EAAE,WAAW,UAAU,CAAC;AAE/D,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAEA,eAAO,QAA+B,SAAkC;AACtE,QAAM,cAAc,OAAO,QAAQ,gBAAgB,WAAW,QAAQ,cAAc;AACpF,QAAM,YAAY,OAAO,QAAQ,cAAc,WAAW,QAAQ,YAAY;AAC9E,QAAM,kBAAkB,SAAS,QAAQ,OAAO,IAAI,QAAQ,UAAU;AAEtE,MAAI,CAAC,eAAe,CAAC,aAAa,CAAC,iBAAiB;AAClD;AAAA,EACF;AAEA,QAAM,UAAU,0BAA0B,WAAW;AACrD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,6BAA6B,WAAW,qBAAqB;AAAA,EAC/E;AAEA,MAAI;AACF,UAAM,QAAQ,eAAe;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,UAAU,OAAO,QAAQ,aAAa,WAAW,QAAQ,WAAW;AAAA,MACpE,gBAAgB,OAAO,QAAQ,mBAAmB,WAAW,QAAQ,iBAAiB;AAAA,IACxF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,MAAM,qBAAqB,EAAE,aAAa,WAAW,IAAI,CAAC;AACjE,UAAM;AAAA,EACR;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,9 +2,11 @@ import { WebhookDeliveryEntity, WebhookEntity } from "../data/entities.js";
|
|
|
2
2
|
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
3
|
import { matchAnyWebhookEventPattern } from "@open-mercato/shared/lib/events/patterns";
|
|
4
4
|
import { getDeclaredEvents } from "@open-mercato/shared/modules/events";
|
|
5
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
5
6
|
import { createWebhookDelivery } from "../lib/delivery.js";
|
|
6
7
|
import { enqueueWebhookDelivery } from "../lib/queue.js";
|
|
7
8
|
import { isWebhookIntegrationEnabled } from "../lib/integration-state.js";
|
|
9
|
+
const logger = createLogger("webhooks");
|
|
8
10
|
const metadata = {
|
|
9
11
|
event: "*",
|
|
10
12
|
persistent: true,
|
|
@@ -99,12 +101,12 @@ async function handler(payload, ctx) {
|
|
|
99
101
|
await webhookEm.flush();
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
|
-
|
|
104
|
+
logger.error("Failed to enqueue outbound delivery", {
|
|
103
105
|
webhookId: webhook.id,
|
|
104
106
|
eventId,
|
|
105
107
|
tenantId,
|
|
106
108
|
organizationId: organizationId ?? webhook.organizationId,
|
|
107
|
-
|
|
109
|
+
err: error
|
|
108
110
|
});
|
|
109
111
|
}
|
|
110
112
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/subscribers/outbound-dispatch.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SubscriberContext } from '@open-mercato/events/types'\nimport { WebhookDeliveryEntity, WebhookEntity } from '../data/entities'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { matchAnyWebhookEventPattern } from '@open-mercato/shared/lib/events/patterns'\nimport { getDeclaredEvents } from '@open-mercato/shared/modules/events'\nimport { createWebhookDelivery } from '../lib/delivery'\nimport { enqueueWebhookDelivery } from '../lib/queue'\nimport { isWebhookIntegrationEnabled } from '../lib/integration-state'\n\nexport const metadata = {\n event: '*',\n persistent: true,\n id: 'webhooks:outbound-dispatch',\n}\n\nfunction shouldSkipOutboundDispatch(eventId: string): boolean {\n if (eventId.startsWith('webhooks.') || eventId.startsWith('application.')) return true\n\n const declaredEvent = getDeclaredEvents().find((event) => event.id === eventId)\n return declaredEvent?.excludeFromTriggers === true\n}\n\nfunction forkOutboundEntityManager(em: EntityManager): EntityManager {\n const fork = (em as unknown as { fork?: (options?: Record<string, unknown>) => EntityManager }).fork\n if (typeof fork !== 'function') return em\n return fork.call(em, { clear: true, useContext: false })\n}\n\nfunction integrationScopeKey(tenantId: string, organizationId: string): string {\n return `${tenantId}:${organizationId}`\n}\n\nexport default async function handler(\n payload: Record<string, unknown>,\n ctx: (SubscriberContext & { eventId?: string }) | { container?: { resolve: <T = unknown>(name: string) => T }; eventId?: string; eventName?: string; resolve?: <T = unknown>(name: string) => T },\n) {\n const eventId = ctx.eventId ?? ctx.eventName ?? (payload.eventId as string) ?? (payload.type as string)\n if (!eventId) return\n if (shouldSkipOutboundDispatch(eventId)) return\n\n const tenantId = payload.tenantId as string | undefined\n const organizationId = payload.organizationId as string | undefined\n if (!tenantId) return\n\n if (eventId.startsWith('webhooks.')) return\n if (eventId.startsWith('query_index.')) return\n\n\n const resolve = ('resolve' in ctx && typeof ctx.resolve === 'function')\n ? ctx.resolve\n : ('container' in ctx && ctx.container && typeof ctx.container.resolve === 'function')\n ? ctx.container.resolve.bind(ctx.container)\n : null\n\n if (!resolve) return\n\n const em = (resolve('em') as EntityManager).fork()\n\n const webhooks = await findWithDecryption(\n em,\n WebhookEntity,\n {\n isActive: true,\n deletedAt: null,\n tenantId,\n ...(organizationId ? { organizationId } : {}),\n },\n {},\n { tenantId, organizationId: organizationId ?? null },\n )\n\n if (!webhooks.length) return\n\n const matchingWebhooks = webhooks.filter((webhook) =>\n matchAnyWebhookEventPattern(eventId, webhook.subscribedEvents),\n )\n\n if (!matchingWebhooks.length) return\n\n const integrationEnabledByScope = new Map<string, Promise<boolean>>()\n\n const resolveIntegrationEnabled = (webhook: WebhookEntity): Promise<boolean> => {\n const key = integrationScopeKey(webhook.tenantId, webhook.organizationId)\n let pending = integrationEnabledByScope.get(key)\n if (!pending) {\n pending = isWebhookIntegrationEnabled(em, {\n tenantId: webhook.tenantId,\n organizationId: webhook.organizationId,\n })\n integrationEnabledByScope.set(key, pending)\n }\n return pending\n }\n\n await Promise.all(\n matchingWebhooks.map(async (webhook) => {\n if (!(await resolveIntegrationEnabled(webhook))) return\n\n const webhookEm = forkOutboundEntityManager(em)\n let createdDeliveryId: string | null = null\n try {\n const delivery = await createWebhookDelivery({\n em: webhookEm,\n webhook,\n eventId,\n payload,\n })\n createdDeliveryId = delivery.id\n\n await enqueueWebhookDelivery({\n deliveryId: delivery.id,\n tenantId: delivery.tenantId,\n organizationId: delivery.organizationId,\n })\n } catch (error) {\n if (createdDeliveryId) {\n const failedDelivery = await findOneWithDecryption(\n webhookEm,\n WebhookDeliveryEntity,\n { id: createdDeliveryId, tenantId: webhook.tenantId, organizationId: webhook.organizationId },\n undefined,\n { tenantId: webhook.tenantId, organizationId: webhook.organizationId },\n )\n if (failedDelivery) {\n failedDelivery.status = 'failed'\n failedDelivery.errorMessage = error instanceof Error ? `Queue enqueue failed: ${error.message}` : 'Queue enqueue failed'\n failedDelivery.nextRetryAt = null\n await webhookEm.flush()\n }\n }\n
|
|
5
|
-
"mappings": "AAEA,SAAS,uBAAuB,qBAAqB;AACrD,SAAS,oBAAoB,6BAA6B;AAC1D,SAAS,mCAAmC;AAC5C,SAAS,yBAAyB;AAClC,SAAS,6BAA6B;AACtC,SAAS,8BAA8B;AACvC,SAAS,mCAAmC;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SubscriberContext } from '@open-mercato/events/types'\nimport { WebhookDeliveryEntity, WebhookEntity } from '../data/entities'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { matchAnyWebhookEventPattern } from '@open-mercato/shared/lib/events/patterns'\nimport { getDeclaredEvents } from '@open-mercato/shared/modules/events'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { createWebhookDelivery } from '../lib/delivery'\nimport { enqueueWebhookDelivery } from '../lib/queue'\nimport { isWebhookIntegrationEnabled } from '../lib/integration-state'\n\nconst logger = createLogger('webhooks')\n\nexport const metadata = {\n event: '*',\n persistent: true,\n id: 'webhooks:outbound-dispatch',\n}\n\nfunction shouldSkipOutboundDispatch(eventId: string): boolean {\n if (eventId.startsWith('webhooks.') || eventId.startsWith('application.')) return true\n\n const declaredEvent = getDeclaredEvents().find((event) => event.id === eventId)\n return declaredEvent?.excludeFromTriggers === true\n}\n\nfunction forkOutboundEntityManager(em: EntityManager): EntityManager {\n const fork = (em as unknown as { fork?: (options?: Record<string, unknown>) => EntityManager }).fork\n if (typeof fork !== 'function') return em\n return fork.call(em, { clear: true, useContext: false })\n}\n\nfunction integrationScopeKey(tenantId: string, organizationId: string): string {\n return `${tenantId}:${organizationId}`\n}\n\nexport default async function handler(\n payload: Record<string, unknown>,\n ctx: (SubscriberContext & { eventId?: string }) | { container?: { resolve: <T = unknown>(name: string) => T }; eventId?: string; eventName?: string; resolve?: <T = unknown>(name: string) => T },\n) {\n const eventId = ctx.eventId ?? ctx.eventName ?? (payload.eventId as string) ?? (payload.type as string)\n if (!eventId) return\n if (shouldSkipOutboundDispatch(eventId)) return\n\n const tenantId = payload.tenantId as string | undefined\n const organizationId = payload.organizationId as string | undefined\n if (!tenantId) return\n\n if (eventId.startsWith('webhooks.')) return\n if (eventId.startsWith('query_index.')) return\n\n\n const resolve = ('resolve' in ctx && typeof ctx.resolve === 'function')\n ? ctx.resolve\n : ('container' in ctx && ctx.container && typeof ctx.container.resolve === 'function')\n ? ctx.container.resolve.bind(ctx.container)\n : null\n\n if (!resolve) return\n\n const em = (resolve('em') as EntityManager).fork()\n\n const webhooks = await findWithDecryption(\n em,\n WebhookEntity,\n {\n isActive: true,\n deletedAt: null,\n tenantId,\n ...(organizationId ? { organizationId } : {}),\n },\n {},\n { tenantId, organizationId: organizationId ?? null },\n )\n\n if (!webhooks.length) return\n\n const matchingWebhooks = webhooks.filter((webhook) =>\n matchAnyWebhookEventPattern(eventId, webhook.subscribedEvents),\n )\n\n if (!matchingWebhooks.length) return\n\n const integrationEnabledByScope = new Map<string, Promise<boolean>>()\n\n const resolveIntegrationEnabled = (webhook: WebhookEntity): Promise<boolean> => {\n const key = integrationScopeKey(webhook.tenantId, webhook.organizationId)\n let pending = integrationEnabledByScope.get(key)\n if (!pending) {\n pending = isWebhookIntegrationEnabled(em, {\n tenantId: webhook.tenantId,\n organizationId: webhook.organizationId,\n })\n integrationEnabledByScope.set(key, pending)\n }\n return pending\n }\n\n await Promise.all(\n matchingWebhooks.map(async (webhook) => {\n if (!(await resolveIntegrationEnabled(webhook))) return\n\n const webhookEm = forkOutboundEntityManager(em)\n let createdDeliveryId: string | null = null\n try {\n const delivery = await createWebhookDelivery({\n em: webhookEm,\n webhook,\n eventId,\n payload,\n })\n createdDeliveryId = delivery.id\n\n await enqueueWebhookDelivery({\n deliveryId: delivery.id,\n tenantId: delivery.tenantId,\n organizationId: delivery.organizationId,\n })\n } catch (error) {\n if (createdDeliveryId) {\n const failedDelivery = await findOneWithDecryption(\n webhookEm,\n WebhookDeliveryEntity,\n { id: createdDeliveryId, tenantId: webhook.tenantId, organizationId: webhook.organizationId },\n undefined,\n { tenantId: webhook.tenantId, organizationId: webhook.organizationId },\n )\n if (failedDelivery) {\n failedDelivery.status = 'failed'\n failedDelivery.errorMessage = error instanceof Error ? `Queue enqueue failed: ${error.message}` : 'Queue enqueue failed'\n failedDelivery.nextRetryAt = null\n await webhookEm.flush()\n }\n }\n logger.error('Failed to enqueue outbound delivery', {\n webhookId: webhook.id,\n eventId,\n tenantId,\n organizationId: organizationId ?? webhook.organizationId,\n err: error,\n })\n }\n }),\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { processWebhookDeliveryJob } from "../lib/delivery.js";
|
|
3
|
+
const logger = createLogger("webhooks").child({ component: "delivery" });
|
|
2
4
|
const metadata = {
|
|
3
5
|
queue: "webhook-deliveries",
|
|
4
6
|
id: "webhooks:delivery-worker",
|
|
@@ -9,10 +11,10 @@ async function handler(job, ctx) {
|
|
|
9
11
|
try {
|
|
10
12
|
await processWebhookDeliveryJob(em, job.data);
|
|
11
13
|
} catch (error) {
|
|
12
|
-
|
|
14
|
+
logger.error("Job processing failed", {
|
|
13
15
|
deliveryId: job.data.deliveryId,
|
|
14
16
|
tenantId: job.data.tenantId,
|
|
15
|
-
|
|
17
|
+
err: error
|
|
16
18
|
});
|
|
17
19
|
throw error;
|
|
18
20
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/webhooks/workers/webhook-delivery.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { processWebhookDeliveryJob, type WebhookDeliveryJob } from '../lib/delivery'\n\nexport const metadata = {\n queue: 'webhook-deliveries',\n id: 'webhooks:delivery-worker',\n concurrency: 10,\n}\n\nexport default async function handler(\n job: { data: WebhookDeliveryJob },\n ctx: { resolve: <T = unknown>(name: string) => T },\n) {\n const em = (ctx.resolve('em') as EntityManager).fork()\n try {\n await processWebhookDeliveryJob(em, job.data)\n } catch (error) {\n
|
|
5
|
-
"mappings": "AACA,SAAS,iCAA0D;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { processWebhookDeliveryJob, type WebhookDeliveryJob } from '../lib/delivery'\n\nconst logger = createLogger('webhooks').child({ component: 'delivery' })\n\nexport const metadata = {\n queue: 'webhook-deliveries',\n id: 'webhooks:delivery-worker',\n concurrency: 10,\n}\n\nexport default async function handler(\n job: { data: WebhookDeliveryJob },\n ctx: { resolve: <T = unknown>(name: string) => T },\n) {\n const em = (ctx.resolve('em') as EntityManager).fork()\n try {\n await processWebhookDeliveryJob(em, job.data)\n } catch (error) {\n logger.error('Job processing failed', {\n deliveryId: job.data.deliveryId,\n tenantId: job.data.tenantId,\n err: error,\n })\n throw error\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,iCAA0D;AAEnE,MAAM,SAAS,aAAa,UAAU,EAAE,MAAM,EAAE,WAAW,WAAW,CAAC;AAEhE,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAEA,eAAO,QACL,KACA,KACA;AACA,QAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,MAAI;AACF,UAAM,0BAA0B,IAAI,IAAI,IAAI;AAAA,EAC9C,SAAS,OAAO;AACd,WAAO,MAAM,yBAAyB;AAAA,MACpC,YAAY,IAAI,KAAK;AAAA,MACrB,UAAU,IAAI,KAAK;AAAA,MACnB,KAAK;AAAA,IACP,CAAC;AACD,UAAM;AAAA,EACR;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/webhooks",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6471.1.9ab5bdd79a",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Webhooks module for Open Mercato — Standard Webhooks compliant outbound/inbound delivery",
|
|
6
6
|
"type": "module",
|
|
@@ -70,19 +70,19 @@
|
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@open-mercato/core": "0.6.6-develop.
|
|
74
|
-
"@open-mercato/queue": "0.6.6-develop.
|
|
75
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
73
|
+
"@open-mercato/core": "0.6.6-develop.6471.1.9ab5bdd79a",
|
|
74
|
+
"@open-mercato/queue": "0.6.6-develop.6471.1.9ab5bdd79a",
|
|
75
|
+
"@open-mercato/ui": "0.6.6-develop.6471.1.9ab5bdd79a",
|
|
76
76
|
"svix": "^1.96.1"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
79
|
"@mikro-orm/postgresql": "^7.0.14",
|
|
80
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
80
|
+
"@open-mercato/shared": "0.6.6-develop.6471.1.9ab5bdd79a",
|
|
81
81
|
"react": "^19.0.0",
|
|
82
82
|
"react-dom": "^19.0.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
85
|
+
"@open-mercato/shared": "0.6.6-develop.6471.1.9ab5bdd79a",
|
|
86
86
|
"@types/jest": "^30.0.0",
|
|
87
87
|
"@types/react": "^19.2.17",
|
|
88
88
|
"@types/react-dom": "^19.2.3",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { createModuleQueue, type Queue } from '@open-mercato/queue'
|
|
3
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
3
4
|
import type { WebhookDeliveryJob } from './delivery'
|
|
4
5
|
|
|
6
|
+
const logger = createLogger('webhooks')
|
|
7
|
+
|
|
5
8
|
const queues = new Map<string, Queue<WebhookDeliveryJob>>()
|
|
6
9
|
const LOCAL_WORKER_PROMISE_KEY = '__openMercatoWebhookLocalWorkerPromise__'
|
|
7
10
|
|
|
@@ -45,7 +48,7 @@ async function ensureLocalWebhookQueueWorkerStarted(): Promise<void> {
|
|
|
45
48
|
})
|
|
46
49
|
})().catch((error) => {
|
|
47
50
|
delete globalStore[LOCAL_WORKER_PROMISE_KEY]
|
|
48
|
-
|
|
51
|
+
logger.error('Failed to start local delivery worker', { err: error })
|
|
49
52
|
throw error
|
|
50
53
|
})
|
|
51
54
|
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import handler from '../outbound-dispatch'
|
|
3
3
|
|
|
4
|
+
jest.mock('@open-mercato/shared/lib/logger', () => {
|
|
5
|
+
const mocked = {
|
|
6
|
+
debug: jest.fn(),
|
|
7
|
+
info: jest.fn(),
|
|
8
|
+
warn: jest.fn(),
|
|
9
|
+
error: jest.fn(),
|
|
10
|
+
child: jest.fn(),
|
|
11
|
+
}
|
|
12
|
+
mocked.child.mockImplementation(() => mocked)
|
|
13
|
+
return { createLogger: jest.fn(() => mocked) }
|
|
14
|
+
})
|
|
15
|
+
|
|
4
16
|
jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
|
|
5
17
|
findWithDecryption: jest.fn(),
|
|
6
18
|
findOneWithDecryption: jest.fn(),
|
|
@@ -22,6 +34,9 @@ jest.mock('../../lib/integration-state', () => ({
|
|
|
22
34
|
|
|
23
35
|
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
24
36
|
import { getDeclaredEvents } from '@open-mercato/shared/modules/events'
|
|
37
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
38
|
+
|
|
39
|
+
const dispatchLoggerError = createLogger('webhooks').error as jest.Mock
|
|
25
40
|
import { createWebhookDelivery } from '../../lib/delivery'
|
|
26
41
|
import { enqueueWebhookDelivery } from '../../lib/queue'
|
|
27
42
|
import { isWebhookIntegrationEnabled } from '../../lib/integration-state'
|
|
@@ -189,7 +204,7 @@ describe('webhooks outbound dispatch subscriber', () => {
|
|
|
189
204
|
})
|
|
190
205
|
|
|
191
206
|
it('scopes the failed-delivery lookup to webhook tenantId and organizationId when enqueue throws', async () => {
|
|
192
|
-
|
|
207
|
+
dispatchLoggerError.mockClear()
|
|
193
208
|
|
|
194
209
|
try {
|
|
195
210
|
const { rootEm, webhookEm } = createDispatchEntityManagers()
|
|
@@ -234,18 +249,18 @@ describe('webhooks outbound dispatch subscriber', () => {
|
|
|
234
249
|
expect.objectContaining({ tenantId: 'tenant-1', organizationId: 'org-1' }),
|
|
235
250
|
)
|
|
236
251
|
expect(webhookEm.flush).toHaveBeenCalled()
|
|
237
|
-
expect(
|
|
238
|
-
'
|
|
252
|
+
expect(dispatchLoggerError).toHaveBeenCalledWith(
|
|
253
|
+
'Failed to enqueue outbound delivery',
|
|
239
254
|
expect.objectContaining({
|
|
240
255
|
webhookId: 'webhook-1',
|
|
241
256
|
eventId: 'catalog.product.created',
|
|
242
257
|
tenantId: 'tenant-1',
|
|
243
258
|
organizationId: 'org-1',
|
|
244
|
-
|
|
259
|
+
err: expect.objectContaining({ message: 'Queue unavailable' }),
|
|
245
260
|
}),
|
|
246
261
|
)
|
|
247
262
|
} finally {
|
|
248
|
-
|
|
263
|
+
dispatchLoggerError.mockClear()
|
|
249
264
|
}
|
|
250
265
|
})
|
|
251
266
|
|
|
@@ -2,10 +2,13 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
2
2
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
3
|
import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
|
|
4
4
|
import { buildFeatureNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
|
|
5
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
5
6
|
import { WebhookEntity } from '../data/entities'
|
|
6
7
|
import { resolveWebhookIntegrationSettings } from '../lib/integration-settings'
|
|
7
8
|
import { notificationTypes } from '../notifications'
|
|
8
9
|
|
|
10
|
+
const logger = createLogger('webhooks').child({ component: 'failed-delivery-notification' })
|
|
11
|
+
|
|
9
12
|
export const metadata = {
|
|
10
13
|
event: 'webhooks.delivery.exhausted',
|
|
11
14
|
persistent: true,
|
|
@@ -76,6 +79,6 @@ export default async function handle(payload: WebhookDeliveryExhaustedPayload, c
|
|
|
76
79
|
organizationId,
|
|
77
80
|
})
|
|
78
81
|
} catch (err) {
|
|
79
|
-
|
|
82
|
+
logger.error('Failed to create notification', { err })
|
|
80
83
|
}
|
|
81
84
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
1
2
|
import { getWebhookEndpointAdapter } from '../lib/adapter-registry'
|
|
2
3
|
|
|
4
|
+
const logger = createLogger('webhooks').child({ component: 'inbound' })
|
|
5
|
+
|
|
3
6
|
export const metadata = {
|
|
4
7
|
event: 'webhooks.inbound.received',
|
|
5
8
|
persistent: true,
|
|
@@ -29,11 +32,7 @@ export default async function handler(payload: Record<string, unknown>) {
|
|
|
29
32
|
organizationId: typeof payload.organizationId === 'string' ? payload.organizationId : undefined,
|
|
30
33
|
})
|
|
31
34
|
} catch (err) {
|
|
32
|
-
|
|
33
|
-
providerKey,
|
|
34
|
-
eventType,
|
|
35
|
-
error: err instanceof Error ? err.message : String(err),
|
|
36
|
-
})
|
|
35
|
+
logger.error('Processing failed', { providerKey, eventType, err })
|
|
37
36
|
throw err
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -4,10 +4,13 @@ import { WebhookDeliveryEntity, WebhookEntity } from '../data/entities'
|
|
|
4
4
|
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
5
5
|
import { matchAnyWebhookEventPattern } from '@open-mercato/shared/lib/events/patterns'
|
|
6
6
|
import { getDeclaredEvents } from '@open-mercato/shared/modules/events'
|
|
7
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
7
8
|
import { createWebhookDelivery } from '../lib/delivery'
|
|
8
9
|
import { enqueueWebhookDelivery } from '../lib/queue'
|
|
9
10
|
import { isWebhookIntegrationEnabled } from '../lib/integration-state'
|
|
10
11
|
|
|
12
|
+
const logger = createLogger('webhooks')
|
|
13
|
+
|
|
11
14
|
export const metadata = {
|
|
12
15
|
event: '*',
|
|
13
16
|
persistent: true,
|
|
@@ -129,12 +132,12 @@ export default async function handler(
|
|
|
129
132
|
await webhookEm.flush()
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
|
-
|
|
135
|
+
logger.error('Failed to enqueue outbound delivery', {
|
|
133
136
|
webhookId: webhook.id,
|
|
134
137
|
eventId,
|
|
135
138
|
tenantId,
|
|
136
139
|
organizationId: organizationId ?? webhook.organizationId,
|
|
137
|
-
|
|
140
|
+
err: error,
|
|
138
141
|
})
|
|
139
142
|
}
|
|
140
143
|
}),
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
2
3
|
import { processWebhookDeliveryJob, type WebhookDeliveryJob } from '../lib/delivery'
|
|
3
4
|
|
|
5
|
+
const logger = createLogger('webhooks').child({ component: 'delivery' })
|
|
6
|
+
|
|
4
7
|
export const metadata = {
|
|
5
8
|
queue: 'webhook-deliveries',
|
|
6
9
|
id: 'webhooks:delivery-worker',
|
|
@@ -15,10 +18,10 @@ export default async function handler(
|
|
|
15
18
|
try {
|
|
16
19
|
await processWebhookDeliveryJob(em, job.data)
|
|
17
20
|
} catch (error) {
|
|
18
|
-
|
|
21
|
+
logger.error('Job processing failed', {
|
|
19
22
|
deliveryId: job.data.deliveryId,
|
|
20
23
|
tenantId: job.data.tenantId,
|
|
21
|
-
|
|
24
|
+
err: error,
|
|
22
25
|
})
|
|
23
26
|
throw error
|
|
24
27
|
}
|