@keystrokehq/scheduler 0.1.4 → 0.2.0

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.mjs CHANGED
@@ -1,65 +1,9 @@
1
1
  import { n as DEFAULT_RETRY_DELAY_MS, r as retryDelayMs, t as defineSchedulerPlugin } from "./contract-E1QJBH6_.mjs";
2
- import { DEFAULT_DATABASE_URL, claimDueTriggerSchedules, claimDueTriggerSchedulesForOrg, claimNextJob, createPostgresCancelChannel as createPgCancelChannel, disableAllTriggerSchedules, disableTriggerSchedulesNotInSlugs, enqueueJob, failWorkflowRun, getProjectScopeId, inferDialect, markJobComplete, markJobFailed, requeueExpiredLeases, resolvePostgresUrlFromEnv, resolveProjectDatabaseUrlFromEnv, scheduleJobRetry, selectActiveEphemeralScheduledAttachmentSlugs, selectTriggerScheduleBySlug, upsertTriggerSchedule } from "@keystrokehq/database";
3
- import { nextTriggerRunAt, resolveCronSchedule } from "@keystrokehq/trigger";
2
+ import { DEFAULT_DATABASE_URL, claimDueTriggers, claimDueTriggersForOrg, claimNextJob, createPostgresCancelChannel as createPgCancelChannel, disableAllTriggerSchedules, disableTriggerSchedulesNotInSlugs, enqueueJob, failWorkflowRun, getProjectScopeId, inferDialect, listEnabledScheduledTriggers, listExecutableAttachmentsByTriggerIds, markJobComplete, markJobFailed, requeueExpiredLeases, resolvePostgresUrlFromEnv, resolveProjectDatabaseUrlFromEnv, scheduleJobRetry, selectActiveEphemeralScheduledTriggerSlugs, selectTriggerBySlug, setTriggerScheduleEnabled, upsertTriggerScheduleFields } from "@keystrokehq/database";
4
3
  import { EventEmitter } from "node:events";
4
+ import { nextTriggerRunAt, resolveCronSchedule } from "@keystrokehq/trigger";
5
5
  import { PgBoss } from "pg-boss";
6
6
  import { createHash } from "node:crypto";
7
- //#region src/schedule-ticker.ts
8
- function enqueueTriggerJob(jobQueue, row, scheduledAt) {
9
- const input = {
10
- kind: "trigger",
11
- targetId: row.attachmentSlug,
12
- runId: crypto.randomUUID(),
13
- trigger: row.kind,
14
- payload: {},
15
- projectId: row.projectId
16
- };
17
- if (scheduledAt) input.scheduledAt = scheduledAt;
18
- return jobQueue.enqueue(input);
19
- }
20
- async function claimDue(scope, asOf, resolveNextRunAt, limit) {
21
- if (scope === "organization") return claimDueTriggerSchedulesForOrg(asOf, resolveNextRunAt, limit);
22
- return claimDueTriggerSchedules(asOf, resolveNextRunAt, limit).then((rows) => rows.map((row) => ({
23
- ...row,
24
- projectId: getProjectScopeId()
25
- })));
26
- }
27
- function createScheduleTicker(ctx) {
28
- const pollIntervalMs = ctx.pollIntervalMs ?? 1e3;
29
- const batchSize = ctx.batchSize ?? 10;
30
- async function fireDueSchedules(asOf = /* @__PURE__ */ new Date()) {
31
- const claimed = await claimDue(ctx.scope ?? "project", asOf, (schedule) => nextTriggerRunAt(schedule, asOf), batchSize);
32
- for (const row of claimed) await enqueueTriggerJob(ctx.jobQueue, row, asOf);
33
- return claimed.length;
34
- }
35
- async function startScheduleTicker(options = {}) {
36
- const intervalMs = options.pollIntervalMs ?? pollIntervalMs;
37
- const limit = options.batchSize ?? batchSize;
38
- const scope = options.scope ?? ctx.scope ?? "project";
39
- let running = true;
40
- const loop = async () => {
41
- while (running) {
42
- try {
43
- const claimed = await claimDue(scope, /* @__PURE__ */ new Date(), (schedule) => nextTriggerRunAt(schedule, /* @__PURE__ */ new Date()), limit);
44
- for (const row of claimed) await enqueueTriggerJob(ctx.jobQueue, row);
45
- } catch {}
46
- await sleep$1(intervalMs);
47
- }
48
- };
49
- loop();
50
- return async () => {
51
- running = false;
52
- };
53
- }
54
- return {
55
- fireDueSchedules,
56
- startScheduleTicker
57
- };
58
- }
59
- function sleep$1(ms) {
60
- return new Promise((resolve) => setTimeout(resolve, ms));
61
- }
62
- //#endregion
63
7
  //#region src/cancel-channel.ts
