@keystrokehq/scheduler 1.0.27 → 1.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -379,11 +379,12 @@ async function syncTriggerSchedules(options) {
|
|
|
379
379
|
const existing = await (0, _keystrokehq_database.selectTriggerBySlug)(spec.triggerSlug);
|
|
380
380
|
if (!existing) continue;
|
|
381
381
|
const nextRunAt = !(existing.schedule !== schedule) && existing.enabled === 1 ? existing.nextRunAt : (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, now);
|
|
382
|
+
const enabled = (await (0, _keystrokehq_database.listExecutableAttachmentsByTriggerIds)([existing.id])).length > 0;
|
|
382
383
|
await (0, _keystrokehq_database.upsertTriggerScheduleFields)({
|
|
383
384
|
triggerSlug: spec.triggerSlug,
|
|
384
385
|
schedule,
|
|
385
386
|
nextRunAt: nextRunAt ?? now,
|
|
386
|
-
enabled
|
|
387
|
+
enabled,
|
|
387
388
|
updatedAt: now
|
|
388
389
|
});
|
|
389
390
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["sleep","EventEmitter","retryDelayMs","PgBoss","retryDelayMs","PgBoss","DEFAULT_DATABASE_URL","defineSchedulerPlugin","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { claimDueTriggers, claimDueTriggersForOrg } from \"@keystrokehq/database\";\nimport { getProjectScopeId } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { EnqueueInput, JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\n scope?: \"project\" | \"organization\";\n};\n\nfunction enqueueTriggerJob(\n jobQueue: JobQueue,\n row: { slug: string; kind: \"cron\" | \"poll\"; projectId: string },\n scheduledAt?: Date,\n): Promise<string> {\n const input: EnqueueInput = {\n kind: \"trigger\",\n targetId: row.slug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n projectId: row.projectId,\n };\n\n if (scheduledAt) {\n input.scheduledAt = scheduledAt;\n }\n\n return jobQueue.enqueue(input);\n}\n\nasync function claimDue(\n scope: \"project\" | \"organization\",\n asOf: Date,\n resolveNextRunAt: (schedule: string) => Date,\n limit: number,\n) {\n if (scope === \"organization\") {\n return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);\n }\n\n return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) =>\n rows.map((row) => ({ ...row, projectId: getProjectScopeId() })),\n );\n}\n\nexport function createScheduleTicker(ctx: ScheduleTickerContext) {\n const pollIntervalMs = ctx.pollIntervalMs ?? 1_000;\n const batchSize = ctx.batchSize ?? 10;\n\n async function fireDueSchedules(asOf = new Date()): Promise<number> {\n const scope = ctx.scope ?? \"project\";\n const claimed = await claimDue(\n scope,\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(\n ctx.jobQueue,\n {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n },\n asOf,\n );\n }\n\n return claimed.length;\n }\n\n async function startScheduleTicker(options: ScheduleTickerOptions = {}): Promise<StopFn> {\n const intervalMs = options.pollIntervalMs ?? pollIntervalMs;\n const limit = options.batchSize ?? batchSize;\n const scope = options.scope ?? ctx.scope ?? \"project\";\n let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n const claimed = await claimDue(\n scope,\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(ctx.jobQueue, {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n });\n }\n } catch {\n // keep ticking\n }\n\n await sleep(intervalMs);\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n };\n }\n\n return { fireDueSchedules, startScheduleTicker };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { EventEmitter } from \"node:events\";\n\nimport type { CancelHandler, StopFn } from \"./types\";\n\n/** In-process cancel pub/sub for single-process queues (memory, db polling). */\nexport function createInProcessCancelChannel(): {\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n} {\n const emitter = new EventEmitter();\n return {\n async publishCancel(runId) {\n emitter.emit(\"cancel\", runId);\n },\n\n async subscribeCancel(handler) {\n const listener = (runId: string) => {\n void handler(runId);\n };\n emitter.on(\"cancel\", listener);\n return async () => {\n emitter.off(\"cancel\", listener);\n };\n },\n };\n}\n","import {\n claimNextJob,\n enqueueJob,\n failWorkflowRun,\n markJobComplete,\n markJobFailed,\n requeueExpiredLeases,\n scheduleJobRetry,\n} from \"@keystrokehq/database\";\nimport type { ClaimedJob } from \"@keystrokehq/database\";\nimport { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nfunction toJobPayload(job: ClaimedJob): Parameters<JobHandler>[0] {\n return {\n jobId: job.id,\n kind: job.kind,\n targetId: job.targetId,\n runId: job.runId,\n trigger: job.trigger,\n payload: job.payload,\n attempt: job.attempt,\n maxAttempts: job.maxAttempts,\n scheduledAt: job.scheduledAt,\n };\n}\n\nexport function createDatabaseJobQueue(): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n\n return {\n async enqueue(input) {\n return enqueueJob(input);\n },\n\n async startWorker(handler: JobHandler, options: WorkerOptions = {}): Promise<StopFn> {\n const workerId = options.workerId ?? crypto.randomUUID();\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n const leaseSweepIntervalMs = options.leaseSweepIntervalMs ?? 30_000;\n let running = true;\n\n const leaseTimer = setInterval(() => {\n void requeueExpiredLeases();\n }, leaseSweepIntervalMs);\n\n const loop = async () => {\n while (running) {\n try {\n const job = await claimNextJob(workerId);\n if (!job) {\n await sleep(pollIntervalMs);\n continue;\n }\n\n try {\n await handler(toJobPayload(job));\n await markJobComplete(job.id);\n } catch (error) {\n if (job.attempt < job.maxAttempts) {\n await scheduleJobRetry(job.id, job.attempt + 1, retryDelayMs(job.attempt));\n } else {\n await markJobFailed(job.id, error);\n if (job.kind === \"workflow\") {\n await failWorkflowRun(job.runId, error);\n }\n }\n }\n } catch {\n await sleep(pollIntervalMs);\n }\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n clearInterval(leaseTimer);\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { PgBoss } from \"pg-boss\";\n\nlet boss: PgBoss | undefined;\n\nfunction resolveDatabaseUrl(url?: string): string {\n const resolved = url ?? resolvePostgresUrlFromEnv(process.env);\n if (!resolved) {\n throw new Error(\n \"DATABASE_URL or POSTGRES_HOST/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB is required\",\n );\n }\n return resolved;\n}\n\nexport async function startPgBoss(url?: string): Promise<PgBoss> {\n if (boss) {\n return boss;\n }\n\n const next = new PgBoss({\n connectionString: resolveDatabaseUrl(url),\n schema: \"pgboss\",\n });\n next.on(\"error\", (error) => {\n console.error(\"[pg-boss]\", error);\n });\n\n await next.start();\n boss = next;\n return next;\n}\n\nexport function getPgBoss(): PgBoss {\n if (!boss) {\n throw new Error(\"PgBoss not started. Call startPgBoss() first.\");\n }\n return boss;\n}\n\nexport async function stopPgBoss(): Promise<void> {\n if (!boss) {\n return;\n }\n\n await boss.stop();\n boss = undefined;\n}\n","export { createPostgresCancelChannel as createPgCancelChannel } from \"@keystrokehq/database\";\n\n/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */\nexport function pgCancelChannelName(\n scope: \"platform\" | \"project\" | \"organization\",\n id?: string,\n): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n if (scope === \"organization\") {\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_org_${sanitized}`;\n }\n\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_${sanitized}`;\n}\n","import { createHash } from \"node:crypto\";\nimport { PgBoss, type JobWithMetadata } from \"pg-boss\";\nimport { createPgCancelChannel, pgCancelChannelName } from \"./pg-cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\n/** Shared queue name for the platform control-plane scope. */\nexport const DEFAULT_QUEUE_NAME = \"keystroke\";\nconst PGBOSS_SCHEMA = \"pgboss\";\n\n/**\n * Derive a per-project pg-boss queue name. pg-boss queue names must be <= 50\n * chars, contain only [A-Za-z0-9_], and not start with a digit. Project ids are\n * usually UUIDs (hyphens, may start with a digit), so we sanitize, and fall back\n * to a hash when the sanitized name would exceed the length limit.\n */\nexport function pgBossProjectQueueName(projectId: string): string {\n const sanitized = projectId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_${createHash(\"sha1\").update(projectId).digest(\"hex\").slice(0, 40)}`;\n}\n\nexport function pgBossOrgQueueName(organizationId: string): string {\n const sanitized = organizationId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_org_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_org_${createHash(\"sha1\").update(organizationId).digest(\"hex\").slice(0, 36)}`;\n}\n\nexport type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\n organizationId?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n targetId: string;\n runId: string;\n trigger: \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n payload?: unknown;\n attempt?: number;\n maxAttempts?: number;\n scheduledAt?: string;\n projectId?: string;\n};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n projectId?: string;\n organizationId?: string;\n};\n\nexport function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const cancelChannel = createPgCancelChannel(\n options.connectionString,\n pgCancelChannelName(\n options.cancelScope ?? \"platform\",\n options.cancelScope === \"organization\" ? options.organizationId : options.projectId,\n ),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(\n queueName,\n {\n ...input,\n payload: input.payload,\n },\n {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\n },\n );\n\n if (!jobId) {\n if (input.dedupeKey) {\n return input.dedupeKey;\n }\n throw new Error(\"Failed to enqueue job\");\n }\n\n return jobId;\n },\n\n async startWorker(handler: JobHandler): Promise<StopFn> {\n let stopped = false;\n\n const workerId = await boss.work<PgBossJobData>(\n queueName,\n { batchSize: 1, includeMetadata: true },\n async (jobs: JobWithMetadata<PgBossJobData>[]) => {\n if (stopped) {\n return;\n }\n\n const job = jobs[0];\n if (!job) {\n return;\n }\n\n const data = job.data;\n const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;\n const attempt = (job.retryCount ?? 0) + 1;\n\n await handler({\n jobId: job.id,\n kind: data.kind,\n targetId: data.targetId,\n runId: data.runId,\n trigger: data.trigger,\n payload: data.payload ?? {},\n attempt,\n maxAttempts,\n exhaustedRetries: attempt >= maxAttempts,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\n projectId: data.projectId,\n });\n },\n );\n\n return async () => {\n stopped = true;\n await boss.offWork(queueName, { id: workerId });\n if (options.stopBossOnWorkerStop) {\n await boss.stop({ graceful: true, timeout: 5_000 });\n }\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport async function createPgBossQueue(options: PgBossQueueOptions): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const boss = new PgBoss({\n connectionString: options.connectionString,\n schema: PGBOSS_SCHEMA,\n });\n await boss.start();\n await boss.createQueue(queueName);\n\n return buildPgBossJobQueue(boss, {\n stopBossOnWorkerStop: true,\n queueName,\n connectionString: options.connectionString,\n cancelScope: options.cancelScope ?? \"project\",\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n options: { connectionString: string; queueName?: string },\n): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, {\n queueName,\n connectionString: options.connectionString,\n cancelScope: \"platform\",\n });\n}\n","import {\n DEFAULT_DATABASE_URL,\n getProjectScopeId,\n inferDialect,\n resolveProjectDatabaseUrlFromEnv,\n} from \"@keystrokehq/database\";\n\nimport {\n defineSchedulerPlugin,\n type SchedulerPlugin,\n type SchedulerPluginContext,\n} from \"./contract\";\nimport { createDatabaseJobQueue } from \"./database-queue\";\nimport { getPgBoss, startPgBoss } from \"./pg-boss-client\";\nimport {\n createPgBossQueue,\n createSharedPgBossJobQueue,\n pgBossOrgQueueName,\n pgBossProjectQueueName,\n} from \"./pg-boss-queue\";\n\nexport type { SchedulerPlugin, SchedulerPluginContext, SchedulerScope } from \"./contract\";\nexport { defineSchedulerPlugin } from \"./contract\";\n\nfunction resolveUrl(ctx: SchedulerPluginContext): string {\n return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;\n}\n\n/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */\nexport function pgBossSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"pg-boss\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n\n if (ctx.scope === \"platform\") {\n await startPgBoss(url);\n return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });\n }\n\n if (ctx.scope === \"organization\") {\n const organizationId = ctx.organizationId;\n if (!organizationId) {\n throw new Error(\"organizationId is required for organization-scoped pg-boss queue\");\n }\n\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossOrgQueueName(organizationId),\n cancelScope: \"organization\",\n organizationId,\n });\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(projectId),\n projectId,\n });\n },\n });\n}\n\n/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */\nexport function pollingSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"polling\",\n async createJobQueue() {\n return createDatabaseJobQueue();\n },\n });\n}\n\n/** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */\nexport function defaultSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"default\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n const dialect = inferDialect(url, ctx.dialect);\n\n if (dialect === \"postgres\") {\n return pgBossSchedulerPlugin().createJobQueue(ctx);\n }\n\n return pollingSchedulerPlugin().createJobQueue(ctx);\n },\n });\n}\n","import { resolveCronSchedule } from \"@keystrokehq/trigger\";\n\nexport type ScheduleOverrideOptions = {\n global?: string;\n byTrigger?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n triggerSlug: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(triggerSlug, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byTrigger,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n selectActiveEphemeralScheduledTriggerSlugs,\n selectTriggerBySlug,\n upsertTriggerScheduleFields,\n} from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { ScheduleSyncOptions } from \"./types\";\nimport { resolveTriggerSchedule } from \"./resolve-schedule\";\n\nexport async function syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void> {\n const now = new Date();\n const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);\n // Ephemeral cron/poll schedules are managed by set_trigger, not project discovery — keep them out\n // of the disable sweep so a deploy/restart does not wipe agent-created triggers.\n const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();\n const slugs = [...projectSlugs, ...ephemeralSlugs];\n\n if (slugs.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInSlugs(slugs, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.triggerSlug,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerBySlug(spec.triggerSlug);\n if (!existing) {\n continue;\n }\n\n const scheduleChanged = existing.schedule !== schedule;\n const nextRunAt =\n !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n await upsertTriggerScheduleFields({\n triggerSlug: spec.triggerSlug,\n schedule,\n nextRunAt: nextRunAt ?? now,\n enabled: true,\n updatedAt: now,\n });\n }\n}\n","import { createScheduleTicker } from \"./schedule-ticker\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport { syncTriggerSchedules as syncTriggerScheduleRows } from \"./sync-trigger-schedules\";\nimport type {\n CreateJobQueueOptions,\n CreateSchedulerOptions,\n JobQueue,\n ScheduleSyncOptions,\n ScheduleTickerOptions,\n Scheduler,\n StopFn,\n} from \"./types\";\n\nasync function createUnderlyingJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n if (options.adapter) {\n return options.adapter;\n }\n\n const plugin = options.plugin ?? defaultSchedulerPlugin();\n return plugin.createJobQueue({\n scope: options.scope ?? \"project\",\n url: options.url,\n dialect: options.dialect,\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nfunction wrapScheduler(jobQueue: JobQueue, scope?: \"project\" | \"organization\"): Scheduler {\n const ticker = createScheduleTicker({ jobQueue, scope });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\n publishCancel: (runId) => jobQueue.publishCancel(runId),\n subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),\n syncTriggerSchedules: (options: ScheduleSyncOptions) => syncTriggerScheduleRows(options),\n startScheduleTicker: (options?: ScheduleTickerOptions) => ticker.startScheduleTicker(options),\n fireDueSchedules: (asOf?: Date) => ticker.fireDueSchedules(asOf),\n };\n}\n\nexport async function createScheduler(options: CreateSchedulerOptions = {}): Promise<Scheduler> {\n const jobQueue = await createUnderlyingJobQueue(options);\n const scope = options.scope === \"organization\" ? \"organization\" : \"project\";\n return wrapScheduler(jobQueue, scope);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(\n jobQueue: JobQueue,\n scope?: \"project\" | \"organization\",\n): Scheduler {\n return wrapScheduler(jobQueue, scope);\n}\n\nexport type { StopFn };\n","import { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { EnqueueInput, JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nexport type MemoryJobQueueOptions = {\n sync?: boolean;\n};\n\nexport function createMemoryJobQueue(options: MemoryJobQueueOptions = {}): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n const pending: Array<{ id: string; input: EnqueueInput; enqueuedAt: Date }> = [];\n let handler: JobHandler | undefined;\n let draining = false;\n\n async function drain(): Promise<void> {\n if (!handler || draining) {\n return;\n }\n\n draining = true;\n try {\n while (pending.length > 0) {\n const next = pending.shift();\n if (!next) {\n break;\n }\n\n const input = next.input;\n await handler({\n jobId: next.id,\n kind: input.kind,\n targetId: input.targetId,\n runId: input.runId,\n trigger: input.trigger,\n payload: input.payload ?? {},\n attempt: input.attempt ?? 1,\n maxAttempts: input.maxAttempts ?? 3,\n scheduledAt: input.scheduledAt ?? next.enqueuedAt,\n });\n }\n } finally {\n draining = false;\n }\n }\n\n return {\n async enqueue(input) {\n const id = crypto.randomUUID();\n pending.push({ id, input, enqueuedAt: new Date() });\n\n if (options.sync) {\n await drain();\n }\n\n return id;\n },\n\n async startWorker(nextHandler: JobHandler, _opts: WorkerOptions = {}): Promise<StopFn> {\n handler = nextHandler;\n\n if (!options.sync) {\n void drain();\n }\n\n return async () => {\n handler = undefined;\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport { retryDelayMs };\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { getPgBoss } from \"./pg-boss-client\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { createSharedPgBossJobQueue } from \"./pg-boss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet sharedJobQueue: JobQueue | undefined;\n\nexport async function createSharedPgBossScheduler(): Promise<Scheduler> {\n const jobQueue = await getSharedPgBossJobQueue();\n return wrapJobQueueAsScheduler(jobQueue);\n}\n\nexport async function getSharedPgBossJobQueue(): Promise<JobQueue> {\n if (sharedJobQueue) {\n return sharedJobQueue;\n }\n\n const connectionString = resolvePostgresUrlFromEnv(process.env);\n if (!connectionString) {\n throw new Error(\"Postgres connection string is required for shared pg-boss queue\");\n }\n\n sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });\n return sharedJobQueue;\n}\n\nexport function resetSharedPgBossJobQueueForTests(): void {\n sharedJobQueue = undefined;\n}\n","import type { SchedulerPlugin } from \"./contract\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport {\n createSharedPgBossScheduler,\n resetSharedPgBossJobQueueForTests,\n} from \"./shared-pgboss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet configuredPlugin: SchedulerPlugin | undefined;\nlet sharedJobQueue: JobQueue | undefined;\n\nexport function configureSharedScheduler(plugin: SchedulerPlugin): void {\n configuredPlugin = plugin;\n sharedJobQueue = undefined;\n}\n\nexport async function createSharedScheduler(): Promise<Scheduler> {\n const plugin = configuredPlugin ?? defaultSchedulerPlugin();\n\n if (plugin.name === \"default\") {\n return createSharedPgBossScheduler();\n }\n\n if (!sharedJobQueue) {\n sharedJobQueue = await plugin.createJobQueue({ scope: \"platform\" });\n }\n\n return wrapJobQueueAsScheduler(sharedJobQueue);\n}\n\nexport function resetSharedSchedulerForTests(): void {\n configuredPlugin = undefined;\n sharedJobQueue = undefined;\n resetSharedPgBossJobQueueForTests();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["sleep","EventEmitter","retryDelayMs","PgBoss","retryDelayMs","PgBoss","DEFAULT_DATABASE_URL","defineSchedulerPlugin","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { claimDueTriggers, claimDueTriggersForOrg } from \"@keystrokehq/database\";\nimport { getProjectScopeId } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { EnqueueInput, JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\n scope?: \"project\" | \"organization\";\n};\n\nfunction enqueueTriggerJob(\n jobQueue: JobQueue,\n row: { slug: string; kind: \"cron\" | \"poll\"; projectId: string },\n scheduledAt?: Date,\n): Promise<string> {\n const input: EnqueueInput = {\n kind: \"trigger\",\n targetId: row.slug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n projectId: row.projectId,\n };\n\n if (scheduledAt) {\n input.scheduledAt = scheduledAt;\n }\n\n return jobQueue.enqueue(input);\n}\n\nasync function claimDue(\n scope: \"project\" | \"organization\",\n asOf: Date,\n resolveNextRunAt: (schedule: string) => Date,\n limit: number,\n) {\n if (scope === \"organization\") {\n return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);\n }\n\n return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) =>\n rows.map((row) => ({ ...row, projectId: getProjectScopeId() })),\n );\n}\n\nexport function createScheduleTicker(ctx: ScheduleTickerContext) {\n const pollIntervalMs = ctx.pollIntervalMs ?? 1_000;\n const batchSize = ctx.batchSize ?? 10;\n\n async function fireDueSchedules(asOf = new Date()): Promise<number> {\n const scope = ctx.scope ?? \"project\";\n const claimed = await claimDue(\n scope,\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(\n ctx.jobQueue,\n {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n },\n asOf,\n );\n }\n\n return claimed.length;\n }\n\n async function startScheduleTicker(options: ScheduleTickerOptions = {}): Promise<StopFn> {\n const intervalMs = options.pollIntervalMs ?? pollIntervalMs;\n const limit = options.batchSize ?? batchSize;\n const scope = options.scope ?? ctx.scope ?? \"project\";\n let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n const claimed = await claimDue(\n scope,\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(ctx.jobQueue, {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n });\n }\n } catch {\n // keep ticking\n }\n\n await sleep(intervalMs);\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n };\n }\n\n return { fireDueSchedules, startScheduleTicker };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { EventEmitter } from \"node:events\";\n\nimport type { CancelHandler, StopFn } from \"./types\";\n\n/** In-process cancel pub/sub for single-process queues (memory, db polling). */\nexport function createInProcessCancelChannel(): {\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n} {\n const emitter = new EventEmitter();\n return {\n async publishCancel(runId) {\n emitter.emit(\"cancel\", runId);\n },\n\n async subscribeCancel(handler) {\n const listener = (runId: string) => {\n void handler(runId);\n };\n emitter.on(\"cancel\", listener);\n return async () => {\n emitter.off(\"cancel\", listener);\n };\n },\n };\n}\n","import {\n claimNextJob,\n enqueueJob,\n failWorkflowRun,\n markJobComplete,\n markJobFailed,\n requeueExpiredLeases,\n scheduleJobRetry,\n} from \"@keystrokehq/database\";\nimport type { ClaimedJob } from \"@keystrokehq/database\";\nimport { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nfunction toJobPayload(job: ClaimedJob): Parameters<JobHandler>[0] {\n return {\n jobId: job.id,\n kind: job.kind,\n targetId: job.targetId,\n runId: job.runId,\n trigger: job.trigger,\n payload: job.payload,\n attempt: job.attempt,\n maxAttempts: job.maxAttempts,\n scheduledAt: job.scheduledAt,\n };\n}\n\nexport function createDatabaseJobQueue(): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n\n return {\n async enqueue(input) {\n return enqueueJob(input);\n },\n\n async startWorker(handler: JobHandler, options: WorkerOptions = {}): Promise<StopFn> {\n const workerId = options.workerId ?? crypto.randomUUID();\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n const leaseSweepIntervalMs = options.leaseSweepIntervalMs ?? 30_000;\n let running = true;\n\n const leaseTimer = setInterval(() => {\n void requeueExpiredLeases();\n }, leaseSweepIntervalMs);\n\n const loop = async () => {\n while (running) {\n try {\n const job = await claimNextJob(workerId);\n if (!job) {\n await sleep(pollIntervalMs);\n continue;\n }\n\n try {\n await handler(toJobPayload(job));\n await markJobComplete(job.id);\n } catch (error) {\n if (job.attempt < job.maxAttempts) {\n await scheduleJobRetry(job.id, job.attempt + 1, retryDelayMs(job.attempt));\n } else {\n await markJobFailed(job.id, error);\n if (job.kind === \"workflow\") {\n await failWorkflowRun(job.runId, error);\n }\n }\n }\n } catch {\n await sleep(pollIntervalMs);\n }\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n clearInterval(leaseTimer);\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { PgBoss } from \"pg-boss\";\n\nlet boss: PgBoss | undefined;\n\nfunction resolveDatabaseUrl(url?: string): string {\n const resolved = url ?? resolvePostgresUrlFromEnv(process.env);\n if (!resolved) {\n throw new Error(\n \"DATABASE_URL or POSTGRES_HOST/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB is required\",\n );\n }\n return resolved;\n}\n\nexport async function startPgBoss(url?: string): Promise<PgBoss> {\n if (boss) {\n return boss;\n }\n\n const next = new PgBoss({\n connectionString: resolveDatabaseUrl(url),\n schema: \"pgboss\",\n });\n next.on(\"error\", (error) => {\n console.error(\"[pg-boss]\", error);\n });\n\n await next.start();\n boss = next;\n return next;\n}\n\nexport function getPgBoss(): PgBoss {\n if (!boss) {\n throw new Error(\"PgBoss not started. Call startPgBoss() first.\");\n }\n return boss;\n}\n\nexport async function stopPgBoss(): Promise<void> {\n if (!boss) {\n return;\n }\n\n await boss.stop();\n boss = undefined;\n}\n","export { createPostgresCancelChannel as createPgCancelChannel } from \"@keystrokehq/database\";\n\n/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */\nexport function pgCancelChannelName(\n scope: \"platform\" | \"project\" | \"organization\",\n id?: string,\n): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n if (scope === \"organization\") {\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_org_${sanitized}`;\n }\n\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_${sanitized}`;\n}\n","import { createHash } from \"node:crypto\";\nimport { PgBoss, type JobWithMetadata } from \"pg-boss\";\nimport { createPgCancelChannel, pgCancelChannelName } from \"./pg-cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\n/** Shared queue name for the platform control-plane scope. */\nexport const DEFAULT_QUEUE_NAME = \"keystroke\";\nconst PGBOSS_SCHEMA = \"pgboss\";\n\n/**\n * Derive a per-project pg-boss queue name. pg-boss queue names must be <= 50\n * chars, contain only [A-Za-z0-9_], and not start with a digit. Project ids are\n * usually UUIDs (hyphens, may start with a digit), so we sanitize, and fall back\n * to a hash when the sanitized name would exceed the length limit.\n */\nexport function pgBossProjectQueueName(projectId: string): string {\n const sanitized = projectId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_${createHash(\"sha1\").update(projectId).digest(\"hex\").slice(0, 40)}`;\n}\n\nexport function pgBossOrgQueueName(organizationId: string): string {\n const sanitized = organizationId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_org_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_org_${createHash(\"sha1\").update(organizationId).digest(\"hex\").slice(0, 36)}`;\n}\n\nexport type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\n organizationId?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n targetId: string;\n runId: string;\n trigger: \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n payload?: unknown;\n attempt?: number;\n maxAttempts?: number;\n scheduledAt?: string;\n projectId?: string;\n};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n projectId?: string;\n organizationId?: string;\n};\n\nexport function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const cancelChannel = createPgCancelChannel(\n options.connectionString,\n pgCancelChannelName(\n options.cancelScope ?? \"platform\",\n options.cancelScope === \"organization\" ? options.organizationId : options.projectId,\n ),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(\n queueName,\n {\n ...input,\n payload: input.payload,\n },\n {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\n },\n );\n\n if (!jobId) {\n if (input.dedupeKey) {\n return input.dedupeKey;\n }\n throw new Error(\"Failed to enqueue job\");\n }\n\n return jobId;\n },\n\n async startWorker(handler: JobHandler): Promise<StopFn> {\n let stopped = false;\n\n const workerId = await boss.work<PgBossJobData>(\n queueName,\n { batchSize: 1, includeMetadata: true },\n async (jobs: JobWithMetadata<PgBossJobData>[]) => {\n if (stopped) {\n return;\n }\n\n const job = jobs[0];\n if (!job) {\n return;\n }\n\n const data = job.data;\n const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;\n const attempt = (job.retryCount ?? 0) + 1;\n\n await handler({\n jobId: job.id,\n kind: data.kind,\n targetId: data.targetId,\n runId: data.runId,\n trigger: data.trigger,\n payload: data.payload ?? {},\n attempt,\n maxAttempts,\n exhaustedRetries: attempt >= maxAttempts,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\n projectId: data.projectId,\n });\n },\n );\n\n return async () => {\n stopped = true;\n await boss.offWork(queueName, { id: workerId });\n if (options.stopBossOnWorkerStop) {\n await boss.stop({ graceful: true, timeout: 5_000 });\n }\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport async function createPgBossQueue(options: PgBossQueueOptions): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const boss = new PgBoss({\n connectionString: options.connectionString,\n schema: PGBOSS_SCHEMA,\n });\n await boss.start();\n await boss.createQueue(queueName);\n\n return buildPgBossJobQueue(boss, {\n stopBossOnWorkerStop: true,\n queueName,\n connectionString: options.connectionString,\n cancelScope: options.cancelScope ?? \"project\",\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n options: { connectionString: string; queueName?: string },\n): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, {\n queueName,\n connectionString: options.connectionString,\n cancelScope: \"platform\",\n });\n}\n","import {\n DEFAULT_DATABASE_URL,\n getProjectScopeId,\n inferDialect,\n resolveProjectDatabaseUrlFromEnv,\n} from \"@keystrokehq/database\";\n\nimport {\n defineSchedulerPlugin,\n type SchedulerPlugin,\n type SchedulerPluginContext,\n} from \"./contract\";\nimport { createDatabaseJobQueue } from \"./database-queue\";\nimport { getPgBoss, startPgBoss } from \"./pg-boss-client\";\nimport {\n createPgBossQueue,\n createSharedPgBossJobQueue,\n pgBossOrgQueueName,\n pgBossProjectQueueName,\n} from \"./pg-boss-queue\";\n\nexport type { SchedulerPlugin, SchedulerPluginContext, SchedulerScope } from \"./contract\";\nexport { defineSchedulerPlugin } from \"./contract\";\n\nfunction resolveUrl(ctx: SchedulerPluginContext): string {\n return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;\n}\n\n/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */\nexport function pgBossSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"pg-boss\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n\n if (ctx.scope === \"platform\") {\n await startPgBoss(url);\n return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });\n }\n\n if (ctx.scope === \"organization\") {\n const organizationId = ctx.organizationId;\n if (!organizationId) {\n throw new Error(\"organizationId is required for organization-scoped pg-boss queue\");\n }\n\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossOrgQueueName(organizationId),\n cancelScope: \"organization\",\n organizationId,\n });\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(projectId),\n projectId,\n });\n },\n });\n}\n\n/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */\nexport function pollingSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"polling\",\n async createJobQueue() {\n return createDatabaseJobQueue();\n },\n });\n}\n\n/** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */\nexport function defaultSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"default\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n const dialect = inferDialect(url, ctx.dialect);\n\n if (dialect === \"postgres\") {\n return pgBossSchedulerPlugin().createJobQueue(ctx);\n }\n\n return pollingSchedulerPlugin().createJobQueue(ctx);\n },\n });\n}\n","import { resolveCronSchedule } from \"@keystrokehq/trigger\";\n\nexport type ScheduleOverrideOptions = {\n global?: string;\n byTrigger?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n triggerSlug: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(triggerSlug, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byTrigger,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n listExecutableAttachmentsByTriggerIds,\n selectActiveEphemeralScheduledTriggerSlugs,\n selectTriggerBySlug,\n upsertTriggerScheduleFields,\n} from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { ScheduleSyncOptions } from \"./types\";\nimport { resolveTriggerSchedule } from \"./resolve-schedule\";\n\nexport async function syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void> {\n const now = new Date();\n const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);\n // Ephemeral cron/poll schedules are managed by set_trigger, not project discovery — keep them out\n // of the disable sweep so a deploy/restart does not wipe agent-created triggers.\n const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();\n const slugs = [...projectSlugs, ...ephemeralSlugs];\n\n if (slugs.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInSlugs(slugs, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.triggerSlug,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerBySlug(spec.triggerSlug);\n if (!existing) {\n continue;\n }\n\n const scheduleChanged = existing.schedule !== schedule;\n const nextRunAt =\n !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n const executable = await listExecutableAttachmentsByTriggerIds([existing.id]);\n const enabled = executable.length > 0;\n\n await upsertTriggerScheduleFields({\n triggerSlug: spec.triggerSlug,\n schedule,\n nextRunAt: nextRunAt ?? now,\n enabled,\n updatedAt: now,\n });\n }\n}\n","import { createScheduleTicker } from \"./schedule-ticker\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport { syncTriggerSchedules as syncTriggerScheduleRows } from \"./sync-trigger-schedules\";\nimport type {\n CreateJobQueueOptions,\n CreateSchedulerOptions,\n JobQueue,\n ScheduleSyncOptions,\n ScheduleTickerOptions,\n Scheduler,\n StopFn,\n} from \"./types\";\n\nasync function createUnderlyingJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n if (options.adapter) {\n return options.adapter;\n }\n\n const plugin = options.plugin ?? defaultSchedulerPlugin();\n return plugin.createJobQueue({\n scope: options.scope ?? \"project\",\n url: options.url,\n dialect: options.dialect,\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nfunction wrapScheduler(jobQueue: JobQueue, scope?: \"project\" | \"organization\"): Scheduler {\n const ticker = createScheduleTicker({ jobQueue, scope });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\n publishCancel: (runId) => jobQueue.publishCancel(runId),\n subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),\n syncTriggerSchedules: (options: ScheduleSyncOptions) => syncTriggerScheduleRows(options),\n startScheduleTicker: (options?: ScheduleTickerOptions) => ticker.startScheduleTicker(options),\n fireDueSchedules: (asOf?: Date) => ticker.fireDueSchedules(asOf),\n };\n}\n\nexport async function createScheduler(options: CreateSchedulerOptions = {}): Promise<Scheduler> {\n const jobQueue = await createUnderlyingJobQueue(options);\n const scope = options.scope === \"organization\" ? \"organization\" : \"project\";\n return wrapScheduler(jobQueue, scope);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(\n jobQueue: JobQueue,\n scope?: \"project\" | \"organization\",\n): Scheduler {\n return wrapScheduler(jobQueue, scope);\n}\n\nexport type { StopFn };\n","import { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { EnqueueInput, JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nexport type MemoryJobQueueOptions = {\n sync?: boolean;\n};\n\nexport function createMemoryJobQueue(options: MemoryJobQueueOptions = {}): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n const pending: Array<{ id: string; input: EnqueueInput; enqueuedAt: Date }> = [];\n let handler: JobHandler | undefined;\n let draining = false;\n\n async function drain(): Promise<void> {\n if (!handler || draining) {\n return;\n }\n\n draining = true;\n try {\n while (pending.length > 0) {\n const next = pending.shift();\n if (!next) {\n break;\n }\n\n const input = next.input;\n await handler({\n jobId: next.id,\n kind: input.kind,\n targetId: input.targetId,\n runId: input.runId,\n trigger: input.trigger,\n payload: input.payload ?? {},\n attempt: input.attempt ?? 1,\n maxAttempts: input.maxAttempts ?? 3,\n scheduledAt: input.scheduledAt ?? next.enqueuedAt,\n });\n }\n } finally {\n draining = false;\n }\n }\n\n return {\n async enqueue(input) {\n const id = crypto.randomUUID();\n pending.push({ id, input, enqueuedAt: new Date() });\n\n if (options.sync) {\n await drain();\n }\n\n return id;\n },\n\n async startWorker(nextHandler: JobHandler, _opts: WorkerOptions = {}): Promise<StopFn> {\n handler = nextHandler;\n\n if (!options.sync) {\n void drain();\n }\n\n return async () => {\n handler = undefined;\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport { retryDelayMs };\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { getPgBoss } from \"./pg-boss-client\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { createSharedPgBossJobQueue } from \"./pg-boss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet sharedJobQueue: JobQueue | undefined;\n\nexport async function createSharedPgBossScheduler(): Promise<Scheduler> {\n const jobQueue = await getSharedPgBossJobQueue();\n return wrapJobQueueAsScheduler(jobQueue);\n}\n\nexport async function getSharedPgBossJobQueue(): Promise<JobQueue> {\n if (sharedJobQueue) {\n return sharedJobQueue;\n }\n\n const connectionString = resolvePostgresUrlFromEnv(process.env);\n if (!connectionString) {\n throw new Error(\"Postgres connection string is required for shared pg-boss queue\");\n }\n\n sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });\n return sharedJobQueue;\n}\n\nexport function resetSharedPgBossJobQueueForTests(): void {\n sharedJobQueue = undefined;\n}\n","import type { SchedulerPlugin } from \"./contract\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport {\n createSharedPgBossScheduler,\n resetSharedPgBossJobQueueForTests,\n} from \"./shared-pgboss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet configuredPlugin: SchedulerPlugin | undefined;\nlet sharedJobQueue: JobQueue | undefined;\n\nexport function configureSharedScheduler(plugin: SchedulerPlugin): void {\n configuredPlugin = plugin;\n sharedJobQueue = undefined;\n}\n\nexport async function createSharedScheduler(): Promise<Scheduler> {\n const plugin = configuredPlugin ?? defaultSchedulerPlugin();\n\n if (plugin.name === \"default\") {\n return createSharedPgBossScheduler();\n }\n\n if (!sharedJobQueue) {\n sharedJobQueue = await plugin.createJobQueue({ scope: \"platform\" });\n }\n\n return wrapJobQueueAsScheduler(sharedJobQueue);\n}\n\nexport function resetSharedSchedulerForTests(): void {\n configuredPlugin = undefined;\n sharedJobQueue = undefined;\n resetSharedPgBossJobQueueForTests();\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as DEFAULT_RETRY_DELAY_MS, r as retryDelayMs, t as defineSchedulerPlugin } from "./contract-E1QJBH6_.mjs";
|
|
2
|
-
import { DEFAULT_DATABASE_URL, claimDueTriggers, claimDueTriggersForOrg, claimNextJob, createPostgresCancelChannel as createPgCancelChannel, disableAllTriggerSchedules, disableTriggerSchedulesNotInSlugs, enqueueJob, failWorkflowRun, getProjectScopeId, inferDialect, markJobComplete, markJobFailed, requeueExpiredLeases, resolvePostgresUrlFromEnv, resolveProjectDatabaseUrlFromEnv, scheduleJobRetry, selectActiveEphemeralScheduledTriggerSlugs, selectTriggerBySlug, upsertTriggerScheduleFields } from "@keystrokehq/database";
|
|
2
|
+
import { DEFAULT_DATABASE_URL, claimDueTriggers, claimDueTriggersForOrg, claimNextJob, createPostgresCancelChannel as createPgCancelChannel, disableAllTriggerSchedules, disableTriggerSchedulesNotInSlugs, enqueueJob, failWorkflowRun, getProjectScopeId, inferDialect, listExecutableAttachmentsByTriggerIds, markJobComplete, markJobFailed, requeueExpiredLeases, resolvePostgresUrlFromEnv, resolveProjectDatabaseUrlFromEnv, scheduleJobRetry, selectActiveEphemeralScheduledTriggerSlugs, selectTriggerBySlug, upsertTriggerScheduleFields } from "@keystrokehq/database";
|
|
3
3
|
import { nextTriggerRunAt, resolveCronSchedule } from "@keystrokehq/trigger";
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
5
5
|
import { PgBoss } from "pg-boss";
|
|
@@ -378,11 +378,12 @@ async function syncTriggerSchedules(options) {
|
|
|
378
378
|
const existing = await selectTriggerBySlug(spec.triggerSlug);
|
|
379
379
|
if (!existing) continue;
|
|
380
380
|
const nextRunAt = !(existing.schedule !== schedule) && existing.enabled === 1 ? existing.nextRunAt : nextTriggerRunAt(schedule, now);
|
|
381
|
+
const enabled = (await listExecutableAttachmentsByTriggerIds([existing.id])).length > 0;
|
|
381
382
|
await upsertTriggerScheduleFields({
|
|
382
383
|
triggerSlug: spec.triggerSlug,
|
|
383
384
|
schedule,
|
|
384
385
|
nextRunAt: nextRunAt ?? now,
|
|
385
|
-
enabled
|
|
386
|
+
enabled,
|
|
386
387
|
updatedAt: now
|
|
387
388
|
});
|
|
388
389
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["sleep","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { claimDueTriggers, claimDueTriggersForOrg } from \"@keystrokehq/database\";\nimport { getProjectScopeId } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { EnqueueInput, JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\n scope?: \"project\" | \"organization\";\n};\n\nfunction enqueueTriggerJob(\n jobQueue: JobQueue,\n row: { slug: string; kind: \"cron\" | \"poll\"; projectId: string },\n scheduledAt?: Date,\n): Promise<string> {\n const input: EnqueueInput = {\n kind: \"trigger\",\n targetId: row.slug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n projectId: row.projectId,\n };\n\n if (scheduledAt) {\n input.scheduledAt = scheduledAt;\n }\n\n return jobQueue.enqueue(input);\n}\n\nasync function claimDue(\n scope: \"project\" | \"organization\",\n asOf: Date,\n resolveNextRunAt: (schedule: string) => Date,\n limit: number,\n) {\n if (scope === \"organization\") {\n return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);\n }\n\n return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) =>\n rows.map((row) => ({ ...row, projectId: getProjectScopeId() })),\n );\n}\n\nexport function createScheduleTicker(ctx: ScheduleTickerContext) {\n const pollIntervalMs = ctx.pollIntervalMs ?? 1_000;\n const batchSize = ctx.batchSize ?? 10;\n\n async function fireDueSchedules(asOf = new Date()): Promise<number> {\n const scope = ctx.scope ?? \"project\";\n const claimed = await claimDue(\n scope,\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(\n ctx.jobQueue,\n {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n },\n asOf,\n );\n }\n\n return claimed.length;\n }\n\n async function startScheduleTicker(options: ScheduleTickerOptions = {}): Promise<StopFn> {\n const intervalMs = options.pollIntervalMs ?? pollIntervalMs;\n const limit = options.batchSize ?? batchSize;\n const scope = options.scope ?? ctx.scope ?? \"project\";\n let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n const claimed = await claimDue(\n scope,\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(ctx.jobQueue, {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n });\n }\n } catch {\n // keep ticking\n }\n\n await sleep(intervalMs);\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n };\n }\n\n return { fireDueSchedules, startScheduleTicker };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { EventEmitter } from \"node:events\";\n\nimport type { CancelHandler, StopFn } from \"./types\";\n\n/** In-process cancel pub/sub for single-process queues (memory, db polling). */\nexport function createInProcessCancelChannel(): {\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n} {\n const emitter = new EventEmitter();\n return {\n async publishCancel(runId) {\n emitter.emit(\"cancel\", runId);\n },\n\n async subscribeCancel(handler) {\n const listener = (runId: string) => {\n void handler(runId);\n };\n emitter.on(\"cancel\", listener);\n return async () => {\n emitter.off(\"cancel\", listener);\n };\n },\n };\n}\n","import {\n claimNextJob,\n enqueueJob,\n failWorkflowRun,\n markJobComplete,\n markJobFailed,\n requeueExpiredLeases,\n scheduleJobRetry,\n} from \"@keystrokehq/database\";\nimport type { ClaimedJob } from \"@keystrokehq/database\";\nimport { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nfunction toJobPayload(job: ClaimedJob): Parameters<JobHandler>[0] {\n return {\n jobId: job.id,\n kind: job.kind,\n targetId: job.targetId,\n runId: job.runId,\n trigger: job.trigger,\n payload: job.payload,\n attempt: job.attempt,\n maxAttempts: job.maxAttempts,\n scheduledAt: job.scheduledAt,\n };\n}\n\nexport function createDatabaseJobQueue(): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n\n return {\n async enqueue(input) {\n return enqueueJob(input);\n },\n\n async startWorker(handler: JobHandler, options: WorkerOptions = {}): Promise<StopFn> {\n const workerId = options.workerId ?? crypto.randomUUID();\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n const leaseSweepIntervalMs = options.leaseSweepIntervalMs ?? 30_000;\n let running = true;\n\n const leaseTimer = setInterval(() => {\n void requeueExpiredLeases();\n }, leaseSweepIntervalMs);\n\n const loop = async () => {\n while (running) {\n try {\n const job = await claimNextJob(workerId);\n if (!job) {\n await sleep(pollIntervalMs);\n continue;\n }\n\n try {\n await handler(toJobPayload(job));\n await markJobComplete(job.id);\n } catch (error) {\n if (job.attempt < job.maxAttempts) {\n await scheduleJobRetry(job.id, job.attempt + 1, retryDelayMs(job.attempt));\n } else {\n await markJobFailed(job.id, error);\n if (job.kind === \"workflow\") {\n await failWorkflowRun(job.runId, error);\n }\n }\n }\n } catch {\n await sleep(pollIntervalMs);\n }\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n clearInterval(leaseTimer);\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { PgBoss } from \"pg-boss\";\n\nlet boss: PgBoss | undefined;\n\nfunction resolveDatabaseUrl(url?: string): string {\n const resolved = url ?? resolvePostgresUrlFromEnv(process.env);\n if (!resolved) {\n throw new Error(\n \"DATABASE_URL or POSTGRES_HOST/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB is required\",\n );\n }\n return resolved;\n}\n\nexport async function startPgBoss(url?: string): Promise<PgBoss> {\n if (boss) {\n return boss;\n }\n\n const next = new PgBoss({\n connectionString: resolveDatabaseUrl(url),\n schema: \"pgboss\",\n });\n next.on(\"error\", (error) => {\n console.error(\"[pg-boss]\", error);\n });\n\n await next.start();\n boss = next;\n return next;\n}\n\nexport function getPgBoss(): PgBoss {\n if (!boss) {\n throw new Error(\"PgBoss not started. Call startPgBoss() first.\");\n }\n return boss;\n}\n\nexport async function stopPgBoss(): Promise<void> {\n if (!boss) {\n return;\n }\n\n await boss.stop();\n boss = undefined;\n}\n","export { createPostgresCancelChannel as createPgCancelChannel } from \"@keystrokehq/database\";\n\n/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */\nexport function pgCancelChannelName(\n scope: \"platform\" | \"project\" | \"organization\",\n id?: string,\n): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n if (scope === \"organization\") {\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_org_${sanitized}`;\n }\n\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_${sanitized}`;\n}\n","import { createHash } from \"node:crypto\";\nimport { PgBoss, type JobWithMetadata } from \"pg-boss\";\nimport { createPgCancelChannel, pgCancelChannelName } from \"./pg-cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\n/** Shared queue name for the platform control-plane scope. */\nexport const DEFAULT_QUEUE_NAME = \"keystroke\";\nconst PGBOSS_SCHEMA = \"pgboss\";\n\n/**\n * Derive a per-project pg-boss queue name. pg-boss queue names must be <= 50\n * chars, contain only [A-Za-z0-9_], and not start with a digit. Project ids are\n * usually UUIDs (hyphens, may start with a digit), so we sanitize, and fall back\n * to a hash when the sanitized name would exceed the length limit.\n */\nexport function pgBossProjectQueueName(projectId: string): string {\n const sanitized = projectId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_${createHash(\"sha1\").update(projectId).digest(\"hex\").slice(0, 40)}`;\n}\n\nexport function pgBossOrgQueueName(organizationId: string): string {\n const sanitized = organizationId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_org_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_org_${createHash(\"sha1\").update(organizationId).digest(\"hex\").slice(0, 36)}`;\n}\n\nexport type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\n organizationId?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n targetId: string;\n runId: string;\n trigger: \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n payload?: unknown;\n attempt?: number;\n maxAttempts?: number;\n scheduledAt?: string;\n projectId?: string;\n};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n projectId?: string;\n organizationId?: string;\n};\n\nexport function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const cancelChannel = createPgCancelChannel(\n options.connectionString,\n pgCancelChannelName(\n options.cancelScope ?? \"platform\",\n options.cancelScope === \"organization\" ? options.organizationId : options.projectId,\n ),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(\n queueName,\n {\n ...input,\n payload: input.payload,\n },\n {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\n },\n );\n\n if (!jobId) {\n if (input.dedupeKey) {\n return input.dedupeKey;\n }\n throw new Error(\"Failed to enqueue job\");\n }\n\n return jobId;\n },\n\n async startWorker(handler: JobHandler): Promise<StopFn> {\n let stopped = false;\n\n const workerId = await boss.work<PgBossJobData>(\n queueName,\n { batchSize: 1, includeMetadata: true },\n async (jobs: JobWithMetadata<PgBossJobData>[]) => {\n if (stopped) {\n return;\n }\n\n const job = jobs[0];\n if (!job) {\n return;\n }\n\n const data = job.data;\n const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;\n const attempt = (job.retryCount ?? 0) + 1;\n\n await handler({\n jobId: job.id,\n kind: data.kind,\n targetId: data.targetId,\n runId: data.runId,\n trigger: data.trigger,\n payload: data.payload ?? {},\n attempt,\n maxAttempts,\n exhaustedRetries: attempt >= maxAttempts,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\n projectId: data.projectId,\n });\n },\n );\n\n return async () => {\n stopped = true;\n await boss.offWork(queueName, { id: workerId });\n if (options.stopBossOnWorkerStop) {\n await boss.stop({ graceful: true, timeout: 5_000 });\n }\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport async function createPgBossQueue(options: PgBossQueueOptions): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const boss = new PgBoss({\n connectionString: options.connectionString,\n schema: PGBOSS_SCHEMA,\n });\n await boss.start();\n await boss.createQueue(queueName);\n\n return buildPgBossJobQueue(boss, {\n stopBossOnWorkerStop: true,\n queueName,\n connectionString: options.connectionString,\n cancelScope: options.cancelScope ?? \"project\",\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n options: { connectionString: string; queueName?: string },\n): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, {\n queueName,\n connectionString: options.connectionString,\n cancelScope: \"platform\",\n });\n}\n","import {\n DEFAULT_DATABASE_URL,\n getProjectScopeId,\n inferDialect,\n resolveProjectDatabaseUrlFromEnv,\n} from \"@keystrokehq/database\";\n\nimport {\n defineSchedulerPlugin,\n type SchedulerPlugin,\n type SchedulerPluginContext,\n} from \"./contract\";\nimport { createDatabaseJobQueue } from \"./database-queue\";\nimport { getPgBoss, startPgBoss } from \"./pg-boss-client\";\nimport {\n createPgBossQueue,\n createSharedPgBossJobQueue,\n pgBossOrgQueueName,\n pgBossProjectQueueName,\n} from \"./pg-boss-queue\";\n\nexport type { SchedulerPlugin, SchedulerPluginContext, SchedulerScope } from \"./contract\";\nexport { defineSchedulerPlugin } from \"./contract\";\n\nfunction resolveUrl(ctx: SchedulerPluginContext): string {\n return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;\n}\n\n/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */\nexport function pgBossSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"pg-boss\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n\n if (ctx.scope === \"platform\") {\n await startPgBoss(url);\n return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });\n }\n\n if (ctx.scope === \"organization\") {\n const organizationId = ctx.organizationId;\n if (!organizationId) {\n throw new Error(\"organizationId is required for organization-scoped pg-boss queue\");\n }\n\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossOrgQueueName(organizationId),\n cancelScope: \"organization\",\n organizationId,\n });\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(projectId),\n projectId,\n });\n },\n });\n}\n\n/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */\nexport function pollingSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"polling\",\n async createJobQueue() {\n return createDatabaseJobQueue();\n },\n });\n}\n\n/** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */\nexport function defaultSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"default\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n const dialect = inferDialect(url, ctx.dialect);\n\n if (dialect === \"postgres\") {\n return pgBossSchedulerPlugin().createJobQueue(ctx);\n }\n\n return pollingSchedulerPlugin().createJobQueue(ctx);\n },\n });\n}\n","import { resolveCronSchedule } from \"@keystrokehq/trigger\";\n\nexport type ScheduleOverrideOptions = {\n global?: string;\n byTrigger?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n triggerSlug: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(triggerSlug, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byTrigger,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n selectActiveEphemeralScheduledTriggerSlugs,\n selectTriggerBySlug,\n upsertTriggerScheduleFields,\n} from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { ScheduleSyncOptions } from \"./types\";\nimport { resolveTriggerSchedule } from \"./resolve-schedule\";\n\nexport async function syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void> {\n const now = new Date();\n const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);\n // Ephemeral cron/poll schedules are managed by set_trigger, not project discovery — keep them out\n // of the disable sweep so a deploy/restart does not wipe agent-created triggers.\n const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();\n const slugs = [...projectSlugs, ...ephemeralSlugs];\n\n if (slugs.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInSlugs(slugs, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.triggerSlug,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerBySlug(spec.triggerSlug);\n if (!existing) {\n continue;\n }\n\n const scheduleChanged = existing.schedule !== schedule;\n const nextRunAt =\n !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n await upsertTriggerScheduleFields({\n triggerSlug: spec.triggerSlug,\n schedule,\n nextRunAt: nextRunAt ?? now,\n enabled: true,\n updatedAt: now,\n });\n }\n}\n","import { createScheduleTicker } from \"./schedule-ticker\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport { syncTriggerSchedules as syncTriggerScheduleRows } from \"./sync-trigger-schedules\";\nimport type {\n CreateJobQueueOptions,\n CreateSchedulerOptions,\n JobQueue,\n ScheduleSyncOptions,\n ScheduleTickerOptions,\n Scheduler,\n StopFn,\n} from \"./types\";\n\nasync function createUnderlyingJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n if (options.adapter) {\n return options.adapter;\n }\n\n const plugin = options.plugin ?? defaultSchedulerPlugin();\n return plugin.createJobQueue({\n scope: options.scope ?? \"project\",\n url: options.url,\n dialect: options.dialect,\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nfunction wrapScheduler(jobQueue: JobQueue, scope?: \"project\" | \"organization\"): Scheduler {\n const ticker = createScheduleTicker({ jobQueue, scope });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\n publishCancel: (runId) => jobQueue.publishCancel(runId),\n subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),\n syncTriggerSchedules: (options: ScheduleSyncOptions) => syncTriggerScheduleRows(options),\n startScheduleTicker: (options?: ScheduleTickerOptions) => ticker.startScheduleTicker(options),\n fireDueSchedules: (asOf?: Date) => ticker.fireDueSchedules(asOf),\n };\n}\n\nexport async function createScheduler(options: CreateSchedulerOptions = {}): Promise<Scheduler> {\n const jobQueue = await createUnderlyingJobQueue(options);\n const scope = options.scope === \"organization\" ? \"organization\" : \"project\";\n return wrapScheduler(jobQueue, scope);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(\n jobQueue: JobQueue,\n scope?: \"project\" | \"organization\",\n): Scheduler {\n return wrapScheduler(jobQueue, scope);\n}\n\nexport type { StopFn };\n","import { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { EnqueueInput, JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nexport type MemoryJobQueueOptions = {\n sync?: boolean;\n};\n\nexport function createMemoryJobQueue(options: MemoryJobQueueOptions = {}): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n const pending: Array<{ id: string; input: EnqueueInput; enqueuedAt: Date }> = [];\n let handler: JobHandler | undefined;\n let draining = false;\n\n async function drain(): Promise<void> {\n if (!handler || draining) {\n return;\n }\n\n draining = true;\n try {\n while (pending.length > 0) {\n const next = pending.shift();\n if (!next) {\n break;\n }\n\n const input = next.input;\n await handler({\n jobId: next.id,\n kind: input.kind,\n targetId: input.targetId,\n runId: input.runId,\n trigger: input.trigger,\n payload: input.payload ?? {},\n attempt: input.attempt ?? 1,\n maxAttempts: input.maxAttempts ?? 3,\n scheduledAt: input.scheduledAt ?? next.enqueuedAt,\n });\n }\n } finally {\n draining = false;\n }\n }\n\n return {\n async enqueue(input) {\n const id = crypto.randomUUID();\n pending.push({ id, input, enqueuedAt: new Date() });\n\n if (options.sync) {\n await drain();\n }\n\n return id;\n },\n\n async startWorker(nextHandler: JobHandler, _opts: WorkerOptions = {}): Promise<StopFn> {\n handler = nextHandler;\n\n if (!options.sync) {\n void drain();\n }\n\n return async () => {\n handler = undefined;\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport { retryDelayMs };\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { getPgBoss } from \"./pg-boss-client\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { createSharedPgBossJobQueue } from \"./pg-boss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet sharedJobQueue: JobQueue | undefined;\n\nexport async function createSharedPgBossScheduler(): Promise<Scheduler> {\n const jobQueue = await getSharedPgBossJobQueue();\n return wrapJobQueueAsScheduler(jobQueue);\n}\n\nexport async function getSharedPgBossJobQueue(): Promise<JobQueue> {\n if (sharedJobQueue) {\n return sharedJobQueue;\n }\n\n const connectionString = resolvePostgresUrlFromEnv(process.env);\n if (!connectionString) {\n throw new Error(\"Postgres connection string is required for shared pg-boss queue\");\n }\n\n sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });\n return sharedJobQueue;\n}\n\nexport function resetSharedPgBossJobQueueForTests(): void {\n sharedJobQueue = undefined;\n}\n","import type { SchedulerPlugin } from \"./contract\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport {\n createSharedPgBossScheduler,\n resetSharedPgBossJobQueueForTests,\n} from \"./shared-pgboss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet configuredPlugin: SchedulerPlugin | undefined;\nlet sharedJobQueue: JobQueue | undefined;\n\nexport function configureSharedScheduler(plugin: SchedulerPlugin): void {\n configuredPlugin = plugin;\n sharedJobQueue = undefined;\n}\n\nexport async function createSharedScheduler(): Promise<Scheduler> {\n const plugin = configuredPlugin ?? defaultSchedulerPlugin();\n\n if (plugin.name === \"default\") {\n return createSharedPgBossScheduler();\n }\n\n if (!sharedJobQueue) {\n sharedJobQueue = await plugin.createJobQueue({ scope: \"platform\" });\n }\n\n return wrapJobQueueAsScheduler(sharedJobQueue);\n}\n\nexport function resetSharedSchedulerForTests(): void {\n configuredPlugin = undefined;\n sharedJobQueue = undefined;\n resetSharedPgBossJobQueueForTests();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["sleep","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { claimDueTriggers, claimDueTriggersForOrg } from \"@keystrokehq/database\";\nimport { getProjectScopeId } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { EnqueueInput, JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\n scope?: \"project\" | \"organization\";\n};\n\nfunction enqueueTriggerJob(\n jobQueue: JobQueue,\n row: { slug: string; kind: \"cron\" | \"poll\"; projectId: string },\n scheduledAt?: Date,\n): Promise<string> {\n const input: EnqueueInput = {\n kind: \"trigger\",\n targetId: row.slug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n projectId: row.projectId,\n };\n\n if (scheduledAt) {\n input.scheduledAt = scheduledAt;\n }\n\n return jobQueue.enqueue(input);\n}\n\nasync function claimDue(\n scope: \"project\" | \"organization\",\n asOf: Date,\n resolveNextRunAt: (schedule: string) => Date,\n limit: number,\n) {\n if (scope === \"organization\") {\n return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);\n }\n\n return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) =>\n rows.map((row) => ({ ...row, projectId: getProjectScopeId() })),\n );\n}\n\nexport function createScheduleTicker(ctx: ScheduleTickerContext) {\n const pollIntervalMs = ctx.pollIntervalMs ?? 1_000;\n const batchSize = ctx.batchSize ?? 10;\n\n async function fireDueSchedules(asOf = new Date()): Promise<number> {\n const scope = ctx.scope ?? \"project\";\n const claimed = await claimDue(\n scope,\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(\n ctx.jobQueue,\n {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n },\n asOf,\n );\n }\n\n return claimed.length;\n }\n\n async function startScheduleTicker(options: ScheduleTickerOptions = {}): Promise<StopFn> {\n const intervalMs = options.pollIntervalMs ?? pollIntervalMs;\n const limit = options.batchSize ?? batchSize;\n const scope = options.scope ?? ctx.scope ?? \"project\";\n let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n const claimed = await claimDue(\n scope,\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(ctx.jobQueue, {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n });\n }\n } catch {\n // keep ticking\n }\n\n await sleep(intervalMs);\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n };\n }\n\n return { fireDueSchedules, startScheduleTicker };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { EventEmitter } from \"node:events\";\n\nimport type { CancelHandler, StopFn } from \"./types\";\n\n/** In-process cancel pub/sub for single-process queues (memory, db polling). */\nexport function createInProcessCancelChannel(): {\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n} {\n const emitter = new EventEmitter();\n return {\n async publishCancel(runId) {\n emitter.emit(\"cancel\", runId);\n },\n\n async subscribeCancel(handler) {\n const listener = (runId: string) => {\n void handler(runId);\n };\n emitter.on(\"cancel\", listener);\n return async () => {\n emitter.off(\"cancel\", listener);\n };\n },\n };\n}\n","import {\n claimNextJob,\n enqueueJob,\n failWorkflowRun,\n markJobComplete,\n markJobFailed,\n requeueExpiredLeases,\n scheduleJobRetry,\n} from \"@keystrokehq/database\";\nimport type { ClaimedJob } from \"@keystrokehq/database\";\nimport { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nfunction toJobPayload(job: ClaimedJob): Parameters<JobHandler>[0] {\n return {\n jobId: job.id,\n kind: job.kind,\n targetId: job.targetId,\n runId: job.runId,\n trigger: job.trigger,\n payload: job.payload,\n attempt: job.attempt,\n maxAttempts: job.maxAttempts,\n scheduledAt: job.scheduledAt,\n };\n}\n\nexport function createDatabaseJobQueue(): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n\n return {\n async enqueue(input) {\n return enqueueJob(input);\n },\n\n async startWorker(handler: JobHandler, options: WorkerOptions = {}): Promise<StopFn> {\n const workerId = options.workerId ?? crypto.randomUUID();\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n const leaseSweepIntervalMs = options.leaseSweepIntervalMs ?? 30_000;\n let running = true;\n\n const leaseTimer = setInterval(() => {\n void requeueExpiredLeases();\n }, leaseSweepIntervalMs);\n\n const loop = async () => {\n while (running) {\n try {\n const job = await claimNextJob(workerId);\n if (!job) {\n await sleep(pollIntervalMs);\n continue;\n }\n\n try {\n await handler(toJobPayload(job));\n await markJobComplete(job.id);\n } catch (error) {\n if (job.attempt < job.maxAttempts) {\n await scheduleJobRetry(job.id, job.attempt + 1, retryDelayMs(job.attempt));\n } else {\n await markJobFailed(job.id, error);\n if (job.kind === \"workflow\") {\n await failWorkflowRun(job.runId, error);\n }\n }\n }\n } catch {\n await sleep(pollIntervalMs);\n }\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n clearInterval(leaseTimer);\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { PgBoss } from \"pg-boss\";\n\nlet boss: PgBoss | undefined;\n\nfunction resolveDatabaseUrl(url?: string): string {\n const resolved = url ?? resolvePostgresUrlFromEnv(process.env);\n if (!resolved) {\n throw new Error(\n \"DATABASE_URL or POSTGRES_HOST/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB is required\",\n );\n }\n return resolved;\n}\n\nexport async function startPgBoss(url?: string): Promise<PgBoss> {\n if (boss) {\n return boss;\n }\n\n const next = new PgBoss({\n connectionString: resolveDatabaseUrl(url),\n schema: \"pgboss\",\n });\n next.on(\"error\", (error) => {\n console.error(\"[pg-boss]\", error);\n });\n\n await next.start();\n boss = next;\n return next;\n}\n\nexport function getPgBoss(): PgBoss {\n if (!boss) {\n throw new Error(\"PgBoss not started. Call startPgBoss() first.\");\n }\n return boss;\n}\n\nexport async function stopPgBoss(): Promise<void> {\n if (!boss) {\n return;\n }\n\n await boss.stop();\n boss = undefined;\n}\n","export { createPostgresCancelChannel as createPgCancelChannel } from \"@keystrokehq/database\";\n\n/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */\nexport function pgCancelChannelName(\n scope: \"platform\" | \"project\" | \"organization\",\n id?: string,\n): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n if (scope === \"organization\") {\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_org_${sanitized}`;\n }\n\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_${sanitized}`;\n}\n","import { createHash } from \"node:crypto\";\nimport { PgBoss, type JobWithMetadata } from \"pg-boss\";\nimport { createPgCancelChannel, pgCancelChannelName } from \"./pg-cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\n/** Shared queue name for the platform control-plane scope. */\nexport const DEFAULT_QUEUE_NAME = \"keystroke\";\nconst PGBOSS_SCHEMA = \"pgboss\";\n\n/**\n * Derive a per-project pg-boss queue name. pg-boss queue names must be <= 50\n * chars, contain only [A-Za-z0-9_], and not start with a digit. Project ids are\n * usually UUIDs (hyphens, may start with a digit), so we sanitize, and fall back\n * to a hash when the sanitized name would exceed the length limit.\n */\nexport function pgBossProjectQueueName(projectId: string): string {\n const sanitized = projectId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_${createHash(\"sha1\").update(projectId).digest(\"hex\").slice(0, 40)}`;\n}\n\nexport function pgBossOrgQueueName(organizationId: string): string {\n const sanitized = organizationId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_org_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_org_${createHash(\"sha1\").update(organizationId).digest(\"hex\").slice(0, 36)}`;\n}\n\nexport type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\n organizationId?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n targetId: string;\n runId: string;\n trigger: \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n payload?: unknown;\n attempt?: number;\n maxAttempts?: number;\n scheduledAt?: string;\n projectId?: string;\n};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n projectId?: string;\n organizationId?: string;\n};\n\nexport function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const cancelChannel = createPgCancelChannel(\n options.connectionString,\n pgCancelChannelName(\n options.cancelScope ?? \"platform\",\n options.cancelScope === \"organization\" ? options.organizationId : options.projectId,\n ),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(\n queueName,\n {\n ...input,\n payload: input.payload,\n },\n {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\n },\n );\n\n if (!jobId) {\n if (input.dedupeKey) {\n return input.dedupeKey;\n }\n throw new Error(\"Failed to enqueue job\");\n }\n\n return jobId;\n },\n\n async startWorker(handler: JobHandler): Promise<StopFn> {\n let stopped = false;\n\n const workerId = await boss.work<PgBossJobData>(\n queueName,\n { batchSize: 1, includeMetadata: true },\n async (jobs: JobWithMetadata<PgBossJobData>[]) => {\n if (stopped) {\n return;\n }\n\n const job = jobs[0];\n if (!job) {\n return;\n }\n\n const data = job.data;\n const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;\n const attempt = (job.retryCount ?? 0) + 1;\n\n await handler({\n jobId: job.id,\n kind: data.kind,\n targetId: data.targetId,\n runId: data.runId,\n trigger: data.trigger,\n payload: data.payload ?? {},\n attempt,\n maxAttempts,\n exhaustedRetries: attempt >= maxAttempts,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\n projectId: data.projectId,\n });\n },\n );\n\n return async () => {\n stopped = true;\n await boss.offWork(queueName, { id: workerId });\n if (options.stopBossOnWorkerStop) {\n await boss.stop({ graceful: true, timeout: 5_000 });\n }\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport async function createPgBossQueue(options: PgBossQueueOptions): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const boss = new PgBoss({\n connectionString: options.connectionString,\n schema: PGBOSS_SCHEMA,\n });\n await boss.start();\n await boss.createQueue(queueName);\n\n return buildPgBossJobQueue(boss, {\n stopBossOnWorkerStop: true,\n queueName,\n connectionString: options.connectionString,\n cancelScope: options.cancelScope ?? \"project\",\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n options: { connectionString: string; queueName?: string },\n): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, {\n queueName,\n connectionString: options.connectionString,\n cancelScope: \"platform\",\n });\n}\n","import {\n DEFAULT_DATABASE_URL,\n getProjectScopeId,\n inferDialect,\n resolveProjectDatabaseUrlFromEnv,\n} from \"@keystrokehq/database\";\n\nimport {\n defineSchedulerPlugin,\n type SchedulerPlugin,\n type SchedulerPluginContext,\n} from \"./contract\";\nimport { createDatabaseJobQueue } from \"./database-queue\";\nimport { getPgBoss, startPgBoss } from \"./pg-boss-client\";\nimport {\n createPgBossQueue,\n createSharedPgBossJobQueue,\n pgBossOrgQueueName,\n pgBossProjectQueueName,\n} from \"./pg-boss-queue\";\n\nexport type { SchedulerPlugin, SchedulerPluginContext, SchedulerScope } from \"./contract\";\nexport { defineSchedulerPlugin } from \"./contract\";\n\nfunction resolveUrl(ctx: SchedulerPluginContext): string {\n return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;\n}\n\n/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */\nexport function pgBossSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"pg-boss\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n\n if (ctx.scope === \"platform\") {\n await startPgBoss(url);\n return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });\n }\n\n if (ctx.scope === \"organization\") {\n const organizationId = ctx.organizationId;\n if (!organizationId) {\n throw new Error(\"organizationId is required for organization-scoped pg-boss queue\");\n }\n\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossOrgQueueName(organizationId),\n cancelScope: \"organization\",\n organizationId,\n });\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(projectId),\n projectId,\n });\n },\n });\n}\n\n/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */\nexport function pollingSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"polling\",\n async createJobQueue() {\n return createDatabaseJobQueue();\n },\n });\n}\n\n/** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */\nexport function defaultSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"default\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n const dialect = inferDialect(url, ctx.dialect);\n\n if (dialect === \"postgres\") {\n return pgBossSchedulerPlugin().createJobQueue(ctx);\n }\n\n return pollingSchedulerPlugin().createJobQueue(ctx);\n },\n });\n}\n","import { resolveCronSchedule } from \"@keystrokehq/trigger\";\n\nexport type ScheduleOverrideOptions = {\n global?: string;\n byTrigger?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n triggerSlug: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(triggerSlug, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byTrigger,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n listExecutableAttachmentsByTriggerIds,\n selectActiveEphemeralScheduledTriggerSlugs,\n selectTriggerBySlug,\n upsertTriggerScheduleFields,\n} from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { ScheduleSyncOptions } from \"./types\";\nimport { resolveTriggerSchedule } from \"./resolve-schedule\";\n\nexport async function syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void> {\n const now = new Date();\n const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);\n // Ephemeral cron/poll schedules are managed by set_trigger, not project discovery — keep them out\n // of the disable sweep so a deploy/restart does not wipe agent-created triggers.\n const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();\n const slugs = [...projectSlugs, ...ephemeralSlugs];\n\n if (slugs.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInSlugs(slugs, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.triggerSlug,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerBySlug(spec.triggerSlug);\n if (!existing) {\n continue;\n }\n\n const scheduleChanged = existing.schedule !== schedule;\n const nextRunAt =\n !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n const executable = await listExecutableAttachmentsByTriggerIds([existing.id]);\n const enabled = executable.length > 0;\n\n await upsertTriggerScheduleFields({\n triggerSlug: spec.triggerSlug,\n schedule,\n nextRunAt: nextRunAt ?? now,\n enabled,\n updatedAt: now,\n });\n }\n}\n","import { createScheduleTicker } from \"./schedule-ticker\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport { syncTriggerSchedules as syncTriggerScheduleRows } from \"./sync-trigger-schedules\";\nimport type {\n CreateJobQueueOptions,\n CreateSchedulerOptions,\n JobQueue,\n ScheduleSyncOptions,\n ScheduleTickerOptions,\n Scheduler,\n StopFn,\n} from \"./types\";\n\nasync function createUnderlyingJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n if (options.adapter) {\n return options.adapter;\n }\n\n const plugin = options.plugin ?? defaultSchedulerPlugin();\n return plugin.createJobQueue({\n scope: options.scope ?? \"project\",\n url: options.url,\n dialect: options.dialect,\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nfunction wrapScheduler(jobQueue: JobQueue, scope?: \"project\" | \"organization\"): Scheduler {\n const ticker = createScheduleTicker({ jobQueue, scope });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\n publishCancel: (runId) => jobQueue.publishCancel(runId),\n subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),\n syncTriggerSchedules: (options: ScheduleSyncOptions) => syncTriggerScheduleRows(options),\n startScheduleTicker: (options?: ScheduleTickerOptions) => ticker.startScheduleTicker(options),\n fireDueSchedules: (asOf?: Date) => ticker.fireDueSchedules(asOf),\n };\n}\n\nexport async function createScheduler(options: CreateSchedulerOptions = {}): Promise<Scheduler> {\n const jobQueue = await createUnderlyingJobQueue(options);\n const scope = options.scope === \"organization\" ? \"organization\" : \"project\";\n return wrapScheduler(jobQueue, scope);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(\n jobQueue: JobQueue,\n scope?: \"project\" | \"organization\",\n): Scheduler {\n return wrapScheduler(jobQueue, scope);\n}\n\nexport type { StopFn };\n","import { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { EnqueueInput, JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nexport type MemoryJobQueueOptions = {\n sync?: boolean;\n};\n\nexport function createMemoryJobQueue(options: MemoryJobQueueOptions = {}): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n const pending: Array<{ id: string; input: EnqueueInput; enqueuedAt: Date }> = [];\n let handler: JobHandler | undefined;\n let draining = false;\n\n async function drain(): Promise<void> {\n if (!handler || draining) {\n return;\n }\n\n draining = true;\n try {\n while (pending.length > 0) {\n const next = pending.shift();\n if (!next) {\n break;\n }\n\n const input = next.input;\n await handler({\n jobId: next.id,\n kind: input.kind,\n targetId: input.targetId,\n runId: input.runId,\n trigger: input.trigger,\n payload: input.payload ?? {},\n attempt: input.attempt ?? 1,\n maxAttempts: input.maxAttempts ?? 3,\n scheduledAt: input.scheduledAt ?? next.enqueuedAt,\n });\n }\n } finally {\n draining = false;\n }\n }\n\n return {\n async enqueue(input) {\n const id = crypto.randomUUID();\n pending.push({ id, input, enqueuedAt: new Date() });\n\n if (options.sync) {\n await drain();\n }\n\n return id;\n },\n\n async startWorker(nextHandler: JobHandler, _opts: WorkerOptions = {}): Promise<StopFn> {\n handler = nextHandler;\n\n if (!options.sync) {\n void drain();\n }\n\n return async () => {\n handler = undefined;\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport { retryDelayMs };\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { getPgBoss } from \"./pg-boss-client\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { createSharedPgBossJobQueue } from \"./pg-boss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet sharedJobQueue: JobQueue | undefined;\n\nexport async function createSharedPgBossScheduler(): Promise<Scheduler> {\n const jobQueue = await getSharedPgBossJobQueue();\n return wrapJobQueueAsScheduler(jobQueue);\n}\n\nexport async function getSharedPgBossJobQueue(): Promise<JobQueue> {\n if (sharedJobQueue) {\n return sharedJobQueue;\n }\n\n const connectionString = resolvePostgresUrlFromEnv(process.env);\n if (!connectionString) {\n throw new Error(\"Postgres connection string is required for shared pg-boss queue\");\n }\n\n sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });\n return sharedJobQueue;\n}\n\nexport function resetSharedPgBossJobQueueForTests(): void {\n sharedJobQueue = undefined;\n}\n","import type { SchedulerPlugin } from \"./contract\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport {\n createSharedPgBossScheduler,\n resetSharedPgBossJobQueueForTests,\n} from \"./shared-pgboss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet configuredPlugin: SchedulerPlugin | undefined;\nlet sharedJobQueue: JobQueue | undefined;\n\nexport function configureSharedScheduler(plugin: SchedulerPlugin): void {\n configuredPlugin = plugin;\n sharedJobQueue = undefined;\n}\n\nexport async function createSharedScheduler(): Promise<Scheduler> {\n const plugin = configuredPlugin ?? defaultSchedulerPlugin();\n\n if (plugin.name === \"default\") {\n return createSharedPgBossScheduler();\n }\n\n if (!sharedJobQueue) {\n sharedJobQueue = await plugin.createJobQueue({ scope: \"platform\" });\n }\n\n return wrapJobQueueAsScheduler(sharedJobQueue);\n}\n\nexport function resetSharedSchedulerForTests(): void {\n configuredPlugin = undefined;\n sharedJobQueue = undefined;\n resetSharedPgBossJobQueueForTests();\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keystrokehq/scheduler",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"tsdown": "^0.22.0",
|
|
42
42
|
"typescript": "^6.0.3",
|
|
43
43
|
"vitest": "^4.1.7",
|
|
44
|
-
"@keystrokehq/database": "0.0.
|
|
44
|
+
"@keystrokehq/database": "0.0.129",
|
|
45
45
|
"@keystrokehq/oxlint-config": "0.0.4",
|
|
46
|
-
"@keystrokehq/trigger": "0.0.
|
|
46
|
+
"@keystrokehq/trigger": "0.0.155",
|
|
47
47
|
"@keystrokehq/tsconfig": "0.0.3",
|
|
48
48
|
"@keystrokehq/tsdown-config": "0.0.3",
|
|
49
49
|
"@keystrokehq/vitest-config": "0.0.5"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@keystrokehq/database": "0.0.
|
|
53
|
-
"@keystrokehq/trigger": "0.0.
|
|
52
|
+
"@keystrokehq/database": "0.0.129",
|
|
53
|
+
"@keystrokehq/trigger": "0.0.155"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"@keystrokehq/database": {
|