@keystrokehq/scheduler 0.1.5 → 0.2.1

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,79 +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, 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
- 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.slug,
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 claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);
22
- return claimDueTriggers(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) {
33
- if (row.kind !== "cron" && row.kind !== "poll") continue;
34
- await enqueueTriggerJob(ctx.jobQueue, {
35
- slug: row.slug,
36
- kind: row.kind,
37
- projectId: row.projectId
38
- }, asOf);
39
- }
40
- return claimed.length;
41
- }
42
- async function startScheduleTicker(options = {}) {
43
- const intervalMs = options.pollIntervalMs ?? pollIntervalMs;
44
- const limit = options.batchSize ?? batchSize;
45
- const scope = options.scope ?? ctx.scope ?? "project";
46
- let running = true;
47
- const loop = async () => {
48
- while (running) {
49
- try {
50
- const claimed = await claimDue(scope, /* @__PURE__ */ new Date(), (schedule) => nextTriggerRunAt(schedule, /* @__PURE__ */ new Date()), limit);
51
- for (const row of claimed) {
52
- if (row.kind !== "cron" && row.kind !== "poll") continue;
53
- await enqueueTriggerJob(ctx.jobQueue, {
54
- slug: row.slug,
55
- kind: row.kind,
56
- projectId: row.projectId
57
- });
58
- }
59
- } catch {}
60
- await sleep$1(intervalMs);
61
- }
62
- };
63
- loop();
64
- return async () => {
65
- running = false;
66
- };
67
- }
68
- return {
69
- fireDueSchedules,
70
- startScheduleTicker
71
- };
72
- }
73
- function sleep$1(ms) {
74
- return new Promise((resolve) => setTimeout(resolve, ms));
75
- }
76
- //#endregion
77
7
  //#region src/cancel-channel.ts
78
8
  /** In-process cancel pub/sub for single-process queues (memory, db polling). */
79
9
  function createInProcessCancelChannel() {
@@ -126,7 +56,7 @@ function createDatabaseJobQueue() {
126
56
  while (running) try {
127
57
  const job = await claimNextJob(workerId);
128
58
  if (!job) {
129
- await sleep(pollIntervalMs);
59
+ await sleep$1(pollIntervalMs);
130
60
  continue;
131
61
  }
132
62
  try {
@@ -140,7 +70,7 @@ function createDatabaseJobQueue() {
140
70
  }
141
71
  }
142
72
  } catch {
143
- await sleep(pollIntervalMs);
73
+ await sleep$1(pollIntervalMs);
144
74
  }
145
75
  };
146
76
  loop();
@@ -153,10 +83,135 @@ function createDatabaseJobQueue() {
153
83
  subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
154
84
  };
155
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
+ }
156
156
  function sleep(ms) {
157
157
  return new Promise((resolve) => setTimeout(resolve, ms));
158
158
  }
159
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
160
215
  //#region src/pg-boss-client.ts
161
216
  let boss;
162
217
  function resolveDatabaseUrl(url) {
@@ -219,6 +274,8 @@ function buildPgBossJobQueue(boss, options) {
219
274
  const queueName = options.queueName ?? "keystroke";
220
275
  const cancelChannel = createPgCancelChannel(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.cancelScope === "organization" ? options.organizationId : options.projectId));
221
276
  return {
277
+ boss,
278
+ queueName,
222
279
  async enqueue(input) {
223
280
  const jobId = await boss.send(queueName, {
224
281
  ...input,
@@ -301,10 +358,132 @@ async function createSharedPgBossJobQueue(boss, options) {
301
358
  });
302
359
  }
303
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
304
467
  //#region src/plugin.ts
305
468
  function resolveUrl(ctx) {
306
469
  return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;
307
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
+ }
308
487
  /** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */
309
488
  function pgBossSchedulerPlugin() {
310
489
  return defineSchedulerPlugin({
@@ -331,7 +510,8 @@ function pgBossSchedulerPlugin() {
331
510
  queueName: pgBossProjectQueueName(projectId),
332
511
  projectId
333
512
  });
334
- }
513
+ },
514
+ createTriggerScheduler: createPgBossTriggerSchedulerForPlugin
335
515
  });
336
516
  }
337
517
  /** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */
@@ -340,7 +520,8 @@ function pollingSchedulerPlugin() {
340
520
  name: "polling",
341
521
  async createJobQueue() {
342
522
  return createDatabaseJobQueue();
343
- }
523
+ },
524
+ createTriggerScheduler: createPollingTriggerScheduler
344
525
  });
345
526
  }
346
527
  /** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */
@@ -350,45 +531,14 @@ function defaultSchedulerPlugin() {
350
531
  async createJobQueue(ctx) {
351
532
  if (inferDialect(resolveUrl(ctx), ctx.dialect) === "postgres") return pgBossSchedulerPlugin().createJobQueue(ctx);
352
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);
353
538
  }
354
539
  });
355
540
  }
356
541
  //#endregion
