@keystrokehq/scheduler 0.1.0 → 0.1.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/README.md +5 -5
- package/dist/contract-C5JvbyQ-.cjs.map +1 -1
- package/dist/{contract-CYKkRmnH.d.cts → contract-DAqQua3D.d.cts} +10 -5
- package/dist/contract-DAqQua3D.d.cts.map +1 -0
- package/dist/{contract-CYKkRmnH.d.mts → contract-DAqQua3D.d.mts} +10 -5
- package/dist/contract-DAqQua3D.d.mts.map +1 -0
- package/dist/contract-E1QJBH6_.mjs.map +1 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.mts +2 -2
- package/dist/index.cjs +85 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +9 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +86 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -10
- package/dist/contract-CYKkRmnH.d.cts.map +0 -1
- package/dist/contract-CYKkRmnH.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Job queue and cron/poll schedule sync for [keystroke](../../README.md). Enqueues
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
9
9
|
- **`createScheduler`** — queue backend from `SchedulerPlugin` (default: pg-boss on Postgres, Drizzle job table on SQLite), then expose `enqueue`, `startWorker`, `syncTriggerSchedules`, `startScheduleTicker`, and `fireDueSchedules`.
|
|
10
|
-
- **`SchedulerPlugin`** — swap backends (`defaultSchedulerPlugin()`, or cloud `@cloud/bull-mq-plugin` BullMQ).
|
|
10
|
+
- **`SchedulerPlugin`** — swap backends (`defaultSchedulerPlugin()`, or cloud `@keystroke-cloud/bull-mq-plugin` BullMQ).
|
|
11
11
|
- **`createMemoryJobQueue`** — in-process queue for tests (optional `sync: true` drain).
|
|
12
12
|
- **Job types** — `JobQueue`, `Scheduler`, `JobHandler`, `EnqueueInput`, `JobTrigger`, `StopFn` for server handlers and enqueue helpers.
|
|
13
13
|
- **Schedule sync** — `syncTriggerSchedules` (on `Scheduler`) upserts rows in `trigger_schedules` via `@keystrokehq/database`, resolving cron strings with **`resolveCronSchedule`** and next run times with **`nextTriggerRunAt`** from `@keystrokehq/trigger`.
|
|
@@ -18,7 +18,7 @@ Queue implementations (`database-queue`, `pg-boss-queue`) and standalone `syncTr
|
|
|
18
18
|
## What belongs somewhere else
|
|
19
19
|
|
|
20
20
|
- Trigger source defs (`defineCronSource`, poll cadence) → `@keystrokehq/trigger`
|
|
21
|
-
- Running attachments, webhooks, poll HTTP → `@keystrokehq/
|
|
21
|
+
- Running attachments, webhooks, poll HTTP → `@keystrokehq/runtime`
|
|
22
22
|
- `trigger_schedules` / `jobs` schema and queries → `@keystrokehq/database`
|
|
23
23
|
- Workflow/agent run logic → `@keystrokehq/workflow` / `@keystrokehq/agent` (server wires handlers)
|
|
24
24
|
|
|
@@ -37,11 +37,11 @@ flowchart TB
|
|
|
37
37
|
|
|
38
38
|
**Allowed runtime deps:** `database`, `trigger`, `pg-boss`, `cron-schedule` (via trigger).
|
|
39
39
|
|
|
40
|
-
**Forbidden:** `
|
|
40
|
+
**Forbidden:** `runtime`, `workflow`, `agent`, `action`, `config`, `build`, `cli`, integrations, HTTP frameworks. Scheduler must stay callable from runtime without circular imports.
|
|
41
41
|
|
|
42
42
|
**Depends on:** `database`, `trigger`, `pg-boss`, `cron-schedule`.
|
|
43
43
|
|
|
44
|
-
**Used by:** `
|
|
44
|
+
**Used by:** `runtime` and `worker` (embedded worker + schedule ticker).
|
|
45
45
|
|
|
46
46
|
## Exports
|
|
47
47
|
|
|
@@ -80,7 +80,7 @@ Import from the package root (`@keystrokehq/scheduler`), not from `src/…`.
|
|
|
80
80
|
1. New queue backend or retry/concurrency behavior? → this package (keep types in `types.ts`).
|
|
81
81
|
2. New trigger schedule expression rules? → `@keystrokehq/trigger` (`nextTriggerRunAt`, `resolveCronSchedule`).
|
|
82
82
|
3. New table or claim query? → `@keystrokehq/database`.
|
|
83
|
-
4. Who runs the job (workflow step, agent prompt, attachment)? → `@keystrokehq/
|
|
83
|
+
4. Who runs the job (workflow step, agent prompt, attachment)? → `@keystrokehq/runtime` job handlers.
|
|
84
84
|
|
|
85
85
|
## Example
|
|
86
86
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-C5JvbyQ-.cjs","names":[],"sources":["../src/types.ts","../src/contract.ts"],"sourcesContent":["export type JobKind = \"workflow\" | \"agent\" | \"trigger\";\n\nexport type JobTrigger = \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n\nexport type JobPayload = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload: unknown;\n attempt: number;\n maxAttempts: number;\n scheduledAt: Date;\n jobId: string;\n};\n\nexport type EnqueueInput = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload?: unknown;\n scheduledAt?: Date;\n attempt?: number;\n maxAttempts?: number;\n};\n\nexport type JobHandler = (job: JobPayload) => Promise<void>;\n\nexport type StopFn = () => Promise<void> | void;\n\nexport type WorkerOptions = {\n workerId?: string;\n pollIntervalMs?: number;\n leaseSweepIntervalMs?: number;\n};\n\nexport type JobQueue = {\n enqueue(input: EnqueueInput): Promise<string>;\n startWorker(handler: JobHandler, options?: WorkerOptions): Promise<StopFn>;\n};\n\nimport type { SchedulerPlugin, SchedulerScope } from \"./contract\";\n\nexport type CreateJobQueueOptions = {\n url?: string;\n dialect?: \"postgres\" | \"sqlite\";\n adapter?: JobQueue;\n plugin?: SchedulerPlugin;\n scope?: SchedulerScope;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type TriggerScheduleSpec = {\n attachmentKey: string;\n kind: \"cron\" | \"poll\";\n schedule: string;\n};\n\nexport type ScheduleSyncOptions = {\n schedules: TriggerScheduleSpec[];\n scheduleOverrides?: {\n global?: string;\n byAttachment?: Record<string, string>;\n };\n};\n\nexport type ScheduleTickerOptions = {\n pollIntervalMs?: number;\n batchSize?: number;\n};\n\nexport type Scheduler = JobQueue & {\n syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void>;\n startScheduleTicker(options?: ScheduleTickerOptions): Promise<StopFn>;\n fireDueSchedules(asOf?: Date): Promise<number>;\n};\n\nexport type CreateSchedulerOptions = CreateJobQueueOptions;\n\nexport const DEFAULT_RETRY_DELAY_MS = 5_000;\n\nexport function retryDelayMs(attempt: number): number {\n return DEFAULT_RETRY_DELAY_MS * 2 ** Math.max(0, attempt - 1);\n}\n","export type {\n JobKind,\n JobTrigger,\n JobPayload,\n EnqueueInput,\n JobHandler,\n StopFn,\n WorkerOptions,\n JobQueue,\n} from \"./types\";\nexport { retryDelayMs, DEFAULT_RETRY_DELAY_MS } from \"./types\";\n\nimport type { JobQueue } from \"./types\";\n\n/**\n * Inlined to keep the contract database-free — structurally identical to\n * `@keystrokehq/database`'s `DatabaseDialect`. Importing it from the database\n * package would re-introduce the dependency `./contract` exists to avoid.\n */\nexport type DatabaseDialect = \"postgres\" | \"sqlite\";\n\nexport type SchedulerScope = \"platform\" | \"project\";\n\nexport type SchedulerPluginContext = {\n scope: SchedulerScope;\n url?: string;\n dialect?: DatabaseDialect;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type SchedulerPlugin = {\n name: string;\n createJobQueue(ctx: SchedulerPluginContext): Promise<JobQueue>;\n};\n\nexport function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin {\n return plugin;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"contract-C5JvbyQ-.cjs","names":[],"sources":["../src/types.ts","../src/contract.ts"],"sourcesContent":["export type JobKind = \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n\nexport type JobTrigger = \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n\nexport type JobPayload = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload: unknown;\n attempt: number;\n maxAttempts: number;\n /** True when this delivery is the final retry attempt. */\n exhaustedRetries?: boolean;\n scheduledAt: Date;\n jobId: string;\n};\n\nexport type EnqueueInput = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload?: unknown;\n scheduledAt?: Date;\n attempt?: number;\n maxAttempts?: number;\n /** Dedupe key — pg-boss singletonKey / BullMQ jobId. */\n dedupeKey?: string;\n};\n\nexport type JobHandler = (job: JobPayload) => Promise<void>;\n\nexport type CancelHandler = (runId: string) => void | Promise<void>;\n\nexport type StopFn = () => Promise<void> | void;\n\nexport type WorkerOptions = {\n workerId?: string;\n pollIntervalMs?: number;\n leaseSweepIntervalMs?: number;\n};\n\nexport type JobQueue = {\n enqueue(input: EnqueueInput): Promise<string>;\n startWorker(handler: JobHandler, options?: WorkerOptions): Promise<StopFn>;\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n};\n\nimport type { SchedulerPlugin, SchedulerScope } from \"./contract\";\n\nexport type CreateJobQueueOptions = {\n url?: string;\n dialect?: \"postgres\" | \"sqlite\";\n adapter?: JobQueue;\n plugin?: SchedulerPlugin;\n scope?: SchedulerScope;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type TriggerScheduleSpec = {\n attachmentKey: string;\n kind: \"cron\" | \"poll\";\n schedule: string;\n};\n\nexport type ScheduleSyncOptions = {\n schedules: TriggerScheduleSpec[];\n scheduleOverrides?: {\n global?: string;\n byAttachment?: Record<string, string>;\n };\n};\n\nexport type ScheduleTickerOptions = {\n pollIntervalMs?: number;\n batchSize?: number;\n};\n\nexport type Scheduler = JobQueue & {\n syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void>;\n startScheduleTicker(options?: ScheduleTickerOptions): Promise<StopFn>;\n fireDueSchedules(asOf?: Date): Promise<number>;\n};\n\nexport type CreateSchedulerOptions = CreateJobQueueOptions;\n\nexport const DEFAULT_RETRY_DELAY_MS = 5_000;\n\nexport function retryDelayMs(attempt: number): number {\n return DEFAULT_RETRY_DELAY_MS * 2 ** Math.max(0, attempt - 1);\n}\n","export type {\n JobKind,\n JobTrigger,\n JobPayload,\n EnqueueInput,\n JobHandler,\n CancelHandler,\n StopFn,\n WorkerOptions,\n JobQueue,\n} from \"./types\";\nexport { retryDelayMs, DEFAULT_RETRY_DELAY_MS } from \"./types\";\n\nimport type { JobQueue } from \"./types\";\n\n/**\n * Inlined to keep the contract database-free — structurally identical to\n * `@keystrokehq/database`'s `DatabaseDialect`. Importing it from the database\n * package would re-introduce the dependency `./contract` exists to avoid.\n */\nexport type DatabaseDialect = \"postgres\" | \"sqlite\";\n\nexport type SchedulerScope = \"platform\" | \"project\";\n\nexport type SchedulerPluginContext = {\n scope: SchedulerScope;\n url?: string;\n dialect?: DatabaseDialect;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type SchedulerPlugin = {\n name: string;\n createJobQueue(ctx: SchedulerPluginContext): Promise<JobQueue>;\n};\n\nexport function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin {\n return plugin;\n}\n"],"mappings":";AAyFA,MAAa,yBAAyB;AAEtC,SAAgB,aAAa,SAAyB;CACpD,OAAO,yBAAyB,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC;AAC9D;;;ACxDA,SAAgB,sBAAsB,QAA0C;CAC9E,OAAO;AACT"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
-
type JobKind = "workflow" | "agent" | "trigger";
|
|
2
|
+
type JobKind = "workflow" | "agent" | "trigger" | "runtime";
|
|
3
3
|
type JobTrigger = "api" | "cron" | "webhook" | "poll" | "retry" | "prompt";
|
|
4
4
|
type JobPayload = {
|
|
5
5
|
kind: JobKind;
|
|
@@ -8,7 +8,8 @@ type JobPayload = {
|
|
|
8
8
|
trigger: JobTrigger;
|
|
9
9
|
payload: unknown;
|
|
10
10
|
attempt: number;
|
|
11
|
-
maxAttempts: number;
|
|
11
|
+
maxAttempts: number; /** True when this delivery is the final retry attempt. */
|
|
12
|
+
exhaustedRetries?: boolean;
|
|
12
13
|
scheduledAt: Date;
|
|
13
14
|
jobId: string;
|
|
14
15
|
};
|
|
@@ -20,9 +21,11 @@ type EnqueueInput = {
|
|
|
20
21
|
payload?: unknown;
|
|
21
22
|
scheduledAt?: Date;
|
|
22
23
|
attempt?: number;
|
|
23
|
-
maxAttempts?: number;
|
|
24
|
+
maxAttempts?: number; /** Dedupe key — pg-boss singletonKey / BullMQ jobId. */
|
|
25
|
+
dedupeKey?: string;
|
|
24
26
|
};
|
|
25
27
|
type JobHandler = (job: JobPayload) => Promise<void>;
|
|
28
|
+
type CancelHandler = (runId: string) => void | Promise<void>;
|
|
26
29
|
type StopFn = () => Promise<void> | void;
|
|
27
30
|
type WorkerOptions = {
|
|
28
31
|
workerId?: string;
|
|
@@ -32,6 +35,8 @@ type WorkerOptions = {
|
|
|
32
35
|
type JobQueue = {
|
|
33
36
|
enqueue(input: EnqueueInput): Promise<string>;
|
|
34
37
|
startWorker(handler: JobHandler, options?: WorkerOptions): Promise<StopFn>;
|
|
38
|
+
publishCancel(runId: string): Promise<void>;
|
|
39
|
+
subscribeCancel(handler: CancelHandler): Promise<StopFn>;
|
|
35
40
|
};
|
|
36
41
|
type CreateJobQueueOptions = {
|
|
37
42
|
url?: string;
|
|
@@ -88,5 +93,5 @@ type SchedulerPlugin = {
|
|
|
88
93
|
};
|
|
89
94
|
declare function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin;
|
|
90
95
|
//#endregion
|
|
91
|
-
export {
|
|
92
|
-
//# sourceMappingURL=contract-
|
|
96
|
+
export { ScheduleTickerOptions as _, defineSchedulerPlugin as a, WorkerOptions as b, CreateSchedulerOptions as c, JobHandler as d, JobKind as f, ScheduleSyncOptions as g, JobTrigger as h, SchedulerScope as i, DEFAULT_RETRY_DELAY_MS as l, JobQueue as m, SchedulerPlugin as n, CancelHandler as o, JobPayload as p, SchedulerPluginContext as r, CreateJobQueueOptions as s, DatabaseDialect as t, EnqueueInput as u, Scheduler as v, retryDelayMs as x, StopFn as y };
|
|
97
|
+
//# sourceMappingURL=contract-DAqQua3D.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-DAqQua3D.d.cts","names":[],"sources":["../src/types.ts","../src/contract.ts"],"mappings":";KAAY,OAAA;AAAA,KAEA,UAAA;AAAA,KAEA,UAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,OAAA;EACA,WAAA,UAToB;EAWpB,gBAAA;EACA,WAAA,EAAa,IAAA;EACb,KAAA;AAAA;AAAA,KAGU,YAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,WAAA,GAAc,IAAA;EACd,OAAA;EACA,WAAA,WAlBS;EAoBT,SAAA;AAAA;AAAA,KAGU,UAAA,IAAc,GAAA,EAAK,UAAA,KAAe,OAAO;AAAA,KAEzC,aAAA,IAAiB,KAAA,oBAAyB,OAAO;AAAA,KAEjD,MAAA,SAAe,OAAO;AAAA,KAEtB,aAAA;EACV,QAAA;EACA,cAAA;EACA,oBAAA;AAAA;AAAA,KAGU,QAAA;EACV,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,OAAA;EAC9B,WAAA,CAAY,OAAA,EAAS,UAAA,EAAY,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,MAAA;EACnE,aAAA,CAAc,KAAA,WAAgB,OAAA;EAC9B,eAAA,CAAgB,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,MAAA;AAAA;AAAA,KAKvC,qBAAA;EACV,GAAA;EACA,OAAA;EACA,OAAA,GAAU,QAAA;EACV,MAAA,GAAS,eAAA;EACT,KAAA,GAAQ,cAAA;EACR,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,mBAAA;EACV,aAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA,EAAW,mBAAA;EACX,iBAAA;IACE,MAAA;IACA,YAAA,GAAe,MAAM;EAAA;AAAA;AAAA,KAIb,qBAAA;EACV,cAAA;EACA,SAAS;AAAA;AAAA,KAGC,SAAA,GAAY,QAAA;EACtB,oBAAA,CAAqB,OAAA,EAAS,mBAAA,GAAsB,OAAA;EACpD,mBAAA,CAAoB,OAAA,GAAU,qBAAA,GAAwB,OAAA,CAAQ,MAAA;EAC9D,gBAAA,CAAiB,IAAA,GAAO,IAAA,GAAO,OAAA;AAAA;AAAA,KAGrB,sBAAA,GAAyB,qBAAqB;AAAA,cAE7C,sBAAA;AAAA,iBAEG,YAAA,CAAa,OAAe;;;;;AA3FzB;AAEnB;;KCkBY,eAAA;AAAA,KAEA,cAAA;AAAA,KAEA,sBAAA;EACV,KAAA,EAAO,cAAA;EACP,GAAA;EACA,OAAA,GAAU,eAAe;EACzB,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,eAAA;EACV,IAAA;EACA,cAAA,CAAe,GAAA,EAAK,sBAAA,GAAyB,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAGvC,qBAAA,CAAsB,MAAA,EAAQ,eAAA,GAAkB,eAAe"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
-
type JobKind = "workflow" | "agent" | "trigger";
|
|
2
|
+
type JobKind = "workflow" | "agent" | "trigger" | "runtime";
|
|
3
3
|
type JobTrigger = "api" | "cron" | "webhook" | "poll" | "retry" | "prompt";
|
|
4
4
|
type JobPayload = {
|
|
5
5
|
kind: JobKind;
|
|
@@ -8,7 +8,8 @@ type JobPayload = {
|
|
|
8
8
|
trigger: JobTrigger;
|
|
9
9
|
payload: unknown;
|
|
10
10
|
attempt: number;
|
|
11
|
-
maxAttempts: number;
|
|
11
|
+
maxAttempts: number; /** True when this delivery is the final retry attempt. */
|
|
12
|
+
exhaustedRetries?: boolean;
|
|
12
13
|
scheduledAt: Date;
|
|
13
14
|
jobId: string;
|
|
14
15
|
};
|
|
@@ -20,9 +21,11 @@ type EnqueueInput = {
|
|
|
20
21
|
payload?: unknown;
|
|
21
22
|
scheduledAt?: Date;
|
|
22
23
|
attempt?: number;
|
|
23
|
-
maxAttempts?: number;
|
|
24
|
+
maxAttempts?: number; /** Dedupe key — pg-boss singletonKey / BullMQ jobId. */
|
|
25
|
+
dedupeKey?: string;
|
|
24
26
|
};
|
|
25
27
|
type JobHandler = (job: JobPayload) => Promise<void>;
|
|
28
|
+
type CancelHandler = (runId: string) => void | Promise<void>;
|
|
26
29
|
type StopFn = () => Promise<void> | void;
|
|
27
30
|
type WorkerOptions = {
|
|
28
31
|
workerId?: string;
|
|
@@ -32,6 +35,8 @@ type WorkerOptions = {
|
|
|
32
35
|
type JobQueue = {
|
|
33
36
|
enqueue(input: EnqueueInput): Promise<string>;
|
|
34
37
|
startWorker(handler: JobHandler, options?: WorkerOptions): Promise<StopFn>;
|
|
38
|
+
publishCancel(runId: string): Promise<void>;
|
|
39
|
+
subscribeCancel(handler: CancelHandler): Promise<StopFn>;
|
|
35
40
|
};
|
|
36
41
|
type CreateJobQueueOptions = {
|
|
37
42
|
url?: string;
|
|
@@ -88,5 +93,5 @@ type SchedulerPlugin = {
|
|
|
88
93
|
};
|
|
89
94
|
declare function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin;
|
|
90
95
|
//#endregion
|
|
91
|
-
export {
|
|
92
|
-
//# sourceMappingURL=contract-
|
|
96
|
+
export { ScheduleTickerOptions as _, defineSchedulerPlugin as a, WorkerOptions as b, CreateSchedulerOptions as c, JobHandler as d, JobKind as f, ScheduleSyncOptions as g, JobTrigger as h, SchedulerScope as i, DEFAULT_RETRY_DELAY_MS as l, JobQueue as m, SchedulerPlugin as n, CancelHandler as o, JobPayload as p, SchedulerPluginContext as r, CreateJobQueueOptions as s, DatabaseDialect as t, EnqueueInput as u, Scheduler as v, retryDelayMs as x, StopFn as y };
|
|
97
|
+
//# sourceMappingURL=contract-DAqQua3D.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-DAqQua3D.d.mts","names":[],"sources":["../src/types.ts","../src/contract.ts"],"mappings":";KAAY,OAAA;AAAA,KAEA,UAAA;AAAA,KAEA,UAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,OAAA;EACA,WAAA,UAToB;EAWpB,gBAAA;EACA,WAAA,EAAa,IAAA;EACb,KAAA;AAAA;AAAA,KAGU,YAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,WAAA,GAAc,IAAA;EACd,OAAA;EACA,WAAA,WAlBS;EAoBT,SAAA;AAAA;AAAA,KAGU,UAAA,IAAc,GAAA,EAAK,UAAA,KAAe,OAAO;AAAA,KAEzC,aAAA,IAAiB,KAAA,oBAAyB,OAAO;AAAA,KAEjD,MAAA,SAAe,OAAO;AAAA,KAEtB,aAAA;EACV,QAAA;EACA,cAAA;EACA,oBAAA;AAAA;AAAA,KAGU,QAAA;EACV,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,OAAA;EAC9B,WAAA,CAAY,OAAA,EAAS,UAAA,EAAY,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,MAAA;EACnE,aAAA,CAAc,KAAA,WAAgB,OAAA;EAC9B,eAAA,CAAgB,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,MAAA;AAAA;AAAA,KAKvC,qBAAA;EACV,GAAA;EACA,OAAA;EACA,OAAA,GAAU,QAAA;EACV,MAAA,GAAS,eAAA;EACT,KAAA,GAAQ,cAAA;EACR,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,mBAAA;EACV,aAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA,EAAW,mBAAA;EACX,iBAAA;IACE,MAAA;IACA,YAAA,GAAe,MAAM;EAAA;AAAA;AAAA,KAIb,qBAAA;EACV,cAAA;EACA,SAAS;AAAA;AAAA,KAGC,SAAA,GAAY,QAAA;EACtB,oBAAA,CAAqB,OAAA,EAAS,mBAAA,GAAsB,OAAA;EACpD,mBAAA,CAAoB,OAAA,GAAU,qBAAA,GAAwB,OAAA,CAAQ,MAAA;EAC9D,gBAAA,CAAiB,IAAA,GAAO,IAAA,GAAO,OAAA;AAAA;AAAA,KAGrB,sBAAA,GAAyB,qBAAqB;AAAA,cAE7C,sBAAA;AAAA,iBAEG,YAAA,CAAa,OAAe;;;;;AA3FzB;AAEnB;;KCkBY,eAAA;AAAA,KAEA,cAAA;AAAA,KAEA,sBAAA;EACV,KAAA,EAAO,cAAA;EACP,GAAA;EACA,OAAA,GAAU,eAAe;EACzB,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,eAAA;EACV,IAAA;EACA,cAAA,CAAe,GAAA,EAAK,sBAAA,GAAyB,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAGvC,qBAAA,CAAsB,MAAA,EAAQ,eAAA,GAAkB,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-E1QJBH6_.mjs","names":[],"sources":["../src/types.ts","../src/contract.ts"],"sourcesContent":["export type JobKind = \"workflow\" | \"agent\" | \"trigger\";\n\nexport type JobTrigger = \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n\nexport type JobPayload = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload: unknown;\n attempt: number;\n maxAttempts: number;\n scheduledAt: Date;\n jobId: string;\n};\n\nexport type EnqueueInput = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload?: unknown;\n scheduledAt?: Date;\n attempt?: number;\n maxAttempts?: number;\n};\n\nexport type JobHandler = (job: JobPayload) => Promise<void>;\n\nexport type StopFn = () => Promise<void> | void;\n\nexport type WorkerOptions = {\n workerId?: string;\n pollIntervalMs?: number;\n leaseSweepIntervalMs?: number;\n};\n\nexport type JobQueue = {\n enqueue(input: EnqueueInput): Promise<string>;\n startWorker(handler: JobHandler, options?: WorkerOptions): Promise<StopFn>;\n};\n\nimport type { SchedulerPlugin, SchedulerScope } from \"./contract\";\n\nexport type CreateJobQueueOptions = {\n url?: string;\n dialect?: \"postgres\" | \"sqlite\";\n adapter?: JobQueue;\n plugin?: SchedulerPlugin;\n scope?: SchedulerScope;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type TriggerScheduleSpec = {\n attachmentKey: string;\n kind: \"cron\" | \"poll\";\n schedule: string;\n};\n\nexport type ScheduleSyncOptions = {\n schedules: TriggerScheduleSpec[];\n scheduleOverrides?: {\n global?: string;\n byAttachment?: Record<string, string>;\n };\n};\n\nexport type ScheduleTickerOptions = {\n pollIntervalMs?: number;\n batchSize?: number;\n};\n\nexport type Scheduler = JobQueue & {\n syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void>;\n startScheduleTicker(options?: ScheduleTickerOptions): Promise<StopFn>;\n fireDueSchedules(asOf?: Date): Promise<number>;\n};\n\nexport type CreateSchedulerOptions = CreateJobQueueOptions;\n\nexport const DEFAULT_RETRY_DELAY_MS = 5_000;\n\nexport function retryDelayMs(attempt: number): number {\n return DEFAULT_RETRY_DELAY_MS * 2 ** Math.max(0, attempt - 1);\n}\n","export type {\n JobKind,\n JobTrigger,\n JobPayload,\n EnqueueInput,\n JobHandler,\n StopFn,\n WorkerOptions,\n JobQueue,\n} from \"./types\";\nexport { retryDelayMs, DEFAULT_RETRY_DELAY_MS } from \"./types\";\n\nimport type { JobQueue } from \"./types\";\n\n/**\n * Inlined to keep the contract database-free — structurally identical to\n * `@keystrokehq/database`'s `DatabaseDialect`. Importing it from the database\n * package would re-introduce the dependency `./contract` exists to avoid.\n */\nexport type DatabaseDialect = \"postgres\" | \"sqlite\";\n\nexport type SchedulerScope = \"platform\" | \"project\";\n\nexport type SchedulerPluginContext = {\n scope: SchedulerScope;\n url?: string;\n dialect?: DatabaseDialect;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type SchedulerPlugin = {\n name: string;\n createJobQueue(ctx: SchedulerPluginContext): Promise<JobQueue>;\n};\n\nexport function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin {\n return plugin;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"contract-E1QJBH6_.mjs","names":[],"sources":["../src/types.ts","../src/contract.ts"],"sourcesContent":["export type JobKind = \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n\nexport type JobTrigger = \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n\nexport type JobPayload = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload: unknown;\n attempt: number;\n maxAttempts: number;\n /** True when this delivery is the final retry attempt. */\n exhaustedRetries?: boolean;\n scheduledAt: Date;\n jobId: string;\n};\n\nexport type EnqueueInput = {\n kind: JobKind;\n targetId: string;\n runId: string;\n trigger: JobTrigger;\n payload?: unknown;\n scheduledAt?: Date;\n attempt?: number;\n maxAttempts?: number;\n /** Dedupe key — pg-boss singletonKey / BullMQ jobId. */\n dedupeKey?: string;\n};\n\nexport type JobHandler = (job: JobPayload) => Promise<void>;\n\nexport type CancelHandler = (runId: string) => void | Promise<void>;\n\nexport type StopFn = () => Promise<void> | void;\n\nexport type WorkerOptions = {\n workerId?: string;\n pollIntervalMs?: number;\n leaseSweepIntervalMs?: number;\n};\n\nexport type JobQueue = {\n enqueue(input: EnqueueInput): Promise<string>;\n startWorker(handler: JobHandler, options?: WorkerOptions): Promise<StopFn>;\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n};\n\nimport type { SchedulerPlugin, SchedulerScope } from \"./contract\";\n\nexport type CreateJobQueueOptions = {\n url?: string;\n dialect?: \"postgres\" | \"sqlite\";\n adapter?: JobQueue;\n plugin?: SchedulerPlugin;\n scope?: SchedulerScope;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type TriggerScheduleSpec = {\n attachmentKey: string;\n kind: \"cron\" | \"poll\";\n schedule: string;\n};\n\nexport type ScheduleSyncOptions = {\n schedules: TriggerScheduleSpec[];\n scheduleOverrides?: {\n global?: string;\n byAttachment?: Record<string, string>;\n };\n};\n\nexport type ScheduleTickerOptions = {\n pollIntervalMs?: number;\n batchSize?: number;\n};\n\nexport type Scheduler = JobQueue & {\n syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void>;\n startScheduleTicker(options?: ScheduleTickerOptions): Promise<StopFn>;\n fireDueSchedules(asOf?: Date): Promise<number>;\n};\n\nexport type CreateSchedulerOptions = CreateJobQueueOptions;\n\nexport const DEFAULT_RETRY_DELAY_MS = 5_000;\n\nexport function retryDelayMs(attempt: number): number {\n return DEFAULT_RETRY_DELAY_MS * 2 ** Math.max(0, attempt - 1);\n}\n","export type {\n JobKind,\n JobTrigger,\n JobPayload,\n EnqueueInput,\n JobHandler,\n CancelHandler,\n StopFn,\n WorkerOptions,\n JobQueue,\n} from \"./types\";\nexport { retryDelayMs, DEFAULT_RETRY_DELAY_MS } from \"./types\";\n\nimport type { JobQueue } from \"./types\";\n\n/**\n * Inlined to keep the contract database-free — structurally identical to\n * `@keystrokehq/database`'s `DatabaseDialect`. Importing it from the database\n * package would re-introduce the dependency `./contract` exists to avoid.\n */\nexport type DatabaseDialect = \"postgres\" | \"sqlite\";\n\nexport type SchedulerScope = \"platform\" | \"project\";\n\nexport type SchedulerPluginContext = {\n scope: SchedulerScope;\n url?: string;\n dialect?: DatabaseDialect;\n projectId?: string;\n organizationId?: string;\n};\n\nexport type SchedulerPlugin = {\n name: string;\n createJobQueue(ctx: SchedulerPluginContext): Promise<JobQueue>;\n};\n\nexport function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin {\n return plugin;\n}\n"],"mappings":";AAyFA,MAAa,yBAAyB;AAEtC,SAAgB,aAAa,SAAyB;CACpD,OAAO,yBAAyB,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC;AAC9D;;;ACxDA,SAAgB,sBAAsB,QAA0C;CAC9E,OAAO;AACT"}
|
package/dist/contract.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as defineSchedulerPlugin, b as
|
|
2
|
-
export { DEFAULT_RETRY_DELAY_MS, DatabaseDialect, type EnqueueInput, type JobHandler, type JobKind, type JobPayload, type JobQueue, type JobTrigger, SchedulerPlugin, SchedulerPluginContext, SchedulerScope, type StopFn, type WorkerOptions, defineSchedulerPlugin, retryDelayMs };
|
|
1
|
+
import { a as defineSchedulerPlugin, b as WorkerOptions, d as JobHandler, f as JobKind, h as JobTrigger, i as SchedulerScope, l as DEFAULT_RETRY_DELAY_MS, m as JobQueue, n as SchedulerPlugin, o as CancelHandler, p as JobPayload, r as SchedulerPluginContext, t as DatabaseDialect, u as EnqueueInput, x as retryDelayMs, y as StopFn } from "./contract-DAqQua3D.cjs";
|
|
2
|
+
export { type CancelHandler, DEFAULT_RETRY_DELAY_MS, DatabaseDialect, type EnqueueInput, type JobHandler, type JobKind, type JobPayload, type JobQueue, type JobTrigger, SchedulerPlugin, SchedulerPluginContext, SchedulerScope, type StopFn, type WorkerOptions, defineSchedulerPlugin, retryDelayMs };
|
package/dist/contract.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as defineSchedulerPlugin, b as
|
|
2
|
-
export { DEFAULT_RETRY_DELAY_MS, DatabaseDialect, type EnqueueInput, type JobHandler, type JobKind, type JobPayload, type JobQueue, type JobTrigger, SchedulerPlugin, SchedulerPluginContext, SchedulerScope, type StopFn, type WorkerOptions, defineSchedulerPlugin, retryDelayMs };
|
|
1
|
+
import { a as defineSchedulerPlugin, b as WorkerOptions, d as JobHandler, f as JobKind, h as JobTrigger, i as SchedulerScope, l as DEFAULT_RETRY_DELAY_MS, m as JobQueue, n as SchedulerPlugin, o as CancelHandler, p as JobPayload, r as SchedulerPluginContext, t as DatabaseDialect, u as EnqueueInput, x as retryDelayMs, y as StopFn } from "./contract-DAqQua3D.mjs";
|
|
2
|
+
export { type CancelHandler, DEFAULT_RETRY_DELAY_MS, DatabaseDialect, type EnqueueInput, type JobHandler, type JobKind, type JobPayload, type JobQueue, type JobTrigger, SchedulerPlugin, SchedulerPluginContext, SchedulerScope, type StopFn, type WorkerOptions, defineSchedulerPlugin, retryDelayMs };
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@ 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
4
|
let _keystrokehq_trigger = require("@keystrokehq/trigger");
|
|
5
|
+
let node_events = require("node:events");
|
|
5
6
|
let pg_boss = require("pg-boss");
|
|
6
7
|
let node_crypto = require("node:crypto");
|
|
7
8
|
//#region src/schedule-ticker.ts
|
|
@@ -12,7 +13,7 @@ function createScheduleTicker(ctx) {
|
|
|
12
13
|
const claimed = await (0, _keystrokehq_database.claimDueTriggerSchedules)(asOf, (schedule) => (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, asOf), batchSize);
|
|
13
14
|
for (const row of claimed) await ctx.jobQueue.enqueue({
|
|
14
15
|
kind: "trigger",
|
|
15
|
-
targetId: row.
|
|
16
|
+
targetId: row.attachmentSlug,
|
|
16
17
|
runId: crypto.randomUUID(),
|
|
17
18
|
trigger: row.kind,
|
|
18
19
|
payload: {},
|
|
@@ -30,7 +31,7 @@ function createScheduleTicker(ctx) {
|
|
|
30
31
|
await (0, _keystrokehq_database.claimDueTriggerSchedules)(/* @__PURE__ */ new Date(), (schedule) => (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, /* @__PURE__ */ new Date()), limit).then(async (claimed) => {
|
|
31
32
|
for (const row of claimed) await ctx.jobQueue.enqueue({
|
|
32
33
|
kind: "trigger",
|
|
33
|
-
targetId: row.
|
|
34
|
+
targetId: row.attachmentSlug,
|
|
34
35
|
runId: crypto.randomUUID(),
|
|
35
36
|
trigger: row.kind,
|
|
36
37
|
payload: {}
|
|
@@ -54,6 +55,26 @@ function sleep$1(ms) {
|
|
|
54
55
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
56
|
}
|
|
56
57
|
//#endregion
|
|
58
|
+
//#region src/cancel-channel.ts
|
|
59
|
+
/** In-process cancel pub/sub for single-process queues (memory, db polling). */
|
|
60
|
+
function createInProcessCancelChannel() {
|
|
61
|
+
const emitter = new node_events.EventEmitter();
|
|
62
|
+
return {
|
|
63
|
+
async publishCancel(runId) {
|
|
64
|
+
emitter.emit("cancel", runId);
|
|
65
|
+
},
|
|
66
|
+
async subscribeCancel(handler) {
|
|
67
|
+
const listener = (runId) => {
|
|
68
|
+
handler(runId);
|
|
69
|
+
};
|
|
70
|
+
emitter.on("cancel", listener);
|
|
71
|
+
return async () => {
|
|
72
|
+
emitter.off("cancel", listener);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
57
78
|
//#region src/database-queue.ts
|
|
58
79
|
function toJobPayload(job) {
|
|
59
80
|
return {
|
|
@@ -69,6 +90,7 @@ function toJobPayload(job) {
|
|
|
69
90
|
};
|
|
70
91
|
}
|
|
71
92
|
function createDatabaseJobQueue() {
|
|
93
|
+
const cancelChannel = createInProcessCancelChannel();
|
|
72
94
|
return {
|
|
73
95
|
async enqueue(input) {
|
|
74
96
|
return (0, _keystrokehq_database.enqueueJob)(input);
|
|
@@ -107,7 +129,9 @@ function createDatabaseJobQueue() {
|
|
|
107
129
|
running = false;
|
|
108
130
|
clearInterval(leaseTimer);
|
|
109
131
|
};
|
|
110
|
-
}
|
|
132
|
+
},
|
|
133
|
+
publishCancel: (runId) => cancelChannel.publishCancel(runId),
|
|
134
|
+
subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
|
|
111
135
|
};
|
|
112
136
|
}
|
|
113
137
|
function sleep(ms) {
|
|
@@ -144,6 +168,13 @@ async function stopPgBoss() {
|
|
|
144
168
|
boss = void 0;
|
|
145
169
|
}
|
|
146
170
|
//#endregion
|
|
171
|
+
//#region src/pg-cancel-channel.ts
|
|
172
|
+
/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */
|
|
173
|
+
function pgCancelChannelName(scope, projectId) {
|
|
174
|
+
if (scope === "platform") return "keystroke_cancel_platform";
|
|
175
|
+
return `keystroke_cancel_${(projectId ?? "default").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
147
178
|
//#region src/pg-boss-queue.ts
|
|
148
179
|
/** Shared queue name for the platform control-plane scope. */
|
|
149
180
|
const DEFAULT_QUEUE_NAME = "keystroke";
|
|
@@ -159,25 +190,35 @@ function pgBossProjectQueueName(projectId) {
|
|
|
159
190
|
if (name.length <= 50) return name;
|
|
160
191
|
return `${DEFAULT_QUEUE_NAME}_${(0, node_crypto.createHash)("sha1").update(projectId).digest("hex").slice(0, 40)}`;
|
|
161
192
|
}
|
|
162
|
-
function buildPgBossJobQueue(boss, options
|
|
193
|
+
function buildPgBossJobQueue(boss, options) {
|
|
163
194
|
const queueName = options.queueName ?? "keystroke";
|
|
195
|
+
const cancelChannel = (0, _keystrokehq_database.createPostgresCancelChannel)(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.projectId));
|
|
164
196
|
return {
|
|
165
197
|
async enqueue(input) {
|
|
166
198
|
const jobId = await boss.send(queueName, input, {
|
|
167
199
|
retryLimit: (input.maxAttempts ?? 3) - 1,
|
|
168
200
|
retryDelay: Math.ceil(require_contract.retryDelayMs(1) / 1e3),
|
|
169
|
-
startAfter: input.scheduledAt
|
|
201
|
+
startAfter: input.scheduledAt,
|
|
202
|
+
singletonKey: input.dedupeKey
|
|
170
203
|
});
|
|
171
|
-
if (!jobId)
|
|
204
|
+
if (!jobId) {
|
|
205
|
+
if (input.dedupeKey) return input.dedupeKey;
|
|
206
|
+
throw new Error("Failed to enqueue job");
|
|
207
|
+
}
|
|
172
208
|
return jobId;
|
|
173
209
|
},
|
|
174
210
|
async startWorker(handler) {
|
|
175
211
|
let stopped = false;
|
|
176
|
-
const workerId = await boss.work(queueName, {
|
|
212
|
+
const workerId = await boss.work(queueName, {
|
|
213
|
+
batchSize: 1,
|
|
214
|
+
includeMetadata: true
|
|
215
|
+
}, async (jobs) => {
|
|
177
216
|
if (stopped) return;
|
|
178
217
|
const job = jobs[0];
|
|
179
218
|
if (!job) return;
|
|
180
219
|
const data = job.data;
|
|
220
|
+
const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;
|
|
221
|
+
const attempt = (job.retryCount ?? 0) + 1;
|
|
181
222
|
await handler({
|
|
182
223
|
jobId: job.id,
|
|
183
224
|
kind: data.kind,
|
|
@@ -185,8 +226,9 @@ function buildPgBossJobQueue(boss, options = {}) {
|
|
|
185
226
|
runId: data.runId,
|
|
186
227
|
trigger: data.trigger,
|
|
187
228
|
payload: data.payload ?? {},
|
|
188
|
-
attempt
|
|
189
|
-
maxAttempts
|
|
229
|
+
attempt,
|
|
230
|
+
maxAttempts,
|
|
231
|
+
exhaustedRetries: attempt >= maxAttempts,
|
|
190
232
|
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : /* @__PURE__ */ new Date()
|
|
191
233
|
});
|
|
192
234
|
});
|
|
@@ -198,7 +240,9 @@ function buildPgBossJobQueue(boss, options = {}) {
|
|
|
198
240
|
timeout: 5e3
|
|
199
241
|
});
|
|
200
242
|
};
|
|
201
|
-
}
|
|
243
|
+
},
|
|
244
|
+
publishCancel: (runId) => cancelChannel.publishCancel(runId),
|
|
245
|
+
subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
|
|
202
246
|
};
|
|
203
247
|
}
|
|
204
248
|
async function createPgBossQueue(options) {
|
|
@@ -211,12 +255,20 @@ async function createPgBossQueue(options) {
|
|
|
211
255
|
await boss.createQueue(queueName);
|
|
212
256
|
return buildPgBossJobQueue(boss, {
|
|
213
257
|
stopBossOnWorkerStop: true,
|
|
214
|
-
queueName
|
|
258
|
+
queueName,
|
|
259
|
+
connectionString: options.connectionString,
|
|
260
|
+
cancelScope: "project",
|
|
261
|
+
projectId: options.projectId
|
|
215
262
|
});
|
|
216
263
|
}
|
|
217
|
-
async function createSharedPgBossJobQueue(boss,
|
|
264
|
+
async function createSharedPgBossJobQueue(boss, options) {
|
|
265
|
+
const queueName = options.queueName ?? "keystroke";
|
|
218
266
|
await boss.createQueue(queueName);
|
|
219
|
-
return buildPgBossJobQueue(boss, {
|
|
267
|
+
return buildPgBossJobQueue(boss, {
|
|
268
|
+
queueName,
|
|
269
|
+
connectionString: options.connectionString,
|
|
270
|
+
cancelScope: "platform"
|
|
271
|
+
});
|
|
220
272
|
}
|
|
221
273
|
//#endregion
|
|
222
274
|
//#region src/plugin.ts
|
|
@@ -231,11 +283,13 @@ function pgBossSchedulerPlugin() {
|
|
|
231
283
|
const url = resolveUrl(ctx);
|
|
232
284
|
if (ctx.scope === "platform") {
|
|
233
285
|
await startPgBoss(url);
|
|
234
|
-
return createSharedPgBossJobQueue(getPgBoss());
|
|
286
|
+
return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });
|
|
235
287
|
}
|
|
288
|
+
const projectId = ctx.projectId ?? (0, _keystrokehq_database.getProjectScopeId)();
|
|
236
289
|
return createPgBossQueue({
|
|
237
290
|
connectionString: url,
|
|
238
|
-
queueName: pgBossProjectQueueName(
|
|
291
|
+
queueName: pgBossProjectQueueName(projectId),
|
|
292
|
+
projectId
|
|
239
293
|
});
|
|
240
294
|
}
|
|
241
295
|
});
|
|
@@ -271,21 +325,21 @@ function resolveTriggerSchedule(attachmentKey, schedule, overrides) {
|
|
|
271
325
|
//#region src/sync-trigger-schedules.ts
|
|
272
326
|
async function syncTriggerSchedules(options) {
|
|
273
327
|
const now = /* @__PURE__ */ new Date();
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
328
|
+
const projectSlugs = options.schedules.map((schedule) => schedule.attachmentKey);
|
|
329
|
+
const ephemeralSlugs = await (0, _keystrokehq_database.selectActiveEphemeralScheduledAttachmentSlugs)();
|
|
330
|
+
const slugs = [...projectSlugs, ...ephemeralSlugs];
|
|
331
|
+
if (slugs.length === 0) {
|
|
278
332
|
await (0, _keystrokehq_database.disableAllTriggerSchedules)(now);
|
|
279
333
|
return;
|
|
280
334
|
}
|
|
281
|
-
await (0, _keystrokehq_database.
|
|
335
|
+
await (0, _keystrokehq_database.disableTriggerSchedulesNotInSlugs)(slugs, now);
|
|
282
336
|
for (const spec of options.schedules) {
|
|
283
337
|
const schedule = resolveTriggerSchedule(spec.attachmentKey, spec.schedule, options.scheduleOverrides);
|
|
284
|
-
const existing = await (0, _keystrokehq_database.
|
|
338
|
+
const existing = await (0, _keystrokehq_database.selectTriggerScheduleBySlug)(spec.attachmentKey);
|
|
285
339
|
const scheduleChanged = existing?.schedule !== schedule;
|
|
286
340
|
const nextRunAt = existing && !scheduleChanged && existing.enabled === 1 ? existing.nextRunAt : (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, now);
|
|
287
341
|
await (0, _keystrokehq_database.upsertTriggerSchedule)({
|
|
288
|
-
|
|
342
|
+
attachmentSlug: spec.attachmentKey,
|
|
289
343
|
kind: spec.kind,
|
|
290
344
|
schedule,
|
|
291
345
|
nextRunAt,
|
|
@@ -311,6 +365,8 @@ function wrapScheduler(jobQueue) {
|
|
|
311
365
|
return {
|
|
312
366
|
enqueue: (input) => jobQueue.enqueue(input),
|
|
313
367
|
startWorker: (handler, options) => jobQueue.startWorker(handler, options),
|
|
368
|
+
publishCancel: (runId) => jobQueue.publishCancel(runId),
|
|
369
|
+
subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),
|
|
314
370
|
syncTriggerSchedules: (options) => syncTriggerSchedules(options),
|
|
315
371
|
startScheduleTicker: (options) => ticker.startScheduleTicker(options),
|
|
316
372
|
fireDueSchedules: (asOf) => ticker.fireDueSchedules(asOf)
|
|
@@ -328,6 +384,7 @@ function wrapJobQueueAsScheduler(jobQueue) {
|
|
|
328
384
|
//#endregion
|
|
329
385
|
//#region src/memory.ts
|
|
330
386
|
function createMemoryJobQueue(options = {}) {
|
|
387
|
+
const cancelChannel = createInProcessCancelChannel();
|
|
331
388
|
const pending = [];
|
|
332
389
|
let handler;
|
|
333
390
|
let draining = false;
|
|
@@ -372,7 +429,9 @@ function createMemoryJobQueue(options = {}) {
|
|
|
372
429
|
return async () => {
|
|
373
430
|
handler = void 0;
|
|
374
431
|
};
|
|
375
|
-
}
|
|
432
|
+
},
|
|
433
|
+
publishCancel: (runId) => cancelChannel.publishCancel(runId),
|
|
434
|
+
subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
|
|
376
435
|
};
|
|
377
436
|
}
|
|
378
437
|
//#endregion
|
|
@@ -383,7 +442,9 @@ async function createSharedPgBossScheduler() {
|
|
|
383
442
|
}
|
|
384
443
|
async function getSharedPgBossJobQueue() {
|
|
385
444
|
if (sharedJobQueue$1) return sharedJobQueue$1;
|
|
386
|
-
|
|
445
|
+
const connectionString = (0, _keystrokehq_database.resolvePostgresUrlFromEnv)(process.env);
|
|
446
|
+
if (!connectionString) throw new Error("Postgres connection string is required for shared pg-boss queue");
|
|
447
|
+
sharedJobQueue$1 = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });
|
|
387
448
|
return sharedJobQueue$1;
|
|
388
449
|
}
|
|
389
450
|
function resetSharedPgBossJobQueueForTests() {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["sleep","retryDelayMs","PgBoss","retryDelayMs","PgBoss","DEFAULT_DATABASE_URL","defineSchedulerPlugin","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/database-queue.ts","../src/pg-boss-client.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","../src/resolve-plugin-from-env.ts"],"sourcesContent":["import { claimDueTriggerSchedules } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\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 claimed = await claimDueTriggerSchedules(\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentKey,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n scheduledAt: 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 let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n await claimDueTriggerSchedules(\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n ).then(async (claimed) => {\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentKey,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n });\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 {\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 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 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}\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","import { createHash } from \"node:crypto\";\nimport { PgBoss, type Job } from \"pg-boss\";\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 type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\";\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};\n\ntype BuildPgBossJobQueueOptions = {\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n};\n\nexport function buildPgBossJobQueue(\n boss: PgBoss,\n options: BuildPgBossJobQueueOptions = {},\n): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(queueName, input, {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n });\n\n if (!jobId) {\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 },\n async (jobs: Job<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\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: data.attempt ?? 1,\n maxAttempts: data.maxAttempts ?? 3,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\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}\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, { stopBossOnWorkerStop: true, queueName });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n queueName: string = DEFAULT_QUEUE_NAME,\n): Promise<JobQueue> {\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, { queueName });\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 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; project scope gets a per-project queue. */\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());\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(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 byAttachment?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n attachmentKey: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(attachmentKey, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byAttachment,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInKeys,\n selectActiveEphemeralScheduledAttachmentKeys,\n selectTriggerScheduleByKey,\n upsertTriggerSchedule,\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 projectKeys = options.schedules.map((schedule) => schedule.attachmentKey);\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 ephemeralKeys = await selectActiveEphemeralScheduledAttachmentKeys();\n const keys = [...projectKeys, ...ephemeralKeys];\n\n if (keys.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInKeys(keys, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.attachmentKey,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerScheduleByKey(spec.attachmentKey);\n const scheduleChanged = existing?.schedule !== schedule;\n const nextRunAt =\n existing && !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n await upsertTriggerSchedule({\n attachmentKey: spec.attachmentKey,\n kind: spec.kind,\n schedule,\n nextRunAt,\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): Scheduler {\n const ticker = createScheduleTicker({ jobQueue });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\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 return wrapScheduler(jobQueue);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(jobQueue: JobQueue): Scheduler {\n return wrapScheduler(jobQueue);\n}\n\nexport type { StopFn };\n","import 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 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}\n\nexport { retryDelayMs };\n","import { 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 sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss());\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","import type { SchedulerPlugin } from \"./contract\";\n\n/** Env var naming a module that exports `createSchedulerPlugin(env): SchedulerPlugin`. */\nexport const SCHEDULER_MODULE_ENV = \"KEYSTROKE_SCHEDULER_MODULE\";\n\ntype SchedulerEnv = Record<string, string | undefined>;\n\nfunction isSchedulerPlugin(value: unknown): value is SchedulerPlugin {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.name === \"string\" && typeof candidate.createJobQueue === \"function\";\n}\n\nfunction getCreateSchedulerPlugin(mod: unknown): ((env: SchedulerEnv) => unknown) | undefined {\n if (typeof mod !== \"object\" || mod === null) {\n return undefined;\n }\n const factory = (mod as Record<string, unknown>).createSchedulerPlugin;\n return typeof factory === \"function\" ? (factory as (env: SchedulerEnv) => unknown) : undefined;\n}\n\n/**\n * Resolve a scheduler plugin selected via env so a producer (platform) and a\n * consumer (container worker) can never diverge on backend. When\n * `KEYSTROKE_SCHEDULER_MODULE` is set, dynamically import that module and call\n * its `createSchedulerPlugin(env)`\n */\nexport async function resolveSchedulerPluginFromEnv(\n env: SchedulerEnv = process.env,\n): Promise<SchedulerPlugin | undefined> {\n const moduleSpecifier = env[SCHEDULER_MODULE_ENV]?.trim();\n if (!moduleSpecifier) {\n return undefined;\n }\n\n const mod: unknown = await import(moduleSpecifier);\n const createSchedulerPlugin = getCreateSchedulerPlugin(mod);\n if (!createSchedulerPlugin) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" must export createSchedulerPlugin(env): SchedulerPlugin`,\n );\n }\n\n const plugin = createSchedulerPlugin(env);\n if (!isSchedulerPlugin(plugin)) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" createSchedulerPlugin(env) did not return a valid SchedulerPlugin (expected { name: string, createJobQueue: function })`,\n );\n }\n\n return plugin;\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","../src/resolve-plugin-from-env.ts"],"sourcesContent":["import { claimDueTriggerSchedules } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\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 claimed = await claimDueTriggerSchedules(\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentSlug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n scheduledAt: 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 let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n await claimDueTriggerSchedules(\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n ).then(async (claimed) => {\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentSlug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n });\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(scope: \"platform\" | \"project\", projectId?: string): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n const sanitized = (projectId ?? \"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 type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\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};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\";\n projectId?: 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(options.cancelScope ?? \"platform\", options.projectId),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(queueName, input, {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\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 });\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: \"project\",\n projectId: options.projectId,\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 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; project scope gets a per-project queue. */\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 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 byAttachment?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n attachmentKey: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(attachmentKey, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byAttachment,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n selectActiveEphemeralScheduledAttachmentSlugs,\n selectTriggerScheduleBySlug,\n upsertTriggerSchedule,\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.attachmentKey);\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 selectActiveEphemeralScheduledAttachmentSlugs();\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.attachmentKey,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerScheduleBySlug(spec.attachmentKey);\n const scheduleChanged = existing?.schedule !== schedule;\n const nextRunAt =\n existing && !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n await upsertTriggerSchedule({\n attachmentSlug: spec.attachmentKey,\n kind: spec.kind,\n schedule,\n nextRunAt,\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): Scheduler {\n const ticker = createScheduleTicker({ jobQueue });\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 return wrapScheduler(jobQueue);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(jobQueue: JobQueue): Scheduler {\n return wrapScheduler(jobQueue);\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","import type { SchedulerPlugin } from \"./contract\";\n\n/** Env var naming a module that exports `createSchedulerPlugin(env): SchedulerPlugin`. */\nexport const SCHEDULER_MODULE_ENV = \"KEYSTROKE_SCHEDULER_MODULE\";\n\ntype SchedulerEnv = Record<string, string | undefined>;\n\nfunction isSchedulerPlugin(value: unknown): value is SchedulerPlugin {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.name === \"string\" && typeof candidate.createJobQueue === \"function\";\n}\n\nfunction getCreateSchedulerPlugin(mod: unknown): ((env: SchedulerEnv) => unknown) | undefined {\n if (typeof mod !== \"object\" || mod === null) {\n return undefined;\n }\n const factory = (mod as Record<string, unknown>).createSchedulerPlugin;\n return typeof factory === \"function\" ? (factory as (env: SchedulerEnv) => unknown) : undefined;\n}\n\n/**\n * Resolve a scheduler plugin selected via env so a producer (platform) and a\n * consumer (container worker) can never diverge on backend. When\n * `KEYSTROKE_SCHEDULER_MODULE` is set, dynamically import that module and call\n * its `createSchedulerPlugin(env)`\n */\nexport async function resolveSchedulerPluginFromEnv(\n env: SchedulerEnv = process.env,\n): Promise<SchedulerPlugin | undefined> {\n const moduleSpecifier = env[SCHEDULER_MODULE_ENV]?.trim();\n if (!moduleSpecifier) {\n return undefined;\n }\n\n const mod: unknown = await import(moduleSpecifier);\n const createSchedulerPlugin = getCreateSchedulerPlugin(mod);\n if (!createSchedulerPlugin) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" must export createSchedulerPlugin(env): SchedulerPlugin`,\n );\n }\n\n const plugin = createSchedulerPlugin(env);\n if (!isSchedulerPlugin(plugin)) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" createSchedulerPlugin(env) did not return a valid SchedulerPlugin (expected { name: string, createJobQueue: function })`,\n );\n }\n\n return plugin;\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as ScheduleTickerOptions, a as defineSchedulerPlugin, b as WorkerOptions, c as CreateSchedulerOptions, d as JobHandler, g as ScheduleSyncOptions, h as JobTrigger, i as SchedulerScope, l as DEFAULT_RETRY_DELAY_MS, m as JobQueue, n as SchedulerPlugin, p as JobPayload, r as SchedulerPluginContext, s as CreateJobQueueOptions, u as EnqueueInput, v as Scheduler, x as retryDelayMs, y as StopFn } from "./contract-DAqQua3D.cjs";
|
|
2
2
|
import { PgBoss } from "pg-boss";
|
|
3
3
|
|
|
4
4
|
//#region src/create-scheduler.d.ts
|
|
@@ -58,11 +58,17 @@ declare const DEFAULT_QUEUE_NAME = "keystroke";
|
|
|
58
58
|
*/
|
|
59
59
|
declare function pgBossProjectQueueName(projectId: string): string;
|
|
60
60
|
type BuildPgBossJobQueueOptions = {
|
|
61
|
+
connectionString: string;
|
|
61
62
|
stopBossOnWorkerStop?: boolean;
|
|
62
63
|
queueName?: string;
|
|
64
|
+
cancelScope?: "platform" | "project";
|
|
65
|
+
projectId?: string;
|
|
63
66
|
};
|
|
64
|
-
declare function buildPgBossJobQueue(boss: PgBoss, options
|
|
65
|
-
declare function createSharedPgBossJobQueue(boss: PgBoss,
|
|
67
|
+
declare function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue;
|
|
68
|
+
declare function createSharedPgBossJobQueue(boss: PgBoss, options: {
|
|
69
|
+
connectionString: string;
|
|
70
|
+
queueName?: string;
|
|
71
|
+
}): Promise<JobQueue>;
|
|
66
72
|
//#endregion
|
|
67
73
|
export { type CreateJobQueueOptions, type CreateSchedulerOptions, DEFAULT_QUEUE_NAME, DEFAULT_RETRY_DELAY_MS, type EnqueueInput, type JobHandler, type JobPayload, type JobQueue, type JobTrigger, type MemoryJobQueueOptions, SCHEDULER_MODULE_ENV, type ScheduleSyncOptions, type ScheduleTickerOptions, type Scheduler, type SchedulerPlugin, type SchedulerPluginContext, type SchedulerScope, type StopFn, type WorkerOptions, buildPgBossJobQueue, configureSharedScheduler, createJobQueue, createMemoryJobQueue, createScheduler, createSharedPgBossJobQueue, createSharedPgBossScheduler, createSharedScheduler, defaultSchedulerPlugin, defineSchedulerPlugin, getPgBoss, getSharedPgBossJobQueue, pgBossProjectQueueName, pgBossSchedulerPlugin, pollingSchedulerPlugin, resetSharedPgBossJobQueueForTests, resetSharedSchedulerForTests, resolveSchedulerPluginFromEnv, retryDelayMs, startPgBoss, stopPgBoss, wrapJobQueueAsScheduler };
|
|
68
74
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/create-scheduler.ts","../src/memory.ts","../src/shared-scheduler.ts","../src/plugin.ts","../src/resolve-plugin-from-env.ts","../src/pg-boss-client.ts","../src/shared-pgboss-queue.ts","../src/pg-boss-queue.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/create-scheduler.ts","../src/memory.ts","../src/shared-scheduler.ts","../src/plugin.ts","../src/resolve-plugin-from-env.ts","../src/pg-boss-client.ts","../src/shared-pgboss-queue.ts","../src/pg-boss-queue.ts"],"mappings":";;;;iBA0CsB,eAAA,CAAgB,OAAA,GAAS,sBAAA,GAA8B,OAAA,CAAQ,SAAA;AAAA,iBAK/D,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,OAAA,CAAQ,QAAA;AAAA,iBAInE,uBAAA,CAAwB,QAAA,EAAU,QAAA,GAAW,SAAS;;;KC/C1D,qBAAA;EACV,IAAI;AAAA;AAAA,iBAGU,oBAAA,CAAqB,OAAA,GAAS,qBAAA,GAA6B,QAAQ;;;iBCInE,wBAAA,CAAyB,MAAuB,EAAf,eAAe;AAAA,iBAK1C,qBAAA,CAAA,GAAyB,OAAO,CAAC,SAAA;AAAA,iBAcvC,4BAAA,CAAA;;;;iBCHA,qBAAA,CAAA,GAAyB,eAAe;;iBAsBxC,sBAAA,CAAA,GAA0B,eAAe;;iBAUzC,sBAAA,CAAA,GAA0B,eAAe;;;;cCzD5C,oBAAA;AAAA,KAER,YAAA,GAAe,MAAM;AJqC1B;;;;;;AAAA,iBIbsB,6BAAA,CACpB,GAAA,GAAK,YAAA,GACJ,OAAA,CAAQ,eAAA;;;iBChBW,WAAA,CAAY,GAAA,YAAe,OAAO,CAAC,MAAA;AAAA,iBAkBzC,SAAA,CAAA,GAAa,MAAM;AAAA,iBAOb,UAAA,CAAA,GAAc,OAAO;;;iBChCrB,2BAAA,CAAA,GAA+B,OAAO,CAAC,SAAA;AAAA,iBAKvC,uBAAA,CAAA,GAA2B,OAAO,CAAC,QAAA;AAAA,iBAczC,iCAAA,CAAA;;;;cCpBH,kBAAA;APmCb;;;;;;AAAA,iBO1BgB,sBAAA,CAAuB,SAAiB;AAAA,KA0BnD,0BAAA;EACH,gBAAA;EACA,oBAAA;EACA,SAAA;EACA,WAAA;EACA,SAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,0BAAA,GAA6B,QAAA;AAAA,iBA6FlE,0BAAA,CACpB,IAAA,EAAM,MAAA,EACN,OAAA;EAAW,gBAAA;EAA0B,SAAA;AAAA,IACpC,OAAA,CAAQ,QAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as ScheduleTickerOptions, a as defineSchedulerPlugin, b as WorkerOptions, c as CreateSchedulerOptions, d as JobHandler, g as ScheduleSyncOptions, h as JobTrigger, i as SchedulerScope, l as DEFAULT_RETRY_DELAY_MS, m as JobQueue, n as SchedulerPlugin, p as JobPayload, r as SchedulerPluginContext, s as CreateJobQueueOptions, u as EnqueueInput, v as Scheduler, x as retryDelayMs, y as StopFn } from "./contract-DAqQua3D.mjs";
|
|
2
2
|
import { PgBoss } from "pg-boss";
|
|
3
3
|
|
|
4
4
|
//#region src/create-scheduler.d.ts
|
|
@@ -58,11 +58,17 @@ declare const DEFAULT_QUEUE_NAME = "keystroke";
|
|
|
58
58
|
*/
|
|
59
59
|
declare function pgBossProjectQueueName(projectId: string): string;
|
|
60
60
|
type BuildPgBossJobQueueOptions = {
|
|
61
|
+
connectionString: string;
|
|
61
62
|
stopBossOnWorkerStop?: boolean;
|
|
62
63
|
queueName?: string;
|
|
64
|
+
cancelScope?: "platform" | "project";
|
|
65
|
+
projectId?: string;
|
|
63
66
|
};
|
|
64
|
-
declare function buildPgBossJobQueue(boss: PgBoss, options
|
|
65
|
-
declare function createSharedPgBossJobQueue(boss: PgBoss,
|
|
67
|
+
declare function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue;
|
|
68
|
+
declare function createSharedPgBossJobQueue(boss: PgBoss, options: {
|
|
69
|
+
connectionString: string;
|
|
70
|
+
queueName?: string;
|
|
71
|
+
}): Promise<JobQueue>;
|
|
66
72
|
//#endregion
|
|
67
73
|
export { type CreateJobQueueOptions, type CreateSchedulerOptions, DEFAULT_QUEUE_NAME, DEFAULT_RETRY_DELAY_MS, type EnqueueInput, type JobHandler, type JobPayload, type JobQueue, type JobTrigger, type MemoryJobQueueOptions, SCHEDULER_MODULE_ENV, type ScheduleSyncOptions, type ScheduleTickerOptions, type Scheduler, type SchedulerPlugin, type SchedulerPluginContext, type SchedulerScope, type StopFn, type WorkerOptions, buildPgBossJobQueue, configureSharedScheduler, createJobQueue, createMemoryJobQueue, createScheduler, createSharedPgBossJobQueue, createSharedPgBossScheduler, createSharedScheduler, defaultSchedulerPlugin, defineSchedulerPlugin, getPgBoss, getSharedPgBossJobQueue, pgBossProjectQueueName, pgBossSchedulerPlugin, pollingSchedulerPlugin, resetSharedPgBossJobQueueForTests, resetSharedSchedulerForTests, resolveSchedulerPluginFromEnv, retryDelayMs, startPgBoss, stopPgBoss, wrapJobQueueAsScheduler };
|
|
68
74
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/create-scheduler.ts","../src/memory.ts","../src/shared-scheduler.ts","../src/plugin.ts","../src/resolve-plugin-from-env.ts","../src/pg-boss-client.ts","../src/shared-pgboss-queue.ts","../src/pg-boss-queue.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/create-scheduler.ts","../src/memory.ts","../src/shared-scheduler.ts","../src/plugin.ts","../src/resolve-plugin-from-env.ts","../src/pg-boss-client.ts","../src/shared-pgboss-queue.ts","../src/pg-boss-queue.ts"],"mappings":";;;;iBA0CsB,eAAA,CAAgB,OAAA,GAAS,sBAAA,GAA8B,OAAA,CAAQ,SAAA;AAAA,iBAK/D,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,OAAA,CAAQ,QAAA;AAAA,iBAInE,uBAAA,CAAwB,QAAA,EAAU,QAAA,GAAW,SAAS;;;KC/C1D,qBAAA;EACV,IAAI;AAAA;AAAA,iBAGU,oBAAA,CAAqB,OAAA,GAAS,qBAAA,GAA6B,QAAQ;;;iBCInE,wBAAA,CAAyB,MAAuB,EAAf,eAAe;AAAA,iBAK1C,qBAAA,CAAA,GAAyB,OAAO,CAAC,SAAA;AAAA,iBAcvC,4BAAA,CAAA;;;;iBCHA,qBAAA,CAAA,GAAyB,eAAe;;iBAsBxC,sBAAA,CAAA,GAA0B,eAAe;;iBAUzC,sBAAA,CAAA,GAA0B,eAAe;;;;cCzD5C,oBAAA;AAAA,KAER,YAAA,GAAe,MAAM;AJqC1B;;;;;;AAAA,iBIbsB,6BAAA,CACpB,GAAA,GAAK,YAAA,GACJ,OAAA,CAAQ,eAAA;;;iBChBW,WAAA,CAAY,GAAA,YAAe,OAAO,CAAC,MAAA;AAAA,iBAkBzC,SAAA,CAAA,GAAa,MAAM;AAAA,iBAOb,UAAA,CAAA,GAAc,OAAO;;;iBChCrB,2BAAA,CAAA,GAA+B,OAAO,CAAC,SAAA;AAAA,iBAKvC,uBAAA,CAAA,GAA2B,OAAO,CAAC,QAAA;AAAA,iBAczC,iCAAA,CAAA;;;;cCpBH,kBAAA;APmCb;;;;;;AAAA,iBO1BgB,sBAAA,CAAuB,SAAiB;AAAA,KA0BnD,0BAAA;EACH,gBAAA;EACA,oBAAA;EACA,SAAA;EACA,WAAA;EACA,SAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,0BAAA,GAA6B,QAAA;AAAA,iBA6FlE,0BAAA,CACpB,IAAA,EAAM,MAAA,EACN,OAAA;EAAW,gBAAA;EAA0B,SAAA;AAAA,IACpC,OAAA,CAAQ,QAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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, claimNextJob, disableAllTriggerSchedules,
|
|
2
|
+
import { DEFAULT_DATABASE_URL, claimDueTriggerSchedules, claimNextJob, createPostgresCancelChannel as createPgCancelChannel, disableAllTriggerSchedules, disableTriggerSchedulesNotInSlugs, enqueueJob, failWorkflowRun, getProjectScopeId, inferDialect, markJobComplete, markJobFailed, requeueExpiredLeases, resolvePostgresUrlFromEnv, resolveProjectDatabaseUrlFromEnv, scheduleJobRetry, selectActiveEphemeralScheduledAttachmentSlugs, selectTriggerScheduleBySlug, upsertTriggerSchedule } from "@keystrokehq/database";
|
|
3
3
|
import { nextTriggerRunAt, resolveCronSchedule } from "@keystrokehq/trigger";
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
4
5
|
import { PgBoss } from "pg-boss";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
//#region src/schedule-ticker.ts
|
|
@@ -11,7 +12,7 @@ function createScheduleTicker(ctx) {
|
|
|
11
12
|
const claimed = await claimDueTriggerSchedules(asOf, (schedule) => nextTriggerRunAt(schedule, asOf), batchSize);
|
|
12
13
|
for (const row of claimed) await ctx.jobQueue.enqueue({
|
|
13
14
|
kind: "trigger",
|
|
14
|
-
targetId: row.
|
|
15
|
+
targetId: row.attachmentSlug,
|
|
15
16
|
runId: crypto.randomUUID(),
|
|
16
17
|
trigger: row.kind,
|
|
17
18
|
payload: {},
|
|
@@ -29,7 +30,7 @@ function createScheduleTicker(ctx) {
|
|
|
29
30
|
await claimDueTriggerSchedules(/* @__PURE__ */ new Date(), (schedule) => nextTriggerRunAt(schedule, /* @__PURE__ */ new Date()), limit).then(async (claimed) => {
|
|
30
31
|
for (const row of claimed) await ctx.jobQueue.enqueue({
|
|
31
32
|
kind: "trigger",
|
|
32
|
-
targetId: row.
|
|
33
|
+
targetId: row.attachmentSlug,
|
|
33
34
|
runId: crypto.randomUUID(),
|
|
34
35
|
trigger: row.kind,
|
|
35
36
|
payload: {}
|
|
@@ -53,6 +54,26 @@ function sleep$1(ms) {
|
|
|
53
54
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
54
55
|
}
|
|
55
56
|
//#endregion
|
|
57
|
+
//#region src/cancel-channel.ts
|
|
58
|
+
/** In-process cancel pub/sub for single-process queues (memory, db polling). */
|
|
59
|
+
function createInProcessCancelChannel() {
|
|
60
|
+
const emitter = new EventEmitter();
|
|
61
|
+
return {
|
|
62
|
+
async publishCancel(runId) {
|
|
63
|
+
emitter.emit("cancel", runId);
|
|
64
|
+
},
|
|
65
|
+
async subscribeCancel(handler) {
|
|
66
|
+
const listener = (runId) => {
|
|
67
|
+
handler(runId);
|
|
68
|
+
};
|
|
69
|
+
emitter.on("cancel", listener);
|
|
70
|
+
return async () => {
|
|
71
|
+
emitter.off("cancel", listener);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
56
77
|
//#region src/database-queue.ts
|
|
57
78
|
function toJobPayload(job) {
|
|
58
79
|
return {
|
|
@@ -68,6 +89,7 @@ function toJobPayload(job) {
|
|
|
68
89
|
};
|
|
69
90
|
}
|
|
70
91
|
function createDatabaseJobQueue() {
|
|
92
|
+
const cancelChannel = createInProcessCancelChannel();
|
|
71
93
|
return {
|
|
72
94
|
async enqueue(input) {
|
|
73
95
|
return enqueueJob(input);
|
|
@@ -106,7 +128,9 @@ function createDatabaseJobQueue() {
|
|
|
106
128
|
running = false;
|
|
107
129
|
clearInterval(leaseTimer);
|
|
108
130
|
};
|
|
109
|
-
}
|
|
131
|
+
},
|
|
132
|
+
publishCancel: (runId) => cancelChannel.publishCancel(runId),
|
|
133
|
+
subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
|
|
110
134
|
};
|
|
111
135
|
}
|
|
112
136
|
function sleep(ms) {
|
|
@@ -143,6 +167,13 @@ async function stopPgBoss() {
|
|
|
143
167
|
boss = void 0;
|
|
144
168
|
}
|
|
145
169
|
//#endregion
|
|
170
|
+
//#region src/pg-cancel-channel.ts
|
|
171
|
+
/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */
|
|
172
|
+
function pgCancelChannelName(scope, projectId) {
|
|
173
|
+
if (scope === "platform") return "keystroke_cancel_platform";
|
|
174
|
+
return `keystroke_cancel_${(projectId ?? "default").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
146
177
|
//#region src/pg-boss-queue.ts
|
|
147
178
|
/** Shared queue name for the platform control-plane scope. */
|
|
148
179
|
const DEFAULT_QUEUE_NAME = "keystroke";
|
|
@@ -158,25 +189,35 @@ function pgBossProjectQueueName(projectId) {
|
|
|
158
189
|
if (name.length <= 50) return name;
|
|
159
190
|
return `${DEFAULT_QUEUE_NAME}_${createHash("sha1").update(projectId).digest("hex").slice(0, 40)}`;
|
|
160
191
|
}
|
|
161
|
-
function buildPgBossJobQueue(boss, options
|
|
192
|
+
function buildPgBossJobQueue(boss, options) {
|
|
162
193
|
const queueName = options.queueName ?? "keystroke";
|
|
194
|
+
const cancelChannel = createPgCancelChannel(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.projectId));
|
|
163
195
|
return {
|
|
164
196
|
async enqueue(input) {
|
|
165
197
|
const jobId = await boss.send(queueName, input, {
|
|
166
198
|
retryLimit: (input.maxAttempts ?? 3) - 1,
|
|
167
199
|
retryDelay: Math.ceil(retryDelayMs(1) / 1e3),
|
|
168
|
-
startAfter: input.scheduledAt
|
|
200
|
+
startAfter: input.scheduledAt,
|
|
201
|
+
singletonKey: input.dedupeKey
|
|
169
202
|
});
|
|
170
|
-
if (!jobId)
|
|
203
|
+
if (!jobId) {
|
|
204
|
+
if (input.dedupeKey) return input.dedupeKey;
|
|
205
|
+
throw new Error("Failed to enqueue job");
|
|
206
|
+
}
|
|
171
207
|
return jobId;
|
|
172
208
|
},
|
|
173
209
|
async startWorker(handler) {
|
|
174
210
|
let stopped = false;
|
|
175
|
-
const workerId = await boss.work(queueName, {
|
|
211
|
+
const workerId = await boss.work(queueName, {
|
|
212
|
+
batchSize: 1,
|
|
213
|
+
includeMetadata: true
|
|
214
|
+
}, async (jobs) => {
|
|
176
215
|
if (stopped) return;
|
|
177
216
|
const job = jobs[0];
|
|
178
217
|
if (!job) return;
|
|
179
218
|
const data = job.data;
|
|
219
|
+
const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;
|
|
220
|
+
const attempt = (job.retryCount ?? 0) + 1;
|
|
180
221
|
await handler({
|
|
181
222
|
jobId: job.id,
|
|
182
223
|
kind: data.kind,
|
|
@@ -184,8 +225,9 @@ function buildPgBossJobQueue(boss, options = {}) {
|
|
|
184
225
|
runId: data.runId,
|
|
185
226
|
trigger: data.trigger,
|
|
186
227
|
payload: data.payload ?? {},
|
|
187
|
-
attempt
|
|
188
|
-
maxAttempts
|
|
228
|
+
attempt,
|
|
229
|
+
maxAttempts,
|
|
230
|
+
exhaustedRetries: attempt >= maxAttempts,
|
|
189
231
|
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : /* @__PURE__ */ new Date()
|
|
190
232
|
});
|
|
191
233
|
});
|
|
@@ -197,7 +239,9 @@ function buildPgBossJobQueue(boss, options = {}) {
|
|
|
197
239
|
timeout: 5e3
|
|
198
240
|
});
|
|
199
241
|
};
|
|
200
|
-
}
|
|
242
|
+
},
|
|
243
|
+
publishCancel: (runId) => cancelChannel.publishCancel(runId),
|
|
244
|
+
subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
|
|
201
245
|
};
|
|
202
246
|
}
|
|
203
247
|
async function createPgBossQueue(options) {
|
|
@@ -210,12 +254,20 @@ async function createPgBossQueue(options) {
|
|
|
210
254
|
await boss.createQueue(queueName);
|
|
211
255
|
return buildPgBossJobQueue(boss, {
|
|
212
256
|
stopBossOnWorkerStop: true,
|
|
213
|
-
queueName
|
|
257
|
+
queueName,
|
|
258
|
+
connectionString: options.connectionString,
|
|
259
|
+
cancelScope: "project",
|
|
260
|
+
projectId: options.projectId
|
|
214
261
|
});
|
|
215
262
|
}
|
|
216
|
-
async function createSharedPgBossJobQueue(boss,
|
|
263
|
+
async function createSharedPgBossJobQueue(boss, options) {
|
|
264
|
+
const queueName = options.queueName ?? "keystroke";
|
|
217
265
|
await boss.createQueue(queueName);
|
|
218
|
-
return buildPgBossJobQueue(boss, {
|
|
266
|
+
return buildPgBossJobQueue(boss, {
|
|
267
|
+
queueName,
|
|
268
|
+
connectionString: options.connectionString,
|
|
269
|
+
cancelScope: "platform"
|
|
270
|
+
});
|
|
219
271
|
}
|
|
220
272
|
//#endregion
|
|
221
273
|
//#region src/plugin.ts
|
|
@@ -230,11 +282,13 @@ function pgBossSchedulerPlugin() {
|
|
|
230
282
|
const url = resolveUrl(ctx);
|
|
231
283
|
if (ctx.scope === "platform") {
|
|
232
284
|
await startPgBoss(url);
|
|
233
|
-
return createSharedPgBossJobQueue(getPgBoss());
|
|
285
|
+
return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });
|
|
234
286
|
}
|
|
287
|
+
const projectId = ctx.projectId ?? getProjectScopeId();
|
|
235
288
|
return createPgBossQueue({
|
|
236
289
|
connectionString: url,
|
|
237
|
-
queueName: pgBossProjectQueueName(
|
|
290
|
+
queueName: pgBossProjectQueueName(projectId),
|
|
291
|
+
projectId
|
|
238
292
|
});
|
|
239
293
|
}
|
|
240
294
|
});
|
|
@@ -270,21 +324,21 @@ function resolveTriggerSchedule(attachmentKey, schedule, overrides) {
|
|
|
270
324
|
//#region src/sync-trigger-schedules.ts
|
|
271
325
|
async function syncTriggerSchedules(options) {
|
|
272
326
|
const now = /* @__PURE__ */ new Date();
|
|
273
|
-
const
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
327
|
+
const projectSlugs = options.schedules.map((schedule) => schedule.attachmentKey);
|
|
328
|
+
const ephemeralSlugs = await selectActiveEphemeralScheduledAttachmentSlugs();
|
|
329
|
+
const slugs = [...projectSlugs, ...ephemeralSlugs];
|
|
330
|
+
if (slugs.length === 0) {
|
|
277
331
|
await disableAllTriggerSchedules(now);
|
|
278
332
|
return;
|
|
279
333
|
}
|
|
280
|
-
await
|
|
334
|
+
await disableTriggerSchedulesNotInSlugs(slugs, now);
|
|
281
335
|
for (const spec of options.schedules) {
|
|
282
336
|
const schedule = resolveTriggerSchedule(spec.attachmentKey, spec.schedule, options.scheduleOverrides);
|
|
283
|
-
const existing = await
|
|
337
|
+
const existing = await selectTriggerScheduleBySlug(spec.attachmentKey);
|
|
284
338
|
const scheduleChanged = existing?.schedule !== schedule;
|
|
285
339
|
const nextRunAt = existing && !scheduleChanged && existing.enabled === 1 ? existing.nextRunAt : nextTriggerRunAt(schedule, now);
|
|
286
340
|
await upsertTriggerSchedule({
|
|
287
|
-
|
|
341
|
+
attachmentSlug: spec.attachmentKey,
|
|
288
342
|
kind: spec.kind,
|
|
289
343
|
schedule,
|
|
290
344
|
nextRunAt,
|
|
@@ -310,6 +364,8 @@ function wrapScheduler(jobQueue) {
|
|
|
310
364
|
return {
|
|
311
365
|
enqueue: (input) => jobQueue.enqueue(input),
|
|
312
366
|
startWorker: (handler, options) => jobQueue.startWorker(handler, options),
|
|
367
|
+
publishCancel: (runId) => jobQueue.publishCancel(runId),
|
|
368
|
+
subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),
|
|
313
369
|
syncTriggerSchedules: (options) => syncTriggerSchedules(options),
|
|
314
370
|
startScheduleTicker: (options) => ticker.startScheduleTicker(options),
|
|
315
371
|
fireDueSchedules: (asOf) => ticker.fireDueSchedules(asOf)
|
|
@@ -327,6 +383,7 @@ function wrapJobQueueAsScheduler(jobQueue) {
|
|
|
327
383
|
//#endregion
|
|
328
384
|
//#region src/memory.ts
|
|
329
385
|
function createMemoryJobQueue(options = {}) {
|
|
386
|
+
const cancelChannel = createInProcessCancelChannel();
|
|
330
387
|
const pending = [];
|
|
331
388
|
let handler;
|
|
332
389
|
let draining = false;
|
|
@@ -371,7 +428,9 @@ function createMemoryJobQueue(options = {}) {
|
|
|
371
428
|
return async () => {
|
|
372
429
|
handler = void 0;
|
|
373
430
|
};
|
|
374
|
-
}
|
|
431
|
+
},
|
|
432
|
+
publishCancel: (runId) => cancelChannel.publishCancel(runId),
|
|
433
|
+
subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler)
|
|
375
434
|
};
|
|
376
435
|
}
|
|
377
436
|
//#endregion
|
|
@@ -382,7 +441,9 @@ async function createSharedPgBossScheduler() {
|
|
|
382
441
|
}
|
|
383
442
|
async function getSharedPgBossJobQueue() {
|
|
384
443
|
if (sharedJobQueue$1) return sharedJobQueue$1;
|
|
385
|
-
|
|
444
|
+
const connectionString = resolvePostgresUrlFromEnv(process.env);
|
|
445
|
+
if (!connectionString) throw new Error("Postgres connection string is required for shared pg-boss queue");
|
|
446
|
+
sharedJobQueue$1 = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });
|
|
386
447
|
return sharedJobQueue$1;
|
|
387
448
|
}
|
|
388
449
|
function resetSharedPgBossJobQueueForTests() {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["sleep","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/database-queue.ts","../src/pg-boss-client.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","../src/resolve-plugin-from-env.ts"],"sourcesContent":["import { claimDueTriggerSchedules } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\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 claimed = await claimDueTriggerSchedules(\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentKey,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n scheduledAt: 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 let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n await claimDueTriggerSchedules(\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n ).then(async (claimed) => {\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentKey,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n });\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 {\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 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 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}\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","import { createHash } from \"node:crypto\";\nimport { PgBoss, type Job } from \"pg-boss\";\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 type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\";\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};\n\ntype BuildPgBossJobQueueOptions = {\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n};\n\nexport function buildPgBossJobQueue(\n boss: PgBoss,\n options: BuildPgBossJobQueueOptions = {},\n): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(queueName, input, {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n });\n\n if (!jobId) {\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 },\n async (jobs: Job<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\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: data.attempt ?? 1,\n maxAttempts: data.maxAttempts ?? 3,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\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}\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, { stopBossOnWorkerStop: true, queueName });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n queueName: string = DEFAULT_QUEUE_NAME,\n): Promise<JobQueue> {\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, { queueName });\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 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; project scope gets a per-project queue. */\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());\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(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 byAttachment?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n attachmentKey: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(attachmentKey, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byAttachment,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInKeys,\n selectActiveEphemeralScheduledAttachmentKeys,\n selectTriggerScheduleByKey,\n upsertTriggerSchedule,\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 projectKeys = options.schedules.map((schedule) => schedule.attachmentKey);\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 ephemeralKeys = await selectActiveEphemeralScheduledAttachmentKeys();\n const keys = [...projectKeys, ...ephemeralKeys];\n\n if (keys.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInKeys(keys, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.attachmentKey,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerScheduleByKey(spec.attachmentKey);\n const scheduleChanged = existing?.schedule !== schedule;\n const nextRunAt =\n existing && !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n await upsertTriggerSchedule({\n attachmentKey: spec.attachmentKey,\n kind: spec.kind,\n schedule,\n nextRunAt,\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): Scheduler {\n const ticker = createScheduleTicker({ jobQueue });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\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 return wrapScheduler(jobQueue);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(jobQueue: JobQueue): Scheduler {\n return wrapScheduler(jobQueue);\n}\n\nexport type { StopFn };\n","import 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 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}\n\nexport { retryDelayMs };\n","import { 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 sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss());\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","import type { SchedulerPlugin } from \"./contract\";\n\n/** Env var naming a module that exports `createSchedulerPlugin(env): SchedulerPlugin`. */\nexport const SCHEDULER_MODULE_ENV = \"KEYSTROKE_SCHEDULER_MODULE\";\n\ntype SchedulerEnv = Record<string, string | undefined>;\n\nfunction isSchedulerPlugin(value: unknown): value is SchedulerPlugin {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.name === \"string\" && typeof candidate.createJobQueue === \"function\";\n}\n\nfunction getCreateSchedulerPlugin(mod: unknown): ((env: SchedulerEnv) => unknown) | undefined {\n if (typeof mod !== \"object\" || mod === null) {\n return undefined;\n }\n const factory = (mod as Record<string, unknown>).createSchedulerPlugin;\n return typeof factory === \"function\" ? (factory as (env: SchedulerEnv) => unknown) : undefined;\n}\n\n/**\n * Resolve a scheduler plugin selected via env so a producer (platform) and a\n * consumer (container worker) can never diverge on backend. When\n * `KEYSTROKE_SCHEDULER_MODULE` is set, dynamically import that module and call\n * its `createSchedulerPlugin(env)`\n */\nexport async function resolveSchedulerPluginFromEnv(\n env: SchedulerEnv = process.env,\n): Promise<SchedulerPlugin | undefined> {\n const moduleSpecifier = env[SCHEDULER_MODULE_ENV]?.trim();\n if (!moduleSpecifier) {\n return undefined;\n }\n\n const mod: unknown = await import(moduleSpecifier);\n const createSchedulerPlugin = getCreateSchedulerPlugin(mod);\n if (!createSchedulerPlugin) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" must export createSchedulerPlugin(env): SchedulerPlugin`,\n );\n }\n\n const plugin = createSchedulerPlugin(env);\n if (!isSchedulerPlugin(plugin)) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" createSchedulerPlugin(env) did not return a valid SchedulerPlugin (expected { name: string, createJobQueue: function })`,\n );\n }\n\n return plugin;\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","../src/resolve-plugin-from-env.ts"],"sourcesContent":["import { claimDueTriggerSchedules } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\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 claimed = await claimDueTriggerSchedules(\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentSlug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n scheduledAt: 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 let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n await claimDueTriggerSchedules(\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n ).then(async (claimed) => {\n for (const row of claimed) {\n await ctx.jobQueue.enqueue({\n kind: \"trigger\",\n targetId: row.attachmentSlug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n });\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(scope: \"platform\" | \"project\", projectId?: string): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n const sanitized = (projectId ?? \"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 type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\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};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\";\n projectId?: 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(options.cancelScope ?? \"platform\", options.projectId),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(queueName, input, {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\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 });\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: \"project\",\n projectId: options.projectId,\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 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; project scope gets a per-project queue. */\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 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 byAttachment?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n attachmentKey: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(attachmentKey, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byAttachment,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n selectActiveEphemeralScheduledAttachmentSlugs,\n selectTriggerScheduleBySlug,\n upsertTriggerSchedule,\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.attachmentKey);\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 selectActiveEphemeralScheduledAttachmentSlugs();\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.attachmentKey,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerScheduleBySlug(spec.attachmentKey);\n const scheduleChanged = existing?.schedule !== schedule;\n const nextRunAt =\n existing && !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n await upsertTriggerSchedule({\n attachmentSlug: spec.attachmentKey,\n kind: spec.kind,\n schedule,\n nextRunAt,\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): Scheduler {\n const ticker = createScheduleTicker({ jobQueue });\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 return wrapScheduler(jobQueue);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(jobQueue: JobQueue): Scheduler {\n return wrapScheduler(jobQueue);\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","import type { SchedulerPlugin } from \"./contract\";\n\n/** Env var naming a module that exports `createSchedulerPlugin(env): SchedulerPlugin`. */\nexport const SCHEDULER_MODULE_ENV = \"KEYSTROKE_SCHEDULER_MODULE\";\n\ntype SchedulerEnv = Record<string, string | undefined>;\n\nfunction isSchedulerPlugin(value: unknown): value is SchedulerPlugin {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const candidate = value as Record<string, unknown>;\n return typeof candidate.name === \"string\" && typeof candidate.createJobQueue === \"function\";\n}\n\nfunction getCreateSchedulerPlugin(mod: unknown): ((env: SchedulerEnv) => unknown) | undefined {\n if (typeof mod !== \"object\" || mod === null) {\n return undefined;\n }\n const factory = (mod as Record<string, unknown>).createSchedulerPlugin;\n return typeof factory === \"function\" ? (factory as (env: SchedulerEnv) => unknown) : undefined;\n}\n\n/**\n * Resolve a scheduler plugin selected via env so a producer (platform) and a\n * consumer (container worker) can never diverge on backend. When\n * `KEYSTROKE_SCHEDULER_MODULE` is set, dynamically import that module and call\n * its `createSchedulerPlugin(env)`\n */\nexport async function resolveSchedulerPluginFromEnv(\n env: SchedulerEnv = process.env,\n): Promise<SchedulerPlugin | undefined> {\n const moduleSpecifier = env[SCHEDULER_MODULE_ENV]?.trim();\n if (!moduleSpecifier) {\n return undefined;\n }\n\n const mod: unknown = await import(moduleSpecifier);\n const createSchedulerPlugin = getCreateSchedulerPlugin(mod);\n if (!createSchedulerPlugin) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" must export createSchedulerPlugin(env): SchedulerPlugin`,\n );\n }\n\n const plugin = createSchedulerPlugin(env);\n if (!isSchedulerPlugin(plugin)) {\n throw new Error(\n `Scheduler module \"${moduleSpecifier}\" createSchedulerPlugin(env) did not return a valid SchedulerPlugin (expected { name: string, createJobQueue: function })`,\n );\n }\n\n return plugin;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keystrokehq/scheduler",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/
|
|
7
|
+
"url": "git+https://github.com/keystrokehq/keystroke.git",
|
|
8
8
|
"directory": "packages/scheduler"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
@@ -16,13 +16,11 @@
|
|
|
16
16
|
"types": "./dist/index.d.mts",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
|
-
"development": "./src/index.ts",
|
|
20
19
|
"types": "./dist/index.d.mts",
|
|
21
20
|
"import": "./dist/index.mjs",
|
|
22
21
|
"require": "./dist/index.cjs"
|
|
23
22
|
},
|
|
24
23
|
"./contract": {
|
|
25
|
-
"development": "./src/contract.ts",
|
|
26
24
|
"types": "./dist/contract.d.mts",
|
|
27
25
|
"import": "./dist/contract.mjs",
|
|
28
26
|
"require": "./dist/contract.cjs"
|
|
@@ -43,16 +41,16 @@
|
|
|
43
41
|
"tsdown": "^0.22.0",
|
|
44
42
|
"typescript": "^6.0.3",
|
|
45
43
|
"vitest": "^4.1.7",
|
|
46
|
-
"@keystrokehq/database": "0.0.
|
|
47
|
-
"@keystrokehq/oxlint-config": "0.0.
|
|
48
|
-
"@keystrokehq/trigger": "0.0.
|
|
44
|
+
"@keystrokehq/database": "0.0.111",
|
|
45
|
+
"@keystrokehq/oxlint-config": "0.0.4",
|
|
46
|
+
"@keystrokehq/trigger": "0.0.122",
|
|
49
47
|
"@keystrokehq/tsconfig": "0.0.3",
|
|
50
48
|
"@keystrokehq/tsdown-config": "0.0.3",
|
|
51
|
-
"@keystrokehq/vitest-config": "0.0.
|
|
49
|
+
"@keystrokehq/vitest-config": "0.0.5"
|
|
52
50
|
},
|
|
53
51
|
"peerDependencies": {
|
|
54
|
-
"@keystrokehq/
|
|
55
|
-
"@keystrokehq/
|
|
52
|
+
"@keystrokehq/trigger": "0.0.122",
|
|
53
|
+
"@keystrokehq/database": "0.0.111"
|
|
56
54
|
},
|
|
57
55
|
"peerDependenciesMeta": {
|
|
58
56
|
"@keystrokehq/database": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contract-CYKkRmnH.d.cts","names":[],"sources":["../src/types.ts","../src/contract.ts"],"mappings":";KAAY,OAAA;AAAA,KAEA,UAAA;AAAA,KAEA,UAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,OAAA;EACA,WAAA;EACA,WAAA,EAAa,IAAA;EACb,KAAA;AAAA;AAAA,KAGU,YAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,WAAA,GAAc,IAAA;EACd,OAAA;EACA,WAAA;AAAA;AAAA,KAGU,UAAA,IAAc,GAAA,EAAK,UAAA,KAAe,OAAO;AAAA,KAEzC,MAAA,SAAe,OAAO;AAAA,KAEtB,aAAA;EACV,QAAA;EACA,cAAA;EACA,oBAAA;AAAA;AAAA,KAGU,QAAA;EACV,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,OAAA;EAC9B,WAAA,CAAY,OAAA,EAAS,UAAA,EAAY,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,MAAA;AAAA;AAAA,KAKzD,qBAAA;EACV,GAAA;EACA,OAAA;EACA,OAAA,GAAU,QAAA;EACV,MAAA,GAAS,eAAA;EACT,KAAA,GAAQ,cAAA;EACR,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,mBAAA;EACV,aAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA,EAAW,mBAAA;EACX,iBAAA;IACE,MAAA;IACA,YAAA,GAAe,MAAM;EAAA;AAAA;AAAA,KAIb,qBAAA;EACV,cAAA;EACA,SAAS;AAAA;AAAA,KAGC,SAAA,GAAY,QAAA;EACtB,oBAAA,CAAqB,OAAA,EAAS,mBAAA,GAAsB,OAAA;EACpD,mBAAA,CAAoB,OAAA,GAAU,qBAAA,GAAwB,OAAA,CAAQ,MAAA;EAC9D,gBAAA,CAAiB,IAAA,GAAO,IAAA,GAAO,OAAA;AAAA;AAAA,KAGrB,sBAAA,GAAyB,qBAAqB;AAAA,cAE7C,sBAAA;AAAA,iBAEG,YAAA,CAAa,OAAe;;;;;AAnFzB;AAEnB;;KCiBY,eAAA;AAAA,KAEA,cAAA;AAAA,KAEA,sBAAA;EACV,KAAA,EAAO,cAAA;EACP,GAAA;EACA,OAAA,GAAU,eAAe;EACzB,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,eAAA;EACV,IAAA;EACA,cAAA,CAAe,GAAA,EAAK,sBAAA,GAAyB,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAGvC,qBAAA,CAAsB,MAAA,EAAQ,eAAA,GAAkB,eAAe"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contract-CYKkRmnH.d.mts","names":[],"sources":["../src/types.ts","../src/contract.ts"],"mappings":";KAAY,OAAA;AAAA,KAEA,UAAA;AAAA,KAEA,UAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,OAAA;EACA,WAAA;EACA,WAAA,EAAa,IAAA;EACb,KAAA;AAAA;AAAA,KAGU,YAAA;EACV,IAAA,EAAM,OAAA;EACN,QAAA;EACA,KAAA;EACA,OAAA,EAAS,UAAA;EACT,OAAA;EACA,WAAA,GAAc,IAAA;EACd,OAAA;EACA,WAAA;AAAA;AAAA,KAGU,UAAA,IAAc,GAAA,EAAK,UAAA,KAAe,OAAO;AAAA,KAEzC,MAAA,SAAe,OAAO;AAAA,KAEtB,aAAA;EACV,QAAA;EACA,cAAA;EACA,oBAAA;AAAA;AAAA,KAGU,QAAA;EACV,OAAA,CAAQ,KAAA,EAAO,YAAA,GAAe,OAAA;EAC9B,WAAA,CAAY,OAAA,EAAS,UAAA,EAAY,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,MAAA;AAAA;AAAA,KAKzD,qBAAA;EACV,GAAA;EACA,OAAA;EACA,OAAA,GAAU,QAAA;EACV,MAAA,GAAS,eAAA;EACT,KAAA,GAAQ,cAAA;EACR,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,mBAAA;EACV,aAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA,EAAW,mBAAA;EACX,iBAAA;IACE,MAAA;IACA,YAAA,GAAe,MAAM;EAAA;AAAA;AAAA,KAIb,qBAAA;EACV,cAAA;EACA,SAAS;AAAA;AAAA,KAGC,SAAA,GAAY,QAAA;EACtB,oBAAA,CAAqB,OAAA,EAAS,mBAAA,GAAsB,OAAA;EACpD,mBAAA,CAAoB,OAAA,GAAU,qBAAA,GAAwB,OAAA,CAAQ,MAAA;EAC9D,gBAAA,CAAiB,IAAA,GAAO,IAAA,GAAO,OAAA;AAAA;AAAA,KAGrB,sBAAA,GAAyB,qBAAqB;AAAA,cAE7C,sBAAA;AAAA,iBAEG,YAAA,CAAa,OAAe;;;;;AAnFzB;AAEnB;;KCiBY,eAAA;AAAA,KAEA,cAAA;AAAA,KAEA,sBAAA;EACV,KAAA,EAAO,cAAA;EACP,GAAA;EACA,OAAA,GAAU,eAAe;EACzB,SAAA;EACA,cAAA;AAAA;AAAA,KAGU,eAAA;EACV,IAAA;EACA,cAAA,CAAe,GAAA,EAAK,sBAAA,GAAyB,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAGvC,qBAAA,CAAsB,MAAA,EAAQ,eAAA,GAAkB,eAAe"}
|