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