357
- //#region src/resolve-schedule.ts
358
- function resolveTriggerSchedule(triggerSlug, schedule, overrides) {
359
- return resolveCronSchedule(triggerSlug, schedule, {
360
- cronScheduleOverride: overrides?.global,
361
- attachmentScheduleOverrides: overrides?.byTrigger
362
- });
363
- }
364
- //#endregion
365
- //#region src/sync-trigger-schedules.ts
366
- async function syncTriggerSchedules(options) {
367
- const now = /* @__PURE__ */ new Date();
368
- const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);
369
- const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();
370
- const slugs = [...projectSlugs, ...ephemeralSlugs];
371
- if (slugs.length === 0) {
372
- await disableAllTriggerSchedules(now);
373
- return;
374
- }
375
- await disableTriggerSchedulesNotInSlugs(slugs, now);
376
- for (const spec of options.schedules) {
377
- const schedule = resolveTriggerSchedule(spec.triggerSlug, spec.schedule, options.scheduleOverrides);
378
- const existing = await selectTriggerBySlug(spec.triggerSlug);
379
- if (!existing) continue;
380
- const nextRunAt = !(existing.schedule !== schedule) && existing.enabled === 1 ? existing.nextRunAt : nextTriggerRunAt(schedule, now);
381
- const enabled = (await listExecutableAttachmentsByTriggerIds([existing.id])).length > 0;
382
- await upsertTriggerScheduleFields({
383
- triggerSlug: spec.triggerSlug,
384
- schedule,
385
- nextRunAt: nextRunAt ?? now,
386
- enabled,
387
- updatedAt: now
388
- });
389
- }
390
- }
391
- //#endregion
392
542
  //#region src/create-scheduler.ts
393
543
  async function createUnderlyingJobQueue(options = {}) {
394
544
  if (options.adapter) return options.adapter;
@@ -400,29 +550,50 @@ async function createUnderlyingJobQueue(options = {}) {
400
550
  organizationId: options.organizationId
401
551
  });
402
552
  }
403
- function wrapScheduler(jobQueue, scope) {
404
- 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({
405
568
  jobQueue,
406
569
  scope
407
570
  });
571
+ }
572
+ function wrapScheduler(jobQueue, triggerScheduler) {
408
573
  return {
409
574
  enqueue: (input) => jobQueue.enqueue(input),
410
- startWorker: (handler, options) => jobQueue.startWorker(handler, options),
575
+ startWorker: (handler, workerOptions) => jobQueue.startWorker(handler, workerOptions),
411
576
  publishCancel: (runId) => jobQueue.publishCancel(runId),
412
577
  subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),
413
- syncTriggerSchedules: (options) => syncTriggerSchedules(options),
414
- startScheduleTicker: (options) => ticker.startScheduleTicker(options),
415
- 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) } : {}
416
583
  };
417
584
  }
418
585
  async function createScheduler(options = {}) {
419
- return wrapScheduler(await createUnderlyingJobQueue(options), options.scope === "organization" ? "organization" : "project");
586
+ const jobQueue = await createUnderlyingJobQueue(options);
587
+ return wrapScheduler(jobQueue, await createTriggerScheduler(options, jobQueue));
420
588
  }
421
589
  async function createJobQueue(options = {}) {
422
590
  return createUnderlyingJobQueue(options);
423
591
  }
424
- function wrapJobQueueAsScheduler(jobQueue, scope) {
425
- return wrapScheduler(jobQueue, scope);
592
+ async function wrapJobQueueAsScheduler(jobQueue, scope) {
593
+ return wrapScheduler(jobQueue, createDatabaseTriggerScheduler({
594
+ jobQueue,
595
+ scope
596
+ }));
426
597
  }
427
598
  //#endregion
428
599
  //#region src/memory.ts
@@ -481,7 +652,7 @@ function createMemoryJobQueue(options = {}) {
481
652
  //#region src/shared-pgboss-queue.ts
482
653
  let sharedJobQueue$1;
483
654
  async function createSharedPgBossScheduler() {
484
- return wrapJobQueueAsScheduler(await getSharedPgBossJobQueue());
655
+ return await wrapJobQueueAsScheduler(await getSharedPgBossJobQueue());
485
656
  }
486
657
  async function getSharedPgBossJobQueue() {
487
658
  if (sharedJobQueue$1) return sharedJobQueue$1;
@@ -505,7 +676,7 @@ async function createSharedScheduler() {
505
676
  const plugin = configuredPlugin ?? defaultSchedulerPlugin();
506
677
  if (plugin.name === "default") return createSharedPgBossScheduler();
507
678
  if (!sharedJobQueue) sharedJobQueue = await plugin.createJobQueue({ scope: "platform" });
508
- return wrapJobQueueAsScheduler(sharedJobQueue);
679
+ return await wrapJobQueueAsScheduler(sharedJobQueue);
509
680
  }
510
681
  function resetSharedSchedulerForTests() {
511
682
  configuredPlugin = void 0;
@@ -513,6 +684,6 @@ function resetSharedSchedulerForTests() {
513
684
  resetSharedPgBossJobQueueForTests();
514
685
  }
515
686
  //#endregion
516
- 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 };
517
688
 
518
689
  //# sourceMappingURL=index.mjs.map