64
8
  /** In-process cancel pub/sub for single-process queues (memory, db polling). */
65
9
  function createInProcessCancelChannel() {
@@ -112,7 +56,7 @@ function createDatabaseJobQueue() {
112
56
  while (running) try {
113
57
  const job = await claimNextJob(workerId);
114
58
  if (!job) {
115
- await sleep(pollIntervalMs);
59
+ await sleep$1(pollIntervalMs);
116
60
  continue;
117
61
  }
118
62
  try {
@@ -126,7 +70,7 @@ function createDatabaseJobQueue() {
126
70
  }
127
71
  }
128
72
  } catch {
129
- await sleep(pollIntervalMs);
73
+ await sleep$1(pollIntervalMs);
130
74
  }
131
75
  };
132
76
  loop();
@@ -139,10 +83,135 @@ function createDatabaseJobQueue() {
139
83
  subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
140
84
  };
141
85
  }
86
+ function sleep$1(ms) {
87
+ return new Promise((resolve) => setTimeout(resolve, ms));
88
+ }
89
+ //#endregion
90
+ //#region src/schedule-ticker.ts
91
+ function enqueueTriggerJob(jobQueue, row, scheduledAt) {
92
+ const input = {
93
+ kind: "trigger",
94
+ targetId: row.slug,
95
+ runId: crypto.randomUUID(),
96
+ trigger: row.kind,
97
+ payload: {},
98
+ projectId: row.projectId
99
+ };
100
+ if (scheduledAt) input.scheduledAt = scheduledAt;
101
+ return jobQueue.enqueue(input);
102
+ }
103
+ async function claimDue(scope, asOf, resolveNextRunAt, limit) {
104
+ if (scope === "organization") return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);
105
+ return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) => rows.map((row) => ({
106
+ ...row,
107
+ projectId: getProjectScopeId()
108
+ })));
109
+ }
110
+ function createScheduleTicker(ctx) {
111
+ const pollIntervalMs = ctx.pollIntervalMs ?? 1e3;
112
+ const batchSize = ctx.batchSize ?? 10;
113
+ async function fireDueSchedules(asOf = /* @__PURE__ */ new Date()) {
114
+ const claimed = await claimDue(ctx.scope ?? "project", asOf, (schedule) => nextTriggerRunAt(schedule, asOf), batchSize);
115
+ for (const row of claimed) {
116
+ if (row.kind !== "cron" && row.kind !== "poll") continue;
117
+ await enqueueTriggerJob(ctx.jobQueue, {
118
+ slug: row.slug,
119
+ kind: row.kind,
120
+ projectId: row.projectId
121
+ }, asOf);
122
+ }
123
+ return claimed.length;
124
+ }
125
+ async function startScheduleTicker(options = {}) {
126
+ const intervalMs = options.pollIntervalMs ?? pollIntervalMs;
127
+ const limit = options.batchSize ?? batchSize;
128
+ const scope = options.scope ?? ctx.scope ?? "project";
129
+ let running = true;
130
+ const loop = async () => {
131
+ while (running) {
132
+ try {
133
+ const claimed = await claimDue(scope, /* @__PURE__ */ new Date(), (schedule) => nextTriggerRunAt(schedule, /* @__PURE__ */ new Date()), limit);
134
+ for (const row of claimed) {
135
+ if (row.kind !== "cron" && row.kind !== "poll") continue;
136
+ await enqueueTriggerJob(ctx.jobQueue, {
137
+ slug: row.slug,
138
+ kind: row.kind,
139
+ projectId: row.projectId
140
+ });
141
+ }
142
+ } catch {}
143
+ await sleep(intervalMs);
144
+ }
145
+ };
146
+ loop();
147
+ return async () => {
148
+ running = false;
149
+ };
150
+ }
151
+ return {
152
+ fireDueSchedules,
153
+ startScheduleTicker
154
+ };
155
+ }
142
156
  function sleep(ms) {
143
157
  return new Promise((resolve) => setTimeout(resolve, ms));
144
158
  }
