notifkit 0.1.6 → 0.1.7
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/README.md +5 -17
- package/dist/index.d.mts +3022 -560
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{main-DsDkyBZi.mjs → main-Bgi2wWck.mjs} +2 -2
- package/dist/{main-DsDkyBZi.mjs.map → main-Bgi2wWck.mjs.map} +1 -1
- package/dist/{main-Db2f2-sW.mjs → main-CFSukWm8.mjs} +73 -39
- package/dist/main-CFSukWm8.mjs.map +1 -0
- package/dist/{main-DfrrNKp9.mjs → main-CRPxM-O3.mjs} +2 -2
- package/dist/{main-DfrrNKp9.mjs.map → main-CRPxM-O3.mjs.map} +1 -1
- package/dist/{main-BnIX5nfp.mjs → main-CjbVdUqa.mjs} +2 -2
- package/dist/{main-BnIX5nfp.mjs.map → main-CjbVdUqa.mjs.map} +1 -1
- package/dist/{main-DDbquL89.mjs → main-D6SG3Isk.mjs} +2 -2
- package/dist/{main-DDbquL89.mjs.map → main-D6SG3Isk.mjs.map} +1 -1
- package/dist/{main-Cwu4iQXc.mjs → main-DFMHcN_d.mjs} +2 -2
- package/dist/{main-Cwu4iQXc.mjs.map → main-DFMHcN_d.mjs.map} +1 -1
- package/dist/{main-CWGEKpLU.mjs → main-Yorxzw0Z.mjs} +2 -2
- package/dist/{main-CWGEKpLU.mjs.map → main-Yorxzw0Z.mjs.map} +1 -1
- package/dist/{main-DdLzptZE.mjs → main-pFAfCkVX.mjs} +2 -2
- package/dist/{main-DdLzptZE.mjs.map → main-pFAfCkVX.mjs.map} +1 -1
- package/dist/{src-CpUnBl_M.mjs → src-CPMwsUCJ.mjs} +69 -40
- package/dist/src-CPMwsUCJ.mjs.map +1 -0
- package/package.json +1 -1
- package/src/client.ts +111 -40
- package/src/contracts/sdk.ts +143 -3
- package/src/services/api/handlers.ts +94 -30
- package/dist/main-Db2f2-sW.mjs.map +0 -1
- package/dist/src-CpUnBl_M.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-DdLzptZE.mjs","names":[],"sources":["../src/services/delivery/throttle.ts","../src/services/delivery/main.ts"],"sourcesContent":["import type { Redis } from \"ioredis\";\n\nexport interface ThrottleResult {\n allowed: boolean;\n retryAfterMs: number;\n}\n\nconst LUA_THROTTLE = `\n local key = KEYS[1]\n local now = tonumber(ARGV[1])\n local windowSeconds = tonumber(ARGV[2])\n local limit = tonumber(ARGV[3])\n local member = ARGV[4]\n\n local clearBefore = now - (windowSeconds * 1000)\n \n -- Cleanup expired scores\n redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)\n \n -- Get current count\n local count = redis.call('ZCARD', key)\n \n if count >= limit then\n -- Find the oldest score\n local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')\n if oldest and oldest[2] then\n return {0, tonumber(oldest[2])}\n end\n return {0, now}\n end\n \n -- Add new request\n redis.call('ZADD', key, now, member)\n redis.call('EXPIRE', key, windowSeconds * 2)\n return {1, 0}\n`;\n\nexport async function throttleProvider(\n redis: Redis,\n channel: string,\n config: { limit: number; windowSeconds: number },\n logger: any,\n): Promise<ThrottleResult> {\n const key = `rate-limit:provider:${channel}`;\n const now = Date.now();\n const zmember = `${now}:${Math.random()}`;\n\n const result = (await redis.eval(\n LUA_THROTTLE,\n 1,\n key,\n now.toString(),\n config.windowSeconds.toString(),\n config.limit.toString(),\n zmember,\n )) as [number, number];\n\n const allowed = result[0] === 1;\n const oldestTimestamp = result[1];\n\n let retryAfterMs = 0;\n if (!allowed) {\n retryAfterMs = Math.max(0, oldestTimestamp + config.windowSeconds * 1000 - now);\n logger.warn(\n { channel, limit: config.limit, windowSeconds: config.windowSeconds, retryAfterMs },\n \"Provider rate limit hit — task must be rescheduled\",\n );\n }\n\n return { allowed, retryAfterMs };\n}\n","import { loadEnv, parseConfig, baseConfigSchema } from \"@/index.js\";\nimport { createLogger } from \"@/index.js\";\nimport { RedisClient, type Redis } from \"@/index.js\";\nimport {\n StreamConsumer,\n PendingMessageScanner,\n type StreamMessage,\n StreamProducer,\n} from \"@/index.js\";\nimport { BaseWorker } from \"@/index.js\";\nimport {\n STREAMS,\n OUTBOUND_STREAMS,\n CONSUMER_GROUPS,\n registry,\n buildStreamEvent,\n type NotificationDispatchedPayload,\n} from \"@/index.js\";\nimport { type StreamName } from \"@/contracts/streams.js\";\nimport { createDatabase } from \"@/db/index.js\";\nimport { deliveryOutbox, scheduledPayloads } from \"@/db/schema.js\";\nimport { ContactRepository, IdempotencyGuard } from \"@/index.js\";\nimport { transportRegistry } from \"@/index.js\";\nimport { globalEmitter, getPriorityBucket, type WorkerOptions } from \"@/shared/index.js\";\nimport { startHealthReporter, NonRetryableError } from \"@/workers/index.js\";\nimport { BatchProcessor, CircuitBreaker } from \"@/shared/index.js\";\nimport { throttleProvider } from \"./throttle.js\";\nimport { metrics } from \"@/metrics/index.js\";\n\n// ─── App-specific config ────────────────────────────────────────────────────\n\nconst deliveryConfigSchema = baseConfigSchema.extend({});\n\n// ─── Bootstrap ─────────────────────────────────────────────────────────────\n\nloadEnv();\nconst config = parseConfig(deliveryConfigSchema, process.env);\n\nlet logger: ReturnType<typeof createLogger>;\nlet redis: RedisClient;\nlet sql: any;\nlet db: any;\n\nlet consumer: StreamConsumer;\nlet pendingScanner: PendingMessageScanner;\nlet worker: BaseWorker;\nlet scheduledProducer: StreamProducer;\nlet enrichedProducers: Record<string, StreamProducer>;\nlet healthInterval: NodeJS.Timeout | null = null;\n\n(global as any)._telemetry = (global as any)._telemetry || {\n count: 0,\n insert: 0,\n provider: 0,\n flush: 0,\n ack: 0,\n dequeue: 0,\n dbupdate: 0,\n flushCount: 0,\n};\n\nexport interface DeliveryWorkerOptions extends WorkerOptions {\n transportRegistry: any;\n idempotency: any;\n redis: Redis;\n scheduledProducer: any;\n enrichedProducers: any;\n contactRepo: any;\n eventsProducer: any;\n globalEmitter: any;\n db: any;\n}\n\nexport class DeliveryWorker extends BaseWorker {\n private readonly transportRegistry: any;\n private readonly idempotency: any;\n private readonly redisCli: Redis;\n private readonly scheduledProducer: any;\n private readonly enrichedProducers: any;\n private readonly contactRepo: any;\n private readonly eventsProducer: any;\n private readonly globalEmitter: any;\n private readonly db: any;\n\n private eventProcessor: BatchProcessor<any, void>;\n private outboxUpdateProcessor: BatchProcessor<any, void>;\n private outboxInsertProcessor: BatchProcessor<any, boolean>;\n private breakers = new Map<string, CircuitBreaker>();\n\n constructor(options: DeliveryWorkerOptions) {\n super(options);\n this.transportRegistry = options.transportRegistry;\n this.idempotency = options.idempotency;\n this.redisCli = options.redis;\n this.scheduledProducer = options.scheduledProducer;\n this.enrichedProducers = options.enrichedProducers;\n this.contactRepo = options.contactRepo;\n this.eventsProducer = options.eventsProducer;\n this.globalEmitter = options.globalEmitter;\n this.db = options.db;\n\n this.eventProcessor = new BatchProcessor<any, void>(1000, 100, async (events) => {\n await this.eventsProducer.publishBatch(events);\n return events.map(() => undefined as void);\n });\n\n this.outboxUpdateProcessor = new BatchProcessor<any, void>(500, 100, async (updates) => {\n const { sql } = await import(\"drizzle-orm\");\n const values = updates.map((update) => ({\n taskId: update.taskId,\n channel: update.channel,\n destination: update.destination,\n providerMessageId: update.providerMessageId,\n }));\n\n const tDbUpdateStart = Date.now();\n await this.db\n .insert(deliveryOutbox)\n .values(values)\n .onConflictDoUpdate({\n target: [deliveryOutbox.taskId, deliveryOutbox.channel, deliveryOutbox.destination],\n set: { providerMessageId: sql`EXCLUDED.provider_message_id` },\n })\n .catch((e: any) => this.logger.error({ err: e }, \"background update failed\"));\n\n (global as any)._telemetry.dbupdate += Date.now() - tDbUpdateStart;\n (global as any)._telemetry.flushCount++;\n return updates.map(() => undefined as void);\n });\n\n this.outboxInsertProcessor = new BatchProcessor(500, 10, async (tasks) => {\n const values = tasks.map((task: any) => ({\n taskId: task.taskId,\n channel: task.channel,\n destination: task.destination,\n }));\n\n await this.db.insert(deliveryOutbox).values(values).onConflictDoNothing();\n return tasks.map(() => true);\n });\n }\n\n override async stop(): Promise<void> {\n await Promise.all([\n this.eventProcessor.flush(),\n this.outboxUpdateProcessor.flush(),\n this.outboxInsertProcessor.flush(),\n ]);\n await super.stop();\n }\n\n private getBreaker(name: string): CircuitBreaker {\n let breaker = this.breakers.get(name);\n if (!breaker) {\n breaker = new CircuitBreaker({ failureThreshold: 5, resetTimeoutMs: 30000 });\n this.breakers.set(name, breaker);\n }\n return breaker;\n }\n\n async process(message: StreamMessage, attempt: number = 1): Promise<void> {\n const { event } = message;\n const publishPromises: Promise<void>[] = [];\n\n const payloadResult = registry.safeParsePayload(\"notification.dispatched\", event.payload);\n if (!payloadResult.success) {\n this.logger.warn(\n { messageId: message.id, issues: payloadResult.error.issues },\n \"invalid notification.dispatched payload — skipping\",\n );\n return;\n }\n\n const task = payloadResult.data as NotificationDispatchedPayload;\n\n const fallbackToNextChannel = async (reason: string) => {\n if (task.fallbackChain && task.fallbackChain.length > 0 && task.recipient) {\n const nextChannel = task.fallbackChain[0];\n const remainingChain = task.fallbackChain.slice(1);\n\n const fallbackPayload: any = {\n // NotificationEnrichedPayload\n projectId: task.projectId,\n rawEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: nextChannel,\n priority: task.priority,\n templateId: task.templateId,\n templateVariables: task.templateVariables,\n aiPrompts: task.aiPrompts,\n recipient: task.recipient,\n scheduledAt: undefined,\n fallbackChain: remainingChain.length > 0 ? remainingChain : undefined,\n };\n\n const p = getPriorityBucket(task.priority);\n const producer = this.enrichedProducers[p] ?? this.enrichedProducers[\"normal\"]!;\n await producer.publish(\n buildStreamEvent(\n \"notification.enriched\",\n fallbackPayload,\n \"delivery\",\n event.metadata.traceId,\n ),\n );\n\n this.logger.info(\n { taskId: task.taskId, reason, nextChannel, traceId: event.metadata.traceId },\n \"Delivery failed completely, rolling over to next channel in fallback chain\",\n );\n return true; // Indicates fallback was triggered\n }\n return false; // No fallback\n };\n\n const transports = this.transportRegistry.getAll(task.channel);\n\n if (transports.length === 0) {\n this.logger.warn(\n { taskId: task.taskId, channel: task.channel },\n \"no transport registered for channel — dropping\",\n );\n const fallbackTriggered = await fallbackToNextChannel(\"no_transport\");\n if (!fallbackTriggered) {\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n \"no transport\",\n task.channel,\n task.projectId,\n );\n await this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: \"no transport registered for channel\",\n failureCode: \"no_transport\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n );\n }\n return;\n }\n\n const limitConfig = transports[0].limits;\n if (limitConfig) {\n const { allowed, retryAfterMs } = await throttleProvider(\n this.redisCli,\n task.channel,\n limitConfig,\n this.logger,\n );\n if (!allowed) {\n task.throttleAttemptCount = (task.throttleAttemptCount ?? 0) + 1;\n const maxAttempts = task.deliveryOptions?.maxAttempts ?? 3;\n\n if (task.throttleAttemptCount > maxAttempts) {\n this.logger.warn(\n { messageId: message.id, taskId: task.taskId, attempts: task.throttleAttemptCount },\n \"provider rate limit max attempts exceeded\",\n );\n const fallbackTriggered = await fallbackToNextChannel(\"provider_throttle_exceeded\");\n if (!fallbackTriggered) {\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n \"provider throttle exceeded\",\n task.channel,\n task.projectId,\n );\n await this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: \"provider rate limit max attempts exceeded\",\n failureCode: \"provider_throttle_exceeded\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n );\n }\n return;\n }\n\n const { sql } = await import(\"drizzle-orm\");\n await this.db\n .insert(scheduledPayloads)\n .values({\n taskId: task.taskId,\n payload: task,\n })\n .onConflictDoUpdate({\n target: scheduledPayloads.taskId,\n set: { payload: sql`EXCLUDED.payload` },\n });\n\n await this.scheduledProducer.publish(\n buildStreamEvent(\n \"notification.scheduled\",\n {\n projectId: task.projectId,\n enrichedEventId: task.enrichedEventId,\n taskId: task.taskId,\n scheduledAt: new Date(Date.now() + retryAfterMs).toISOString(),\n throttleAttemptCount: task.throttleAttemptCount,\n },\n \"delivery\",\n `${task.taskId}:throttle:${Date.now()}`,\n ),\n );\n\n return;\n }\n }\n\n const tInsertStart = Date.now();\n const idempotencyKey = task.taskId;\n if (!(await this.idempotency.checkAndMark(idempotencyKey, 60))) {\n this.logger.info(\n { messageId: message.id, taskId: task.taskId, channel: task.channel, attempt },\n \"duplicate delivery — skipping\",\n );\n return;\n }\n\n try {\n await this.outboxInsertProcessor.add(task);\n const insertTime = Date.now() - tInsertStart;\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.dispatched\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n templateId: task.templateId,\n attempt,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n\n // For push: we just need to send to the pre-resolved destination,\n // but if it's invalid, we deactivate it.\n if (task.channel === \"push\") {\n let lastResult: any = { success: false, error: \"No transports\" };\n\n for (const transport of transports) {\n try {\n const tProv = Date.now();\n const breaker = this.getBreaker(`${task.channel}:${transport.constructor.name}`);\n\n lastResult = await breaker.execute(async () => {\n const timeoutMs = task.deliveryOptions?.timeoutMs ?? 10_000;\n const controller = new AbortController();\n const timeout = setTimeout(() => {\n controller.abort(new Error(`Transport timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n (task as any).signal = controller.signal;\n const res: any = await Promise.race([\n transport.send(task),\n new Promise((_, reject) => {\n if (controller.signal.aborted) return reject(controller.signal.reason);\n controller.signal.addEventListener(\"abort\", () =>\n reject(controller.signal.reason),\n );\n }),\n ]);\n if (!res.success && !res.invalidToken) {\n throw new Error(res.error ?? \"Transport failed\");\n }\n return res;\n } finally {\n clearTimeout(timeout);\n }\n });\n\n (global as any)._telemetry.provider += Date.now() - tProv;\n if (lastResult.success || lastResult.invalidToken) break;\n } catch (err: any) {\n lastResult = { success: false, error: err.message };\n }\n }\n\n if (lastResult.invalidToken) {\n await this.contactRepo.deactivate(\n task.projectId,\n task.recipientId,\n task.channel as any,\n task.destination,\n );\n this.logger.info(\n { channel: task.channel, target: task.destination },\n \"deactivated invalid contact target\",\n );\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n \"invalidToken\",\n task.channel,\n task.projectId,\n );\n metrics.deliveryFailed.inc({ channel: task.channel, reason: \"invalid_token\" });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: \"invalid_token\",\n failureCode: \"push_failure\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n\n const fallbackTriggered = await fallbackToNextChannel(\"invalid_token\");\n if (!fallbackTriggered) {\n throw new NonRetryableError(\"Push delivery failed: invalid token\");\n }\n } else if (lastResult.success) {\n // Record providerMessageId in outbox before publishing event\n const providerMessageId = lastResult.providerMessageId || \"push-success\";\n\n publishPromises.push(\n this.outboxUpdateProcessor.add({\n taskId: task.taskId,\n channel: task.channel,\n destination: task.destination,\n providerMessageId,\n }),\n );\n\n this.logger.debug(\n { taskId: task.taskId, messageId: providerMessageId },\n \"push delivered\",\n );\n this.globalEmitter.emit(\n \"delivery:delivered\",\n task.taskId,\n providerMessageId,\n task.channel,\n task.projectId,\n );\n metrics.deliverySuccess.inc({ channel: task.channel });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.delivered\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n channel: task.channel,\n deliveredAt: new Date().toISOString(),\n providerMessageId,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n } else {\n this.logger.warn(\n { taskId: task.taskId, error: lastResult.error },\n \"push delivery failed\",\n );\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n lastResult.error ?? \"push delivery failed\",\n task.channel,\n task.projectId,\n );\n metrics.deliveryFailed.inc({ channel: task.channel, reason: \"push_error\" });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: lastResult.error ?? \"push delivery failed\",\n failureCode: \"push_failure\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n\n const fallbackTriggered = await fallbackToNextChannel(\"push_delivery_failed\");\n if (!fallbackTriggered) {\n throw new NonRetryableError(lastResult.error ?? \"Push delivery failed\");\n }\n }\n } else {\n // Other channels (email, sms, webhook, whatsapp) use the pre-resolved destination\n let result: any = { success: false, error: \"No transports\" };\n for (const transport of transports) {\n try {\n const tProv = Date.now();\n const breaker = this.getBreaker(`${task.channel}:${transport.constructor.name}`);\n\n result = await breaker.execute(async () => {\n const timeoutMs = task.deliveryOptions?.timeoutMs ?? 10_000;\n const controller = new AbortController();\n const timeout = setTimeout(() => {\n controller.abort(new Error(`Transport timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n (task as any).signal = controller.signal;\n const res: any = await Promise.race([\n transport.send(task),\n new Promise((_, reject) => {\n if (controller.signal.aborted) return reject(controller.signal.reason);\n controller.signal.addEventListener(\"abort\", () =>\n reject(controller.signal.reason),\n );\n }),\n ]);\n if (!res.success) {\n throw new Error(res.error ?? \"Transport failed\");\n }\n return res;\n } finally {\n clearTimeout(timeout);\n }\n });\n\n (global as any)._telemetry.provider += Date.now() - tProv;\n if (result.success) break;\n } catch (err: any) {\n result = { success: false, error: err.message };\n }\n }\n\n if (result.success) {\n // Record providerMessageId in outbox before publishing event\n const providerMessageId = result.providerMessageId || \"success\";\n\n publishPromises.push(\n this.outboxUpdateProcessor.add({\n taskId: task.taskId,\n channel: task.channel,\n destination: task.destination,\n providerMessageId,\n }),\n );\n\n this.globalEmitter.emit(\n \"delivery:delivered\",\n task.taskId,\n providerMessageId,\n task.channel,\n task.projectId,\n );\n metrics.deliverySuccess.inc({ channel: task.channel });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.delivered\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n channel: task.channel,\n deliveredAt: new Date().toISOString(),\n providerMessageId,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n } else {\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n channel: task.channel,\n failureReason: result.error ?? \"delivery failed completely\",\n failureCode: \"provider_error\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n }\n\n if (!result.success) {\n this.logger.warn(\n { taskId: task.taskId, channel: task.channel, error: result.error },\n \"delivery failed completely across providers\",\n );\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n result.error ?? \"delivery failed completely\",\n task.channel,\n task.projectId,\n );\n metrics.deliveryFailed.inc({ channel: task.channel, reason: \"provider_error\" });\n\n const fallbackTriggered = await fallbackToNextChannel(\"all_providers_failed\");\n if (!fallbackTriggered) {\n throw new NonRetryableError(result.error ?? \"delivery failed\");\n }\n } else {\n this.logger.info(\n { taskId: task.taskId, channel: task.channel, messageId: result.providerMessageId },\n \"notification delivered\",\n );\n }\n }\n\n const tFlushStart = Date.now();\n await Promise.all(publishPromises).catch((err) => {\n this.logger.error(\n { err, taskId: task.taskId },\n \"failed to publish post-dispatch events, swallowing error to prevent duplicate delivery\",\n );\n });\n const flushTime = Date.now() - tFlushStart;\n\n await this.idempotency.markProcessed(idempotencyKey);\n\n const t = ((global as any)._telemetry = (global as any)._telemetry || {\n count: 0,\n insert: 0,\n provider: 0,\n flush: 0,\n ack: 0,\n });\n t.count++;\n t.insert += insertTime;\n t.flush += flushTime;\n\n if (t.count % 1000 === 0) {\n this.logger.debug(\n `[Metrics 1000 msgs] Dequeue: ${t.dequeue / 1000}ms, DB Insert: ${t.insert / 1000}ms, Provider: ${t.provider / 1000}ms, Wait for Flush: ${t.flush / 1000}ms, Ack: ${t.ack / 1000}ms | DB Update (avg per flush): ${t.dbupdate / Math.max(1, t.flushCount)}ms`,\n );\n t.count = 0;\n t.insert = 0;\n t.provider = 0;\n t.flush = 0;\n t.dequeue = 0;\n t.ack = 0;\n t.dbupdate = 0;\n t.flushCount = 0;\n }\n } catch (err) {\n await this.idempotency.unmark(idempotencyKey).catch(() => {});\n throw err;\n }\n }\n}\n\nexport async function startDeliveryWorker() {\n logger = createLogger({ name: \"delivery\", level: config.LOG_LEVEL });\n\n redis = new RedisClient({ url: config.REDIS_URL, name: \"delivery\", logger });\n const dbData = createDatabase({ url: config.DATABASE_URL, applicationName: \"delivery\", logger });\n sql = dbData.sql;\n db = dbData.db;\n consumer = new StreamConsumer({\n redis: redis.native,\n stream: OUTBOUND_STREAMS as unknown as StreamName[],\n group: CONSUMER_GROUPS.DELIVERY,\n consumer: `delivery-${process.pid}`,\n dlqStream: STREAMS.DEAD_LETTER,\n batchSize: config.WORKER_CONCURRENCY,\n logger,\n });\n\n scheduledProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.SCHEDULED,\n logger,\n });\n\n pendingScanner = new PendingMessageScanner({\n redis: redis.native,\n stream: OUTBOUND_STREAMS as unknown as StreamName[],\n group: CONSUMER_GROUPS.DELIVERY,\n consumer: `delivery-${process.pid}`,\n logger,\n });\n\n enrichedProducers = {\n critical: new StreamProducer({\n redis: redis.native,\n stream: STREAMS.ENRICHED_CRITICAL,\n logger,\n }),\n normal: new StreamProducer({ redis: redis.native, stream: STREAMS.ENRICHED_NORMAL, logger }),\n low: new StreamProducer({ redis: redis.native, stream: STREAMS.ENRICHED_LOW, logger }),\n };\n\n const contactRepo = new ContactRepository(db);\n\n const idempotency = new IdempotencyGuard({\n redis: redis.native,\n keyPrefix: \"notif:processed:delivery\",\n ttlSeconds: 86_400,\n });\n\n // ─── Stage 3: Delivery Worker ───────────────────────────────────────────────\n //\n // Pipeline:\n // 1. Parse payload as notification.dispatched\n // 2. Resolve active device tokens from DB\n // 3. Send via registered transport\n // 4. Deactivate invalid tokens in DB\n\n const eventsProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.EVENTS_INBOUND,\n logger,\n });\n\n worker = new DeliveryWorker({\n consumer,\n pendingScanner,\n logger,\n concurrency: config.WORKER_CONCURRENCY,\n transportRegistry,\n idempotency,\n redis: redis.native,\n scheduledProducer,\n enrichedProducers,\n contactRepo,\n eventsProducer,\n globalEmitter,\n db,\n });\n\n // ─── Health check interval ──────────────────────────────────────────────────\n\n healthInterval = startHealthReporter(\"delivery\", worker, redis, logger);\n\n logger.info(\n { env: config.NODE_ENV, channels: transportRegistry.registeredChannels() },\n \"delivery starting\",\n );\n await worker.start();\n}\n\n// ─── Shutdown ──────────────────────────────────────────────────────────────\n\nexport async function stopDeliveryWorker(): Promise<void> {\n logger?.info(\"shutdown initiated\");\n if (healthInterval) {\n clearInterval(healthInterval);\n healthInterval = null;\n }\n if (worker) await worker.stop();\n if (sql) await sql.end();\n if (redis) await redis.disconnect();\n logger?.info(\"delivery stopped\");\n}\n"],"mappings":";;;AAOA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BrB,eAAsB,iBACpB,OACA,SACA,QACA,QACyB;CACzB,MAAM,MAAM,uBAAuB;CACnC,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,UAAU,GAAG,IAAI,GAAG,KAAK,OAAO;CAEtC,MAAM,SAAU,MAAM,MAAM,KAC1B,cACA,GACA,KACA,IAAI,SAAS,GACb,OAAO,cAAc,SAAS,GAC9B,OAAO,MAAM,SAAS,GACtB,OACF;CAEA,MAAM,UAAU,OAAO,OAAO;CAC9B,MAAM,kBAAkB,OAAO;CAE/B,IAAI,eAAe;CACnB,IAAI,CAAC,SAAS;EACZ,eAAe,KAAK,IAAI,GAAG,kBAAkB,OAAO,gBAAgB,MAAO,GAAG;EAC9E,OAAO,KACL;GAAE;GAAS,OAAO,OAAO;GAAO,eAAe,OAAO;GAAe;EAAa,GAClF,oDACF;CACF;CAEA,OAAO;EAAE;EAAS;CAAa;AACjC;;;ACvCA,MAAM,uBAAuB,iBAAiB,OAAO,CAAC,CAAC;AAIvD,QAAQ;AACR,MAAM,SAAS,YAAY,sBAAsB,QAAQ,GAAG;AAE5D,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AAEJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI,iBAAwC;AAE5C,OAAgB,aAAc,OAAe,cAAc;CACzD,OAAO;CACP,QAAQ;CACR,UAAU;CACV,OAAO;CACP,KAAK;CACL,SAAS;CACT,UAAU;CACV,YAAY;AACd;AAcA,IAAa,iBAAb,cAAoC,WAAW;CAC7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA,2BAAmB,IAAI,IAA4B;CAEnD,YAAY,SAAgC;EAC1C,MAAM,OAAO;EACb,KAAK,oBAAoB,QAAQ;EACjC,KAAK,cAAc,QAAQ;EAC3B,KAAK,WAAW,QAAQ;EACxB,KAAK,oBAAoB,QAAQ;EACjC,KAAK,oBAAoB,QAAQ;EACjC,KAAK,cAAc,QAAQ;EAC3B,KAAK,iBAAiB,QAAQ;EAC9B,KAAK,gBAAgB,QAAQ;EAC7B,KAAK,KAAK,QAAQ;EAElB,KAAK,iBAAiB,IAAI,eAA0B,KAAM,KAAK,OAAO,WAAW;GAC/E,MAAM,KAAK,eAAe,aAAa,MAAM;GAC7C,OAAO,OAAO,UAAU,KAAA,CAAiB;EAC3C,CAAC;EAED,KAAK,wBAAwB,IAAI,eAA0B,KAAK,KAAK,OAAO,YAAY;GACtF,MAAM,EAAE,QAAQ,MAAM,OAAO;GAC7B,MAAM,SAAS,QAAQ,KAAK,YAAY;IACtC,QAAQ,OAAO;IACf,SAAS,OAAO;IAChB,aAAa,OAAO;IACpB,mBAAmB,OAAO;GAC5B,EAAE;GAEF,MAAM,iBAAiB,KAAK,IAAI;GAChC,MAAM,KAAK,GACR,OAAO,cAAc,CAAC,CACtB,OAAO,MAAM,CAAC,CACd,mBAAmB;IAClB,QAAQ;KAAC,eAAe;KAAQ,eAAe;KAAS,eAAe;IAAW;IAClF,KAAK,EAAE,mBAAmB,GAAG,+BAA+B;GAC9D,CAAC,CAAC,CACD,OAAO,MAAW,KAAK,OAAO,MAAM,EAAE,KAAK,EAAE,GAAG,0BAA0B,CAAC;GAE9E,OAAgB,WAAW,YAAY,KAAK,IAAI,IAAI;GACpD,OAAgB,WAAW;GAC3B,OAAO,QAAQ,UAAU,KAAA,CAAiB;EAC5C,CAAC;EAED,KAAK,wBAAwB,IAAI,eAAe,KAAK,IAAI,OAAO,UAAU;GACxE,MAAM,SAAS,MAAM,KAAK,UAAe;IACvC,QAAQ,KAAK;IACb,SAAS,KAAK;IACd,aAAa,KAAK;GACpB,EAAE;GAEF,MAAM,KAAK,GAAG,OAAO,cAAc,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,oBAAoB;GACxE,OAAO,MAAM,UAAU,IAAI;EAC7B,CAAC;CACH;CAEA,MAAe,OAAsB;EACnC,MAAM,QAAQ,IAAI;GAChB,KAAK,eAAe,MAAM;GAC1B,KAAK,sBAAsB,MAAM;GACjC,KAAK,sBAAsB,MAAM;EACnC,CAAC;EACD,MAAM,MAAM,KAAK;CACnB;CAEA,WAAmB,MAA8B;EAC/C,IAAI,UAAU,KAAK,SAAS,IAAI,IAAI;EACpC,IAAI,CAAC,SAAS;GACZ,UAAU,IAAI,eAAe;IAAE,kBAAkB;IAAG,gBAAgB;GAAM,CAAC;GAC3E,KAAK,SAAS,IAAI,MAAM,OAAO;EACjC;EACA,OAAO;CACT;CAEA,MAAM,QAAQ,SAAwB,UAAkB,GAAkB;EACxE,MAAM,EAAE,UAAU;EAClB,MAAM,kBAAmC,CAAC;EAE1C,MAAM,gBAAgB,SAAS,iBAAiB,2BAA2B,MAAM,OAAO;EACxF,IAAI,CAAC,cAAc,SAAS;GAC1B,KAAK,OAAO,KACV;IAAE,WAAW,QAAQ;IAAI,QAAQ,cAAc,MAAM;GAAO,GAC5D,oDACF;GACA;EACF;EAEA,MAAM,OAAO,cAAc;EAE3B,MAAM,wBAAwB,OAAO,WAAmB;GACtD,IAAI,KAAK,iBAAiB,KAAK,cAAc,SAAS,KAAK,KAAK,WAAW;IACzE,MAAM,cAAc,KAAK,cAAc;IACvC,MAAM,iBAAiB,KAAK,cAAc,MAAM,CAAC;IAEjD,MAAM,kBAAuB;KAE3B,WAAW,KAAK;KAChB,YAAY,KAAK;KACjB,aAAa,KAAK;KAClB,SAAS;KACT,UAAU,KAAK;KACf,YAAY,KAAK;KACjB,mBAAmB,KAAK;KACxB,WAAW,KAAK;KAChB,WAAW,KAAK;KAChB,aAAa,KAAA;KACb,eAAe,eAAe,SAAS,IAAI,iBAAiB,KAAA;IAC9D;IAEA,MAAM,IAAI,kBAAkB,KAAK,QAAQ;IAEzC,OADiB,KAAK,kBAAkB,MAAM,KAAK,kBAAkB,UAAA,CACtD,QACb,iBACE,yBACA,iBACA,YACA,MAAM,SAAS,OACjB,CACF;IAEA,KAAK,OAAO,KACV;KAAE,QAAQ,KAAK;KAAQ;KAAQ;KAAa,SAAS,MAAM,SAAS;IAAQ,GAC5E,4EACF;IACA,OAAO;GACT;GACA,OAAO;EACT;EAEA,MAAM,aAAa,KAAK,kBAAkB,OAAO,KAAK,OAAO;EAE7D,IAAI,WAAW,WAAW,GAAG;GAC3B,KAAK,OAAO,KACV;IAAE,QAAQ,KAAK;IAAQ,SAAS,KAAK;GAAQ,GAC7C,gDACF;GAEA,IAAI,CAAC,MAD2B,sBAAsB,cAAc,GAC5C;IACtB,KAAK,cAAc,KACjB,mBACA,KAAK,QACL,gBACA,KAAK,SACL,KAAK,SACP;IACA,MAAM,KAAK,eAAe,IACxB,iBACE,uBACA;KACE,WAAW,KAAK;KAChB,QAAQ,KAAK;KACb,iBAAiB,KAAK;KACtB,aAAa,KAAK;KAClB,SAAS,KAAK;KACd,eAAe;KACf,aAAa;KACb,WAAW;KACX;KACA,YAAY,KAAK;KACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;KAClE,YAAY,KAAK;IACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF;GACF;GACA;EACF;EAEA,MAAM,cAAc,WAAW,EAAE,CAAC;EAClC,IAAI,aAAa;GACf,MAAM,EAAE,SAAS,iBAAiB,MAAM,iBACtC,KAAK,UACL,KAAK,SACL,aACA,KAAK,MACP;GACA,IAAI,CAAC,SAAS;IACZ,KAAK,wBAAwB,KAAK,wBAAwB,KAAK;IAC/D,MAAM,cAAc,KAAK,iBAAiB,eAAe;IAEzD,IAAI,KAAK,uBAAuB,aAAa;KAC3C,KAAK,OAAO,KACV;MAAE,WAAW,QAAQ;MAAI,QAAQ,KAAK;MAAQ,UAAU,KAAK;KAAqB,GAClF,2CACF;KAEA,IAAI,CAAC,MAD2B,sBAAsB,4BAA4B,GAC1D;MACtB,KAAK,cAAc,KACjB,mBACA,KAAK,QACL,8BACA,KAAK,SACL,KAAK,SACP;MACA,MAAM,KAAK,eAAe,IACxB,iBACE,uBACA;OACE,WAAW,KAAK;OAChB,QAAQ,KAAK;OACb,iBAAiB,KAAK;OACtB,aAAa,KAAK;OAClB,SAAS,KAAK;OACd,eAAe;OACf,aAAa;OACb,WAAW;OACX;OACA,YAAY,KAAK;OACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;OAClE,YAAY,KAAK;MACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF;KACF;KACA;IACF;IAEA,MAAM,EAAE,QAAQ,MAAM,OAAO;IAC7B,MAAM,KAAK,GACR,OAAO,iBAAiB,CAAC,CACzB,OAAO;KACN,QAAQ,KAAK;KACb,SAAS;IACX,CAAC,CAAC,CACD,mBAAmB;KAClB,QAAQ,kBAAkB;KAC1B,KAAK,EAAE,SAAS,GAAG,mBAAmB;IACxC,CAAC;IAEH,MAAM,KAAK,kBAAkB,QAC3B,iBACE,0BACA;KACE,WAAW,KAAK;KAChB,iBAAiB,KAAK;KACtB,QAAQ,KAAK;KACb,aAAa,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY,CAAC,CAAC,YAAY;KAC7D,sBAAsB,KAAK;IAC7B,GACA,YACA,GAAG,KAAK,OAAO,YAAY,KAAK,IAAI,GACtC,CACF;IAEA;GACF;EACF;EAEA,MAAM,eAAe,KAAK,IAAI;EAC9B,MAAM,iBAAiB,KAAK;EAC5B,IAAI,CAAE,MAAM,KAAK,YAAY,aAAa,gBAAgB,EAAE,GAAI;GAC9D,KAAK,OAAO,KACV;IAAE,WAAW,QAAQ;IAAI,QAAQ,KAAK;IAAQ,SAAS,KAAK;IAAS;GAAQ,GAC7E,+BACF;GACA;EACF;EAEA,IAAI;GACF,MAAM,KAAK,sBAAsB,IAAI,IAAI;GACzC,MAAM,aAAa,KAAK,IAAI,IAAI;GAEhC,gBAAgB,KACd,KAAK,eAAe,IAClB,iBACE,2BACA;IACE,WAAW,KAAK;IAChB,QAAQ,KAAK;IACb,iBAAiB,KAAK;IACtB,aAAa,KAAK;IAClB,SAAS,KAAK;IACd,YAAY,KAAK;IACjB;IACA,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;IAClE,YAAY,KAAK;GACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF,CACF;GAIA,IAAI,KAAK,YAAY,QAAQ;IAC3B,IAAI,aAAkB;KAAE,SAAS;KAAO,OAAO;IAAgB;IAE/D,KAAK,MAAM,aAAa,YACtB,IAAI;KACF,MAAM,QAAQ,KAAK,IAAI;KAGvB,aAAa,MAFG,KAAK,WAAW,GAAG,KAAK,QAAQ,GAAG,UAAU,YAAY,MAEhD,CAAC,CAAC,QAAQ,YAAY;MAC7C,MAAM,YAAY,KAAK,iBAAiB,aAAa;MACrD,MAAM,aAAa,IAAI,gBAAgB;MACvC,MAAM,UAAU,iBAAiB;OAC/B,WAAW,sBAAM,IAAI,MAAM,2BAA2B,UAAU,GAAG,CAAC;MACtE,GAAG,SAAS;MAEZ,IAAI;OACF,KAAc,SAAS,WAAW;OAClC,MAAM,MAAW,MAAM,QAAQ,KAAK,CAClC,UAAU,KAAK,IAAI,GACnB,IAAI,SAAS,GAAG,WAAW;QACzB,IAAI,WAAW,OAAO,SAAS,OAAO,OAAO,WAAW,OAAO,MAAM;QACrE,WAAW,OAAO,iBAAiB,eACjC,OAAO,WAAW,OAAO,MAAM,CACjC;OACF,CAAC,CACH,CAAC;OACD,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,cACvB,MAAM,IAAI,MAAM,IAAI,SAAS,kBAAkB;OAEjD,OAAO;MACT,UAAU;OACR,aAAa,OAAO;MACtB;KACF,CAAC;KAED,OAAgB,WAAW,YAAY,KAAK,IAAI,IAAI;KACpD,IAAI,WAAW,WAAW,WAAW,cAAc;IACrD,SAAS,KAAU;KACjB,aAAa;MAAE,SAAS;MAAO,OAAO,IAAI;KAAQ;IACpD;IAGF,IAAI,WAAW,cAAc;KAC3B,MAAM,KAAK,YAAY,WACrB,KAAK,WACL,KAAK,aACL,KAAK,SACL,KAAK,WACP;KACA,KAAK,OAAO,KACV;MAAE,SAAS,KAAK;MAAS,QAAQ,KAAK;KAAY,GAClD,oCACF;KACA,KAAK,cAAc,KACjB,mBACA,KAAK,QACL,gBACA,KAAK,SACL,KAAK,SACP;KACA,QAAQ,eAAe,IAAI;MAAE,SAAS,KAAK;MAAS,QAAQ;KAAgB,CAAC;KAE7E,gBAAgB,KACd,KAAK,eAAe,IAClB,iBACE,uBACA;MACE,WAAW,KAAK;MAChB,QAAQ,KAAK;MACb,iBAAiB,KAAK;MACtB,aAAa,KAAK;MAClB,SAAS,KAAK;MACd,eAAe;MACf,aAAa;MACb,WAAW;MACX;MACA,YAAY,KAAK;MACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;MAClE,YAAY,KAAK;KACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF,CACF;KAGA,IAAI,CAAC,MAD2B,sBAAsB,eAAe,GAEnE,MAAM,IAAI,kBAAkB,qCAAqC;IAErE,OAAO,IAAI,WAAW,SAAS;KAE7B,MAAM,oBAAoB,WAAW,qBAAqB;KAE1D,gBAAgB,KACd,KAAK,sBAAsB,IAAI;MAC7B,QAAQ,KAAK;MACb,SAAS,KAAK;MACd,aAAa,KAAK;MAClB;KACF,CAAC,CACH;KAEA,KAAK,OAAO,MACV;MAAE,QAAQ,KAAK;MAAQ,WAAW;KAAkB,GACpD,gBACF;KACA,KAAK,cAAc,KACjB,sBACA,KAAK,QACL,mBACA,KAAK,SACL,KAAK,SACP;KACA,QAAQ,gBAAgB,IAAI,EAAE,SAAS,KAAK,QAAQ,CAAC;KAErD,gBAAgB,KACd,KAAK,eAAe,IAClB,iBACE,0BACA;MACE,WAAW,KAAK;MAChB,QAAQ,KAAK;MACb,iBAAiB,KAAK;MACtB,SAAS,KAAK;MACd,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;MACpC;MACA,YAAY,KAAK;MACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;MAClE,YAAY,KAAK;KACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF,CACF;IACF,OAAO;KACL,KAAK,OAAO,KACV;MAAE,QAAQ,KAAK;MAAQ,OAAO,WAAW;KAAM,GAC/C,sBACF;KACA,KAAK,cAAc,KACjB,mBACA,KAAK,QACL,WAAW,SAAS,wBACpB,KAAK,SACL,KAAK,SACP;KACA,QAAQ,eAAe,IAAI;MAAE,SAAS,KAAK;MAAS,QAAQ;KAAa,CAAC;KAE1E,gBAAgB,KACd,KAAK,eAAe,IAClB,iBACE,uBACA;MACE,WAAW,KAAK;MAChB,QAAQ,KAAK;MACb,iBAAiB,KAAK;MACtB,aAAa,KAAK;MAClB,SAAS,KAAK;MACd,eAAe,WAAW,SAAS;MACnC,aAAa;MACb,WAAW;MACX;MACA,YAAY,KAAK;MACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;MAClE,YAAY,KAAK;KACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF,CACF;KAGA,IAAI,CAAC,MAD2B,sBAAsB,sBAAsB,GAE1E,MAAM,IAAI,kBAAkB,WAAW,SAAS,sBAAsB;IAE1E;GACF,OAAO;IAEL,IAAI,SAAc;KAAE,SAAS;KAAO,OAAO;IAAgB;IAC3D,KAAK,MAAM,aAAa,YACtB,IAAI;KACF,MAAM,QAAQ,KAAK,IAAI;KAGvB,SAAS,MAFO,KAAK,WAAW,GAAG,KAAK,QAAQ,GAAG,UAAU,YAAY,MAEpD,CAAC,CAAC,QAAQ,YAAY;MACzC,MAAM,YAAY,KAAK,iBAAiB,aAAa;MACrD,MAAM,aAAa,IAAI,gBAAgB;MACvC,MAAM,UAAU,iBAAiB;OAC/B,WAAW,sBAAM,IAAI,MAAM,2BAA2B,UAAU,GAAG,CAAC;MACtE,GAAG,SAAS;MAEZ,IAAI;OACF,KAAc,SAAS,WAAW;OAClC,MAAM,MAAW,MAAM,QAAQ,KAAK,CAClC,UAAU,KAAK,IAAI,GACnB,IAAI,SAAS,GAAG,WAAW;QACzB,IAAI,WAAW,OAAO,SAAS,OAAO,OAAO,WAAW,OAAO,MAAM;QACrE,WAAW,OAAO,iBAAiB,eACjC,OAAO,WAAW,OAAO,MAAM,CACjC;OACF,CAAC,CACH,CAAC;OACD,IAAI,CAAC,IAAI,SACP,MAAM,IAAI,MAAM,IAAI,SAAS,kBAAkB;OAEjD,OAAO;MACT,UAAU;OACR,aAAa,OAAO;MACtB;KACF,CAAC;KAED,OAAgB,WAAW,YAAY,KAAK,IAAI,IAAI;KACpD,IAAI,OAAO,SAAS;IACtB,SAAS,KAAU;KACjB,SAAS;MAAE,SAAS;MAAO,OAAO,IAAI;KAAQ;IAChD;IAGF,IAAI,OAAO,SAAS;KAElB,MAAM,oBAAoB,OAAO,qBAAqB;KAEtD,gBAAgB,KACd,KAAK,sBAAsB,IAAI;MAC7B,QAAQ,KAAK;MACb,SAAS,KAAK;MACd,aAAa,KAAK;MAClB;KACF,CAAC,CACH;KAEA,KAAK,cAAc,KACjB,sBACA,KAAK,QACL,mBACA,KAAK,SACL,KAAK,SACP;KACA,QAAQ,gBAAgB,IAAI,EAAE,SAAS,KAAK,QAAQ,CAAC;KAErD,gBAAgB,KACd,KAAK,eAAe,IAClB,iBACE,0BACA;MACE,WAAW,KAAK;MAChB,QAAQ,KAAK;MACb,iBAAiB,KAAK;MACtB,SAAS,KAAK;MACd,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;MACpC;MACA,YAAY,KAAK;MACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;MAClE,YAAY,KAAK;KACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF,CACF;IACF,OACE,gBAAgB,KACd,KAAK,eAAe,IAClB,iBACE,uBACA;KACE,WAAW,KAAK;KAChB,QAAQ,KAAK;KACb,iBAAiB,KAAK;KACtB,SAAS,KAAK;KACd,eAAe,OAAO,SAAS;KAC/B,aAAa;KACb,WAAW;KACX;KACA,YAAY,KAAK;KACjB,oBACE,MAAM,SAAS,WAAW,aAAa,MAAM,SAAS,UAAU,KAAA;KAClE,YAAY,KAAK;IACnB,GACA,YACA,MAAM,SAAS,OACjB,CACF,CACF;IAGF,IAAI,CAAC,OAAO,SAAS;KACnB,KAAK,OAAO,KACV;MAAE,QAAQ,KAAK;MAAQ,SAAS,KAAK;MAAS,OAAO,OAAO;KAAM,GAClE,6CACF;KACA,KAAK,cAAc,KACjB,mBACA,KAAK,QACL,OAAO,SAAS,8BAChB,KAAK,SACL,KAAK,SACP;KACA,QAAQ,eAAe,IAAI;MAAE,SAAS,KAAK;MAAS,QAAQ;KAAiB,CAAC;KAG9E,IAAI,CAAC,MAD2B,sBAAsB,sBAAsB,GAE1E,MAAM,IAAI,kBAAkB,OAAO,SAAS,iBAAiB;IAEjE,OACE,KAAK,OAAO,KACV;KAAE,QAAQ,KAAK;KAAQ,SAAS,KAAK;KAAS,WAAW,OAAO;IAAkB,GAClF,wBACF;GAEJ;GAEA,MAAM,cAAc,KAAK,IAAI;GAC7B,MAAM,QAAQ,IAAI,eAAe,CAAC,CAAC,OAAO,QAAQ;IAChD,KAAK,OAAO,MACV;KAAE;KAAK,QAAQ,KAAK;IAAO,GAC3B,wFACF;GACF,CAAC;GACD,MAAM,YAAY,KAAK,IAAI,IAAI;GAE/B,MAAM,KAAK,YAAY,cAAc,cAAc;GAEnD,MAAM,IAAK,OAAgB,aAAc,OAAe,cAAc;IACpE,OAAO;IACP,QAAQ;IACR,UAAU;IACV,OAAO;IACP,KAAK;GACP;GACA,EAAE;GACF,EAAE,UAAU;GACZ,EAAE,SAAS;GAEX,IAAI,EAAE,QAAQ,QAAS,GAAG;IACxB,KAAK,OAAO,MACV,gCAAgC,EAAE,UAAU,IAAK,iBAAiB,EAAE,SAAS,IAAK,gBAAgB,EAAE,WAAW,IAAK,sBAAsB,EAAE,QAAQ,IAAK,WAAW,EAAE,MAAM,IAAK,kCAAkC,EAAE,WAAW,KAAK,IAAI,GAAG,EAAE,UAAU,EAAE,GAC5P;IACA,EAAE,QAAQ;IACV,EAAE,SAAS;IACX,EAAE,WAAW;IACb,EAAE,QAAQ;IACV,EAAE,UAAU;IACZ,EAAE,MAAM;IACR,EAAE,WAAW;IACb,EAAE,aAAa;GACjB;EACF,SAAS,KAAK;GACZ,MAAM,KAAK,YAAY,OAAO,cAAc,CAAC,CAAC,YAAY,CAAC,CAAC;GAC5D,MAAM;EACR;CACF;AACF;AAEA,eAAsB,sBAAsB;CAC1C,SAAS,aAAa;EAAE,MAAM;EAAY,OAAO,OAAO;CAAU,CAAC;CAEnE,QAAQ,IAAI,YAAY;EAAE,KAAK,OAAO;EAAW,MAAM;EAAY;CAAO,CAAC;CAC3E,MAAM,SAAS,eAAe;EAAE,KAAK,OAAO;EAAc,iBAAiB;EAAY;CAAO,CAAC;CAC/F,MAAM,OAAO;CACb,KAAK,OAAO;CACZ,WAAW,IAAI,eAAe;EAC5B,OAAO,MAAM;EACb,QAAQ;EACR,OAAO,gBAAgB;EACvB,UAAU,YAAY,QAAQ;EAC9B,WAAW,QAAQ;EACnB,WAAW,OAAO;EAClB;CACF,CAAC;CAED,oBAAoB,IAAI,eAAe;EACrC,OAAO,MAAM;EACb,QAAQ,QAAQ;EAChB;CACF,CAAC;CAED,iBAAiB,IAAI,sBAAsB;EACzC,OAAO,MAAM;EACb,QAAQ;EACR,OAAO,gBAAgB;EACvB,UAAU,YAAY,QAAQ;EAC9B;CACF,CAAC;CAED,oBAAoB;EAClB,UAAU,IAAI,eAAe;GAC3B,OAAO,MAAM;GACb,QAAQ,QAAQ;GAChB;EACF,CAAC;EACD,QAAQ,IAAI,eAAe;GAAE,OAAO,MAAM;GAAQ,QAAQ,QAAQ;GAAiB;EAAO,CAAC;EAC3F,KAAK,IAAI,eAAe;GAAE,OAAO,MAAM;GAAQ,QAAQ,QAAQ;GAAc;EAAO,CAAC;CACvF;CAEA,MAAM,cAAc,IAAI,kBAAkB,EAAE;CAE5C,MAAM,cAAc,IAAI,iBAAiB;EACvC,OAAO,MAAM;EACb,WAAW;EACX,YAAY;CACd,CAAC;CAUD,MAAM,iBAAiB,IAAI,eAAe;EACxC,OAAO,MAAM;EACb,QAAQ,QAAQ;EAChB;CACF,CAAC;CAED,SAAS,IAAI,eAAe;EAC1B;EACA;EACA;EACA,aAAa,OAAO;EACpB;EACA;EACA,OAAO,MAAM;EACb;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAID,iBAAiB,oBAAoB,YAAY,QAAQ,OAAO,MAAM;CAEtE,OAAO,KACL;EAAE,KAAK,OAAO;EAAU,UAAU,kBAAkB,mBAAmB;CAAE,GACzE,mBACF;CACA,MAAM,OAAO,MAAM;AACrB;AAIA,eAAsB,qBAAoC;CACxD,QAAQ,KAAK,oBAAoB;CACjC,IAAI,gBAAgB;EAClB,cAAc,cAAc;EAC5B,iBAAiB;CACnB;CACA,IAAI,QAAQ,MAAM,OAAO,KAAK;CAC9B,IAAI,KAAK,MAAM,IAAI,IAAI;CACvB,IAAI,OAAO,MAAM,MAAM,WAAW;CAClC,QAAQ,KAAK,kBAAkB;AACjC"}
|
|
1
|
+
{"version":3,"file":"main-pFAfCkVX.mjs","names":[],"sources":["../src/services/delivery/throttle.ts","../src/services/delivery/main.ts"],"sourcesContent":["import type { Redis } from \"ioredis\";\n\nexport interface ThrottleResult {\n allowed: boolean;\n retryAfterMs: number;\n}\n\nconst LUA_THROTTLE = `\n local key = KEYS[1]\n local now = tonumber(ARGV[1])\n local windowSeconds = tonumber(ARGV[2])\n local limit = tonumber(ARGV[3])\n local member = ARGV[4]\n\n local clearBefore = now - (windowSeconds * 1000)\n \n -- Cleanup expired scores\n redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)\n \n -- Get current count\n local count = redis.call('ZCARD', key)\n \n if count >= limit then\n -- Find the oldest score\n local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')\n if oldest and oldest[2] then\n return {0, tonumber(oldest[2])}\n end\n return {0, now}\n end\n \n -- Add new request\n redis.call('ZADD', key, now, member)\n redis.call('EXPIRE', key, windowSeconds * 2)\n return {1, 0}\n`;\n\nexport async function throttleProvider(\n redis: Redis,\n channel: string,\n config: { limit: number; windowSeconds: number },\n logger: any,\n): Promise<ThrottleResult> {\n const key = `rate-limit:provider:${channel}`;\n const now = Date.now();\n const zmember = `${now}:${Math.random()}`;\n\n const result = (await redis.eval(\n LUA_THROTTLE,\n 1,\n key,\n now.toString(),\n config.windowSeconds.toString(),\n config.limit.toString(),\n zmember,\n )) as [number, number];\n\n const allowed = result[0] === 1;\n const oldestTimestamp = result[1];\n\n let retryAfterMs = 0;\n if (!allowed) {\n retryAfterMs = Math.max(0, oldestTimestamp + config.windowSeconds * 1000 - now);\n logger.warn(\n { channel, limit: config.limit, windowSeconds: config.windowSeconds, retryAfterMs },\n \"Provider rate limit hit — task must be rescheduled\",\n );\n }\n\n return { allowed, retryAfterMs };\n}\n","import { loadEnv, parseConfig, baseConfigSchema } from \"@/index.js\";\nimport { createLogger } from \"@/index.js\";\nimport { RedisClient, type Redis } from \"@/index.js\";\nimport {\n StreamConsumer,\n PendingMessageScanner,\n type StreamMessage,\n StreamProducer,\n} from \"@/index.js\";\nimport { BaseWorker } from \"@/index.js\";\nimport {\n STREAMS,\n OUTBOUND_STREAMS,\n CONSUMER_GROUPS,\n registry,\n buildStreamEvent,\n type NotificationDispatchedPayload,\n} from \"@/index.js\";\nimport { type StreamName } from \"@/contracts/streams.js\";\nimport { createDatabase } from \"@/db/index.js\";\nimport { deliveryOutbox, scheduledPayloads } from \"@/db/schema.js\";\nimport { ContactRepository, IdempotencyGuard } from \"@/index.js\";\nimport { transportRegistry } from \"@/index.js\";\nimport { globalEmitter, getPriorityBucket, type WorkerOptions } from \"@/shared/index.js\";\nimport { startHealthReporter, NonRetryableError } from \"@/workers/index.js\";\nimport { BatchProcessor, CircuitBreaker } from \"@/shared/index.js\";\nimport { throttleProvider } from \"./throttle.js\";\nimport { metrics } from \"@/metrics/index.js\";\n\n// ─── App-specific config ────────────────────────────────────────────────────\n\nconst deliveryConfigSchema = baseConfigSchema.extend({});\n\n// ─── Bootstrap ─────────────────────────────────────────────────────────────\n\nloadEnv();\nconst config = parseConfig(deliveryConfigSchema, process.env);\n\nlet logger: ReturnType<typeof createLogger>;\nlet redis: RedisClient;\nlet sql: any;\nlet db: any;\n\nlet consumer: StreamConsumer;\nlet pendingScanner: PendingMessageScanner;\nlet worker: BaseWorker;\nlet scheduledProducer: StreamProducer;\nlet enrichedProducers: Record<string, StreamProducer>;\nlet healthInterval: NodeJS.Timeout | null = null;\n\n(global as any)._telemetry = (global as any)._telemetry || {\n count: 0,\n insert: 0,\n provider: 0,\n flush: 0,\n ack: 0,\n dequeue: 0,\n dbupdate: 0,\n flushCount: 0,\n};\n\nexport interface DeliveryWorkerOptions extends WorkerOptions {\n transportRegistry: any;\n idempotency: any;\n redis: Redis;\n scheduledProducer: any;\n enrichedProducers: any;\n contactRepo: any;\n eventsProducer: any;\n globalEmitter: any;\n db: any;\n}\n\nexport class DeliveryWorker extends BaseWorker {\n private readonly transportRegistry: any;\n private readonly idempotency: any;\n private readonly redisCli: Redis;\n private readonly scheduledProducer: any;\n private readonly enrichedProducers: any;\n private readonly contactRepo: any;\n private readonly eventsProducer: any;\n private readonly globalEmitter: any;\n private readonly db: any;\n\n private eventProcessor: BatchProcessor<any, void>;\n private outboxUpdateProcessor: BatchProcessor<any, void>;\n private outboxInsertProcessor: BatchProcessor<any, boolean>;\n private breakers = new Map<string, CircuitBreaker>();\n\n constructor(options: DeliveryWorkerOptions) {\n super(options);\n this.transportRegistry = options.transportRegistry;\n this.idempotency = options.idempotency;\n this.redisCli = options.redis;\n this.scheduledProducer = options.scheduledProducer;\n this.enrichedProducers = options.enrichedProducers;\n this.contactRepo = options.contactRepo;\n this.eventsProducer = options.eventsProducer;\n this.globalEmitter = options.globalEmitter;\n this.db = options.db;\n\n this.eventProcessor = new BatchProcessor<any, void>(1000, 100, async (events) => {\n await this.eventsProducer.publishBatch(events);\n return events.map(() => undefined as void);\n });\n\n this.outboxUpdateProcessor = new BatchProcessor<any, void>(500, 100, async (updates) => {\n const { sql } = await import(\"drizzle-orm\");\n const values = updates.map((update) => ({\n taskId: update.taskId,\n channel: update.channel,\n destination: update.destination,\n providerMessageId: update.providerMessageId,\n }));\n\n const tDbUpdateStart = Date.now();\n await this.db\n .insert(deliveryOutbox)\n .values(values)\n .onConflictDoUpdate({\n target: [deliveryOutbox.taskId, deliveryOutbox.channel, deliveryOutbox.destination],\n set: { providerMessageId: sql`EXCLUDED.provider_message_id` },\n })\n .catch((e: any) => this.logger.error({ err: e }, \"background update failed\"));\n\n (global as any)._telemetry.dbupdate += Date.now() - tDbUpdateStart;\n (global as any)._telemetry.flushCount++;\n return updates.map(() => undefined as void);\n });\n\n this.outboxInsertProcessor = new BatchProcessor(500, 10, async (tasks) => {\n const values = tasks.map((task: any) => ({\n taskId: task.taskId,\n channel: task.channel,\n destination: task.destination,\n }));\n\n await this.db.insert(deliveryOutbox).values(values).onConflictDoNothing();\n return tasks.map(() => true);\n });\n }\n\n override async stop(): Promise<void> {\n await Promise.all([\n this.eventProcessor.flush(),\n this.outboxUpdateProcessor.flush(),\n this.outboxInsertProcessor.flush(),\n ]);\n await super.stop();\n }\n\n private getBreaker(name: string): CircuitBreaker {\n let breaker = this.breakers.get(name);\n if (!breaker) {\n breaker = new CircuitBreaker({ failureThreshold: 5, resetTimeoutMs: 30000 });\n this.breakers.set(name, breaker);\n }\n return breaker;\n }\n\n async process(message: StreamMessage, attempt: number = 1): Promise<void> {\n const { event } = message;\n const publishPromises: Promise<void>[] = [];\n\n const payloadResult = registry.safeParsePayload(\"notification.dispatched\", event.payload);\n if (!payloadResult.success) {\n this.logger.warn(\n { messageId: message.id, issues: payloadResult.error.issues },\n \"invalid notification.dispatched payload — skipping\",\n );\n return;\n }\n\n const task = payloadResult.data as NotificationDispatchedPayload;\n\n const fallbackToNextChannel = async (reason: string) => {\n if (task.fallbackChain && task.fallbackChain.length > 0 && task.recipient) {\n const nextChannel = task.fallbackChain[0];\n const remainingChain = task.fallbackChain.slice(1);\n\n const fallbackPayload: any = {\n // NotificationEnrichedPayload\n projectId: task.projectId,\n rawEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: nextChannel,\n priority: task.priority,\n templateId: task.templateId,\n templateVariables: task.templateVariables,\n aiPrompts: task.aiPrompts,\n recipient: task.recipient,\n scheduledAt: undefined,\n fallbackChain: remainingChain.length > 0 ? remainingChain : undefined,\n };\n\n const p = getPriorityBucket(task.priority);\n const producer = this.enrichedProducers[p] ?? this.enrichedProducers[\"normal\"]!;\n await producer.publish(\n buildStreamEvent(\n \"notification.enriched\",\n fallbackPayload,\n \"delivery\",\n event.metadata.traceId,\n ),\n );\n\n this.logger.info(\n { taskId: task.taskId, reason, nextChannel, traceId: event.metadata.traceId },\n \"Delivery failed completely, rolling over to next channel in fallback chain\",\n );\n return true; // Indicates fallback was triggered\n }\n return false; // No fallback\n };\n\n const transports = this.transportRegistry.getAll(task.channel);\n\n if (transports.length === 0) {\n this.logger.warn(\n { taskId: task.taskId, channel: task.channel },\n \"no transport registered for channel — dropping\",\n );\n const fallbackTriggered = await fallbackToNextChannel(\"no_transport\");\n if (!fallbackTriggered) {\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n \"no transport\",\n task.channel,\n task.projectId,\n );\n await this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: \"no transport registered for channel\",\n failureCode: \"no_transport\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n );\n }\n return;\n }\n\n const limitConfig = transports[0].limits;\n if (limitConfig) {\n const { allowed, retryAfterMs } = await throttleProvider(\n this.redisCli,\n task.channel,\n limitConfig,\n this.logger,\n );\n if (!allowed) {\n task.throttleAttemptCount = (task.throttleAttemptCount ?? 0) + 1;\n const maxAttempts = task.deliveryOptions?.maxAttempts ?? 3;\n\n if (task.throttleAttemptCount > maxAttempts) {\n this.logger.warn(\n { messageId: message.id, taskId: task.taskId, attempts: task.throttleAttemptCount },\n \"provider rate limit max attempts exceeded\",\n );\n const fallbackTriggered = await fallbackToNextChannel(\"provider_throttle_exceeded\");\n if (!fallbackTriggered) {\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n \"provider throttle exceeded\",\n task.channel,\n task.projectId,\n );\n await this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: \"provider rate limit max attempts exceeded\",\n failureCode: \"provider_throttle_exceeded\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n );\n }\n return;\n }\n\n const { sql } = await import(\"drizzle-orm\");\n await this.db\n .insert(scheduledPayloads)\n .values({\n taskId: task.taskId,\n payload: task,\n })\n .onConflictDoUpdate({\n target: scheduledPayloads.taskId,\n set: { payload: sql`EXCLUDED.payload` },\n });\n\n await this.scheduledProducer.publish(\n buildStreamEvent(\n \"notification.scheduled\",\n {\n projectId: task.projectId,\n enrichedEventId: task.enrichedEventId,\n taskId: task.taskId,\n scheduledAt: new Date(Date.now() + retryAfterMs).toISOString(),\n throttleAttemptCount: task.throttleAttemptCount,\n },\n \"delivery\",\n `${task.taskId}:throttle:${Date.now()}`,\n ),\n );\n\n return;\n }\n }\n\n const tInsertStart = Date.now();\n const idempotencyKey = task.taskId;\n if (!(await this.idempotency.checkAndMark(idempotencyKey, 60))) {\n this.logger.info(\n { messageId: message.id, taskId: task.taskId, channel: task.channel, attempt },\n \"duplicate delivery — skipping\",\n );\n return;\n }\n\n try {\n await this.outboxInsertProcessor.add(task);\n const insertTime = Date.now() - tInsertStart;\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.dispatched\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n templateId: task.templateId,\n attempt,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n\n // For push: we just need to send to the pre-resolved destination,\n // but if it's invalid, we deactivate it.\n if (task.channel === \"push\") {\n let lastResult: any = { success: false, error: \"No transports\" };\n\n for (const transport of transports) {\n try {\n const tProv = Date.now();\n const breaker = this.getBreaker(`${task.channel}:${transport.constructor.name}`);\n\n lastResult = await breaker.execute(async () => {\n const timeoutMs = task.deliveryOptions?.timeoutMs ?? 10_000;\n const controller = new AbortController();\n const timeout = setTimeout(() => {\n controller.abort(new Error(`Transport timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n (task as any).signal = controller.signal;\n const res: any = await Promise.race([\n transport.send(task),\n new Promise((_, reject) => {\n if (controller.signal.aborted) return reject(controller.signal.reason);\n controller.signal.addEventListener(\"abort\", () =>\n reject(controller.signal.reason),\n );\n }),\n ]);\n if (!res.success && !res.invalidToken) {\n throw new Error(res.error ?? \"Transport failed\");\n }\n return res;\n } finally {\n clearTimeout(timeout);\n }\n });\n\n (global as any)._telemetry.provider += Date.now() - tProv;\n if (lastResult.success || lastResult.invalidToken) break;\n } catch (err: any) {\n lastResult = { success: false, error: err.message };\n }\n }\n\n if (lastResult.invalidToken) {\n await this.contactRepo.deactivate(\n task.projectId,\n task.recipientId,\n task.channel as any,\n task.destination,\n );\n this.logger.info(\n { channel: task.channel, target: task.destination },\n \"deactivated invalid contact target\",\n );\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n \"invalidToken\",\n task.channel,\n task.projectId,\n );\n metrics.deliveryFailed.inc({ channel: task.channel, reason: \"invalid_token\" });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: \"invalid_token\",\n failureCode: \"push_failure\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n\n const fallbackTriggered = await fallbackToNextChannel(\"invalid_token\");\n if (!fallbackTriggered) {\n throw new NonRetryableError(\"Push delivery failed: invalid token\");\n }\n } else if (lastResult.success) {\n // Record providerMessageId in outbox before publishing event\n const providerMessageId = lastResult.providerMessageId || \"push-success\";\n\n publishPromises.push(\n this.outboxUpdateProcessor.add({\n taskId: task.taskId,\n channel: task.channel,\n destination: task.destination,\n providerMessageId,\n }),\n );\n\n this.logger.debug(\n { taskId: task.taskId, messageId: providerMessageId },\n \"push delivered\",\n );\n this.globalEmitter.emit(\n \"delivery:delivered\",\n task.taskId,\n providerMessageId,\n task.channel,\n task.projectId,\n );\n metrics.deliverySuccess.inc({ channel: task.channel });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.delivered\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n channel: task.channel,\n deliveredAt: new Date().toISOString(),\n providerMessageId,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n } else {\n this.logger.warn(\n { taskId: task.taskId, error: lastResult.error },\n \"push delivery failed\",\n );\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n lastResult.error ?? \"push delivery failed\",\n task.channel,\n task.projectId,\n );\n metrics.deliveryFailed.inc({ channel: task.channel, reason: \"push_error\" });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n recipientId: task.recipientId,\n channel: task.channel,\n failureReason: lastResult.error ?? \"push delivery failed\",\n failureCode: \"push_failure\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n\n const fallbackTriggered = await fallbackToNextChannel(\"push_delivery_failed\");\n if (!fallbackTriggered) {\n throw new NonRetryableError(lastResult.error ?? \"Push delivery failed\");\n }\n }\n } else {\n // Other channels (email, sms, webhook, whatsapp) use the pre-resolved destination\n let result: any = { success: false, error: \"No transports\" };\n for (const transport of transports) {\n try {\n const tProv = Date.now();\n const breaker = this.getBreaker(`${task.channel}:${transport.constructor.name}`);\n\n result = await breaker.execute(async () => {\n const timeoutMs = task.deliveryOptions?.timeoutMs ?? 10_000;\n const controller = new AbortController();\n const timeout = setTimeout(() => {\n controller.abort(new Error(`Transport timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n (task as any).signal = controller.signal;\n const res: any = await Promise.race([\n transport.send(task),\n new Promise((_, reject) => {\n if (controller.signal.aborted) return reject(controller.signal.reason);\n controller.signal.addEventListener(\"abort\", () =>\n reject(controller.signal.reason),\n );\n }),\n ]);\n if (!res.success) {\n throw new Error(res.error ?? \"Transport failed\");\n }\n return res;\n } finally {\n clearTimeout(timeout);\n }\n });\n\n (global as any)._telemetry.provider += Date.now() - tProv;\n if (result.success) break;\n } catch (err: any) {\n result = { success: false, error: err.message };\n }\n }\n\n if (result.success) {\n // Record providerMessageId in outbox before publishing event\n const providerMessageId = result.providerMessageId || \"success\";\n\n publishPromises.push(\n this.outboxUpdateProcessor.add({\n taskId: task.taskId,\n channel: task.channel,\n destination: task.destination,\n providerMessageId,\n }),\n );\n\n this.globalEmitter.emit(\n \"delivery:delivered\",\n task.taskId,\n providerMessageId,\n task.channel,\n task.projectId,\n );\n metrics.deliverySuccess.inc({ channel: task.channel });\n\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.delivered\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n channel: task.channel,\n deliveredAt: new Date().toISOString(),\n providerMessageId,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n } else {\n publishPromises.push(\n this.eventProcessor.add(\n buildStreamEvent(\n \"notification.failed\",\n {\n projectId: task.projectId,\n taskId: task.taskId,\n enrichedEventId: task.enrichedEventId,\n channel: task.channel,\n failureReason: result.error ?? \"delivery failed completely\",\n failureCode: \"provider_error\",\n retryable: false,\n attempt,\n templateId: task.templateId,\n workflowInstanceId:\n event.metadata.source === \"workflow\" ? event.metadata.traceId : undefined,\n campaignId: task.campaignId,\n },\n \"delivery\",\n event.metadata.traceId,\n ),\n ),\n );\n }\n\n if (!result.success) {\n this.logger.warn(\n { taskId: task.taskId, channel: task.channel, error: result.error },\n \"delivery failed completely across providers\",\n );\n this.globalEmitter.emit(\n \"delivery:failed\",\n task.taskId,\n result.error ?? \"delivery failed completely\",\n task.channel,\n task.projectId,\n );\n metrics.deliveryFailed.inc({ channel: task.channel, reason: \"provider_error\" });\n\n const fallbackTriggered = await fallbackToNextChannel(\"all_providers_failed\");\n if (!fallbackTriggered) {\n throw new NonRetryableError(result.error ?? \"delivery failed\");\n }\n } else {\n this.logger.info(\n { taskId: task.taskId, channel: task.channel, messageId: result.providerMessageId },\n \"notification delivered\",\n );\n }\n }\n\n const tFlushStart = Date.now();\n await Promise.all(publishPromises).catch((err) => {\n this.logger.error(\n { err, taskId: task.taskId },\n \"failed to publish post-dispatch events, swallowing error to prevent duplicate delivery\",\n );\n });\n const flushTime = Date.now() - tFlushStart;\n\n await this.idempotency.markProcessed(idempotencyKey);\n\n const t = ((global as any)._telemetry = (global as any)._telemetry || {\n count: 0,\n insert: 0,\n provider: 0,\n flush: 0,\n ack: 0,\n });\n t.count++;\n t.insert += insertTime;\n t.flush += flushTime;\n\n if (t.count % 1000 === 0) {\n this.logger.debug(\n `[Metrics 1000 msgs] Dequeue: ${t.dequeue / 1000}ms, DB Insert: ${t.insert / 1000}ms, Provider: ${t.provider / 1000}ms, Wait for Flush: ${t.flush / 1000}ms, Ack: ${t.ack / 1000}ms | DB Update (avg per flush): ${t.dbupdate / Math.max(1, t.flushCount)}ms`,\n );\n t.count = 0;\n t.insert = 0;\n t.provider = 0;\n t.flush = 0;\n t.dequeue = 0;\n t.ack = 0;\n t.dbupdate = 0;\n t.flushCount = 0;\n }\n } catch (err) {\n await this.idempotency.unmark(idempotencyKey).catch(() => {});\n throw err;\n }\n }\n}\n\nexport async function startDeliveryWorker() {\n logger = createLogger({ name: \"delivery\", level: config.LOG_LEVEL });\n\n redis = new RedisClient({ url: config.REDIS_URL, name: \"delivery\", logger });\n const dbData = createDatabase({ url: config.DATABASE_URL, applicationName: \"delivery\", logger });\n sql = dbData.sql;\n db = dbData.db;\n consumer = new StreamConsumer({\n redis: redis.native,\n stream: OUTBOUND_STREAMS as unknown as StreamName[],\n group: CONSUMER_GROUPS.DELIVERY,\n consumer: `delivery-${process.pid}`,\n dlqStream: STREAMS.DEAD_LETTER,\n batchSize: config.WORKER_CONCURRENCY,\n logger,\n });\n\n scheduledProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.SCHEDULED,\n logger,\n });\n\n pendingScanner = new PendingMessageScanner({\n redis: redis.native,\n stream: OUTBOUND_STREAMS as unknown as StreamName[],\n group: CONSUMER_GROUPS.DELIVERY,\n consumer: `delivery-${process.pid}`,\n logger,\n });\n\n enrichedProducers = {\n critical: new StreamProducer({\n redis: redis.native,\n stream: STREAMS.ENRICHED_CRITICAL,\n logger,\n }),\n normal: new StreamProducer({ redis: redis.native, stream: STREAMS.ENRICHED_NORMAL, logger }),\n low: new StreamProducer({ redis: redis.native, stream: STREAMS.ENRICHED_LOW, logger }),\n };\n\n const contactRepo = new ContactRepository(db);\n\n const idempotency = new IdempotencyGuard({\n redis: redis.native,\n keyPrefix: \"notif:processed:delivery\",\n ttlSeconds: 86_400,\n });\n\n // ─── Stage 3: Delivery Worker ───────────────────────────────────────────────\n //\n // Pipeline:\n // 1. Parse payload as notification.dispatched\n // 2. Resolve active device tokens from DB\n // 3. Send via registered transport\n // 4. Deactivate invalid tokens in DB\n\n const eventsProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.EVENTS_INBOUND,\n logger,\n });\n\n worker = new DeliveryWorker({\n consumer,\n pendingScanner,\n logger,\n concurrency: config.WORKER_CONCURRENCY,\n transportRegistry,\n idempotency,\n redis: redis.native,\n scheduledProducer,\n enrichedProducers,\n contactRepo,\n eventsProducer,\n globalEmitter,\n db,\n });\n\n // ─── Health check interval ──────────────────────────────────────────────────\n\n healthInterval = startHealthReporter(\"delivery\", worker, redis, logger);\n\n logger.info(\n { env: config.NODE_ENV, channels: transportRegistry.registeredChannels() },\n \"delivery starting\",\n );\n await worker.start();\n}\n\n// ─── Shutdown ──────────────────────────────────────────────────────────────\n\nexport async function stopDeliveryWorker(): Promise<void> {\n logger?.info(\"shutdown initiated\");\n if (healthInterval) {\n clearInterval(healthInterval);\n healthInterval = null;\n }\n if (worker) await worker.stop();\n if (sql) await sql.end();\n if (redis) await redis.disconnect();\n logger?.info(\"delivery stopped\");\n}\n"],"mappings":"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"}
|
|
@@ -291,6 +291,7 @@ const ContactChannelSchema = z.enum([
|
|
|
291
291
|
"sms",
|
|
292
292
|
"push",
|
|
293
293
|
"webhook",
|
|
294
|
+
"in-app",
|
|
294
295
|
"telegram",
|
|
295
296
|
"discord",
|
|
296
297
|
"whatsapp",
|
|
@@ -298,14 +299,24 @@ const ContactChannelSchema = z.enum([
|
|
|
298
299
|
]);
|
|
299
300
|
/** Accept a single value or an array; always normalise to a non-empty array. */
|
|
300
301
|
const stringOrArray = z.union([z.string().min(1), z.array(z.string().min(1))]).transform((v) => Array.isArray(v) ? v : [v]);
|
|
302
|
+
const UserContactInputSchema = z.object({
|
|
303
|
+
channel: ContactChannelSchema,
|
|
304
|
+
target: z.string().min(1),
|
|
305
|
+
label: z.string().optional(),
|
|
306
|
+
isPrimary: z.boolean().optional(),
|
|
307
|
+
enabled: z.boolean().optional(),
|
|
308
|
+
preferences: PreferencesSchema.optional()
|
|
309
|
+
});
|
|
301
310
|
/**
|
|
302
|
-
* addUser({ id, email, phone, pushToken, segments, preferences })
|
|
303
|
-
* email / phone / pushToken accept a single string or an array.
|
|
311
|
+
* addUser({ id, contacts, email, phone, pushToken, segments, preferences })
|
|
312
|
+
* email / phone / pushToken accept a single string or an array for backwards compatibility.
|
|
313
|
+
* Prefer `contacts: [{ channel, target }]`.
|
|
304
314
|
*/
|
|
305
315
|
const AddUserSchema = z.object({
|
|
306
316
|
id: z.string().min(1),
|
|
307
317
|
language: z.string().optional(),
|
|
308
318
|
timezone: z.string().optional(),
|
|
319
|
+
contacts: z.array(UserContactInputSchema).optional(),
|
|
309
320
|
email: stringOrArray.optional(),
|
|
310
321
|
phone: stringOrArray.optional(),
|
|
311
322
|
pushToken: stringOrArray.optional(),
|
|
@@ -316,18 +327,23 @@ const AddUserSchema = z.object({
|
|
|
316
327
|
const UpdateUserSchema = z.object({
|
|
317
328
|
language: z.string().optional(),
|
|
318
329
|
timezone: z.string().optional(),
|
|
330
|
+
contacts: z.array(UserContactInputSchema).optional(),
|
|
319
331
|
email: stringOrArray.optional(),
|
|
320
332
|
phone: stringOrArray.optional(),
|
|
321
333
|
pushToken: stringOrArray.optional(),
|
|
322
334
|
segments: z.array(z.string().min(1)).optional(),
|
|
323
335
|
preferences: PreferencesSchema.optional()
|
|
324
336
|
});
|
|
325
|
-
/** addUserContact(userId, channel, { target, preferences
|
|
337
|
+
/** addUserContact(userId, channel, { target, preferences, label, isPrimary, enabled }) */
|
|
326
338
|
const AddContactSchema = z.object({
|
|
327
339
|
channel: ContactChannelSchema,
|
|
328
340
|
target: z.string().min(1),
|
|
341
|
+
label: z.string().optional(),
|
|
342
|
+
isPrimary: z.boolean().optional(),
|
|
343
|
+
enabled: z.boolean().optional(),
|
|
329
344
|
preferences: PreferencesSchema.optional()
|
|
330
345
|
});
|
|
346
|
+
const BatchAddContactsSchema = z.union([AddContactSchema, z.array(AddContactSchema).min(1)]);
|
|
331
347
|
const TemplateSchema = z.object({
|
|
332
348
|
id: z.string().min(1),
|
|
333
349
|
channel: NotificationChannelSchema,
|
|
@@ -341,6 +357,7 @@ const InlineUserSchema = z.object({
|
|
|
341
357
|
id: z.string().min(1),
|
|
342
358
|
language: z.string().optional(),
|
|
343
359
|
timezone: z.string().optional(),
|
|
360
|
+
contacts: z.array(UserContactInputSchema).optional(),
|
|
344
361
|
email: stringOrArray.optional(),
|
|
345
362
|
phone: stringOrArray.optional(),
|
|
346
363
|
pushToken: stringOrArray.optional(),
|
|
@@ -3064,29 +3081,41 @@ var NotifkitClient = class {
|
|
|
3064
3081
|
}
|
|
3065
3082
|
return data;
|
|
3066
3083
|
}
|
|
3067
|
-
/** Sync templates with the server. */
|
|
3084
|
+
/** Sync templates with the server. Accepts either { templates: [...] } or an array of templates directly. */
|
|
3068
3085
|
async syncTemplates(input) {
|
|
3069
|
-
|
|
3086
|
+
const payload = Array.isArray(input) ? { templates: input } : input;
|
|
3087
|
+
return this.request("/v1/templates", "PUT", payload);
|
|
3088
|
+
}
|
|
3089
|
+
async addUser(idOrInput, contacts, options) {
|
|
3090
|
+
if (typeof idOrInput === "string") return this.request("/v1/users", "POST", {
|
|
3091
|
+
id: idOrInput,
|
|
3092
|
+
contacts: contacts ?? [],
|
|
3093
|
+
...options
|
|
3094
|
+
});
|
|
3095
|
+
return this.request("/v1/users", "POST", idOrInput);
|
|
3070
3096
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
return this.request("/v1/users", "POST", input);
|
|
3097
|
+
async identify(idOrInput, contacts, options) {
|
|
3098
|
+
return this.addUser(idOrInput, contacts, options);
|
|
3074
3099
|
}
|
|
3075
3100
|
/** Update user profile. */
|
|
3076
3101
|
async updateUser(id, input) {
|
|
3077
|
-
return this.request(`/v1/users/${id}`, "PATCH", input);
|
|
3102
|
+
return this.request(`/v1/users/${encodeURIComponent(id)}`, "PATCH", input);
|
|
3078
3103
|
}
|
|
3079
3104
|
/** Delete user profile. */
|
|
3080
3105
|
async deleteUser(id) {
|
|
3081
|
-
return this.request(`/v1/users/${id}`, "DELETE");
|
|
3106
|
+
return this.request(`/v1/users/${encodeURIComponent(id)}`, "DELETE");
|
|
3082
3107
|
}
|
|
3083
|
-
/** Add contact
|
|
3108
|
+
/** Add contact target to user profile. */
|
|
3084
3109
|
async addContact(userId, input) {
|
|
3085
|
-
return this.request(`/v1/users/${userId}/contacts`, "POST", input);
|
|
3110
|
+
return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts`, "POST", input);
|
|
3111
|
+
}
|
|
3112
|
+
/** Add multiple contact targets to user profile in a batch. */
|
|
3113
|
+
async addContacts(userId, contacts) {
|
|
3114
|
+
return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts`, "POST", contacts);
|
|
3086
3115
|
}
|
|
3087
3116
|
/** Delete a specific contact channel target. */
|
|
3088
3117
|
async deleteContact(userId, channel, target) {
|
|
3089
|
-
return this.request(`/v1/users/${userId}/contacts/${channel}/${target}`, "DELETE");
|
|
3118
|
+
return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts/${encodeURIComponent(channel)}/${encodeURIComponent(target)}`, "DELETE");
|
|
3090
3119
|
}
|
|
3091
3120
|
/** Request a notification dispatch. */
|
|
3092
3121
|
async notify(input) {
|
|
@@ -3119,11 +3148,11 @@ var NotifkitClient = class {
|
|
|
3119
3148
|
}
|
|
3120
3149
|
/** Get a workflow instance by ID. */
|
|
3121
3150
|
async getWorkflow(instanceId) {
|
|
3122
|
-
return this.request(`/v1/workflows/instances/${instanceId}`, "GET");
|
|
3151
|
+
return this.request(`/v1/workflows/instances/${encodeURIComponent(instanceId)}`, "GET");
|
|
3123
3152
|
}
|
|
3124
3153
|
/** Cancel a running/suspended workflow instance. */
|
|
3125
3154
|
async cancelWorkflow(instanceId) {
|
|
3126
|
-
return this.request(`/v1/workflows/instances/${instanceId}`, "DELETE");
|
|
3155
|
+
return this.request(`/v1/workflows/instances/${encodeURIComponent(instanceId)}`, "DELETE");
|
|
3127
3156
|
}
|
|
3128
3157
|
/** Get notification logs for the project. */
|
|
3129
3158
|
async getNotificationLogs(options) {
|
|
@@ -3159,11 +3188,11 @@ var NotifkitClient = class {
|
|
|
3159
3188
|
}
|
|
3160
3189
|
/** Delete a template. */
|
|
3161
3190
|
async deleteTemplate(id) {
|
|
3162
|
-
return this.request(`/v1/templates/${id}`, "DELETE");
|
|
3191
|
+
return this.request(`/v1/templates/${encodeURIComponent(id)}`, "DELETE");
|
|
3163
3192
|
}
|
|
3164
3193
|
/** Get a user's contacts. */
|
|
3165
3194
|
async getUserContacts(userId) {
|
|
3166
|
-
return this.request(`/v1/users/${userId}/contacts`, "GET");
|
|
3195
|
+
return this.request(`/v1/users/${encodeURIComponent(userId)}/contacts`, "GET");
|
|
3167
3196
|
}
|
|
3168
3197
|
/** List projects (Admin only). */
|
|
3169
3198
|
async listProjects() {
|
|
@@ -3171,23 +3200,23 @@ var NotifkitClient = class {
|
|
|
3171
3200
|
}
|
|
3172
3201
|
/** Delete a project (Admin only). */
|
|
3173
3202
|
async deleteProject(id) {
|
|
3174
|
-
return this.request(`/v1/projects/${id}`, "DELETE");
|
|
3203
|
+
return this.request(`/v1/projects/${encodeURIComponent(id)}`, "DELETE");
|
|
3175
3204
|
}
|
|
3176
3205
|
/** Create a new project API key (Admin only). */
|
|
3177
3206
|
async createProjectKey(id, input) {
|
|
3178
|
-
return this.request(`/v1/projects/${id}/keys`, "POST", input || {});
|
|
3207
|
+
return this.request(`/v1/projects/${encodeURIComponent(id)}/keys`, "POST", input || {});
|
|
3179
3208
|
}
|
|
3180
3209
|
/** List project API keys (Admin only). */
|
|
3181
3210
|
async listProjectKeys(id) {
|
|
3182
|
-
return this.request(`/v1/projects/${id}/keys`, "GET");
|
|
3211
|
+
return this.request(`/v1/projects/${encodeURIComponent(id)}/keys`, "GET");
|
|
3183
3212
|
}
|
|
3184
3213
|
/** Delete a project API key (Admin only). */
|
|
3185
3214
|
async deleteProjectKey(id, keyId) {
|
|
3186
|
-
return this.request(`/v1/projects/${id}/keys/${keyId}`, "DELETE");
|
|
3215
|
+
return this.request(`/v1/projects/${encodeURIComponent(id)}/keys/${encodeURIComponent(keyId)}`, "DELETE");
|
|
3187
3216
|
}
|
|
3188
3217
|
/** Update project settings (Admin only). */
|
|
3189
3218
|
async updateProject(id, input) {
|
|
3190
|
-
return this.request(`/v1/projects/${id}`, "PATCH", input);
|
|
3219
|
+
return this.request(`/v1/projects/${encodeURIComponent(id)}`, "PATCH", input);
|
|
3191
3220
|
}
|
|
3192
3221
|
/** List unique segment tags. */
|
|
3193
3222
|
async listSegments() {
|
|
@@ -3373,35 +3402,35 @@ var NotifkitServer = class extends EventEmitter {
|
|
|
3373
3402
|
}
|
|
3374
3403
|
const startupPromises = [];
|
|
3375
3404
|
if (services.includes("api")) {
|
|
3376
|
-
const { startApiServer } = await import("./main-
|
|
3405
|
+
const { startApiServer } = await import("./main-CFSukWm8.mjs");
|
|
3377
3406
|
startupPromises.push(startApiServer());
|
|
3378
3407
|
}
|
|
3379
3408
|
if (services.includes("delivery")) {
|
|
3380
|
-
const { startDeliveryWorker } = await import("./main-
|
|
3409
|
+
const { startDeliveryWorker } = await import("./main-pFAfCkVX.mjs");
|
|
3381
3410
|
startupPromises.push(startDeliveryWorker());
|
|
3382
3411
|
}
|
|
3383
3412
|
if (services.includes("engine")) {
|
|
3384
|
-
const { startEngineWorker } = await import("./main-
|
|
3413
|
+
const { startEngineWorker } = await import("./main-Yorxzw0Z.mjs");
|
|
3385
3414
|
startupPromises.push(startEngineWorker());
|
|
3386
3415
|
}
|
|
3387
3416
|
if (services.includes("enricher")) {
|
|
3388
|
-
const { startEnricherWorker } = await import("./main-
|
|
3417
|
+
const { startEnricherWorker } = await import("./main-Bgi2wWck.mjs");
|
|
3389
3418
|
startupPromises.push(startEnricherWorker());
|
|
3390
3419
|
}
|
|
3391
3420
|
if (services.includes("scheduler")) {
|
|
3392
|
-
const { startSchedulerWorker } = await import("./main-
|
|
3421
|
+
const { startSchedulerWorker } = await import("./main-DFMHcN_d.mjs");
|
|
3393
3422
|
startupPromises.push(startSchedulerWorker());
|
|
3394
3423
|
}
|
|
3395
3424
|
if (services.includes("ai")) {
|
|
3396
|
-
const { startAiWorker } = await import("./main-
|
|
3425
|
+
const { startAiWorker } = await import("./main-D6SG3Isk.mjs");
|
|
3397
3426
|
startupPromises.push(startAiWorker());
|
|
3398
3427
|
}
|
|
3399
3428
|
if (services.includes("workflow")) {
|
|
3400
|
-
const { startWorkflowWorker } = await import("./main-
|
|
3429
|
+
const { startWorkflowWorker } = await import("./main-CjbVdUqa.mjs");
|
|
3401
3430
|
startupPromises.push(startWorkflowWorker());
|
|
3402
3431
|
}
|
|
3403
3432
|
if (services.includes("events")) {
|
|
3404
|
-
const { startEventWorker } = await import("./main-
|
|
3433
|
+
const { startEventWorker } = await import("./main-CRPxM-O3.mjs");
|
|
3405
3434
|
startupPromises.push(startEventWorker());
|
|
3406
3435
|
}
|
|
3407
3436
|
const handleSignal = async (signal) => {
|
|
@@ -3441,35 +3470,35 @@ var NotifkitServer = class extends EventEmitter {
|
|
|
3441
3470
|
"events"
|
|
3442
3471
|
] : this.options.services;
|
|
3443
3472
|
if (services.includes("api")) {
|
|
3444
|
-
const { stopApiServer } = await import("./main-
|
|
3473
|
+
const { stopApiServer } = await import("./main-CFSukWm8.mjs");
|
|
3445
3474
|
await stopApiServer();
|
|
3446
3475
|
}
|
|
3447
3476
|
if (services.includes("delivery")) {
|
|
3448
|
-
const { stopDeliveryWorker } = await import("./main-
|
|
3477
|
+
const { stopDeliveryWorker } = await import("./main-pFAfCkVX.mjs");
|
|
3449
3478
|
await stopDeliveryWorker();
|
|
3450
3479
|
}
|
|
3451
3480
|
if (services.includes("engine")) {
|
|
3452
|
-
const { stopEngineWorker } = await import("./main-
|
|
3481
|
+
const { stopEngineWorker } = await import("./main-Yorxzw0Z.mjs");
|
|
3453
3482
|
await stopEngineWorker();
|
|
3454
3483
|
}
|
|
3455
3484
|
if (services.includes("enricher")) {
|
|
3456
|
-
const { stopEnricherWorker } = await import("./main-
|
|
3485
|
+
const { stopEnricherWorker } = await import("./main-Bgi2wWck.mjs");
|
|
3457
3486
|
await stopEnricherWorker();
|
|
3458
3487
|
}
|
|
3459
3488
|
if (services.includes("scheduler")) {
|
|
3460
|
-
const { stopSchedulerWorker } = await import("./main-
|
|
3489
|
+
const { stopSchedulerWorker } = await import("./main-DFMHcN_d.mjs");
|
|
3461
3490
|
await stopSchedulerWorker();
|
|
3462
3491
|
}
|
|
3463
3492
|
if (services.includes("ai")) {
|
|
3464
|
-
const { stopAiWorker } = await import("./main-
|
|
3493
|
+
const { stopAiWorker } = await import("./main-D6SG3Isk.mjs");
|
|
3465
3494
|
await stopAiWorker();
|
|
3466
3495
|
}
|
|
3467
3496
|
if (services.includes("workflow")) {
|
|
3468
|
-
const { stopWorkflowWorker } = await import("./main-
|
|
3497
|
+
const { stopWorkflowWorker } = await import("./main-CjbVdUqa.mjs");
|
|
3469
3498
|
await stopWorkflowWorker();
|
|
3470
3499
|
}
|
|
3471
3500
|
if (services.includes("events")) {
|
|
3472
|
-
const { stopEventWorker } = await import("./main-
|
|
3501
|
+
const { stopEventWorker } = await import("./main-CRPxM-O3.mjs");
|
|
3473
3502
|
await stopEventWorker();
|
|
3474
3503
|
}
|
|
3475
3504
|
if (this.pgContainer) {
|
|
@@ -3483,6 +3512,6 @@ var NotifkitServer = class extends EventEmitter {
|
|
|
3483
3512
|
}
|
|
3484
3513
|
};
|
|
3485
3514
|
//#endregion
|
|
3486
|
-
export { childLogger as $,
|
|
3515
|
+
export { childLogger as $, INBOUND_STREAMS as $t, ProjectSettingsCache as A, NotificationRequestedPayloadSchema as At, LUA_RELEASE_LOCK as B, PreferencesSchema as Bt, ProjectRepository as C, DeliveryOptionsSchema as Ct, WorkflowRepository as D, NotificationEnrichedPayloadSchema as Dt, UserRepository as E, NotificationScheduledPayloadSchema as Et, sleep as F, ContactChannelSchema as Ft, normaliseTarget as G, UpdateProjectSchema as Gt, LUA_SCHEDULER_CLAIM as H, SyncTemplatesSchema as Ht, DataLoader as I, CreateWorkflowSchema as It, PendingMessageScanner as J, WorkflowNotifyPayloadSchema as Jt, LRUCache as K, UpdateUserSchema as Kt, CircuitBreaker as L, IngestEventSchema as Lt, AppError as M, AddContactSchema as Mt, ValidationError as N, AddUserSchema as Nt, Redis as O, RecipientProfileSchema as Ot, generateId as P, BatchAddContactsSchema as Pt, metrics as Q, ENRICHED_STREAMS as Qt, BatchProcessor as R, InlineUserSchema as Rt, PreferenceRepository as S, NotificationDeliveredPayloadSchema as St, TemplateRepository as T, RenderedContentSchema as Tt, LUA_SCHEDULER_POLL as U, TemplateSchema as Ut, LUA_RENEW_LOCK as V, QuietHoursSchema as Vt, getPriorityBucket as W, TriggerWorkflowSchema as Wt, StreamProducer as X, buildStreamEvent as Xt, StreamConsumer as Y, WorkflowStepSchema as Yt, getMetricsRegistry as Z, CONSUMER_GROUPS as Zt, escapeHeader as _, readBaseConfig as _n, workflowWaiters as _t, SuspendExecutionError as a, StreamEventSchema as an, runMigrations as at, renderWithTemplate as b, NotificationSkippedPayloadSchema as bt, BaseWorker as c, EventMetadataSchema as cn, projectApiKeys as ct, buildUnsubscribeHeaders as d, NotificationStatusSchema as dn, suppressions as dt, OUTBOUND_STREAMS as en, createLogger as et, signUnsubscribeToken as f, AI_DEFAULTS as fn, userTopicPreferences as ft, TemplateCache as g, parseConfig as gn, workflowSteps as gt, templateRegistry as h, loadEnv as hn, workflowInstances as ht, workflowRegistry as i, StreamEventMetadataSchema as in, createDatabase as it, UserThrottle as j, NotificationTargetSchema as jt, RedisClient as k, NotificationCreatedPayloadSchema as kt, NonRetryableError as l, NotificationChannelSchema as ln, projects as lt, renderTemplate as m, getAiConfig as mn, workflowDefinitions as mt, NotifkitClient as n, STREAMS as nn, withRequestId as nt, buildStepNotifyPayload as o, EventRegistry as on, deliveryOutbox as ot, verifyUnsubscribeToken as p, baseConfigSchema as pn, users as pt, globalEmitter as q, UserContactInputSchema as qt, workflow as r, EventEnvelopeSchema as rn, IdempotencyGuard as rt, resolveStepTarget as s, registry as sn, messageLogs as st, NotifkitServer as t, PUBSUB_CHANNELS as tn, withContext as tt, startHealthReporter as u, NotificationPrioritySchema as un, scheduledPayloads as ut, escapeHtml as v, setAiConfig as vn, NotificationAiPendingPayloadSchema as vt, SegmentRepository as w, NotificationDispatchedPayloadSchema as wt, ContactRepository as x, NotificationFailedPayloadSchema as xt, interpolate as y, setGlobalConfig as yn, NotificationCanceledPayloadSchema as yt, AsyncSemaphore as z, NotifyRequestSchema as zt };
|
|
3487
3516
|
|
|
3488
|
-
//# sourceMappingURL=src-
|
|
3517
|
+
//# sourceMappingURL=src-CPMwsUCJ.mjs.map
|