notifkit 0.1.0 → 0.1.2
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 +352 -51
- package/dist/index.d.mts +31 -8
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{main-D-oWWzR3.mjs → main-B-jwm8ED.mjs} +2 -2
- package/dist/{main-D-oWWzR3.mjs.map → main-B-jwm8ED.mjs.map} +1 -1
- package/dist/{main-Dlfy9mWs.mjs → main-B561M1d3.mjs} +2 -2
- package/dist/{main-Dlfy9mWs.mjs.map → main-B561M1d3.mjs.map} +1 -1
- package/dist/{main-BHYZfBBq.mjs → main-C45e7grq.mjs} +2 -2
- package/dist/{main-BHYZfBBq.mjs.map → main-C45e7grq.mjs.map} +1 -1
- package/dist/{main-Dztc2dqR.mjs → main-CAH0_Q6d.mjs} +2 -2
- package/dist/{main-Dztc2dqR.mjs.map → main-CAH0_Q6d.mjs.map} +1 -1
- package/dist/{main-ClEeP5qw.mjs → main-CCfc45ev.mjs} +2 -2
- package/dist/{main-ClEeP5qw.mjs.map → main-CCfc45ev.mjs.map} +1 -1
- package/dist/{main-BIcKzWHE.mjs → main-Ce9dcrsg.mjs} +2 -2
- package/dist/{main-BIcKzWHE.mjs.map → main-Ce9dcrsg.mjs.map} +1 -1
- package/dist/{main-Ok9cQJ7q.mjs → main-DtHWhueo.mjs} +56 -19
- package/dist/main-DtHWhueo.mjs.map +1 -0
- package/dist/{main-4H6vNXvy.mjs → main-DyfbnJc3.mjs} +2 -2
- package/dist/{main-4H6vNXvy.mjs.map → main-DyfbnJc3.mjs.map} +1 -1
- package/dist/{src-DrSN2wCg.mjs → src-C-PfEDMY.mjs} +63 -30
- package/dist/src-C-PfEDMY.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/main-Ok9cQJ7q.mjs.map +0 -1
- package/dist/src-DrSN2wCg.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-4H6vNXvy.mjs","names":["sql"],"sources":["../src/services/workflow/main.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { loadEnv, readBaseConfig } from \"@/index.js\";\nimport { createLogger } from \"@/index.js\";\nimport { RedisClient, type Redis } from \"@/index.js\";\nimport {\n StreamConsumer,\n PendingMessageScanner,\n StreamProducer,\n type StreamMessage,\n} from \"@/index.js\";\nimport { BaseWorker } from \"@/index.js\";\nimport { STREAMS, CONSUMER_GROUPS, buildStreamEvent } from \"@/index.js\";\nimport { type StreamName } from \"@/contracts/streams.js\";\nimport { createDatabase } from \"@/db/index.js\";\nimport {\n workflowInstances,\n workflowSteps,\n workflowWaiters,\n workflowDefinitions,\n} from \"@/db/schema.js\";\nimport { eq, and } from \"drizzle-orm\";\nimport {\n workflowRegistry,\n SuspendExecutionError,\n buildStepNotifyPayload,\n type WorkflowContext,\n type WorkflowStepContext,\n} from \"@/workflows/index.js\";\nimport {\n type WorkerOptions,\n LUA_SCHEDULER_POLL,\n LUA_RELEASE_LOCK,\n LUA_RENEW_LOCK,\n} from \"@/shared/index.js\";\nimport { startHealthReporter } from \"@/workers/index.js\";\n\n/** How long a workflow instance lock is held before it self-expires. */\nconst WORKFLOW_LOCK_TTL_SECONDS = 60;\n/** Renew the lock well inside its TTL so long-running handlers keep it. */\nconst WORKFLOW_LOCK_RENEW_MS = (WORKFLOW_LOCK_TTL_SECONDS / 3) * 1000;\n/** How long a claimed workflow timer stays invisible to other pollers. */\nconst WORKFLOW_TIMER_VISIBILITY_MS = 60_000;\n\nloadEnv();\nconst config = readBaseConfig();\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 healthInterval: NodeJS.Timeout | null = null;\nlet pollInterval: NodeJS.Timeout | null = null;\nlet reaperInterval: NodeJS.Timeout | null = null;\n\nlet notificationProducer: StreamProducer;\nlet workflowProducer: StreamProducer;\n\nexport interface WorkflowWorkerOptions extends WorkerOptions {\n redis: Redis;\n db: any;\n workflowProducer: any;\n notificationProducer: any;\n}\n\nexport class WorkflowWorker extends BaseWorker {\n private readonly redisCli: Redis;\n private readonly dbConn: any;\n private readonly workflowProducer: any;\n private readonly notificationProducer: any;\n\n private eventBuffer: {\n producer: any;\n event: any;\n resolve: (result?: { messageId: string; notificationId: string }) => void;\n reject: (err: any) => void;\n }[] = [];\n private flushTimer: NodeJS.Timeout | null = null;\n\n constructor(options: WorkflowWorkerOptions) {\n super(options);\n this.redisCli = options.redis;\n this.dbConn = options.db;\n this.workflowProducer = options.workflowProducer;\n this.notificationProducer = options.notificationProducer;\n\n this.flushTimer = setInterval(() => void this.flushWorkerBuffers(), 100);\n }\n\n override async stop(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushWorkerBuffers();\n await super.stop();\n }\n\n private async flushWorkerBuffers(): Promise<void> {\n if (this.eventBuffer.length === 0) return;\n\n const events = this.eventBuffer;\n this.eventBuffer = [];\n\n try {\n const byProducer = new Map<any, typeof events>();\n for (const item of events) {\n if (!byProducer.has(item.producer)) byProducer.set(item.producer, []);\n byProducer.get(item.producer)!.push(item);\n }\n\n for (const [producer, batch] of byProducer) {\n const { messageIds, eventIds } = await producer.publishBatch(batch.map((b) => b.event));\n for (let i = 0; i < batch.length; i++) {\n const mId = messageIds[i];\n const eId = eventIds[i];\n if (mId && eId) batch[i]!.resolve({ messageId: mId, notificationId: eId });\n }\n }\n } catch (err: any) {\n this.logger.error({ err }, \"failed to flush workflow worker buffer\");\n for (const e of events) e.reject(err);\n }\n }\n\n async process(message: StreamMessage): Promise<void> {\n const { event } = message;\n const publishPromises: Promise<void>[] = [];\n\n if (event.type !== \"workflow.triggered\" && event.type !== \"workflow.resumed\") {\n return;\n }\n\n const payload = event.payload as any;\n const name = payload.name;\n const instanceId = payload.instanceId;\n\n if (!name || !instanceId || !payload.projectId) {\n this.logger.warn(\"Missing name, instanceId, or projectId in workflow event\");\n return;\n }\n\n let handler = workflowRegistry.get(name);\n if (!handler) {\n // Fallback: check dynamic JSON workflows\n const defRows = await this.dbConn\n .select()\n .from(workflowDefinitions)\n .where(\n and(\n eq(workflowDefinitions.projectId, payload.projectId),\n eq(workflowDefinitions.name, name),\n ),\n )\n .limit(1);\n\n if (defRows.length === 0) {\n this.logger.warn(\n { name, projectId: payload.projectId },\n \"No handler or dynamic definition found for workflow\",\n );\n return;\n }\n\n const def = defRows[0];\n handler = async ({ step }) => {\n const steps = def.steps as any[];\n for (const stepDef of steps) {\n if (stepDef.action === \"notify\") {\n // A payload target wins; naming none inherits the instance user.\n await step.notify(stepDef.payload);\n } else if (stepDef.action === \"wait\") {\n await step.wait(stepDef.duration);\n } else if (stepDef.action === \"waitForEvent\") {\n await step.waitForEvent(stepDef.event, stepDef.options);\n } else {\n this.logger.warn(\n { action: (stepDef as any)?.action, name },\n \"unknown workflow step action\",\n );\n }\n }\n };\n }\n\n const lockKey = `lock:workflow:${instanceId}`;\n const lockToken = randomUUID();\n const acquired = await this.redisCli.set(\n lockKey,\n lockToken,\n \"EX\",\n WORKFLOW_LOCK_TTL_SECONDS,\n \"NX\",\n );\n if (!acquired) {\n this.logger.info({ instanceId }, \"Workflow is locked by another process, skipping\");\n return;\n }\n\n // Keep the lock alive while the handler runs; without this a handler that\n // outlives the TTL lets a second resume execute the same steps in parallel.\n const renewTimer = setInterval(() => {\n void this.redisCli\n .eval(LUA_RENEW_LOCK, 1, lockKey, lockToken, String(WORKFLOW_LOCK_TTL_SECONDS))\n .catch((err: unknown) => {\n this.logger.warn({ err, instanceId }, \"failed to renew workflow lock\");\n });\n }, WORKFLOW_LOCK_RENEW_MS);\n\n try {\n let instance = (\n await this.dbConn\n .select()\n .from(workflowInstances)\n .where(eq(workflowInstances.id, instanceId))\n .limit(1)\n )[0];\n if (!instance) {\n const rows = await this.dbConn\n .insert(workflowInstances)\n .values({\n id: instanceId,\n projectId: payload.projectId,\n name: name,\n status: \"pending\",\n input: payload.input || {},\n })\n .returning();\n instance = rows[0]!;\n }\n\n if (instance.status !== \"pending\") {\n this.logger.info({ instanceId }, \"Workflow is not pending, skipping\");\n return;\n }\n\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"running\" })\n .where(eq(workflowInstances.id, instanceId));\n\n // Load existing steps\n const existingSteps = await this.dbConn\n .select()\n .from(workflowSteps)\n .where(eq(workflowSteps.instanceId, instanceId));\n const stepOutputMap = new Map<string, any>();\n for (const s of existingSteps) {\n stepOutputMap.set(s.stepIndex, s.output);\n }\n\n let currentStepIndex = 0;\n\n const stepProxy: WorkflowStepContext = {\n notify: async (args) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) return stepOutputMap.get(stepId);\n\n // The step payload is the same shape as notify(), but the wire event\n // is not — translate rather than spread.\n const requested = buildStepNotifyPayload(\n args,\n instance!.input,\n payload.projectId,\n `wf-${instanceId}-${stepId}`,\n );\n\n const result = await new Promise<{ messageId: string; notificationId: string }>(\n (resolve, reject) => {\n this.eventBuffer.push({\n producer: this.notificationProducer,\n event: buildStreamEvent(\n \"notification.requested\",\n requested as unknown as Record<string, unknown>,\n \"workflow\",\n event.metadata.traceId,\n ),\n resolve: resolve as any,\n reject,\n });\n },\n );\n\n const output = {\n success: true,\n messageId: result.messageId,\n notificationId: result.notificationId,\n };\n\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"notify\",\n output,\n });\n\n return output;\n },\n wait: async (duration) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) return;\n\n // Simple duration parse (e.g. '2h' -> ms)\n let ms = 0;\n if (duration.endsWith(\"d\")) ms = parseInt(duration) * 24 * 60 * 60 * 1000;\n else if (duration.endsWith(\"h\")) ms = parseInt(duration) * 60 * 60 * 1000;\n else if (duration.endsWith(\"m\")) ms = parseInt(duration) * 60 * 1000;\n else if (duration.endsWith(\"s\")) ms = parseInt(duration) * 1000;\n else throw new Error(`Invalid wait duration: ${duration}`);\n\n const resumeAt = Date.now() + ms;\n\n // Persist the wake-up signal before committing the suspended state.\n // If the process dies after this point the source event is retried;\n // if it dies after the database write, the timer is already durable.\n await this.redisCli.zadd(\n \"notif:workflow:timers\",\n resumeAt,\n JSON.stringify({\n instanceId: instance!.id,\n name: instance!.name,\n projectId: payload.projectId,\n input: instance!.input,\n }),\n );\n\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"wait\",\n output: { scheduledAt: resumeAt },\n });\n\n throw new SuspendExecutionError(\"wait\", { duration });\n },\n waitForEvent: async (eventName, options) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) {\n const out = stepOutputMap.get(stepId);\n if (out && typeof out === \"object\" && (out as any).timedOut === true) {\n return null;\n }\n return out;\n }\n\n options = options || {};\n options.timeout = options.timeout || \"24h\";\n options.match = options.match || {};\n\n let ms = 0;\n if (options.timeout.endsWith(\"d\")) ms = parseInt(options.timeout) * 24 * 60 * 60 * 1000;\n else if (options.timeout.endsWith(\"h\")) ms = parseInt(options.timeout) * 60 * 60 * 1000;\n else if (options.timeout.endsWith(\"m\")) ms = parseInt(options.timeout) * 60 * 1000;\n else if (options.timeout.endsWith(\"s\")) ms = parseInt(options.timeout) * 1000;\n else throw new Error(`Invalid waitForEvent timeout: ${options.timeout}`);\n\n const resumeAt = Date.now() + ms;\n\n await this.redisCli.zadd(\n \"notif:workflow:timers\",\n resumeAt,\n JSON.stringify({\n instanceId: instance!.id,\n name: instance!.name,\n projectId: payload.projectId,\n input: instance!.input,\n isEventTimeout: true,\n eventName,\n stepId,\n }),\n );\n\n // Register waiter\n await this.dbConn.insert(workflowWaiters).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n eventName: eventName,\n matchCriteria: options.match,\n expiresAt: new Date(resumeAt),\n });\n\n // Register step as pending event\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"waitForEvent\",\n output: null, // this will be updated by event worker or timeout\n });\n\n throw new SuspendExecutionError(\"waitForEvent\", { eventName });\n },\n run: async (stepName, fn) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) return stepOutputMap.get(stepId);\n\n const result = await fn();\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"run\",\n output: result,\n });\n return result;\n },\n };\n\n const ctx: WorkflowContext = {\n step: stepProxy,\n event: (instance!.input as any) || { user: { id: \"unknown\" } },\n };\n\n try {\n await handler(ctx);\n // If we reach here, workflow completed\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"completed\" })\n .where(eq(workflowInstances.id, instance!.id));\n this.logger.info({ instanceId: instance!.id }, \"Workflow completed successfully\");\n } catch (err: any) {\n if (err instanceof SuspendExecutionError || err.name === \"SuspendExecutionError\") {\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"pending\" })\n .where(eq(workflowInstances.id, instance!.id));\n this.logger.info({ instanceId: instance!.id, reason: err.reason }, \"Workflow suspended\");\n } else {\n this.logger.error({ err, instanceId: instance!.id }, \"Workflow failed\");\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"failed\" })\n .where(eq(workflowInstances.id, instance!.id));\n }\n }\n } finally {\n clearInterval(renewTimer);\n // Compare-and-delete: never release a lock a later process re-acquired.\n await this.redisCli.eval(LUA_RELEASE_LOCK, 1, lockKey, lockToken);\n }\n\n await Promise.all(publishPromises);\n }\n}\n\nexport function __injectForTests(r: any, d: any, wp: any, np: any) {\n redis = r;\n db = d;\n workflowProducer = wp;\n notificationProducer = np;\n}\n\nexport async function startWorkflowWorker() {\n logger = createLogger({ name: \"workflow-worker\", level: config.LOG_LEVEL });\n redis = new RedisClient({ url: config.REDIS_URL, name: \"workflow\", logger });\n const dbData = createDatabase({ url: config.DATABASE_URL, applicationName: \"workflow\", logger });\n sql = dbData.sql;\n db = dbData.db;\n notificationProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.INBOUND_NORMAL,\n logger,\n });\n workflowProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.WORKFLOW_INBOUND,\n logger,\n });\n consumer = new StreamConsumer({\n redis: redis.native,\n stream: STREAMS.WORKFLOW_INBOUND as StreamName,\n group: CONSUMER_GROUPS.WORKFLOW as any,\n consumer: `workflow-${process.pid}`,\n dlqStream: STREAMS.DEAD_LETTER,\n batchSize: config.WORKER_CONCURRENCY,\n logger,\n });\n\n pendingScanner = new PendingMessageScanner({\n redis: redis.native,\n stream: STREAMS.WORKFLOW_INBOUND as StreamName,\n group: CONSUMER_GROUPS.WORKFLOW as any,\n consumer: `workflow-${process.pid}`,\n logger,\n });\n\n worker = new WorkflowWorker({\n consumer,\n pendingScanner,\n logger,\n concurrency: config.WORKER_CONCURRENCY,\n redis: redis.native,\n db,\n workflowProducer,\n notificationProducer,\n });\n\n // Polling loop for timers\n pollInterval = setInterval(() => {\n void (async () => {\n try {\n const now = Date.now();\n const tasks = (await redis.native.eval(\n LUA_SCHEDULER_POLL,\n 1,\n \"notif:workflow:timers\",\n now,\n 100,\n WORKFLOW_TIMER_VISIBILITY_MS,\n )) as string[];\n\n for (const taskStr of tasks) {\n const task = JSON.parse(taskStr);\n\n // Check if workflow is already completed/failed\n const inst = (\n await db\n .select()\n .from(workflowInstances)\n .where(eq(workflowInstances.id, task.instanceId))\n .limit(1)\n )[0];\n if (!inst || inst.status === \"completed\" || inst.status === \"failed\") {\n await redis.native.zrem(\"notif:workflow:timers\", taskStr);\n continue;\n }\n // A timer can be observed while the worker is still persisting the\n // corresponding suspension. Leave the claimed member in place; its\n // visibility timeout will make it eligible once the instance becomes\n // pending instead of losing the wake-up signal.\n if (inst.status === \"running\") continue;\n\n if (task.isEventTimeout) {\n // It's a timeout for waitForEvent. Clean up only this step's waiter\n const deleted = await db\n .delete(workflowWaiters)\n .where(\n and(\n eq(workflowWaiters.instanceId, task.instanceId),\n eq(workflowWaiters.eventName, task.eventName),\n ),\n )\n .returning();\n\n // If the waiter was already deleted (by EventWorker when event arrived before timeout),\n // this timer is stale. Clean up from Redis and skip re-resuming the workflow.\n if (deleted.length === 0) {\n await redis.native.zrem(\"notif:workflow:timers\", taskStr);\n continue;\n }\n\n // Record timedOut state on the pending waitForEvent step\n const steps = await db\n .select()\n .from(workflowSteps)\n .where(\n and(\n eq(workflowSteps.instanceId, task.instanceId),\n eq(workflowSteps.action, \"waitForEvent\"),\n ),\n );\n const pendingStep = steps.find((s: any) => s.output === null);\n if (pendingStep) {\n await db\n .update(workflowSteps)\n .set({ output: { timedOut: true } })\n .where(eq(workflowSteps.id, pendingStep.id));\n }\n }\n\n await workflowProducer.publish(\n buildStreamEvent(\"workflow.resumed\", task, \"scheduler\", undefined),\n );\n await redis.native.zrem(\"notif:workflow:timers\", taskStr);\n logger.info({ instanceId: task.instanceId }, \"Workflow resumed from timer\");\n }\n } catch (err) {\n logger.error({ err }, \"error in workflow polling loop\");\n }\n })();\n }, 5000);\n\n reaperInterval = setInterval(() => {\n void (async () => {\n try {\n const runningInstances = await db\n .select({ id: workflowInstances.id })\n .from(workflowInstances)\n .where(eq(workflowInstances.status, \"running\"));\n for (const inst of runningInstances) {\n const lockKey = `lock:workflow:${inst.id}`;\n const hasLock = await redis.native.exists(lockKey);\n if (!hasLock) {\n await db\n .update(workflowInstances)\n .set({ status: \"pending\" })\n .where(eq(workflowInstances.id, inst.id));\n logger.info({ instanceId: inst.id }, \"Reaped stuck workflow instance (lock expired)\");\n }\n }\n } catch (err) {\n logger.error({ err }, \"error in stuck workflow reaper\");\n }\n })();\n }, 60000);\n\n healthInterval = startHealthReporter(\"workflow\", worker, redis, logger);\n\n logger.info(\"workflow worker starting\");\n await worker.start();\n}\n\nexport async function stopWorkflowWorker(): Promise<void> {\n logger?.info(\"shutdown initiated\");\n if (healthInterval) clearInterval(healthInterval);\n if (pollInterval) clearInterval(pollInterval);\n if (reaperInterval) clearInterval(reaperInterval);\n if (worker) await worker.stop();\n if (sql) await sql.end();\n if (redis) await redis.disconnect();\n logger?.info(\"workflow worker stopped\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"main-DyfbnJc3.mjs","names":["sql"],"sources":["../src/services/workflow/main.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { loadEnv, readBaseConfig } from \"@/index.js\";\nimport { createLogger } from \"@/index.js\";\nimport { RedisClient, type Redis } from \"@/index.js\";\nimport {\n StreamConsumer,\n PendingMessageScanner,\n StreamProducer,\n type StreamMessage,\n} from \"@/index.js\";\nimport { BaseWorker } from \"@/index.js\";\nimport { STREAMS, CONSUMER_GROUPS, buildStreamEvent } from \"@/index.js\";\nimport { type StreamName } from \"@/contracts/streams.js\";\nimport { createDatabase } from \"@/db/index.js\";\nimport {\n workflowInstances,\n workflowSteps,\n workflowWaiters,\n workflowDefinitions,\n} from \"@/db/schema.js\";\nimport { eq, and } from \"drizzle-orm\";\nimport {\n workflowRegistry,\n SuspendExecutionError,\n buildStepNotifyPayload,\n type WorkflowContext,\n type WorkflowStepContext,\n} from \"@/workflows/index.js\";\nimport {\n type WorkerOptions,\n LUA_SCHEDULER_POLL,\n LUA_RELEASE_LOCK,\n LUA_RENEW_LOCK,\n} from \"@/shared/index.js\";\nimport { startHealthReporter } from \"@/workers/index.js\";\n\n/** How long a workflow instance lock is held before it self-expires. */\nconst WORKFLOW_LOCK_TTL_SECONDS = 60;\n/** Renew the lock well inside its TTL so long-running handlers keep it. */\nconst WORKFLOW_LOCK_RENEW_MS = (WORKFLOW_LOCK_TTL_SECONDS / 3) * 1000;\n/** How long a claimed workflow timer stays invisible to other pollers. */\nconst WORKFLOW_TIMER_VISIBILITY_MS = 60_000;\n\nloadEnv();\nconst config = readBaseConfig();\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 healthInterval: NodeJS.Timeout | null = null;\nlet pollInterval: NodeJS.Timeout | null = null;\nlet reaperInterval: NodeJS.Timeout | null = null;\n\nlet notificationProducer: StreamProducer;\nlet workflowProducer: StreamProducer;\n\nexport interface WorkflowWorkerOptions extends WorkerOptions {\n redis: Redis;\n db: any;\n workflowProducer: any;\n notificationProducer: any;\n}\n\nexport class WorkflowWorker extends BaseWorker {\n private readonly redisCli: Redis;\n private readonly dbConn: any;\n private readonly workflowProducer: any;\n private readonly notificationProducer: any;\n\n private eventBuffer: {\n producer: any;\n event: any;\n resolve: (result?: { messageId: string; notificationId: string }) => void;\n reject: (err: any) => void;\n }[] = [];\n private flushTimer: NodeJS.Timeout | null = null;\n\n constructor(options: WorkflowWorkerOptions) {\n super(options);\n this.redisCli = options.redis;\n this.dbConn = options.db;\n this.workflowProducer = options.workflowProducer;\n this.notificationProducer = options.notificationProducer;\n\n this.flushTimer = setInterval(() => void this.flushWorkerBuffers(), 100);\n }\n\n override async stop(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n await this.flushWorkerBuffers();\n await super.stop();\n }\n\n private async flushWorkerBuffers(): Promise<void> {\n if (this.eventBuffer.length === 0) return;\n\n const events = this.eventBuffer;\n this.eventBuffer = [];\n\n try {\n const byProducer = new Map<any, typeof events>();\n for (const item of events) {\n if (!byProducer.has(item.producer)) byProducer.set(item.producer, []);\n byProducer.get(item.producer)!.push(item);\n }\n\n for (const [producer, batch] of byProducer) {\n const { messageIds, eventIds } = await producer.publishBatch(batch.map((b) => b.event));\n for (let i = 0; i < batch.length; i++) {\n const mId = messageIds[i];\n const eId = eventIds[i];\n if (mId && eId) batch[i]!.resolve({ messageId: mId, notificationId: eId });\n }\n }\n } catch (err: any) {\n this.logger.error({ err }, \"failed to flush workflow worker buffer\");\n for (const e of events) e.reject(err);\n }\n }\n\n async process(message: StreamMessage): Promise<void> {\n const { event } = message;\n const publishPromises: Promise<void>[] = [];\n\n if (event.type !== \"workflow.triggered\" && event.type !== \"workflow.resumed\") {\n return;\n }\n\n const payload = event.payload as any;\n const name = payload.name;\n const instanceId = payload.instanceId;\n\n if (!name || !instanceId || !payload.projectId) {\n this.logger.warn(\"Missing name, instanceId, or projectId in workflow event\");\n return;\n }\n\n let handler = workflowRegistry.get(name);\n if (!handler) {\n // Fallback: check dynamic JSON workflows\n const defRows = await this.dbConn\n .select()\n .from(workflowDefinitions)\n .where(\n and(\n eq(workflowDefinitions.projectId, payload.projectId),\n eq(workflowDefinitions.name, name),\n ),\n )\n .limit(1);\n\n if (defRows.length === 0) {\n this.logger.warn(\n { name, projectId: payload.projectId },\n \"No handler or dynamic definition found for workflow\",\n );\n return;\n }\n\n const def = defRows[0];\n handler = async ({ step }) => {\n const steps = def.steps as any[];\n for (const stepDef of steps) {\n if (stepDef.action === \"notify\") {\n // A payload target wins; naming none inherits the instance user.\n await step.notify(stepDef.payload);\n } else if (stepDef.action === \"wait\") {\n await step.wait(stepDef.duration);\n } else if (stepDef.action === \"waitForEvent\") {\n await step.waitForEvent(stepDef.event, stepDef.options);\n } else {\n this.logger.warn(\n { action: (stepDef as any)?.action, name },\n \"unknown workflow step action\",\n );\n }\n }\n };\n }\n\n const lockKey = `lock:workflow:${instanceId}`;\n const lockToken = randomUUID();\n const acquired = await this.redisCli.set(\n lockKey,\n lockToken,\n \"EX\",\n WORKFLOW_LOCK_TTL_SECONDS,\n \"NX\",\n );\n if (!acquired) {\n this.logger.info({ instanceId }, \"Workflow is locked by another process, skipping\");\n return;\n }\n\n // Keep the lock alive while the handler runs; without this a handler that\n // outlives the TTL lets a second resume execute the same steps in parallel.\n const renewTimer = setInterval(() => {\n void this.redisCli\n .eval(LUA_RENEW_LOCK, 1, lockKey, lockToken, String(WORKFLOW_LOCK_TTL_SECONDS))\n .catch((err: unknown) => {\n this.logger.warn({ err, instanceId }, \"failed to renew workflow lock\");\n });\n }, WORKFLOW_LOCK_RENEW_MS);\n\n try {\n let instance = (\n await this.dbConn\n .select()\n .from(workflowInstances)\n .where(eq(workflowInstances.id, instanceId))\n .limit(1)\n )[0];\n if (!instance) {\n const rows = await this.dbConn\n .insert(workflowInstances)\n .values({\n id: instanceId,\n projectId: payload.projectId,\n name: name,\n status: \"pending\",\n input: payload.input || {},\n })\n .returning();\n instance = rows[0]!;\n }\n\n if (instance.status !== \"pending\") {\n this.logger.info({ instanceId }, \"Workflow is not pending, skipping\");\n return;\n }\n\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"running\" })\n .where(eq(workflowInstances.id, instanceId));\n\n // Load existing steps\n const existingSteps = await this.dbConn\n .select()\n .from(workflowSteps)\n .where(eq(workflowSteps.instanceId, instanceId));\n const stepOutputMap = new Map<string, any>();\n for (const s of existingSteps) {\n stepOutputMap.set(s.stepIndex, s.output);\n }\n\n let currentStepIndex = 0;\n\n const stepProxy: WorkflowStepContext = {\n notify: async (args) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) return stepOutputMap.get(stepId);\n\n // The step payload is the same shape as notify(), but the wire event\n // is not — translate rather than spread.\n const requested = buildStepNotifyPayload(\n args,\n instance!.input,\n payload.projectId,\n `wf-${instanceId}-${stepId}`,\n );\n\n const result = await new Promise<{ messageId: string; notificationId: string }>(\n (resolve, reject) => {\n this.eventBuffer.push({\n producer: this.notificationProducer,\n event: buildStreamEvent(\n \"notification.requested\",\n requested as unknown as Record<string, unknown>,\n \"workflow\",\n event.metadata.traceId,\n ),\n resolve: resolve as any,\n reject,\n });\n },\n );\n\n const output = {\n success: true,\n messageId: result.messageId,\n notificationId: result.notificationId,\n };\n\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"notify\",\n output,\n });\n\n return output;\n },\n wait: async (duration) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) return;\n\n // Simple duration parse (e.g. '2h' -> ms)\n let ms = 0;\n if (duration.endsWith(\"d\")) ms = parseInt(duration) * 24 * 60 * 60 * 1000;\n else if (duration.endsWith(\"h\")) ms = parseInt(duration) * 60 * 60 * 1000;\n else if (duration.endsWith(\"m\")) ms = parseInt(duration) * 60 * 1000;\n else if (duration.endsWith(\"s\")) ms = parseInt(duration) * 1000;\n else throw new Error(`Invalid wait duration: ${duration}`);\n\n const resumeAt = Date.now() + ms;\n\n // Persist the wake-up signal before committing the suspended state.\n // If the process dies after this point the source event is retried;\n // if it dies after the database write, the timer is already durable.\n await this.redisCli.zadd(\n \"notif:workflow:timers\",\n resumeAt,\n JSON.stringify({\n instanceId: instance!.id,\n name: instance!.name,\n projectId: payload.projectId,\n input: instance!.input,\n }),\n );\n\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"wait\",\n output: { scheduledAt: resumeAt },\n });\n\n throw new SuspendExecutionError(\"wait\", { duration });\n },\n waitForEvent: async (eventName, options) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) {\n const out = stepOutputMap.get(stepId);\n if (out && typeof out === \"object\" && (out as any).timedOut === true) {\n return null;\n }\n return out;\n }\n\n options = options || {};\n options.timeout = options.timeout || \"24h\";\n options.match = options.match || {};\n\n let ms = 0;\n if (options.timeout.endsWith(\"d\")) ms = parseInt(options.timeout) * 24 * 60 * 60 * 1000;\n else if (options.timeout.endsWith(\"h\")) ms = parseInt(options.timeout) * 60 * 60 * 1000;\n else if (options.timeout.endsWith(\"m\")) ms = parseInt(options.timeout) * 60 * 1000;\n else if (options.timeout.endsWith(\"s\")) ms = parseInt(options.timeout) * 1000;\n else throw new Error(`Invalid waitForEvent timeout: ${options.timeout}`);\n\n const resumeAt = Date.now() + ms;\n\n await this.redisCli.zadd(\n \"notif:workflow:timers\",\n resumeAt,\n JSON.stringify({\n instanceId: instance!.id,\n name: instance!.name,\n projectId: payload.projectId,\n input: instance!.input,\n isEventTimeout: true,\n eventName,\n stepId,\n }),\n );\n\n // Register waiter\n await this.dbConn.insert(workflowWaiters).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n eventName: eventName,\n matchCriteria: options.match,\n expiresAt: new Date(resumeAt),\n });\n\n // Register step as pending event\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"waitForEvent\",\n output: null, // this will be updated by event worker or timeout\n });\n\n throw new SuspendExecutionError(\"waitForEvent\", { eventName });\n },\n run: async (stepName, fn) => {\n const stepId = String(currentStepIndex++);\n if (stepOutputMap.has(stepId)) return stepOutputMap.get(stepId);\n\n const result = await fn();\n await this.dbConn.insert(workflowSteps).values({\n instanceId: instance!.id,\n projectId: payload.projectId,\n stepIndex: stepId,\n action: \"run\",\n output: result,\n });\n return result;\n },\n };\n\n const ctx: WorkflowContext = {\n step: stepProxy,\n event: (instance!.input as any) || { user: { id: \"unknown\" } },\n };\n\n try {\n await handler(ctx);\n // If we reach here, workflow completed\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"completed\" })\n .where(eq(workflowInstances.id, instance!.id));\n this.logger.info({ instanceId: instance!.id }, \"Workflow completed successfully\");\n } catch (err: any) {\n if (err instanceof SuspendExecutionError || err.name === \"SuspendExecutionError\") {\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"pending\" })\n .where(eq(workflowInstances.id, instance!.id));\n this.logger.info({ instanceId: instance!.id, reason: err.reason }, \"Workflow suspended\");\n } else {\n this.logger.error({ err, instanceId: instance!.id }, \"Workflow failed\");\n await this.dbConn\n .update(workflowInstances)\n .set({ status: \"failed\" })\n .where(eq(workflowInstances.id, instance!.id));\n }\n }\n } finally {\n clearInterval(renewTimer);\n // Compare-and-delete: never release a lock a later process re-acquired.\n await this.redisCli.eval(LUA_RELEASE_LOCK, 1, lockKey, lockToken);\n }\n\n await Promise.all(publishPromises);\n }\n}\n\nexport function __injectForTests(r: any, d: any, wp: any, np: any) {\n redis = r;\n db = d;\n workflowProducer = wp;\n notificationProducer = np;\n}\n\nexport async function startWorkflowWorker() {\n logger = createLogger({ name: \"workflow-worker\", level: config.LOG_LEVEL });\n redis = new RedisClient({ url: config.REDIS_URL, name: \"workflow\", logger });\n const dbData = createDatabase({ url: config.DATABASE_URL, applicationName: \"workflow\", logger });\n sql = dbData.sql;\n db = dbData.db;\n notificationProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.INBOUND_NORMAL,\n logger,\n });\n workflowProducer = new StreamProducer({\n redis: redis.native,\n stream: STREAMS.WORKFLOW_INBOUND,\n logger,\n });\n consumer = new StreamConsumer({\n redis: redis.native,\n stream: STREAMS.WORKFLOW_INBOUND as StreamName,\n group: CONSUMER_GROUPS.WORKFLOW as any,\n consumer: `workflow-${process.pid}`,\n dlqStream: STREAMS.DEAD_LETTER,\n batchSize: config.WORKER_CONCURRENCY,\n logger,\n });\n\n pendingScanner = new PendingMessageScanner({\n redis: redis.native,\n stream: STREAMS.WORKFLOW_INBOUND as StreamName,\n group: CONSUMER_GROUPS.WORKFLOW as any,\n consumer: `workflow-${process.pid}`,\n logger,\n });\n\n worker = new WorkflowWorker({\n consumer,\n pendingScanner,\n logger,\n concurrency: config.WORKER_CONCURRENCY,\n redis: redis.native,\n db,\n workflowProducer,\n notificationProducer,\n });\n\n // Polling loop for timers\n pollInterval = setInterval(() => {\n void (async () => {\n try {\n const now = Date.now();\n const tasks = (await redis.native.eval(\n LUA_SCHEDULER_POLL,\n 1,\n \"notif:workflow:timers\",\n now,\n 100,\n WORKFLOW_TIMER_VISIBILITY_MS,\n )) as string[];\n\n for (const taskStr of tasks) {\n const task = JSON.parse(taskStr);\n\n // Check if workflow is already completed/failed\n const inst = (\n await db\n .select()\n .from(workflowInstances)\n .where(eq(workflowInstances.id, task.instanceId))\n .limit(1)\n )[0];\n if (!inst || inst.status === \"completed\" || inst.status === \"failed\") {\n await redis.native.zrem(\"notif:workflow:timers\", taskStr);\n continue;\n }\n // A timer can be observed while the worker is still persisting the\n // corresponding suspension. Leave the claimed member in place; its\n // visibility timeout will make it eligible once the instance becomes\n // pending instead of losing the wake-up signal.\n if (inst.status === \"running\") continue;\n\n if (task.isEventTimeout) {\n // It's a timeout for waitForEvent. Clean up only this step's waiter\n const deleted = await db\n .delete(workflowWaiters)\n .where(\n and(\n eq(workflowWaiters.instanceId, task.instanceId),\n eq(workflowWaiters.eventName, task.eventName),\n ),\n )\n .returning();\n\n // If the waiter was already deleted (by EventWorker when event arrived before timeout),\n // this timer is stale. Clean up from Redis and skip re-resuming the workflow.\n if (deleted.length === 0) {\n await redis.native.zrem(\"notif:workflow:timers\", taskStr);\n continue;\n }\n\n // Record timedOut state on the pending waitForEvent step\n const steps = await db\n .select()\n .from(workflowSteps)\n .where(\n and(\n eq(workflowSteps.instanceId, task.instanceId),\n eq(workflowSteps.action, \"waitForEvent\"),\n ),\n );\n const pendingStep = steps.find((s: any) => s.output === null);\n if (pendingStep) {\n await db\n .update(workflowSteps)\n .set({ output: { timedOut: true } })\n .where(eq(workflowSteps.id, pendingStep.id));\n }\n }\n\n await workflowProducer.publish(\n buildStreamEvent(\"workflow.resumed\", task, \"scheduler\", undefined),\n );\n await redis.native.zrem(\"notif:workflow:timers\", taskStr);\n logger.info({ instanceId: task.instanceId }, \"Workflow resumed from timer\");\n }\n } catch (err) {\n logger.error({ err }, \"error in workflow polling loop\");\n }\n })();\n }, 5000);\n\n reaperInterval = setInterval(() => {\n void (async () => {\n try {\n const runningInstances = await db\n .select({ id: workflowInstances.id })\n .from(workflowInstances)\n .where(eq(workflowInstances.status, \"running\"));\n for (const inst of runningInstances) {\n const lockKey = `lock:workflow:${inst.id}`;\n const hasLock = await redis.native.exists(lockKey);\n if (!hasLock) {\n await db\n .update(workflowInstances)\n .set({ status: \"pending\" })\n .where(eq(workflowInstances.id, inst.id));\n logger.info({ instanceId: inst.id }, \"Reaped stuck workflow instance (lock expired)\");\n }\n }\n } catch (err) {\n logger.error({ err }, \"error in stuck workflow reaper\");\n }\n })();\n }, 60000);\n\n healthInterval = startHealthReporter(\"workflow\", worker, redis, logger);\n\n logger.info(\"workflow worker starting\");\n await worker.start();\n}\n\nexport async function stopWorkflowWorker(): Promise<void> {\n logger?.info(\"shutdown initiated\");\n if (healthInterval) clearInterval(healthInterval);\n if (pollInterval) clearInterval(pollInterval);\n if (reaperInterval) clearInterval(reaperInterval);\n if (worker) await worker.stop();\n if (sql) await sql.end();\n if (redis) await redis.disconnect();\n logger?.info(\"workflow worker stopped\");\n}\n"],"mappings":"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"}
|
|
@@ -2180,13 +2180,21 @@ var UserRepository = class {
|
|
|
2180
2180
|
async delete(projectId, userId) {
|
|
2181
2181
|
return (await this.db.delete(users).where(and(eq(users.externalId, userId), eq(users.projectId, projectId))).returning()).length > 0;
|
|
2182
2182
|
}
|
|
2183
|
-
async list(projectId, limit, cursor) {
|
|
2184
|
-
|
|
2183
|
+
async list(projectId, limit, cursor, filters) {
|
|
2184
|
+
const conditions = [eq(users.projectId, projectId)];
|
|
2185
2185
|
if (cursor) {
|
|
2186
2186
|
const cursorDate = new Date(parseInt(cursor, 10));
|
|
2187
|
-
|
|
2187
|
+
if (!isNaN(cursorDate.getTime())) conditions.push(sql`${users.createdAt} < ${cursorDate.toISOString()}`);
|
|
2188
2188
|
}
|
|
2189
|
-
|
|
2189
|
+
if (filters?.language) conditions.push(sql`(${users.attributes}->>'language') = ${filters.language}`);
|
|
2190
|
+
if (filters?.timezone) conditions.push(sql`(${users.attributes}->>'timezone') = ${filters.timezone}`);
|
|
2191
|
+
if (filters?.search) {
|
|
2192
|
+
const term = `%${filters.search.trim()}%`;
|
|
2193
|
+
conditions.push(sql`(${users.externalId} ILIKE ${term} OR (${users.attributes}->>'email') ILIKE ${term})`);
|
|
2194
|
+
}
|
|
2195
|
+
if (filters?.segment) conditions.push(sql`EXISTS (SELECT 1 FROM ${userSegments} WHERE ${userSegments.userId} = ${users.id} AND ${userSegments.segment} = ${filters.segment})`);
|
|
2196
|
+
if (filters?.channel) conditions.push(sql`EXISTS (SELECT 1 FROM ${userContacts} WHERE ${userContacts.userId} = ${users.id} AND ${userContacts.channel} = ${filters.channel})`);
|
|
2197
|
+
const items = (await this.db.select().from(users).where(and(...conditions)).orderBy(desc(users.createdAt)).limit(limit)).map((r) => {
|
|
2190
2198
|
const attrs = r.attributes;
|
|
2191
2199
|
return {
|
|
2192
2200
|
userId: r.externalId,
|
|
@@ -2530,7 +2538,9 @@ function escapeHeader(unsafe) {
|
|
|
2530
2538
|
return String(unsafe).replace(/[\r\n]+/g, " ").trim();
|
|
2531
2539
|
}
|
|
2532
2540
|
function interpolate(tmpl, variables, sanitize = true) {
|
|
2533
|
-
return tmpl.replace(/\{\{(\w+)\}\}/g, (_, k) => {
|
|
2541
|
+
return tmpl.replace(/\{\{\{(\w+)\}\}\}/g, (_, k) => {
|
|
2542
|
+
return String(variables[k] ?? "");
|
|
2543
|
+
}).replace(/\{\{(\w+)\}\}/g, (_, k) => {
|
|
2534
2544
|
const val = String(variables[k] ?? "");
|
|
2535
2545
|
return sanitize ? escapeHtml(val) : val;
|
|
2536
2546
|
});
|
|
@@ -2565,12 +2575,15 @@ function applyEscape(value, mode) {
|
|
|
2565
2575
|
/**
|
|
2566
2576
|
* Interpolate `{{var}}` placeholders in a single leaf string.
|
|
2567
2577
|
*
|
|
2568
|
-
*
|
|
2569
|
-
*
|
|
2570
|
-
* data cannot break out of it.
|
|
2578
|
+
* `{{{var}}}` (triple braces) interpolates raw unescaped values.
|
|
2579
|
+
* `{{var}}` (double braces) applies contextual escaping to the substituted value.
|
|
2571
2580
|
*/
|
|
2572
2581
|
function interpolateLeaf(tmpl, variables, mode) {
|
|
2573
|
-
return tmpl.replace(/\{\{(\w+)\}\}/g, (_, k) => {
|
|
2582
|
+
return tmpl.replace(/\{\{\{(\w+)\}\}\}/g, (_, k) => {
|
|
2583
|
+
const raw = variables[k];
|
|
2584
|
+
if (raw === void 0 || raw === null) return "";
|
|
2585
|
+
return typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
2586
|
+
}).replace(/\{\{(\w+)\}\}/g, (_, k) => {
|
|
2574
2587
|
const raw = variables[k];
|
|
2575
2588
|
if (raw === void 0 || raw === null) return "";
|
|
2576
2589
|
return applyEscape(typeof raw === "string" ? raw : JSON.stringify(raw), mode);
|
|
@@ -3075,8 +3088,12 @@ var NotifkitClient = class {
|
|
|
3075
3088
|
return this.syncTemplates({ templates: this.options.templates });
|
|
3076
3089
|
}
|
|
3077
3090
|
/** List registered workflow definitions. */
|
|
3078
|
-
async listWorkflows() {
|
|
3079
|
-
|
|
3091
|
+
async listWorkflows(options) {
|
|
3092
|
+
const params = new URLSearchParams();
|
|
3093
|
+
if (options?.limit) params.set("limit", options.limit.toString());
|
|
3094
|
+
if (options?.search) params.set("search", options.search);
|
|
3095
|
+
const qs = params.toString();
|
|
3096
|
+
return this.request(`/v1/workflows${qs ? `?${qs}` : ""}`, "GET");
|
|
3080
3097
|
}
|
|
3081
3098
|
/** Get a workflow instance by ID. */
|
|
3082
3099
|
async getWorkflow(instanceId) {
|
|
@@ -3097,6 +3114,9 @@ var NotifkitClient = class {
|
|
|
3097
3114
|
if (options.workflowInstanceId) params.append("workflowInstanceId", options.workflowInstanceId);
|
|
3098
3115
|
if (options.channel) params.append("channel", options.channel);
|
|
3099
3116
|
if (options.status) params.append("status", options.status);
|
|
3117
|
+
if (options.taskId) params.append("taskId", options.taskId);
|
|
3118
|
+
if (options.campaign) params.append("campaign", options.campaign);
|
|
3119
|
+
if (options.search) params.append("search", options.search);
|
|
3100
3120
|
const str = params.toString();
|
|
3101
3121
|
if (str) url += `?${str}`;
|
|
3102
3122
|
}
|
|
@@ -3107,6 +3127,11 @@ var NotifkitClient = class {
|
|
|
3107
3127
|
const params = new URLSearchParams();
|
|
3108
3128
|
if (options?.limit) params.set("limit", options.limit.toString());
|
|
3109
3129
|
if (options?.cursor) params.set("cursor", options.cursor);
|
|
3130
|
+
if (options?.search) params.set("search", options.search);
|
|
3131
|
+
if (options?.segment) params.set("segment", options.segment);
|
|
3132
|
+
if (options?.language) params.set("language", options.language);
|
|
3133
|
+
if (options?.timezone) params.set("timezone", options.timezone);
|
|
3134
|
+
if (options?.channel) params.set("channel", options.channel);
|
|
3110
3135
|
const qs = params.toString();
|
|
3111
3136
|
return this.request(`/v1/users${qs ? `?${qs}` : ""}`, "GET");
|
|
3112
3137
|
}
|
|
@@ -3148,8 +3173,15 @@ var NotifkitClient = class {
|
|
|
3148
3173
|
}
|
|
3149
3174
|
/** List campaign labels seen in the delivery log, most recent activity first. */
|
|
3150
3175
|
async listCampaigns(options) {
|
|
3151
|
-
const
|
|
3152
|
-
|
|
3176
|
+
const params = new URLSearchParams();
|
|
3177
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
3178
|
+
if (options?.search) params.set("search", options.search);
|
|
3179
|
+
if (options?.channel) params.set("channel", options.channel);
|
|
3180
|
+
if (options?.since) params.set("since", options.since instanceof Date ? options.since.toISOString() : options.since);
|
|
3181
|
+
if (options?.until) params.set("until", options.until instanceof Date ? options.until.toISOString() : options.until);
|
|
3182
|
+
if (options?.minMessages) params.set("minMessages", String(options.minMessages));
|
|
3183
|
+
const qs = params.toString();
|
|
3184
|
+
return this.request(`/v1/campaigns${qs ? `?${qs}` : ""}`, "GET");
|
|
3153
3185
|
}
|
|
3154
3186
|
/** Delivery and engagement funnel for one campaign. */
|
|
3155
3187
|
async getCampaignStats(campaign) {
|
|
@@ -3161,6 +3193,7 @@ var NotifkitClient = class {
|
|
|
3161
3193
|
if (options?.limit) params.set("limit", options.limit.toString());
|
|
3162
3194
|
if (options?.channel) params.set("channel", options.channel);
|
|
3163
3195
|
if (options?.reason) params.set("reason", options.reason);
|
|
3196
|
+
if (options?.target) params.set("target", options.target);
|
|
3164
3197
|
const qs = params.toString();
|
|
3165
3198
|
return this.request(`/v1/suppressions${qs ? `?${qs}` : ""}`, "GET");
|
|
3166
3199
|
}
|
|
@@ -3316,35 +3349,35 @@ var NotifkitServer = class extends EventEmitter {
|
|
|
3316
3349
|
}
|
|
3317
3350
|
const startupPromises = [];
|
|
3318
3351
|
if (services.includes("api")) {
|
|
3319
|
-
const { startApiServer } = await import("./main-
|
|
3352
|
+
const { startApiServer } = await import("./main-DtHWhueo.mjs");
|
|
3320
3353
|
startupPromises.push(startApiServer());
|
|
3321
3354
|
}
|
|
3322
3355
|
if (services.includes("delivery")) {
|
|
3323
|
-
const { startDeliveryWorker } = await import("./main-
|
|
3356
|
+
const { startDeliveryWorker } = await import("./main-CCfc45ev.mjs");
|
|
3324
3357
|
startupPromises.push(startDeliveryWorker());
|
|
3325
3358
|
}
|
|
3326
3359
|
if (services.includes("engine")) {
|
|
3327
|
-
const { startEngineWorker } = await import("./main-
|
|
3360
|
+
const { startEngineWorker } = await import("./main-B561M1d3.mjs");
|
|
3328
3361
|
startupPromises.push(startEngineWorker());
|
|
3329
3362
|
}
|
|
3330
3363
|
if (services.includes("enricher")) {
|
|
3331
|
-
const { startEnricherWorker } = await import("./main-
|
|
3364
|
+
const { startEnricherWorker } = await import("./main-Ce9dcrsg.mjs");
|
|
3332
3365
|
startupPromises.push(startEnricherWorker());
|
|
3333
3366
|
}
|
|
3334
3367
|
if (services.includes("scheduler")) {
|
|
3335
|
-
const { startSchedulerWorker } = await import("./main-
|
|
3368
|
+
const { startSchedulerWorker } = await import("./main-C45e7grq.mjs");
|
|
3336
3369
|
startupPromises.push(startSchedulerWorker());
|
|
3337
3370
|
}
|
|
3338
3371
|
if (services.includes("ai")) {
|
|
3339
|
-
const { startAiWorker } = await import("./main-
|
|
3372
|
+
const { startAiWorker } = await import("./main-CAH0_Q6d.mjs");
|
|
3340
3373
|
startupPromises.push(startAiWorker());
|
|
3341
3374
|
}
|
|
3342
3375
|
if (services.includes("workflow")) {
|
|
3343
|
-
const { startWorkflowWorker } = await import("./main-
|
|
3376
|
+
const { startWorkflowWorker } = await import("./main-DyfbnJc3.mjs");
|
|
3344
3377
|
startupPromises.push(startWorkflowWorker());
|
|
3345
3378
|
}
|
|
3346
3379
|
if (services.includes("events")) {
|
|
3347
|
-
const { startEventWorker } = await import("./main-
|
|
3380
|
+
const { startEventWorker } = await import("./main-B-jwm8ED.mjs");
|
|
3348
3381
|
startupPromises.push(startEventWorker());
|
|
3349
3382
|
}
|
|
3350
3383
|
const handleSignal = async (signal) => {
|
|
@@ -3377,35 +3410,35 @@ var NotifkitServer = class extends EventEmitter {
|
|
|
3377
3410
|
"events"
|
|
3378
3411
|
] : this.options.services;
|
|
3379
3412
|
if (services.includes("api")) {
|
|
3380
|
-
const { stopApiServer } = await import("./main-
|
|
3413
|
+
const { stopApiServer } = await import("./main-DtHWhueo.mjs");
|
|
3381
3414
|
await stopApiServer();
|
|
3382
3415
|
}
|
|
3383
3416
|
if (services.includes("delivery")) {
|
|
3384
|
-
const { stopDeliveryWorker } = await import("./main-
|
|
3417
|
+
const { stopDeliveryWorker } = await import("./main-CCfc45ev.mjs");
|
|
3385
3418
|
await stopDeliveryWorker();
|
|
3386
3419
|
}
|
|
3387
3420
|
if (services.includes("engine")) {
|
|
3388
|
-
const { stopEngineWorker } = await import("./main-
|
|
3421
|
+
const { stopEngineWorker } = await import("./main-B561M1d3.mjs");
|
|
3389
3422
|
await stopEngineWorker();
|
|
3390
3423
|
}
|
|
3391
3424
|
if (services.includes("enricher")) {
|
|
3392
|
-
const { stopEnricherWorker } = await import("./main-
|
|
3425
|
+
const { stopEnricherWorker } = await import("./main-Ce9dcrsg.mjs");
|
|
3393
3426
|
await stopEnricherWorker();
|
|
3394
3427
|
}
|
|
3395
3428
|
if (services.includes("scheduler")) {
|
|
3396
|
-
const { stopSchedulerWorker } = await import("./main-
|
|
3429
|
+
const { stopSchedulerWorker } = await import("./main-C45e7grq.mjs");
|
|
3397
3430
|
await stopSchedulerWorker();
|
|
3398
3431
|
}
|
|
3399
3432
|
if (services.includes("ai")) {
|
|
3400
|
-
const { stopAiWorker } = await import("./main-
|
|
3433
|
+
const { stopAiWorker } = await import("./main-CAH0_Q6d.mjs");
|
|
3401
3434
|
await stopAiWorker();
|
|
3402
3435
|
}
|
|
3403
3436
|
if (services.includes("workflow")) {
|
|
3404
|
-
const { stopWorkflowWorker } = await import("./main-
|
|
3437
|
+
const { stopWorkflowWorker } = await import("./main-DyfbnJc3.mjs");
|
|
3405
3438
|
await stopWorkflowWorker();
|
|
3406
3439
|
}
|
|
3407
3440
|
if (services.includes("events")) {
|
|
3408
|
-
const { stopEventWorker } = await import("./main-
|
|
3441
|
+
const { stopEventWorker } = await import("./main-B-jwm8ED.mjs");
|
|
3409
3442
|
await stopEventWorker();
|
|
3410
3443
|
}
|
|
3411
3444
|
if (this.pgContainer) {
|
|
@@ -3421,4 +3454,4 @@ var NotifkitServer = class extends EventEmitter {
|
|
|
3421
3454
|
//#endregion
|
|
3422
3455
|
export { childLogger as $, PUBSUB_CHANNELS as $t, ProjectSettingsCache as A, NotificationRequestedPayloadSchema as At, LUA_RELEASE_LOCK as B, QuietHoursSchema as Bt, ProjectRepository as C, DeliveryOptionsSchema as Ct, WorkflowRepository as D, NotificationEnrichedPayloadSchema as Dt, UserRepository as E, NotificationScheduledPayloadSchema as Et, sleep as F, CreateWorkflowSchema as Ft, normaliseTarget as G, UpdateUserSchema as Gt, LUA_SCHEDULER_CLAIM as H, TemplateSchema as Ht, DataLoader as I, IngestEventSchema as It, PendingMessageScanner as J, buildStreamEvent as Jt, LRUCache as K, WorkflowNotifyPayloadSchema as Kt, CircuitBreaker as L, InlineUserSchema as Lt, AppError as M, AddContactSchema as Mt, ValidationError as N, AddUserSchema as Nt, Redis as O, RecipientProfileSchema as Ot, generateId as P, ContactChannelSchema as Pt, metrics as Q, OUTBOUND_STREAMS as Qt, BatchProcessor as R, NotifyRequestSchema as Rt, PreferenceRepository as S, NotificationDeliveredPayloadSchema as St, TemplateRepository as T, RenderedContentSchema as Tt, LUA_SCHEDULER_POLL as U, TriggerWorkflowSchema as Ut, LUA_RENEW_LOCK as V, SyncTemplatesSchema as Vt, getPriorityBucket as W, UpdateProjectSchema as Wt, StreamProducer as X, ENRICHED_STREAMS as Xt, StreamConsumer as Y, CONSUMER_GROUPS as Yt, getMetricsRegistry as Z, INBOUND_STREAMS as Zt, escapeHeader as _, setGlobalConfig as _n, workflowWaiters as _t, SuspendExecutionError as a, registry as an, runMigrations as at, renderWithTemplate as b, NotificationSkippedPayloadSchema as bt, BaseWorker as c, NotificationPrioritySchema as cn, projectApiKeys as ct, buildUnsubscribeHeaders as d, baseConfigSchema as dn, suppressions as dt, STREAMS as en, createLogger as et, signUnsubscribeToken as f, getAiConfig as fn, userTopicPreferences as ft, TemplateCache as g, setAiConfig as gn, workflowSteps as gt, templateRegistry as h, readBaseConfig as hn, workflowInstances as ht, workflowRegistry as i, EventRegistry as in, createDatabase as it, UserThrottle as j, NotificationTargetSchema as jt, RedisClient as k, NotificationCreatedPayloadSchema as kt, NonRetryableError as l, NotificationStatusSchema as ln, projects as lt, renderTemplate as m, parseConfig as mn, workflowDefinitions as mt, NotifkitClient as n, StreamEventMetadataSchema as nn, withRequestId as nt, buildStepNotifyPayload as o, EventMetadataSchema as on, deliveryOutbox as ot, verifyUnsubscribeToken as p, loadEnv as pn, users as pt, globalEmitter as q, WorkflowStepSchema as qt, workflow as r, StreamEventSchema as rn, IdempotencyGuard as rt, resolveStepTarget as s, NotificationChannelSchema as sn, messageLogs as st, NotifkitServer as t, EventEnvelopeSchema as tn, withContext as tt, startHealthReporter as u, AI_DEFAULTS as un, scheduledPayloads as ut, escapeHtml as v, NotificationAiPendingPayloadSchema as vt, SegmentRepository as w, NotificationDispatchedPayloadSchema as wt, ContactRepository as x, NotificationFailedPayloadSchema as xt, interpolate as y, NotificationCanceledPayloadSchema as yt, AsyncSemaphore as z, PreferencesSchema as zt };
|
|
3423
3456
|
|
|
3424
|
-
//# sourceMappingURL=src-
|
|
3457
|
+
//# sourceMappingURL=src-C-PfEDMY.mjs.map
|