145
159
  //#endregion
160
+ //#region src/resolve-schedule.ts
161
+ function resolveTriggerSchedule(triggerSlug, schedule, overrides) {
162
+ return resolveCronSchedule(triggerSlug, schedule, {
163
+ cronScheduleOverride: overrides?.global,
164
+ attachmentScheduleOverrides: overrides?.byTrigger
165
+ });
166
+ }
167
+ //#endregion
168
+ //#region src/sync-trigger-schedules.ts
169
+ async function syncTriggerSchedules(options) {
170
+ const now = /* @__PURE__ */ new Date();
171
+ const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);
172
+ const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();
173
+ const slugs = [...projectSlugs, ...ephemeralSlugs];
174
+ if (slugs.length === 0) {
175
+ await disableAllTriggerSchedules(now);
176
+ return;
177
+ }
178
+ await disableTriggerSchedulesNotInSlugs(slugs, now);
179
+ for (const spec of options.schedules) {
180
+ const schedule = resolveTriggerSchedule(spec.triggerSlug, spec.schedule, options.scheduleOverrides);
181
+ const existing = await selectTriggerBySlug(spec.triggerSlug);
182
+ if (!existing) continue;
183
+ const nextRunAt = !(existing.schedule !== schedule) && existing.enabled === 1 ? existing.nextRunAt : nextTriggerRunAt(schedule, now);
184
+ const enabled = (await listExecutableAttachmentsByTriggerIds([existing.id])).length > 0;
185
+ await upsertTriggerScheduleFields({
186
+ triggerSlug: spec.triggerSlug,
187
+ schedule,
188
+ nextRunAt: nextRunAt ?? now,
189
+ enabled,
190
+ updatedAt: now
191
+ });
192
+ }
193
+ }
194
+ //#endregion
195
+ //#region src/database-trigger-scheduler.ts
196
+ /** Postgres poll loop — default trigger scheduler for pg-boss, polling, and memory paths. */
197
+ function createDatabaseTriggerScheduler(options) {
198
+ const ticker = createScheduleTicker({
199
+ jobQueue: options.jobQueue,
200
+ scope: options.scope,
201
+ pollIntervalMs: options.pollIntervalMs,
202
+ batchSize: options.batchSize
203
+ });
204
+ return {
205
+ sync: (syncOptions) => syncTriggerSchedules(syncOptions),
206
+ start: (startOptions) => ticker.startScheduleTicker(startOptions),
207
+ fireDue: (asOf) => ticker.fireDueSchedules(asOf),
208
+ async remove(triggerSlug) {
209
+ await setTriggerScheduleEnabled(triggerSlug, false, /* @__PURE__ */ new Date());
210
+ },
211
+ async upsert(_spec) {}
212
+ };
213
+ }
214
+ //#endregion
146
215
  //#region src/pg-boss-client.ts
147
216
  let boss;
148
217
  function resolveDatabaseUrl(url) {
@@ -205,6 +274,8 @@ function buildPgBossJobQueue(boss, options) {
205
274
  const queueName = options.queueName ?? "keystroke";
206
275
  const cancelChannel = createPgCancelChannel(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.cancelScope === "organization" ? options.organizationId : options.projectId));
207
276
  return {
277
+ boss,
278
+ queueName,
208
279
  async enqueue(input) {
209
280
  const jobId = await boss.send(queueName, {
210
281
  ...input,
@@ -287,10 +358,132 @@ async function createSharedPgBossJobQueue(boss, options) {
287
358
  });
288
359
  }
289
360
  //#endregion
361
+ //#region src/pg-boss-trigger-scheduler.ts
362
+ /**
363
+ * pg-boss schedule key for one trigger. Mirrors {@link bullmqTriggerSchedulerId}
364
+ * so the firing backend is interchangeable: `trigger-<projectId>-<slug>`. pg-boss
365
+ * keys allow hyphens, so colons (legal in ids) are swapped for `-`.
366
+ */
367
+ function pgBossTriggerScheduleKey(spec) {
368
+ return `trigger-${(spec.projectId ?? getProjectScopeId()).replaceAll(":", "-")}-${spec.triggerSlug.replaceAll(":", "-")}`;
369
+ }
370
+ /** Key prefix scoping reconcile cleanup to the active project (org queues are shared). */
371
+ function pgBossTriggerScheduleKeyPrefix(projectId) {
372
+ return `trigger-${(projectId ?? getProjectScopeId()).replaceAll(":", "-")}-`;
373
+ }
374
+ function triggerJobData(spec) {
375
+ return {
376
+ kind: "trigger",
377
+ targetId: spec.triggerSlug,
378
+ runId: "",
379
+ trigger: spec.kind,
380
+ payload: {},
381
+ projectId: spec.projectId ?? getProjectScopeId()
382
+ };
383
+ }
384
+ function rowToSpec(row) {
385
+ if (row.kind !== "cron" && row.kind !== "poll") return;
386
+ if (!row.schedule) return;
387
+ return {
388
+ triggerSlug: row.slug,
389
+ kind: row.kind,
390
+ schedule: row.schedule,
391
+ projectId: row.projectId
392
+ };
393
+ }
394
+ /**
395
+ * Trigger scheduler backed by pg-boss native cron scheduling. Mirrors
396
+ * {@link createBullmqTriggerScheduler}: `boss.schedule` registers a recurring job
397
+ * onto the project/org queue (no per-second DB poll), and reconcile keeps the
398
+ * `pgboss.schedule` table in sync with the `triggers` table (source of truth).
399
+ *
400
+ * Granularity: pg-boss cron is minute-level (it checks schedules every ~30s and
401
+ * dedupes per minute), so sub-minute schedules effectively fire at most once per
402
+ * minute — unlike the DB ticker's 1s loop.
403
+ */
404
+ function createPgBossTriggerScheduler(_ctx, queue) {
405
+ async function upsertScheduler(spec, overrides) {
406
+ const schedule = resolveTriggerSchedule(spec.triggerSlug, spec.schedule, overrides);
407
+ const key = pgBossTriggerScheduleKey(spec);
408
+ await queue.boss.schedule(queue.queueName, schedule, triggerJobData({
409
+ ...spec,
410
+ schedule
411
+ }), { key });
412
+ }
413
+ async function reconcileFromDatabase(overrides) {
414
+ const projectPrefix = pgBossTriggerScheduleKeyPrefix();
415
+ const rows = await listEnabledScheduledTriggers();
416
+ const expectedKeys = /* @__PURE__ */ new Set();
417
+ const failures = [];
418
+ for (const row of rows) {
419
+ const spec = rowToSpec(row);
420
+ if (!spec) continue;
421
+ expectedKeys.add(pgBossTriggerScheduleKey(spec));
422
+ try {
423
+ await upsertScheduler(spec, overrides);
424
+ } catch (error) {
425
+ failures.push({
426
+ triggerSlug: spec.triggerSlug,
427
+ error
428
+ });
429
+ console.error(`[pg-boss-trigger-scheduler] failed to schedule ${spec.triggerSlug}`, error);
430
+ }
431
+ }
432
+ const schedules = await queue.boss.getSchedules(queue.queueName);
433
+ for (const schedule of schedules) {
434
+ const key = schedule.key;
435
+ if (!key || !key.startsWith(projectPrefix)) continue;
436
+ if (!expectedKeys.has(key)) try {
437
+ await queue.boss.unschedule(queue.queueName, key);
438
+ } catch (error) {
439
+ failures.push({
440
+ triggerSlug: key,
441
+ error
442
+ });
443
+ console.error(`[pg-boss-trigger-scheduler] failed to unschedule stale ${key}`, error);
444
+ }
445
+ }
446
+ if (failures.length > 0) throw new Error(`pg-boss trigger scheduler reconcile failed for ${failures.length} trigger(s)`);
447
+ }
448
+ return {
449
+ async sync(syncOptions) {
450
+ await syncTriggerSchedules(syncOptions);
451
+ await reconcileFromDatabase(syncOptions.scheduleOverrides);
452
+ },
453
+ async start(_options) {
454
+ return async () => void 0;
455
+ },
456
+ fireDue: async () => 0,
457
+ upsert: upsertScheduler,
458
+ async remove(triggerSlug, projectId) {
459
+ await queue.boss.unschedule(queue.queueName, pgBossTriggerScheduleKey({
460
+ triggerSlug,
461
+ projectId
462
+ }));
463
+ }
464
+ };
465
+ }
466
+ //#endregion
290
467
  //#region src/plugin.ts
291
468
  function resolveUrl(ctx) {
292
469
  return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;
293
470
  }
471
+ /** DB poll-loop trigger scheduler — used by the SQLite/self-host polling backend. */
472
+ function createPollingTriggerScheduler(ctx, queue) {
473
+ const scope = ctx.scope === "organization" ? "organization" : "project";
474
+ return Promise.resolve(createDatabaseTriggerScheduler({
475
+ jobQueue: queue,
476
+ scope
477
+ }));
478
+ }
479
+ function isPgBossJobQueue(queue) {
480
+ return "boss" in queue && "queueName" in queue;
481
+ }
482
+ /** Native pg-boss cron scheduler; falls back to the DB ticker if the queue isn't pg-boss. */
483
+ function createPgBossTriggerSchedulerForPlugin(ctx, queue) {
484
+ if (!isPgBossJobQueue(queue)) return createPollingTriggerScheduler(ctx, queue);
485
+ return Promise.resolve(createPgBossTriggerScheduler(ctx, queue));
486
+ }
294
487
  /** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */
295
488
  function pgBossSchedulerPlugin() {
296
489
  return defineSchedulerPlugin({
@@ -317,7 +510,8 @@ function pgBossSchedulerPlugin() {
317
510
  queueName: pgBossProjectQueueName(projectId),
318
511
  projectId
319
512
  });
320
- }
513
+ },
514
+ createTriggerScheduler: createPgBossTriggerSchedulerForPlugin
321
515
  });
322
516
  }
323
517
  /** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */
@@ -326,7 +520,8 @@ function pollingSchedulerPlugin() {
326
520
  name: "polling",
327
521
  async createJobQueue() {
328
522
  return createDatabaseJobQueue();
329
- }
523
+ },
524
+ createTriggerScheduler: createPollingTriggerScheduler
330
525
  });
331
526
  }
332
527
  /** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */
@@ -336,45 +531,14 @@ function defaultSchedulerPlugin() {
336
531
  async createJobQueue(ctx) {
337
532
  if (inferDialect(resolveUrl(ctx), ctx.dialect) === "postgres") return pgBossSchedulerPlugin().createJobQueue(ctx);
338
533
  return pollingSchedulerPlugin().createJobQueue(ctx);
534
+ },
535
+ async createTriggerScheduler(ctx, queue) {
536
+ if (inferDialect(resolveUrl(ctx), ctx.dialect) === "postgres") return pgBossSchedulerPlugin().createTriggerScheduler(ctx, queue);
537
+ return pollingSchedulerPlugin().createTriggerScheduler(ctx, queue);
339
538
  }
340
539
  });
341
540
  }
342
541
  //#endregion
343
- //#region src/resolve-schedule.ts
344
- function resolveTriggerSchedule(attachmentKey, schedule, overrides) {
345
- return resolveCronSchedule(attachmentKey, schedule, {
346
- cronScheduleOverride: overrides?.global,
347
- attachmentScheduleOverrides: overrides?.byAttachment
348
- });
349
- }
350
- //#endregion
351
- //#region src/sync-trigger-schedules.ts
352
- async function syncTriggerSchedules(options) {
353
- const now = /* @__PURE__ */ new Date();
354
- const projectSlugs = options.schedules.map((schedule) => schedule.attachmentKey);
355
- const ephemeralSlugs = await selectActiveEphemeralScheduledAttachmentSlugs();
356
- const slugs = [...projectSlugs, ...ephemeralSlugs];
357
- if (slugs.length === 0) {
358
- await disableAllTriggerSchedules(now);
359
- return;
360
- }
361
- await disableTriggerSchedulesNotInSlugs(slugs, now);
362
- for (const spec of options.schedules) {
363
- const schedule = resolveTriggerSchedule(spec.attachmentKey, spec.schedule, options.scheduleOverrides);
364
- const existing = await selectTriggerScheduleBySlug(spec.attachmentKey);
365
- const scheduleChanged = existing?.schedule !== schedule;
366
- const nextRunAt = existing && !scheduleChanged && existing.enabled === 1 ? existing.nextRunAt : nextTriggerRunAt(schedule, now);
367
- await upsertTriggerSchedule({
368
- attachmentSlug: spec.attachmentKey,
369
- kind: spec.kind,
370
- schedule,
371
- nextRunAt,
372
- enabled: true,
373
- updatedAt: now
374
- });
375
- }
376
- }
377
- //#endregion
378
542
  //#region src/create-scheduler.ts
379
543
  async function createUnderlyingJobQueue(options = {}) {
380
544
  if (options.adapter) return options.adapter;
@@ -386,29 +550,50 @@ async function createUnderlyingJobQueue(options = {}) {
386
550
  organizationId: options.organizationId
387
551
  });
388
552
  }
389
- function wrapScheduler(jobQueue, scope) {
390
- const ticker = createScheduleTicker({
553
+ function pluginContext(options = {}) {
554
+ return {
555
+ scope: options.scope ?? "project",
556
+ url: options.url,
557
+ dialect: options.dialect,
558
+ projectId: options.projectId,
559
+ organizationId: options.organizationId
560
+ };
561
+ }
562
+ async function createTriggerScheduler(options, jobQueue) {
563
+ const scope = options.scope === "organization" ? "organization" : "project";
564
+ const plugin = options.plugin ?? defaultSchedulerPlugin();
565
+ const ctx = pluginContext(options);
566
+ if (plugin.createTriggerScheduler) return plugin.createTriggerScheduler(ctx, jobQueue);
567
+ return createDatabaseTriggerScheduler({
391
568
  jobQueue,
392
569
  scope
393
570
  });
571
+ }
572
+ function wrapScheduler(jobQueue, triggerScheduler) {
394
573
  return {
395
574
  enqueue: (input) => jobQueue.enqueue(input),
396
- startWorker: (handler, options) => jobQueue.startWorker(handler, options),
575
+ startWorker: (handler, workerOptions) => jobQueue.startWorker(handler, workerOptions),
397
576
  publishCancel: (runId) => jobQueue.publishCancel(runId),
398
577
  subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),
399
- syncTriggerSchedules: (options) => syncTriggerSchedules(options),
400
- startScheduleTicker: (options) => ticker.startScheduleTicker(options),
401
- fireDueSchedules: (asOf) => ticker.fireDueSchedules(asOf)
578
+ syncTriggerSchedules: (options) => triggerScheduler.sync(options),
579
+ startScheduleTicker: (options) => triggerScheduler.start(options),
580
+ fireDueSchedules: (asOf) => triggerScheduler.fireDue?.(asOf) ?? Promise.resolve(0),
581
+ ...triggerScheduler.upsert ? { upsertTriggerSchedule: (spec, overrides) => triggerScheduler.upsert(spec, overrides) } : {},
582
+ ...triggerScheduler.remove ? { removeTriggerSchedule: (triggerSlug, projectId) => triggerScheduler.remove(triggerSlug, projectId) } : {}
402
583
  };
403
584
  }
404
585
  async function createScheduler(options = {}) {
405
- return wrapScheduler(await createUnderlyingJobQueue(options), options.scope === "organization" ? "organization" : "project");
586
+ const jobQueue = await createUnderlyingJobQueue(options);
587
+ return wrapScheduler(jobQueue, await createTriggerScheduler(options, jobQueue));
406
588
  }
407
589
  async function createJobQueue(options = {}) {
408
590
  return createUnderlyingJobQueue(options);
409
591
  }
410
- function wrapJobQueueAsScheduler(jobQueue, scope) {
411
- return wrapScheduler(jobQueue, scope);
592
+ async function wrapJobQueueAsScheduler(jobQueue, scope) {
593
+ return wrapScheduler(jobQueue, createDatabaseTriggerScheduler({
594
+ jobQueue,
595
+ scope
596
+ }));
412
597
  }
413
598
  //#endregion
414
599
  //#region src/memory.ts
@@ -467,7 +652,7 @@ function createMemoryJobQueue(options = {}) {
467
652
  //#region src/shared-pgboss-queue.ts
468
653
  let sharedJobQueue$1;
469
654
  async function createSharedPgBossScheduler() {
470
- return wrapJobQueueAsScheduler(await getSharedPgBossJobQueue());
655
+ return await wrapJobQueueAsScheduler(await getSharedPgBossJobQueue());
471
656
  }
472
657
  async function getSharedPgBossJobQueue() {
473
658
  if (sharedJobQueue$1) return sharedJobQueue$1;
@@ -491,7 +676,7 @@ async function createSharedScheduler() {
491
676
  const plugin = configuredPlugin ?? defaultSchedulerPlugin();
492
677
  if (plugin.name === "default") return createSharedPgBossScheduler();
493
678
  if (!sharedJobQueue) sharedJobQueue = await plugin.createJobQueue({ scope: "platform" });
494
- return wrapJobQueueAsScheduler(sharedJobQueue);
679
+ return await wrapJobQueueAsScheduler(sharedJobQueue);
495
680
  }
496
681
  function resetSharedSchedulerForTests() {
497
682
  configuredPlugin = void 0;
@@ -499,6 +684,6 @@ function resetSharedSchedulerForTests() {
499
684
  resetSharedPgBossJobQueueForTests();
500
685
  }
501
686
  //#endregion
502
- export { DEFAULT_QUEUE_NAME, DEFAULT_RETRY_DELAY_MS, buildPgBossJobQueue, configureSharedScheduler, createJobQueue, createMemoryJobQueue, createScheduler, createSharedPgBossJobQueue, createSharedPgBossScheduler, createSharedScheduler, defaultSchedulerPlugin, defineSchedulerPlugin, getPgBoss, getSharedPgBossJobQueue, pgBossProjectQueueName, pgBossSchedulerPlugin, pollingSchedulerPlugin, resetSharedPgBossJobQueueForTests, resetSharedSchedulerForTests, retryDelayMs, startPgBoss, stopPgBoss, wrapJobQueueAsScheduler };
687
+ export { DEFAULT_QUEUE_NAME, DEFAULT_RETRY_DELAY_MS, buildPgBossJobQueue, configureSharedScheduler, createDatabaseTriggerScheduler, createJobQueue, createMemoryJobQueue, createPgBossTriggerScheduler, createScheduler, createSharedPgBossJobQueue, createSharedPgBossScheduler, createSharedScheduler, defaultSchedulerPlugin, defineSchedulerPlugin, getPgBoss, getSharedPgBossJobQueue, pgBossProjectQueueName, pgBossSchedulerPlugin, pgBossTriggerScheduleKey, pgBossTriggerScheduleKeyPrefix, pollingSchedulerPlugin, resetSharedPgBossJobQueueForTests, resetSharedSchedulerForTests, resolveTriggerSchedule, retryDelayMs, startPgBoss, stopPgBoss, syncTriggerSchedules, wrapJobQueueAsScheduler };
503
688
 
504
689
  //# sourceMappingURL=index.mjs.map