@keystrokehq/scheduler 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{contract-DAqQua3D.d.cts → contract-BGjcxdxT.d.cts} +11 -8
- package/dist/contract-BGjcxdxT.d.cts.map +1 -0
- package/dist/{contract-DAqQua3D.d.mts → contract-BGjcxdxT.d.mts} +11 -8
- package/dist/contract-BGjcxdxT.d.mts.map +1 -0
- package/dist/contract-C5JvbyQ-.cjs.map +1 -1
- package/dist/contract-E1QJBH6_.mjs.map +1 -1
- package/dist/contract.d.cts +1 -1
- package/dist/contract.d.mts +1 -1
- package/dist/index.cjs +88 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +89 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/contract-DAqQua3D.d.cts.map +0 -1
- package/dist/contract-DAqQua3D.d.mts.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
-
type JobKind = "workflow" | "agent" | "trigger" | "runtime";
|
|
2
|
+
type JobKind = "workflow" | "agent" | "trigger" | "trigger-overview" | "runtime";
|
|
3
3
|
type JobTrigger = "api" | "cron" | "webhook" | "poll" | "retry" | "prompt";
|
|
4
4
|
type JobPayload = {
|
|
5
5
|
kind: JobKind;
|
|
@@ -11,7 +11,8 @@ type JobPayload = {
|
|
|
11
11
|
maxAttempts: number; /** True when this delivery is the final retry attempt. */
|
|
12
12
|
exhaustedRetries?: boolean;
|
|
13
13
|
scheduledAt: Date;
|
|
14
|
-
jobId: string;
|
|
14
|
+
jobId: string; /** Project scope for org-wide worker queues. */
|
|
15
|
+
projectId?: string;
|
|
15
16
|
};
|
|
16
17
|
type EnqueueInput = {
|
|
17
18
|
kind: JobKind;
|
|
@@ -22,7 +23,8 @@ type EnqueueInput = {
|
|
|
22
23
|
scheduledAt?: Date;
|
|
23
24
|
attempt?: number;
|
|
24
25
|
maxAttempts?: number; /** Dedupe key — pg-boss singletonKey / BullMQ jobId. */
|
|
25
|
-
dedupeKey?: string;
|
|
26
|
+
dedupeKey?: string; /** Project scope for org-wide worker queues. */
|
|
27
|
+
projectId?: string;
|
|
26
28
|
};
|
|
27
29
|
type JobHandler = (job: JobPayload) => Promise<void>;
|
|
28
30
|
type CancelHandler = (runId: string) => void | Promise<void>;
|
|
@@ -48,7 +50,7 @@ type CreateJobQueueOptions = {
|
|
|
48
50
|
organizationId?: string;
|
|
49
51
|
};
|
|
50
52
|
type TriggerScheduleSpec = {
|
|
51
|
-
|
|
53
|
+
triggerSlug: string;
|
|
52
54
|
kind: "cron" | "poll";
|
|
53
55
|
schedule: string;
|
|
54
56
|
};
|
|
@@ -56,12 +58,13 @@ type ScheduleSyncOptions = {
|
|
|
56
58
|
schedules: TriggerScheduleSpec[];
|
|
57
59
|
scheduleOverrides?: {
|
|
58
60
|
global?: string;
|
|
59
|
-
|
|
61
|
+
byTrigger?: Record<string, string>;
|
|
60
62
|
};
|
|
61
63
|
};
|
|
62
64
|
type ScheduleTickerOptions = {
|
|
63
65
|
pollIntervalMs?: number;
|
|
64
|
-
batchSize?: number;
|
|
66
|
+
batchSize?: number; /** When `organization`, claims due schedules across all projects in the org schema. */
|
|
67
|
+
scope?: "project" | "organization";
|
|
65
68
|
};
|
|
66
69
|
type Scheduler = JobQueue & {
|
|
67
70
|
syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void>;
|
|
@@ -79,7 +82,7 @@ declare function retryDelayMs(attempt: number): number;
|
|
|
79
82
|
* package would re-introduce the dependency `./contract` exists to avoid.
|
|
80
83
|
*/
|
|
81
84
|
type DatabaseDialect = "postgres" | "sqlite";
|
|
82
|
-
type SchedulerScope = "platform" | "project";
|
|
85
|
+
type SchedulerScope = "platform" | "project" | "organization";
|
|
83
86
|
type SchedulerPluginContext = {
|
|
84
87
|
scope: SchedulerScope;
|
|
85
88
|
url?: string;
|
|
@@ -94,4 +97,4 @@ type SchedulerPlugin = {
|
|
|
94
97
|
declare function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin;
|
|
95
98
|
//#endregion
|
|
96
99
|
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-
|
|
100
|
+
//# sourceMappingURL=contract-BGjcxdxT.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-BGjcxdxT.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,UAVM;EAYN,SAAA;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,WAlBA;EAoBA,SAAA,WAjBA;EAmBA,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,WAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA,EAAW,mBAAA;EACX,iBAAA;IACE,MAAA;IACA,SAAA,GAAY,MAAM;EAAA;AAAA;AAAA,KAIV,qBAAA;EACV,cAAA;EACA,SAAA,WA7C2B;EA+C3B,KAAA;AAAA;AAAA,KAGU,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;;;;;AAjGzB;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" | "runtime";
|
|
2
|
+
type JobKind = "workflow" | "agent" | "trigger" | "trigger-overview" | "runtime";
|
|
3
3
|
type JobTrigger = "api" | "cron" | "webhook" | "poll" | "retry" | "prompt";
|
|
4
4
|
type JobPayload = {
|
|
5
5
|
kind: JobKind;
|
|
@@ -11,7 +11,8 @@ type JobPayload = {
|
|
|
11
11
|
maxAttempts: number; /** True when this delivery is the final retry attempt. */
|
|
12
12
|
exhaustedRetries?: boolean;
|
|
13
13
|
scheduledAt: Date;
|
|
14
|
-
jobId: string;
|
|
14
|
+
jobId: string; /** Project scope for org-wide worker queues. */
|
|
15
|
+
projectId?: string;
|
|
15
16
|
};
|
|
16
17
|
type EnqueueInput = {
|
|
17
18
|
kind: JobKind;
|
|
@@ -22,7 +23,8 @@ type EnqueueInput = {
|
|
|
22
23
|
scheduledAt?: Date;
|
|
23
24
|
attempt?: number;
|
|
24
25
|
maxAttempts?: number; /** Dedupe key — pg-boss singletonKey / BullMQ jobId. */
|
|
25
|
-
dedupeKey?: string;
|
|
26
|
+
dedupeKey?: string; /** Project scope for org-wide worker queues. */
|
|
27
|
+
projectId?: string;
|
|
26
28
|
};
|
|
27
29
|
type JobHandler = (job: JobPayload) => Promise<void>;
|
|
28
30
|
type CancelHandler = (runId: string) => void | Promise<void>;
|
|
@@ -48,7 +50,7 @@ type CreateJobQueueOptions = {
|
|
|
48
50
|
organizationId?: string;
|
|
49
51
|
};
|
|
50
52
|
type TriggerScheduleSpec = {
|
|
51
|
-
|
|
53
|
+
triggerSlug: string;
|
|
52
54
|
kind: "cron" | "poll";
|
|
53
55
|
schedule: string;
|
|
54
56
|
};
|
|
@@ -56,12 +58,13 @@ type ScheduleSyncOptions = {
|
|
|
56
58
|
schedules: TriggerScheduleSpec[];
|
|
57
59
|
scheduleOverrides?: {
|
|
58
60
|
global?: string;
|
|
59
|
-
|
|
61
|
+
byTrigger?: Record<string, string>;
|
|
60
62
|
};
|
|
61
63
|
};
|
|
62
64
|
type ScheduleTickerOptions = {
|
|
63
65
|
pollIntervalMs?: number;
|
|
64
|
-
batchSize?: number;
|
|
66
|
+
batchSize?: number; /** When `organization`, claims due schedules across all projects in the org schema. */
|
|
67
|
+
scope?: "project" | "organization";
|
|
65
68
|
};
|
|
66
69
|
type Scheduler = JobQueue & {
|
|
67
70
|
syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void>;
|
|
@@ -79,7 +82,7 @@ declare function retryDelayMs(attempt: number): number;
|
|
|
79
82
|
* package would re-introduce the dependency `./contract` exists to avoid.
|
|
80
83
|
*/
|
|
81
84
|
type DatabaseDialect = "postgres" | "sqlite";
|
|
82
|
-
type SchedulerScope = "platform" | "project";
|
|
85
|
+
type SchedulerScope = "platform" | "project" | "organization";
|
|
83
86
|
type SchedulerPluginContext = {
|
|
84
87
|
scope: SchedulerScope;
|
|
85
88
|
url?: string;
|
|
@@ -94,4 +97,4 @@ type SchedulerPlugin = {
|
|
|
94
97
|
declare function defineSchedulerPlugin(plugin: SchedulerPlugin): SchedulerPlugin;
|
|
95
98
|
//#endregion
|
|
96
99
|
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-
|
|
100
|
+
//# sourceMappingURL=contract-BGjcxdxT.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-BGjcxdxT.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,UAVM;EAYN,SAAA;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,WAlBA;EAoBA,SAAA,WAjBA;EAmBA,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,WAAA;EACA,IAAA;EACA,QAAA;AAAA;AAAA,KAGU,mBAAA;EACV,SAAA,EAAW,mBAAA;EACX,iBAAA;IACE,MAAA;IACA,SAAA,GAAY,MAAM;EAAA;AAAA;AAAA,KAIV,qBAAA;EACV,cAAA;EACA,SAAA,WA7C2B;EA+C3B,KAAA;AAAA;AAAA,KAGU,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;;;;;AAjGzB;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-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
|
|
1
|
+
{"version":3,"file":"contract-C5JvbyQ-.cjs","names":[],"sources":["../src/types.ts","../src/contract.ts"],"sourcesContent":["export type JobKind = \"workflow\" | \"agent\" | \"trigger\" | \"trigger-overview\" | \"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 /** Project scope for org-wide worker queues. */\n projectId?: 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 /** Project scope for org-wide worker queues. */\n projectId?: 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 triggerSlug: string;\n kind: \"cron\" | \"poll\";\n schedule: string;\n};\n\nexport type ScheduleSyncOptions = {\n schedules: TriggerScheduleSpec[];\n scheduleOverrides?: {\n global?: string;\n byTrigger?: Record<string, string>;\n };\n};\n\nexport type ScheduleTickerOptions = {\n pollIntervalMs?: number;\n batchSize?: number;\n /** When `organization`, claims due schedules across all projects in the org schema. */\n scope?: \"project\" | \"organization\";\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\" | \"organization\";\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":";AA+FA,MAAa,yBAAyB;AAEtC,SAAgB,aAAa,SAAyB;CACpD,OAAO,yBAAyB,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC;AAC9D;;;AC9DA,SAAgB,sBAAsB,QAA0C;CAC9E,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
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
|
|
1
|
+
{"version":3,"file":"contract-E1QJBH6_.mjs","names":[],"sources":["../src/types.ts","../src/contract.ts"],"sourcesContent":["export type JobKind = \"workflow\" | \"agent\" | \"trigger\" | \"trigger-overview\" | \"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 /** Project scope for org-wide worker queues. */\n projectId?: 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 /** Project scope for org-wide worker queues. */\n projectId?: 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 triggerSlug: string;\n kind: \"cron\" | \"poll\";\n schedule: string;\n};\n\nexport type ScheduleSyncOptions = {\n schedules: TriggerScheduleSpec[];\n scheduleOverrides?: {\n global?: string;\n byTrigger?: Record<string, string>;\n };\n};\n\nexport type ScheduleTickerOptions = {\n pollIntervalMs?: number;\n batchSize?: number;\n /** When `organization`, claims due schedules across all projects in the org schema. */\n scope?: \"project\" | \"organization\";\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\" | \"organization\";\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":";AA+FA,MAAa,yBAAyB;AAEtC,SAAgB,aAAa,SAAyB;CACpD,OAAO,yBAAyB,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC;AAC9D;;;AC9DA,SAAgB,sBAAsB,QAA0C;CAC9E,OAAO;AACT"}
|
package/dist/contract.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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-
|
|
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-BGjcxdxT.cjs";
|
|
2
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 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-
|
|
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-BGjcxdxT.mjs";
|
|
2
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
|
@@ -6,37 +6,57 @@ let node_events = require("node:events");
|
|
|
6
6
|
let pg_boss = require("pg-boss");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/schedule-ticker.ts
|
|
9
|
+
function enqueueTriggerJob(jobQueue, row, scheduledAt) {
|
|
10
|
+
const input = {
|
|
11
|
+
kind: "trigger",
|
|
12
|
+
targetId: row.slug,
|
|
13
|
+
runId: crypto.randomUUID(),
|
|
14
|
+
trigger: row.kind,
|
|
15
|
+
payload: {},
|
|
16
|
+
projectId: row.projectId
|
|
17
|
+
};
|
|
18
|
+
if (scheduledAt) input.scheduledAt = scheduledAt;
|
|
19
|
+
return jobQueue.enqueue(input);
|
|
20
|
+
}
|
|
21
|
+
async function claimDue(scope, asOf, resolveNextRunAt, limit) {
|
|
22
|
+
if (scope === "organization") return (0, _keystrokehq_database.claimDueTriggersForOrg)(asOf, resolveNextRunAt, limit);
|
|
23
|
+
return (0, _keystrokehq_database.claimDueTriggers)(asOf, resolveNextRunAt, limit).then((rows) => rows.map((row) => ({
|
|
24
|
+
...row,
|
|
25
|
+
projectId: (0, _keystrokehq_database.getProjectScopeId)()
|
|
26
|
+
})));
|
|
27
|
+
}
|
|
9
28
|
function createScheduleTicker(ctx) {
|
|
10
29
|
const pollIntervalMs = ctx.pollIntervalMs ?? 1e3;
|
|
11
30
|
const batchSize = ctx.batchSize ?? 10;
|
|
12
31
|
async function fireDueSchedules(asOf = /* @__PURE__ */ new Date()) {
|
|
13
|
-
const claimed = await (
|
|
14
|
-
for (const row of claimed)
|
|
15
|
-
kind
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
32
|
+
const claimed = await claimDue(ctx.scope ?? "project", asOf, (schedule) => (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, asOf), batchSize);
|
|
33
|
+
for (const row of claimed) {
|
|
34
|
+
if (row.kind !== "cron" && row.kind !== "poll") continue;
|
|
35
|
+
await enqueueTriggerJob(ctx.jobQueue, {
|
|
36
|
+
slug: row.slug,
|
|
37
|
+
kind: row.kind,
|
|
38
|
+
projectId: row.projectId
|
|
39
|
+
}, asOf);
|
|
40
|
+
}
|
|
22
41
|
return claimed.length;
|
|
23
42
|
}
|
|
24
43
|
async function startScheduleTicker(options = {}) {
|
|
25
44
|
const intervalMs = options.pollIntervalMs ?? pollIntervalMs;
|
|
26
45
|
const limit = options.batchSize ?? batchSize;
|
|
46
|
+
const scope = options.scope ?? ctx.scope ?? "project";
|
|
27
47
|
let running = true;
|
|
28
48
|
const loop = async () => {
|
|
29
49
|
while (running) {
|
|
30
50
|
try {
|
|
31
|
-
await (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
const claimed = await claimDue(scope, /* @__PURE__ */ new Date(), (schedule) => (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, /* @__PURE__ */ new Date()), limit);
|
|
52
|
+
for (const row of claimed) {
|
|
53
|
+
if (row.kind !== "cron" && row.kind !== "poll") continue;
|
|
54
|
+
await enqueueTriggerJob(ctx.jobQueue, {
|
|
55
|
+
slug: row.slug,
|
|
56
|
+
kind: row.kind,
|
|
57
|
+
projectId: row.projectId
|
|
38
58
|
});
|
|
39
|
-
}
|
|
59
|
+
}
|
|
40
60
|
} catch {}
|
|
41
61
|
await sleep$1(intervalMs);
|
|
42
62
|
}
|
|
@@ -170,9 +190,10 @@ async function stopPgBoss() {
|
|
|
170
190
|
//#endregion
|
|
171
191
|
//#region src/pg-cancel-channel.ts
|
|
172
192
|
/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */
|
|
173
|
-
function pgCancelChannelName(scope,
|
|
193
|
+
function pgCancelChannelName(scope, id) {
|
|
174
194
|
if (scope === "platform") return "keystroke_cancel_platform";
|
|
175
|
-
return `
|
|
195
|
+
if (scope === "organization") return `keystroke_cancel_org_${(id ?? "default").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
|
|
196
|
+
return `keystroke_cancel_${(id ?? "default").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
|
|
176
197
|
}
|
|
177
198
|
//#endregion
|
|
178
199
|
//#region src/pg-boss-queue.ts
|
|
@@ -190,12 +211,20 @@ function pgBossProjectQueueName(projectId) {
|
|
|
190
211
|
if (name.length <= 50) return name;
|
|
191
212
|
return `${DEFAULT_QUEUE_NAME}_${(0, node_crypto.createHash)("sha1").update(projectId).digest("hex").slice(0, 40)}`;
|
|
192
213
|
}
|
|
214
|
+
function pgBossOrgQueueName(organizationId) {
|
|
215
|
+
const name = `${DEFAULT_QUEUE_NAME}_org_${organizationId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
216
|
+
if (name.length <= 50) return name;
|
|
217
|
+
return `${DEFAULT_QUEUE_NAME}_org_${(0, node_crypto.createHash)("sha1").update(organizationId).digest("hex").slice(0, 36)}`;
|
|
218
|
+
}
|
|
193
219
|
function buildPgBossJobQueue(boss, options) {
|
|
194
220
|
const queueName = options.queueName ?? "keystroke";
|
|
195
|
-
const cancelChannel = (0, _keystrokehq_database.createPostgresCancelChannel)(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.projectId));
|
|
221
|
+
const cancelChannel = (0, _keystrokehq_database.createPostgresCancelChannel)(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.cancelScope === "organization" ? options.organizationId : options.projectId));
|
|
196
222
|
return {
|
|
197
223
|
async enqueue(input) {
|
|
198
|
-
const jobId = await boss.send(queueName,
|
|
224
|
+
const jobId = await boss.send(queueName, {
|
|
225
|
+
...input,
|
|
226
|
+
payload: input.payload
|
|
227
|
+
}, {
|
|
199
228
|
retryLimit: (input.maxAttempts ?? 3) - 1,
|
|
200
229
|
retryDelay: Math.ceil(require_contract.retryDelayMs(1) / 1e3),
|
|
201
230
|
startAfter: input.scheduledAt,
|
|
@@ -229,7 +258,8 @@ function buildPgBossJobQueue(boss, options) {
|
|
|
229
258
|
attempt,
|
|
230
259
|
maxAttempts,
|
|
231
260
|
exhaustedRetries: attempt >= maxAttempts,
|
|
232
|
-
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : /* @__PURE__ */ new Date()
|
|
261
|
+
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : /* @__PURE__ */ new Date(),
|
|
262
|
+
projectId: data.projectId
|
|
233
263
|
});
|
|
234
264
|
});
|
|
235
265
|
return async () => {
|
|
@@ -257,8 +287,9 @@ async function createPgBossQueue(options) {
|
|
|
257
287
|
stopBossOnWorkerStop: true,
|
|
258
288
|
queueName,
|
|
259
289
|
connectionString: options.connectionString,
|
|
260
|
-
cancelScope: "project",
|
|
261
|
-
projectId: options.projectId
|
|
290
|
+
cancelScope: options.cancelScope ?? "project",
|
|
291
|
+
projectId: options.projectId,
|
|
292
|
+
organizationId: options.organizationId
|
|
262
293
|
});
|
|
263
294
|
}
|
|
264
295
|
async function createSharedPgBossJobQueue(boss, options) {
|
|
@@ -275,7 +306,7 @@ async function createSharedPgBossJobQueue(boss, options) {
|
|
|
275
306
|
function resolveUrl(ctx) {
|
|
276
307
|
return ctx.url ?? (0, _keystrokehq_database.resolveProjectDatabaseUrlFromEnv)(process.env) ?? _keystrokehq_database.DEFAULT_DATABASE_URL;
|
|
277
308
|
}
|
|
278
|
-
/** Postgres pg-boss backend. Platform scope shares one queue;
|
|
309
|
+
/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */
|
|
279
310
|
function pgBossSchedulerPlugin() {
|
|
280
311
|
return require_contract.defineSchedulerPlugin({
|
|
281
312
|
name: "pg-boss",
|
|
@@ -285,6 +316,16 @@ function pgBossSchedulerPlugin() {
|
|
|
285
316
|
await startPgBoss(url);
|
|
286
317
|
return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });
|
|
287
318
|
}
|
|
319
|
+
if (ctx.scope === "organization") {
|
|
320
|
+
const organizationId = ctx.organizationId;
|
|
321
|
+
if (!organizationId) throw new Error("organizationId is required for organization-scoped pg-boss queue");
|
|
322
|
+
return createPgBossQueue({
|
|
323
|
+
connectionString: url,
|
|
324
|
+
queueName: pgBossOrgQueueName(organizationId),
|
|
325
|
+
cancelScope: "organization",
|
|
326
|
+
organizationId
|
|
327
|
+
});
|
|
328
|
+
}
|
|
288
329
|
const projectId = ctx.projectId ?? (0, _keystrokehq_database.getProjectScopeId)();
|
|
289
330
|
return createPgBossQueue({
|
|
290
331
|
connectionString: url,
|
|
@@ -315,18 +356,18 @@ function defaultSchedulerPlugin() {
|
|
|
315
356
|
}
|
|
316
357
|
//#endregion
|
|
317
358
|
//#region src/resolve-schedule.ts
|
|
318
|
-
function resolveTriggerSchedule(
|
|
319
|
-
return (0, _keystrokehq_trigger.resolveCronSchedule)(
|
|
359
|
+
function resolveTriggerSchedule(triggerSlug, schedule, overrides) {
|
|
360
|
+
return (0, _keystrokehq_trigger.resolveCronSchedule)(triggerSlug, schedule, {
|
|
320
361
|
cronScheduleOverride: overrides?.global,
|
|
321
|
-
attachmentScheduleOverrides: overrides?.
|
|
362
|
+
attachmentScheduleOverrides: overrides?.byTrigger
|
|
322
363
|
});
|
|
323
364
|
}
|
|
324
365
|
//#endregion
|
|
325
366
|
//#region src/sync-trigger-schedules.ts
|
|
326
367
|
async function syncTriggerSchedules(options) {
|
|
327
368
|
const now = /* @__PURE__ */ new Date();
|
|
328
|
-
const projectSlugs = options.schedules.map((schedule) => schedule.
|
|
329
|
-
const ephemeralSlugs = await (0, _keystrokehq_database.
|
|
369
|
+
const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);
|
|
370
|
+
const ephemeralSlugs = await (0, _keystrokehq_database.selectActiveEphemeralScheduledTriggerSlugs)();
|
|
330
371
|
const slugs = [...projectSlugs, ...ephemeralSlugs];
|
|
331
372
|
if (slugs.length === 0) {
|
|
332
373
|
await (0, _keystrokehq_database.disableAllTriggerSchedules)(now);
|
|
@@ -334,16 +375,16 @@ async function syncTriggerSchedules(options) {
|
|
|
334
375
|
}
|
|
335
376
|
await (0, _keystrokehq_database.disableTriggerSchedulesNotInSlugs)(slugs, now);
|
|
336
377
|
for (const spec of options.schedules) {
|
|
337
|
-
const schedule = resolveTriggerSchedule(spec.
|
|
338
|
-
const existing = await (0, _keystrokehq_database.
|
|
339
|
-
|
|
340
|
-
const nextRunAt = existing
|
|
341
|
-
await (0, _keystrokehq_database.
|
|
342
|
-
|
|
343
|
-
|
|
378
|
+
const schedule = resolveTriggerSchedule(spec.triggerSlug, spec.schedule, options.scheduleOverrides);
|
|
379
|
+
const existing = await (0, _keystrokehq_database.selectTriggerBySlug)(spec.triggerSlug);
|
|
380
|
+
if (!existing) continue;
|
|
381
|
+
const nextRunAt = !(existing.schedule !== schedule) && existing.enabled === 1 ? existing.nextRunAt : (0, _keystrokehq_trigger.nextTriggerRunAt)(schedule, now);
|
|
382
|
+
const enabled = (await (0, _keystrokehq_database.listExecutableAttachmentsByTriggerIds)([existing.id])).length > 0;
|
|
383
|
+
await (0, _keystrokehq_database.upsertTriggerScheduleFields)({
|
|
384
|
+
triggerSlug: spec.triggerSlug,
|
|
344
385
|
schedule,
|
|
345
|
-
nextRunAt,
|
|
346
|
-
enabled
|
|
386
|
+
nextRunAt: nextRunAt ?? now,
|
|
387
|
+
enabled,
|
|
347
388
|
updatedAt: now
|
|
348
389
|
});
|
|
349
390
|
}
|
|
@@ -360,8 +401,11 @@ async function createUnderlyingJobQueue(options = {}) {
|
|
|
360
401
|
organizationId: options.organizationId
|
|
361
402
|
});
|
|
362
403
|
}
|
|
363
|
-
function wrapScheduler(jobQueue) {
|
|
364
|
-
const ticker = createScheduleTicker({
|
|
404
|
+
function wrapScheduler(jobQueue, scope) {
|
|
405
|
+
const ticker = createScheduleTicker({
|
|
406
|
+
jobQueue,
|
|
407
|
+
scope
|
|
408
|
+
});
|
|
365
409
|
return {
|
|
366
410
|
enqueue: (input) => jobQueue.enqueue(input),
|
|
367
411
|
startWorker: (handler, options) => jobQueue.startWorker(handler, options),
|
|
@@ -373,13 +417,13 @@ function wrapScheduler(jobQueue) {
|
|
|
373
417
|
};
|
|
374
418
|
}
|
|
375
419
|
async function createScheduler(options = {}) {
|
|
376
|
-
return wrapScheduler(await createUnderlyingJobQueue(options));
|
|
420
|
+
return wrapScheduler(await createUnderlyingJobQueue(options), options.scope === "organization" ? "organization" : "project");
|
|
377
421
|
}
|
|
378
422
|
async function createJobQueue(options = {}) {
|
|
379
423
|
return createUnderlyingJobQueue(options);
|
|
380
424
|
}
|
|
381
|
-
function wrapJobQueueAsScheduler(jobQueue) {
|
|
382
|
-
return wrapScheduler(jobQueue);
|
|
425
|
+
function wrapJobQueueAsScheduler(jobQueue, scope) {
|
|
426
|
+
return wrapScheduler(jobQueue, scope);
|
|
383
427
|
}
|
|
384
428
|
//#endregion
|
|
385
429
|
//#region src/memory.ts
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["sleep","EventEmitter","retryDelayMs","PgBoss","retryDelayMs","PgBoss","DEFAULT_DATABASE_URL","defineSchedulerPlugin","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["sleep","EventEmitter","retryDelayMs","PgBoss","retryDelayMs","PgBoss","DEFAULT_DATABASE_URL","defineSchedulerPlugin","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { claimDueTriggers, claimDueTriggersForOrg } from \"@keystrokehq/database\";\nimport { getProjectScopeId } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { EnqueueInput, JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\n scope?: \"project\" | \"organization\";\n};\n\nfunction enqueueTriggerJob(\n jobQueue: JobQueue,\n row: { slug: string; kind: \"cron\" | \"poll\"; projectId: string },\n scheduledAt?: Date,\n): Promise<string> {\n const input: EnqueueInput = {\n kind: \"trigger\",\n targetId: row.slug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n projectId: row.projectId,\n };\n\n if (scheduledAt) {\n input.scheduledAt = scheduledAt;\n }\n\n return jobQueue.enqueue(input);\n}\n\nasync function claimDue(\n scope: \"project\" | \"organization\",\n asOf: Date,\n resolveNextRunAt: (schedule: string) => Date,\n limit: number,\n) {\n if (scope === \"organization\") {\n return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);\n }\n\n return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) =>\n rows.map((row) => ({ ...row, projectId: getProjectScopeId() })),\n );\n}\n\nexport function createScheduleTicker(ctx: ScheduleTickerContext) {\n const pollIntervalMs = ctx.pollIntervalMs ?? 1_000;\n const batchSize = ctx.batchSize ?? 10;\n\n async function fireDueSchedules(asOf = new Date()): Promise<number> {\n const scope = ctx.scope ?? \"project\";\n const claimed = await claimDue(\n scope,\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(\n ctx.jobQueue,\n {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n },\n asOf,\n );\n }\n\n return claimed.length;\n }\n\n async function startScheduleTicker(options: ScheduleTickerOptions = {}): Promise<StopFn> {\n const intervalMs = options.pollIntervalMs ?? pollIntervalMs;\n const limit = options.batchSize ?? batchSize;\n const scope = options.scope ?? ctx.scope ?? \"project\";\n let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n const claimed = await claimDue(\n scope,\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(ctx.jobQueue, {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n });\n }\n } catch {\n // keep ticking\n }\n\n await sleep(intervalMs);\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n };\n }\n\n return { fireDueSchedules, startScheduleTicker };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { EventEmitter } from \"node:events\";\n\nimport type { CancelHandler, StopFn } from \"./types\";\n\n/** In-process cancel pub/sub for single-process queues (memory, db polling). */\nexport function createInProcessCancelChannel(): {\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n} {\n const emitter = new EventEmitter();\n return {\n async publishCancel(runId) {\n emitter.emit(\"cancel\", runId);\n },\n\n async subscribeCancel(handler) {\n const listener = (runId: string) => {\n void handler(runId);\n };\n emitter.on(\"cancel\", listener);\n return async () => {\n emitter.off(\"cancel\", listener);\n };\n },\n };\n}\n","import {\n claimNextJob,\n enqueueJob,\n failWorkflowRun,\n markJobComplete,\n markJobFailed,\n requeueExpiredLeases,\n scheduleJobRetry,\n} from \"@keystrokehq/database\";\nimport type { ClaimedJob } from \"@keystrokehq/database\";\nimport { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nfunction toJobPayload(job: ClaimedJob): Parameters<JobHandler>[0] {\n return {\n jobId: job.id,\n kind: job.kind,\n targetId: job.targetId,\n runId: job.runId,\n trigger: job.trigger,\n payload: job.payload,\n attempt: job.attempt,\n maxAttempts: job.maxAttempts,\n scheduledAt: job.scheduledAt,\n };\n}\n\nexport function createDatabaseJobQueue(): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n\n return {\n async enqueue(input) {\n return enqueueJob(input);\n },\n\n async startWorker(handler: JobHandler, options: WorkerOptions = {}): Promise<StopFn> {\n const workerId = options.workerId ?? crypto.randomUUID();\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n const leaseSweepIntervalMs = options.leaseSweepIntervalMs ?? 30_000;\n let running = true;\n\n const leaseTimer = setInterval(() => {\n void requeueExpiredLeases();\n }, leaseSweepIntervalMs);\n\n const loop = async () => {\n while (running) {\n try {\n const job = await claimNextJob(workerId);\n if (!job) {\n await sleep(pollIntervalMs);\n continue;\n }\n\n try {\n await handler(toJobPayload(job));\n await markJobComplete(job.id);\n } catch (error) {\n if (job.attempt < job.maxAttempts) {\n await scheduleJobRetry(job.id, job.attempt + 1, retryDelayMs(job.attempt));\n } else {\n await markJobFailed(job.id, error);\n if (job.kind === \"workflow\") {\n await failWorkflowRun(job.runId, error);\n }\n }\n }\n } catch {\n await sleep(pollIntervalMs);\n }\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n clearInterval(leaseTimer);\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { PgBoss } from \"pg-boss\";\n\nlet boss: PgBoss | undefined;\n\nfunction resolveDatabaseUrl(url?: string): string {\n const resolved = url ?? resolvePostgresUrlFromEnv(process.env);\n if (!resolved) {\n throw new Error(\n \"DATABASE_URL or POSTGRES_HOST/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB is required\",\n );\n }\n return resolved;\n}\n\nexport async function startPgBoss(url?: string): Promise<PgBoss> {\n if (boss) {\n return boss;\n }\n\n const next = new PgBoss({\n connectionString: resolveDatabaseUrl(url),\n schema: \"pgboss\",\n });\n next.on(\"error\", (error) => {\n console.error(\"[pg-boss]\", error);\n });\n\n await next.start();\n boss = next;\n return next;\n}\n\nexport function getPgBoss(): PgBoss {\n if (!boss) {\n throw new Error(\"PgBoss not started. Call startPgBoss() first.\");\n }\n return boss;\n}\n\nexport async function stopPgBoss(): Promise<void> {\n if (!boss) {\n return;\n }\n\n await boss.stop();\n boss = undefined;\n}\n","export { createPostgresCancelChannel as createPgCancelChannel } from \"@keystrokehq/database\";\n\n/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */\nexport function pgCancelChannelName(\n scope: \"platform\" | \"project\" | \"organization\",\n id?: string,\n): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n if (scope === \"organization\") {\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_org_${sanitized}`;\n }\n\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_${sanitized}`;\n}\n","import { createHash } from \"node:crypto\";\nimport { PgBoss, type JobWithMetadata } from \"pg-boss\";\nimport { createPgCancelChannel, pgCancelChannelName } from \"./pg-cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\n/** Shared queue name for the platform control-plane scope. */\nexport const DEFAULT_QUEUE_NAME = \"keystroke\";\nconst PGBOSS_SCHEMA = \"pgboss\";\n\n/**\n * Derive a per-project pg-boss queue name. pg-boss queue names must be <= 50\n * chars, contain only [A-Za-z0-9_], and not start with a digit. Project ids are\n * usually UUIDs (hyphens, may start with a digit), so we sanitize, and fall back\n * to a hash when the sanitized name would exceed the length limit.\n */\nexport function pgBossProjectQueueName(projectId: string): string {\n const sanitized = projectId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_${createHash(\"sha1\").update(projectId).digest(\"hex\").slice(0, 40)}`;\n}\n\nexport function pgBossOrgQueueName(organizationId: string): string {\n const sanitized = organizationId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_org_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_org_${createHash(\"sha1\").update(organizationId).digest(\"hex\").slice(0, 36)}`;\n}\n\nexport type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\n organizationId?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n targetId: string;\n runId: string;\n trigger: \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n payload?: unknown;\n attempt?: number;\n maxAttempts?: number;\n scheduledAt?: string;\n projectId?: string;\n};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n projectId?: string;\n organizationId?: string;\n};\n\nexport function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const cancelChannel = createPgCancelChannel(\n options.connectionString,\n pgCancelChannelName(\n options.cancelScope ?? \"platform\",\n options.cancelScope === \"organization\" ? options.organizationId : options.projectId,\n ),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(\n queueName,\n {\n ...input,\n payload: input.payload,\n },\n {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\n },\n );\n\n if (!jobId) {\n if (input.dedupeKey) {\n return input.dedupeKey;\n }\n throw new Error(\"Failed to enqueue job\");\n }\n\n return jobId;\n },\n\n async startWorker(handler: JobHandler): Promise<StopFn> {\n let stopped = false;\n\n const workerId = await boss.work<PgBossJobData>(\n queueName,\n { batchSize: 1, includeMetadata: true },\n async (jobs: JobWithMetadata<PgBossJobData>[]) => {\n if (stopped) {\n return;\n }\n\n const job = jobs[0];\n if (!job) {\n return;\n }\n\n const data = job.data;\n const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;\n const attempt = (job.retryCount ?? 0) + 1;\n\n await handler({\n jobId: job.id,\n kind: data.kind,\n targetId: data.targetId,\n runId: data.runId,\n trigger: data.trigger,\n payload: data.payload ?? {},\n attempt,\n maxAttempts,\n exhaustedRetries: attempt >= maxAttempts,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\n projectId: data.projectId,\n });\n },\n );\n\n return async () => {\n stopped = true;\n await boss.offWork(queueName, { id: workerId });\n if (options.stopBossOnWorkerStop) {\n await boss.stop({ graceful: true, timeout: 5_000 });\n }\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport async function createPgBossQueue(options: PgBossQueueOptions): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const boss = new PgBoss({\n connectionString: options.connectionString,\n schema: PGBOSS_SCHEMA,\n });\n await boss.start();\n await boss.createQueue(queueName);\n\n return buildPgBossJobQueue(boss, {\n stopBossOnWorkerStop: true,\n queueName,\n connectionString: options.connectionString,\n cancelScope: options.cancelScope ?? \"project\",\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n options: { connectionString: string; queueName?: string },\n): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, {\n queueName,\n connectionString: options.connectionString,\n cancelScope: \"platform\",\n });\n}\n","import {\n DEFAULT_DATABASE_URL,\n getProjectScopeId,\n inferDialect,\n resolveProjectDatabaseUrlFromEnv,\n} from \"@keystrokehq/database\";\n\nimport {\n defineSchedulerPlugin,\n type SchedulerPlugin,\n type SchedulerPluginContext,\n} from \"./contract\";\nimport { createDatabaseJobQueue } from \"./database-queue\";\nimport { getPgBoss, startPgBoss } from \"./pg-boss-client\";\nimport {\n createPgBossQueue,\n createSharedPgBossJobQueue,\n pgBossOrgQueueName,\n pgBossProjectQueueName,\n} from \"./pg-boss-queue\";\n\nexport type { SchedulerPlugin, SchedulerPluginContext, SchedulerScope } from \"./contract\";\nexport { defineSchedulerPlugin } from \"./contract\";\n\nfunction resolveUrl(ctx: SchedulerPluginContext): string {\n return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;\n}\n\n/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */\nexport function pgBossSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"pg-boss\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n\n if (ctx.scope === \"platform\") {\n await startPgBoss(url);\n return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });\n }\n\n if (ctx.scope === \"organization\") {\n const organizationId = ctx.organizationId;\n if (!organizationId) {\n throw new Error(\"organizationId is required for organization-scoped pg-boss queue\");\n }\n\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossOrgQueueName(organizationId),\n cancelScope: \"organization\",\n organizationId,\n });\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(projectId),\n projectId,\n });\n },\n });\n}\n\n/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */\nexport function pollingSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"polling\",\n async createJobQueue() {\n return createDatabaseJobQueue();\n },\n });\n}\n\n/** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */\nexport function defaultSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"default\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n const dialect = inferDialect(url, ctx.dialect);\n\n if (dialect === \"postgres\") {\n return pgBossSchedulerPlugin().createJobQueue(ctx);\n }\n\n return pollingSchedulerPlugin().createJobQueue(ctx);\n },\n });\n}\n","import { resolveCronSchedule } from \"@keystrokehq/trigger\";\n\nexport type ScheduleOverrideOptions = {\n global?: string;\n byTrigger?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n triggerSlug: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(triggerSlug, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byTrigger,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n listExecutableAttachmentsByTriggerIds,\n selectActiveEphemeralScheduledTriggerSlugs,\n selectTriggerBySlug,\n upsertTriggerScheduleFields,\n} from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { ScheduleSyncOptions } from \"./types\";\nimport { resolveTriggerSchedule } from \"./resolve-schedule\";\n\nexport async function syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void> {\n const now = new Date();\n const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);\n // Ephemeral cron/poll schedules are managed by set_trigger, not project discovery — keep them out\n // of the disable sweep so a deploy/restart does not wipe agent-created triggers.\n const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();\n const slugs = [...projectSlugs, ...ephemeralSlugs];\n\n if (slugs.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInSlugs(slugs, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.triggerSlug,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerBySlug(spec.triggerSlug);\n if (!existing) {\n continue;\n }\n\n const scheduleChanged = existing.schedule !== schedule;\n const nextRunAt =\n !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n const executable = await listExecutableAttachmentsByTriggerIds([existing.id]);\n const enabled = executable.length > 0;\n\n await upsertTriggerScheduleFields({\n triggerSlug: spec.triggerSlug,\n schedule,\n nextRunAt: nextRunAt ?? now,\n enabled,\n updatedAt: now,\n });\n }\n}\n","import { createScheduleTicker } from \"./schedule-ticker\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport { syncTriggerSchedules as syncTriggerScheduleRows } from \"./sync-trigger-schedules\";\nimport type {\n CreateJobQueueOptions,\n CreateSchedulerOptions,\n JobQueue,\n ScheduleSyncOptions,\n ScheduleTickerOptions,\n Scheduler,\n StopFn,\n} from \"./types\";\n\nasync function createUnderlyingJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n if (options.adapter) {\n return options.adapter;\n }\n\n const plugin = options.plugin ?? defaultSchedulerPlugin();\n return plugin.createJobQueue({\n scope: options.scope ?? \"project\",\n url: options.url,\n dialect: options.dialect,\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nfunction wrapScheduler(jobQueue: JobQueue, scope?: \"project\" | \"organization\"): Scheduler {\n const ticker = createScheduleTicker({ jobQueue, scope });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\n publishCancel: (runId) => jobQueue.publishCancel(runId),\n subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),\n syncTriggerSchedules: (options: ScheduleSyncOptions) => syncTriggerScheduleRows(options),\n startScheduleTicker: (options?: ScheduleTickerOptions) => ticker.startScheduleTicker(options),\n fireDueSchedules: (asOf?: Date) => ticker.fireDueSchedules(asOf),\n };\n}\n\nexport async function createScheduler(options: CreateSchedulerOptions = {}): Promise<Scheduler> {\n const jobQueue = await createUnderlyingJobQueue(options);\n const scope = options.scope === \"organization\" ? \"organization\" : \"project\";\n return wrapScheduler(jobQueue, scope);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(\n jobQueue: JobQueue,\n scope?: \"project\" | \"organization\",\n): Scheduler {\n return wrapScheduler(jobQueue, scope);\n}\n\nexport type { StopFn };\n","import { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { EnqueueInput, JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nexport type MemoryJobQueueOptions = {\n sync?: boolean;\n};\n\nexport function createMemoryJobQueue(options: MemoryJobQueueOptions = {}): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n const pending: Array<{ id: string; input: EnqueueInput; enqueuedAt: Date }> = [];\n let handler: JobHandler | undefined;\n let draining = false;\n\n async function drain(): Promise<void> {\n if (!handler || draining) {\n return;\n }\n\n draining = true;\n try {\n while (pending.length > 0) {\n const next = pending.shift();\n if (!next) {\n break;\n }\n\n const input = next.input;\n await handler({\n jobId: next.id,\n kind: input.kind,\n targetId: input.targetId,\n runId: input.runId,\n trigger: input.trigger,\n payload: input.payload ?? {},\n attempt: input.attempt ?? 1,\n maxAttempts: input.maxAttempts ?? 3,\n scheduledAt: input.scheduledAt ?? next.enqueuedAt,\n });\n }\n } finally {\n draining = false;\n }\n }\n\n return {\n async enqueue(input) {\n const id = crypto.randomUUID();\n pending.push({ id, input, enqueuedAt: new Date() });\n\n if (options.sync) {\n await drain();\n }\n\n return id;\n },\n\n async startWorker(nextHandler: JobHandler, _opts: WorkerOptions = {}): Promise<StopFn> {\n handler = nextHandler;\n\n if (!options.sync) {\n void drain();\n }\n\n return async () => {\n handler = undefined;\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport { retryDelayMs };\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { getPgBoss } from \"./pg-boss-client\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { createSharedPgBossJobQueue } from \"./pg-boss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet sharedJobQueue: JobQueue | undefined;\n\nexport async function createSharedPgBossScheduler(): Promise<Scheduler> {\n const jobQueue = await getSharedPgBossJobQueue();\n return wrapJobQueueAsScheduler(jobQueue);\n}\n\nexport async function getSharedPgBossJobQueue(): Promise<JobQueue> {\n if (sharedJobQueue) {\n return sharedJobQueue;\n }\n\n const connectionString = resolvePostgresUrlFromEnv(process.env);\n if (!connectionString) {\n throw new Error(\"Postgres connection string is required for shared pg-boss queue\");\n }\n\n sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });\n return sharedJobQueue;\n}\n\nexport function resetSharedPgBossJobQueueForTests(): void {\n sharedJobQueue = undefined;\n}\n","import type { SchedulerPlugin } from \"./contract\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport {\n createSharedPgBossScheduler,\n resetSharedPgBossJobQueueForTests,\n} from \"./shared-pgboss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet configuredPlugin: SchedulerPlugin | undefined;\nlet sharedJobQueue: JobQueue | undefined;\n\nexport function configureSharedScheduler(plugin: SchedulerPlugin): void {\n configuredPlugin = plugin;\n sharedJobQueue = undefined;\n}\n\nexport async function createSharedScheduler(): Promise<Scheduler> {\n const plugin = configuredPlugin ?? defaultSchedulerPlugin();\n\n if (plugin.name === \"default\") {\n return createSharedPgBossScheduler();\n }\n\n if (!sharedJobQueue) {\n sharedJobQueue = await plugin.createJobQueue({ scope: \"platform\" });\n }\n\n return wrapJobQueueAsScheduler(sharedJobQueue);\n}\n\nexport function resetSharedSchedulerForTests(): void {\n configuredPlugin = undefined;\n sharedJobQueue = undefined;\n resetSharedPgBossJobQueueForTests();\n}\n"],"mappings":";;;;;;;;AAYA,SAAS,kBACP,UACA,KACA,aACiB;CACjB,MAAM,QAAsB;EAC1B,MAAM;EACN,UAAU,IAAI;EACd,OAAO,OAAO,WAAW;EACzB,SAAS,IAAI;EACb,SAAS,CAAC;EACV,WAAW,IAAI;CACjB;CAEA,IAAI,aACF,MAAM,cAAc;CAGtB,OAAO,SAAS,QAAQ,KAAK;AAC/B;AAEA,eAAe,SACb,OACA,MACA,kBACA,OACA;CACA,IAAI,UAAU,gBACZ,QAAA,GAAA,sBAAA,wBAA8B,MAAM,kBAAkB,KAAK;CAG7D,QAAA,GAAA,sBAAA,kBAAwB,MAAM,kBAAkB,KAAK,EAAE,MAAM,SAC3D,KAAK,KAAK,SAAS;EAAE,GAAG;EAAK,YAAA,GAAA,sBAAA,mBAA6B;CAAE,EAAE,CAChE;AACF;AAEA,SAAgB,qBAAqB,KAA4B;CAC/D,MAAM,iBAAiB,IAAI,kBAAkB;CAC7C,MAAM,YAAY,IAAI,aAAa;CAEnC,eAAe,iBAAiB,uBAAO,IAAI,KAAK,GAAoB;EAElE,MAAM,UAAU,MAAM,SADR,IAAI,SAAS,WAGzB,OACC,cAAA,GAAA,qBAAA,kBAA8B,UAAU,IAAI,GAC7C,SACF;EAEA,KAAK,MAAM,OAAO,SAAS;GACzB,IAAI,IAAI,SAAS,UAAU,IAAI,SAAS,QACtC;GAEF,MAAM,kBACJ,IAAI,UACJ;IACE,MAAM,IAAI;IACV,MAAM,IAAI;IACV,WAAW,IAAI;GACjB,GACA,IACF;EACF;EAEA,OAAO,QAAQ;CACjB;CAEA,eAAe,oBAAoB,UAAiC,CAAC,GAAoB;EACvF,MAAM,aAAa,QAAQ,kBAAkB;EAC7C,MAAM,QAAQ,QAAQ,aAAa;EACnC,MAAM,QAAQ,QAAQ,SAAS,IAAI,SAAS;EAC5C,IAAI,UAAU;EAEd,MAAM,OAAO,YAAY;GACvB,OAAO,SAAS;IACd,IAAI;KACF,MAAM,UAAU,MAAM,SACpB,uBACA,IAAI,KAAK,IACR,cAAA,GAAA,qBAAA,kBAA8B,0BAAU,IAAI,KAAK,CAAC,GACnD,KACF;KAEA,KAAK,MAAM,OAAO,SAAS;MACzB,IAAI,IAAI,SAAS,UAAU,IAAI,SAAS,QACtC;MAEF,MAAM,kBAAkB,IAAI,UAAU;OACpC,MAAM,IAAI;OACV,MAAM,IAAI;OACV,WAAW,IAAI;MACjB,CAAC;KACH;IACF,QAAQ,CAER;IAEA,MAAMA,QAAM,UAAU;GACxB;EACF;EAEA,KAAU;EAEV,OAAO,YAAY;GACjB,UAAU;EACZ;CACF;CAEA,OAAO;EAAE;EAAkB;CAAoB;AACjD;AAEA,SAASA,QAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;;ACxHA,SAAgB,+BAGd;CACA,MAAM,UAAU,IAAIC,YAAAA,aAAa;CACjC,OAAO;EACL,MAAM,cAAc,OAAO;GACzB,QAAQ,KAAK,UAAU,KAAK;EAC9B;EAEA,MAAM,gBAAgB,SAAS;GAC7B,MAAM,YAAY,UAAkB;IAClC,QAAa,KAAK;GACpB;GACA,QAAQ,GAAG,UAAU,QAAQ;GAC7B,OAAO,YAAY;IACjB,QAAQ,IAAI,UAAU,QAAQ;GAChC;EACF;CACF;AACF;;;ACXA,SAAS,aAAa,KAA4C;CAChE,OAAO;EACL,OAAO,IAAI;EACX,MAAM,IAAI;EACV,UAAU,IAAI;EACd,OAAO,IAAI;EACX,SAAS,IAAI;EACb,SAAS,IAAI;EACb,SAAS,IAAI;EACb,aAAa,IAAI;EACjB,aAAa,IAAI;CACnB;AACF;AAEA,SAAgB,yBAAmC;CACjD,MAAM,gBAAgB,6BAA6B;CAEnD,OAAO;EACL,MAAM,QAAQ,OAAO;GACnB,QAAA,GAAA,sBAAA,YAAkB,KAAK;EACzB;EAEA,MAAM,YAAY,SAAqB,UAAyB,CAAC,GAAoB;GACnF,MAAM,WAAW,QAAQ,YAAY,OAAO,WAAW;GACvD,MAAM,iBAAiB,QAAQ,kBAAkB;GACjD,MAAM,uBAAuB,QAAQ,wBAAwB;GAC7D,IAAI,UAAU;GAEd,MAAM,aAAa,kBAAkB;IACnC,CAAA,GAAA,sBAAA,sBAA0B;GAC5B,GAAG,oBAAoB;GAEvB,MAAM,OAAO,YAAY;IACvB,OAAO,SACL,IAAI;KACF,MAAM,MAAM,OAAA,GAAA,sBAAA,cAAmB,QAAQ;KACvC,IAAI,CAAC,KAAK;MACR,MAAM,MAAM,cAAc;MAC1B;KACF;KAEA,IAAI;MACF,MAAM,QAAQ,aAAa,GAAG,CAAC;MAC/B,OAAA,GAAA,sBAAA,iBAAsB,IAAI,EAAE;KAC9B,SAAS,OAAO;MACd,IAAI,IAAI,UAAU,IAAI,aACpB,OAAA,GAAA,sBAAA,kBAAuB,IAAI,IAAI,IAAI,UAAU,GAAGC,iBAAAA,aAAa,IAAI,OAAO,CAAC;WACpE;OACL,OAAA,GAAA,sBAAA,eAAoB,IAAI,IAAI,KAAK;OACjC,IAAI,IAAI,SAAS,YACf,OAAA,GAAA,sBAAA,iBAAsB,IAAI,OAAO,KAAK;MAE1C;KACF;IACF,QAAQ;KACN,MAAM,MAAM,cAAc;IAC5B;GAEJ;GAEA,KAAU;GAEV,OAAO,YAAY;IACjB,UAAU;IACV,cAAc,UAAU;GAC1B;EACF;EAEA,gBAAgB,UAAU,cAAc,cAAc,KAAK;EAC3D,kBAAkB,YAAY,cAAc,gBAAgB,OAAO;CACrE;AACF;AAEA,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;ACtFA,IAAI;AAEJ,SAAS,mBAAmB,KAAsB;CAChD,MAAM,WAAW,QAAA,GAAA,sBAAA,2BAAiC,QAAQ,GAAG;CAC7D,IAAI,CAAC,UACH,MAAM,IAAI,MACR,uFACF;CAEF,OAAO;AACT;AAEA,eAAsB,YAAY,KAA+B;CAC/D,IAAI,MACF,OAAO;CAGT,MAAM,OAAO,IAAIC,QAAAA,OAAO;EACtB,kBAAkB,mBAAmB,GAAG;EACxC,QAAQ;CACV,CAAC;CACD,KAAK,GAAG,UAAU,UAAU;EAC1B,QAAQ,MAAM,aAAa,KAAK;CAClC,CAAC;CAED,MAAM,KAAK,MAAM;CACjB,OAAO;CACP,OAAO;AACT;AAEA,SAAgB,YAAoB;CAClC,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,+CAA+C;CAEjE,OAAO;AACT;AAEA,eAAsB,aAA4B;CAChD,IAAI,CAAC,MACH;CAGF,MAAM,KAAK,KAAK;CAChB,OAAO,KAAA;AACT;;;;AC5CA,SAAgB,oBACd,OACA,IACQ;CACR,IAAI,UAAU,YACZ,OAAO;CAGT,IAAI,UAAU,gBAEZ,OAAO,yBADY,MAAM,WAAW,QAAQ,gBAAgB,GAAG,EAAE,YAC1B;CAIzC,OAAO,qBADY,MAAM,WAAW,QAAQ,gBAAgB,GAAG,EAAE,YAC9B;AACrC;;;;ACXA,MAAa,qBAAqB;AAClC,MAAM,gBAAgB;;;;;;;AAQtB,SAAgB,uBAAuB,WAA2B;CAEhE,MAAM,OAAO,GAAG,mBAAmB,GADjB,UAAU,QAAQ,kBAAkB,GACR;CAC9C,IAAI,KAAK,UAAU,IACjB,OAAO;CAET,OAAO,GAAG,mBAAmB,IAAA,GAAA,YAAA,YAAc,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAChG;AAEA,SAAgB,mBAAmB,gBAAgC;CAEjE,MAAM,OAAO,GAAG,mBAAmB,OADjB,eAAe,QAAQ,kBAAkB,GACT;CAClD,IAAI,KAAK,UAAU,IACjB,OAAO;CAET,OAAO,GAAG,mBAAmB,QAAA,GAAA,YAAA,YAAkB,MAAM,EAAE,OAAO,cAAc,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACzG;AA+BA,SAAgB,oBAAoB,MAAc,SAA+C;CAC/F,MAAM,YAAY,QAAQ,aAAA;CAC1B,MAAM,iBAAA,GAAA,sBAAA,6BACJ,QAAQ,kBACR,oBACE,QAAQ,eAAe,YACvB,QAAQ,gBAAgB,iBAAiB,QAAQ,iBAAiB,QAAQ,SAC5E,CACF;CAEA,OAAO;EACL,MAAM,QAAQ,OAAO;GACnB,MAAM,QAAQ,MAAM,KAAK,KACvB,WACA;IACE,GAAG;IACH,SAAS,MAAM;GACjB,GACA;IACE,aAAa,MAAM,eAAe,KAAK;IACvC,YAAY,KAAK,KAAKC,iBAAAA,aAAa,CAAC,IAAI,GAAI;IAC5C,YAAY,MAAM;IAClB,cAAc,MAAM;GACtB,CACF;GAEA,IAAI,CAAC,OAAO;IACV,IAAI,MAAM,WACR,OAAO,MAAM;IAEf,MAAM,IAAI,MAAM,uBAAuB;GACzC;GAEA,OAAO;EACT;EAEA,MAAM,YAAY,SAAsC;GACtD,IAAI,UAAU;GAEd,MAAM,WAAW,MAAM,KAAK,KAC1B,WACA;IAAE,WAAW;IAAG,iBAAiB;GAAK,GACtC,OAAO,SAA2C;IAChD,IAAI,SACF;IAGF,MAAM,MAAM,KAAK;IACjB,IAAI,CAAC,KACH;IAGF,MAAM,OAAO,IAAI;IACjB,MAAM,cAAc,KAAK,gBAAgB,IAAI,cAAc,KAAK;IAChE,MAAM,WAAW,IAAI,cAAc,KAAK;IAExC,MAAM,QAAQ;KACZ,OAAO,IAAI;KACX,MAAM,KAAK;KACX,UAAU,KAAK;KACf,OAAO,KAAK;KACZ,SAAS,KAAK;KACd,SAAS,KAAK,WAAW,CAAC;KAC1B;KACA;KACA,kBAAkB,WAAW;KAC7B,aAAa,KAAK,cAAc,IAAI,KAAK,KAAK,WAAW,oBAAI,IAAI,KAAK;KACtE,WAAW,KAAK;IAClB,CAAC;GACH,CACF;GAEA,OAAO,YAAY;IACjB,UAAU;IACV,MAAM,KAAK,QAAQ,WAAW,EAAE,IAAI,SAAS,CAAC;IAC9C,IAAI,QAAQ,sBACV,MAAM,KAAK,KAAK;KAAE,UAAU;KAAM,SAAS;IAAM,CAAC;GAEtD;EACF;EAEA,gBAAgB,UAAU,cAAc,cAAc,KAAK;EAC3D,kBAAkB,YAAY,cAAc,gBAAgB,OAAO;CACrE;AACF;AAEA,eAAsB,kBAAkB,SAAgD;CACtF,MAAM,YAAY,QAAQ,aAAA;CAC1B,MAAM,OAAO,IAAIC,QAAAA,OAAO;EACtB,kBAAkB,QAAQ;EAC1B,QAAQ;CACV,CAAC;CACD,MAAM,KAAK,MAAM;CACjB,MAAM,KAAK,YAAY,SAAS;CAEhC,OAAO,oBAAoB,MAAM;EAC/B,sBAAsB;EACtB;EACA,kBAAkB,QAAQ;EAC1B,aAAa,QAAQ,eAAe;EACpC,WAAW,QAAQ;EACnB,gBAAgB,QAAQ;CAC1B,CAAC;AACH;AAEA,eAAsB,2BACpB,MACA,SACmB;CACnB,MAAM,YAAY,QAAQ,aAAA;CAC1B,MAAM,KAAK,YAAY,SAAS;CAChC,OAAO,oBAAoB,MAAM;EAC/B;EACA,kBAAkB,QAAQ;EAC1B,aAAa;CACf,CAAC;AACH;;;AC3JA,SAAS,WAAW,KAAqC;CACvD,OAAO,IAAI,QAAA,GAAA,sBAAA,kCAAwC,QAAQ,GAAG,KAAKC,sBAAAA;AACrE;;AAGA,SAAgB,wBAAyC;CACvD,OAAOC,iBAAAA,sBAAsB;EAC3B,MAAM;EACN,MAAM,eAAe,KAAK;GACxB,MAAM,MAAM,WAAW,GAAG;GAE1B,IAAI,IAAI,UAAU,YAAY;IAC5B,MAAM,YAAY,GAAG;IACrB,OAAO,2BAA2B,UAAU,GAAG,EAAE,kBAAkB,IAAI,CAAC;GAC1E;GAEA,IAAI,IAAI,UAAU,gBAAgB;IAChC,MAAM,iBAAiB,IAAI;IAC3B,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,kEAAkE;IAGpF,OAAO,kBAAkB;KACvB,kBAAkB;KAClB,WAAW,mBAAmB,cAAc;KAC5C,aAAa;KACb;IACF,CAAC;GACH;GAEA,MAAM,YAAY,IAAI,cAAA,GAAA,sBAAA,mBAA+B;GACrD,OAAO,kBAAkB;IACvB,kBAAkB;IAClB,WAAW,uBAAuB,SAAS;IAC3C;GACF,CAAC;EACH;CACF,CAAC;AACH;;AAGA,SAAgB,yBAA0C;CACxD,OAAOA,iBAAAA,sBAAsB;EAC3B,MAAM;EACN,MAAM,iBAAiB;GACrB,OAAO,uBAAuB;EAChC;CACF,CAAC;AACH;;AAGA,SAAgB,yBAA0C;CACxD,OAAOA,iBAAAA,sBAAsB;EAC3B,MAAM;EACN,MAAM,eAAe,KAAK;GAIxB,KAAA,GAAA,sBAAA,cAHY,WAAW,GACQ,GAAG,IAAI,OAE5B,MAAM,YACd,OAAO,sBAAsB,EAAE,eAAe,GAAG;GAGnD,OAAO,uBAAuB,EAAE,eAAe,GAAG;EACpD;CACF,CAAC;AACH;;;AClFA,SAAgB,uBACd,aACA,UACA,WACQ;CACR,QAAA,GAAA,qBAAA,qBAA2B,aAAa,UAAU;EAChD,sBAAsB,WAAW;EACjC,6BAA6B,WAAW;CAC1C,CAAC;AACH;;;ACJA,eAAsB,qBAAqB,SAA6C;CACtF,MAAM,sBAAM,IAAI,KAAK;CACrB,MAAM,eAAe,QAAQ,UAAU,KAAK,aAAa,SAAS,WAAW;CAG7E,MAAM,iBAAiB,OAAA,GAAA,sBAAA,4CAAiD;CACxE,MAAM,QAAQ,CAAC,GAAG,cAAc,GAAG,cAAc;CAEjD,IAAI,MAAM,WAAW,GAAG;EACtB,OAAA,GAAA,sBAAA,4BAAiC,GAAG;EACpC;CACF;CAEA,OAAA,GAAA,sBAAA,mCAAwC,OAAO,GAAG;CAElD,KAAK,MAAM,QAAQ,QAAQ,WAAW;EACpC,MAAM,WAAW,uBACf,KAAK,aACL,KAAK,UACL,QAAQ,iBACV;EACA,MAAM,WAAW,OAAA,GAAA,sBAAA,qBAA0B,KAAK,WAAW;EAC3D,IAAI,CAAC,UACH;EAIF,MAAM,YACJ,EAFsB,SAAS,aAAa,aAExB,SAAS,YAAY,IACrC,SAAS,aAAA,GAAA,qBAAA,kBACQ,UAAU,GAAG;EAGpC,MAAM,WAAU,OAAA,GAAA,sBAAA,uCAD+C,CAAC,SAAS,EAAE,CAAC,GACjD,SAAS;EAEpC,OAAA,GAAA,sBAAA,6BAAkC;GAChC,aAAa,KAAK;GAClB;GACA,WAAW,aAAa;GACxB;GACA,WAAW;EACb,CAAC;CACH;AACF;;;AC1CA,eAAe,yBAAyB,UAAiC,CAAC,GAAsB;CAC9F,IAAI,QAAQ,SACV,OAAO,QAAQ;CAIjB,QADe,QAAQ,UAAU,uBAAuB,GAC1C,eAAe;EAC3B,OAAO,QAAQ,SAAS;EACxB,KAAK,QAAQ;EACb,SAAS,QAAQ;EACjB,WAAW,QAAQ;EACnB,gBAAgB,QAAQ;CAC1B,CAAC;AACH;AAEA,SAAS,cAAc,UAAoB,OAA+C;CACxF,MAAM,SAAS,qBAAqB;EAAE;EAAU;CAAM,CAAC;CAEvD,OAAO;EACL,UAAU,UAAU,SAAS,QAAQ,KAAK;EAC1C,cAAc,SAAS,YAAY,SAAS,YAAY,SAAS,OAAO;EACxE,gBAAgB,UAAU,SAAS,cAAc,KAAK;EACtD,kBAAkB,YAAY,SAAS,gBAAgB,OAAO;EAC9D,uBAAuB,YAAiCC,qBAAwB,OAAO;EACvF,sBAAsB,YAAoC,OAAO,oBAAoB,OAAO;EAC5F,mBAAmB,SAAgB,OAAO,iBAAiB,IAAI;CACjE;AACF;AAEA,eAAsB,gBAAgB,UAAkC,CAAC,GAAuB;CAG9F,OAAO,cAAc,MAFE,yBAAyB,OAAO,GACzC,QAAQ,UAAU,iBAAiB,iBAAiB,SAC9B;AACtC;AAEA,eAAsB,eAAe,UAAiC,CAAC,GAAsB;CAC3F,OAAO,yBAAyB,OAAO;AACzC;AAEA,SAAgB,wBACd,UACA,OACW;CACX,OAAO,cAAc,UAAU,KAAK;AACtC;;;ACjDA,SAAgB,qBAAqB,UAAiC,CAAC,GAAa;CAClF,MAAM,gBAAgB,6BAA6B;CACnD,MAAM,UAAwE,CAAC;CAC/E,IAAI;CACJ,IAAI,WAAW;CAEf,eAAe,QAAuB;EACpC,IAAI,CAAC,WAAW,UACd;EAGF,WAAW;EACX,IAAI;GACF,OAAO,QAAQ,SAAS,GAAG;IACzB,MAAM,OAAO,QAAQ,MAAM;IAC3B,IAAI,CAAC,MACH;IAGF,MAAM,QAAQ,KAAK;IACnB,MAAM,QAAQ;KACZ,OAAO,KAAK;KACZ,MAAM,MAAM;KACZ,UAAU,MAAM;KAChB,OAAO,MAAM;KACb,SAAS,MAAM;KACf,SAAS,MAAM,WAAW,CAAC;KAC3B,SAAS,MAAM,WAAW;KAC1B,aAAa,MAAM,eAAe;KAClC,aAAa,MAAM,eAAe,KAAK;IACzC,CAAC;GACH;EACF,UAAU;GACR,WAAW;EACb;CACF;CAEA,OAAO;EACL,MAAM,QAAQ,OAAO;GACnB,MAAM,KAAK,OAAO,WAAW;GAC7B,QAAQ,KAAK;IAAE;IAAI;IAAO,4BAAY,IAAI,KAAK;GAAE,CAAC;GAElD,IAAI,QAAQ,MACV,MAAM,MAAM;GAGd,OAAO;EACT;EAEA,MAAM,YAAY,aAAyB,QAAuB,CAAC,GAAoB;GACrF,UAAU;GAEV,IAAI,CAAC,QAAQ,MACX,MAAW;GAGb,OAAO,YAAY;IACjB,UAAU,KAAA;GACZ;EACF;EAEA,gBAAgB,UAAU,cAAc,cAAc,KAAK;EAC3D,kBAAkB,YAAY,cAAc,gBAAgB,OAAO;CACrE;AACF;;;AClEA,IAAIC;AAEJ,eAAsB,8BAAkD;CAEtE,OAAO,wBAAwB,MADR,wBAAwB,CACR;AACzC;AAEA,eAAsB,0BAA6C;CACjE,IAAIA,kBACF,OAAOA;CAGT,MAAM,oBAAA,GAAA,sBAAA,2BAA6C,QAAQ,GAAG;CAC9D,IAAI,CAAC,kBACH,MAAM,IAAI,MAAM,iEAAiE;CAGnF,mBAAiB,MAAM,2BAA2B,UAAU,GAAG,EAAE,iBAAiB,CAAC;CACnF,OAAOA;AACT;AAEA,SAAgB,oCAA0C;CACxD,mBAAiB,KAAA;AACnB;;;ACpBA,IAAI;AACJ,IAAI;AAEJ,SAAgB,yBAAyB,QAA+B;CACtE,mBAAmB;CACnB,iBAAiB,KAAA;AACnB;AAEA,eAAsB,wBAA4C;CAChE,MAAM,SAAS,oBAAoB,uBAAuB;CAE1D,IAAI,OAAO,SAAS,WAClB,OAAO,4BAA4B;CAGrC,IAAI,CAAC,gBACH,iBAAiB,MAAM,OAAO,eAAe,EAAE,OAAO,WAAW,CAAC;CAGpE,OAAO,wBAAwB,cAAc;AAC/C;AAEA,SAAgB,+BAAqC;CACnD,mBAAmB,KAAA;CACnB,iBAAiB,KAAA;CACjB,kCAAkC;AACpC"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-
|
|
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-BGjcxdxT.cjs";
|
|
2
2
|
import { PgBoss } from "pg-boss";
|
|
3
3
|
|
|
4
4
|
//#region src/create-scheduler.d.ts
|
|
5
5
|
declare function createScheduler(options?: CreateSchedulerOptions): Promise<Scheduler>;
|
|
6
6
|
declare function createJobQueue(options?: CreateJobQueueOptions): Promise<JobQueue>;
|
|
7
|
-
declare function wrapJobQueueAsScheduler(jobQueue: JobQueue): Scheduler;
|
|
7
|
+
declare function wrapJobQueueAsScheduler(jobQueue: JobQueue, scope?: "project" | "organization"): Scheduler;
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/memory.d.ts
|
|
10
10
|
type MemoryJobQueueOptions = {
|
|
@@ -18,7 +18,7 @@ declare function createSharedScheduler(): Promise<Scheduler>;
|
|
|
18
18
|
declare function resetSharedSchedulerForTests(): void;
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/plugin.d.ts
|
|
21
|
-
/** Postgres pg-boss backend. Platform scope shares one queue;
|
|
21
|
+
/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */
|
|
22
22
|
declare function pgBossSchedulerPlugin(): SchedulerPlugin;
|
|
23
23
|
/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */
|
|
24
24
|
declare function pollingSchedulerPlugin(): SchedulerPlugin;
|
|
@@ -49,8 +49,9 @@ type BuildPgBossJobQueueOptions = {
|
|
|
49
49
|
connectionString: string;
|
|
50
50
|
stopBossOnWorkerStop?: boolean;
|
|
51
51
|
queueName?: string;
|
|
52
|
-
cancelScope?: "platform" | "project";
|
|
52
|
+
cancelScope?: "platform" | "project" | "organization";
|
|
53
53
|
projectId?: string;
|
|
54
|
+
organizationId?: string;
|
|
54
55
|
};
|
|
55
56
|
declare function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue;
|
|
56
57
|
declare function createSharedPgBossJobQueue(boss: PgBoss, options: {
|
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/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,
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/create-scheduler.ts","../src/memory.ts","../src/shared-scheduler.ts","../src/plugin.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,iBAM/D,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,OAAA,CAAQ,QAAA;AAAA,iBAInE,uBAAA,CACd,QAAA,EAAU,QAAA,EACV,KAAA,gCACC,SAAS;;;KCnDA,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;;;;iBCFA,qBAAA,CAAA,GAAyB,eAAe;;iBAoCxC,sBAAA,CAAA,GAA0B,eAAe;;iBAUzC,sBAAA,CAAA,GAA0B,eAAe;;;iBC5DnC,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;ANmCb;;;;;;AAAA,iBM1BgB,sBAAA,CAAuB,SAAiB;AAAA,KAsCnD,0BAAA;EACH,gBAAA;EACA,oBAAA;EACA,SAAA;EACA,WAAA;EACA,SAAA;EACA,cAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,0BAAA,GAA6B,QAAA;AAAA,iBAyGlE,0BAAA,CACpB,IAAA,EAAM,MAAA,EACN,OAAA;EAAW,gBAAA;EAA0B,SAAA;AAAA,IACpC,OAAA,CAAQ,QAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-
|
|
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-BGjcxdxT.mjs";
|
|
2
2
|
import { PgBoss } from "pg-boss";
|
|
3
3
|
|
|
4
4
|
//#region src/create-scheduler.d.ts
|
|
5
5
|
declare function createScheduler(options?: CreateSchedulerOptions): Promise<Scheduler>;
|
|
6
6
|
declare function createJobQueue(options?: CreateJobQueueOptions): Promise<JobQueue>;
|
|
7
|
-
declare function wrapJobQueueAsScheduler(jobQueue: JobQueue): Scheduler;
|
|
7
|
+
declare function wrapJobQueueAsScheduler(jobQueue: JobQueue, scope?: "project" | "organization"): Scheduler;
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/memory.d.ts
|
|
10
10
|
type MemoryJobQueueOptions = {
|
|
@@ -18,7 +18,7 @@ declare function createSharedScheduler(): Promise<Scheduler>;
|
|
|
18
18
|
declare function resetSharedSchedulerForTests(): void;
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/plugin.d.ts
|
|
21
|
-
/** Postgres pg-boss backend. Platform scope shares one queue;
|
|
21
|
+
/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */
|
|
22
22
|
declare function pgBossSchedulerPlugin(): SchedulerPlugin;
|
|
23
23
|
/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */
|
|
24
24
|
declare function pollingSchedulerPlugin(): SchedulerPlugin;
|
|
@@ -49,8 +49,9 @@ type BuildPgBossJobQueueOptions = {
|
|
|
49
49
|
connectionString: string;
|
|
50
50
|
stopBossOnWorkerStop?: boolean;
|
|
51
51
|
queueName?: string;
|
|
52
|
-
cancelScope?: "platform" | "project";
|
|
52
|
+
cancelScope?: "platform" | "project" | "organization";
|
|
53
53
|
projectId?: string;
|
|
54
|
+
organizationId?: string;
|
|
54
55
|
};
|
|
55
56
|
declare function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue;
|
|
56
57
|
declare function createSharedPgBossJobQueue(boss: PgBoss, options: {
|
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/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,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/create-scheduler.ts","../src/memory.ts","../src/shared-scheduler.ts","../src/plugin.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,iBAM/D,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,OAAA,CAAQ,QAAA;AAAA,iBAInE,uBAAA,CACd,QAAA,EAAU,QAAA,EACV,KAAA,gCACC,SAAS;;;KCnDA,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;;;;iBCFA,qBAAA,CAAA,GAAyB,eAAe;;iBAoCxC,sBAAA,CAAA,GAA0B,eAAe;;iBAUzC,sBAAA,CAAA,GAA0B,eAAe;;;iBC5DnC,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;ANmCb;;;;;;AAAA,iBM1BgB,sBAAA,CAAuB,SAAiB;AAAA,KAsCnD,0BAAA;EACH,gBAAA;EACA,oBAAA;EACA,SAAA;EACA,WAAA;EACA,SAAA;EACA,cAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,0BAAA,GAA6B,QAAA;AAAA,iBAyGlE,0BAAA,CACpB,IAAA,EAAM,MAAA,EACN,OAAA;EAAW,gBAAA;EAA0B,SAAA;AAAA,IACpC,OAAA,CAAQ,QAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,41 +1,61 @@
|
|
|
1
1
|
import { n as DEFAULT_RETRY_DELAY_MS, r as retryDelayMs, t as defineSchedulerPlugin } from "./contract-E1QJBH6_.mjs";
|
|
2
|
-
import { DEFAULT_DATABASE_URL,
|
|
2
|
+
import { DEFAULT_DATABASE_URL, claimDueTriggers, claimDueTriggersForOrg, claimNextJob, createPostgresCancelChannel as createPgCancelChannel, disableAllTriggerSchedules, disableTriggerSchedulesNotInSlugs, enqueueJob, failWorkflowRun, getProjectScopeId, inferDialect, listExecutableAttachmentsByTriggerIds, markJobComplete, markJobFailed, requeueExpiredLeases, resolvePostgresUrlFromEnv, resolveProjectDatabaseUrlFromEnv, scheduleJobRetry, selectActiveEphemeralScheduledTriggerSlugs, selectTriggerBySlug, upsertTriggerScheduleFields } from "@keystrokehq/database";
|
|
3
3
|
import { nextTriggerRunAt, resolveCronSchedule } from "@keystrokehq/trigger";
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
5
5
|
import { PgBoss } from "pg-boss";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
//#region src/schedule-ticker.ts
|
|
8
|
+
function enqueueTriggerJob(jobQueue, row, scheduledAt) {
|
|
9
|
+
const input = {
|
|
10
|
+
kind: "trigger",
|
|
11
|
+
targetId: row.slug,
|
|
12
|
+
runId: crypto.randomUUID(),
|
|
13
|
+
trigger: row.kind,
|
|
14
|
+
payload: {},
|
|
15
|
+
projectId: row.projectId
|
|
16
|
+
};
|
|
17
|
+
if (scheduledAt) input.scheduledAt = scheduledAt;
|
|
18
|
+
return jobQueue.enqueue(input);
|
|
19
|
+
}
|
|
20
|
+
async function claimDue(scope, asOf, resolveNextRunAt, limit) {
|
|
21
|
+
if (scope === "organization") return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);
|
|
22
|
+
return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) => rows.map((row) => ({
|
|
23
|
+
...row,
|
|
24
|
+
projectId: getProjectScopeId()
|
|
25
|
+
})));
|
|
26
|
+
}
|
|
8
27
|
function createScheduleTicker(ctx) {
|
|
9
28
|
const pollIntervalMs = ctx.pollIntervalMs ?? 1e3;
|
|
10
29
|
const batchSize = ctx.batchSize ?? 10;
|
|
11
30
|
async function fireDueSchedules(asOf = /* @__PURE__ */ new Date()) {
|
|
12
|
-
const claimed = await
|
|
13
|
-
for (const row of claimed)
|
|
14
|
-
kind
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
31
|
+
const claimed = await claimDue(ctx.scope ?? "project", asOf, (schedule) => nextTriggerRunAt(schedule, asOf), batchSize);
|
|
32
|
+
for (const row of claimed) {
|
|
33
|
+
if (row.kind !== "cron" && row.kind !== "poll") continue;
|
|
34
|
+
await enqueueTriggerJob(ctx.jobQueue, {
|
|
35
|
+
slug: row.slug,
|
|
36
|
+
kind: row.kind,
|
|
37
|
+
projectId: row.projectId
|
|
38
|
+
}, asOf);
|
|
39
|
+
}
|
|
21
40
|
return claimed.length;
|
|
22
41
|
}
|
|
23
42
|
async function startScheduleTicker(options = {}) {
|
|
24
43
|
const intervalMs = options.pollIntervalMs ?? pollIntervalMs;
|
|
25
44
|
const limit = options.batchSize ?? batchSize;
|
|
45
|
+
const scope = options.scope ?? ctx.scope ?? "project";
|
|
26
46
|
let running = true;
|
|
27
47
|
const loop = async () => {
|
|
28
48
|
while (running) {
|
|
29
49
|
try {
|
|
30
|
-
await
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
const claimed = await claimDue(scope, /* @__PURE__ */ new Date(), (schedule) => nextTriggerRunAt(schedule, /* @__PURE__ */ new Date()), limit);
|
|
51
|
+
for (const row of claimed) {
|
|
52
|
+
if (row.kind !== "cron" && row.kind !== "poll") continue;
|
|
53
|
+
await enqueueTriggerJob(ctx.jobQueue, {
|
|
54
|
+
slug: row.slug,
|
|
55
|
+
kind: row.kind,
|
|
56
|
+
projectId: row.projectId
|
|
37
57
|
});
|
|
38
|
-
}
|
|
58
|
+
}
|
|
39
59
|
} catch {}
|
|
40
60
|
await sleep$1(intervalMs);
|
|
41
61
|
}
|
|
@@ -169,9 +189,10 @@ async function stopPgBoss() {
|
|
|
169
189
|
//#endregion
|
|
170
190
|
//#region src/pg-cancel-channel.ts
|
|
171
191
|
/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */
|
|
172
|
-
function pgCancelChannelName(scope,
|
|
192
|
+
function pgCancelChannelName(scope, id) {
|
|
173
193
|
if (scope === "platform") return "keystroke_cancel_platform";
|
|
174
|
-
return `
|
|
194
|
+
if (scope === "organization") return `keystroke_cancel_org_${(id ?? "default").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
|
|
195
|
+
return `keystroke_cancel_${(id ?? "default").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
|
|
175
196
|
}
|
|
176
197
|
//#endregion
|
|
177
198
|
//#region src/pg-boss-queue.ts
|
|
@@ -189,12 +210,20 @@ function pgBossProjectQueueName(projectId) {
|
|
|
189
210
|
if (name.length <= 50) return name;
|
|
190
211
|
return `${DEFAULT_QUEUE_NAME}_${createHash("sha1").update(projectId).digest("hex").slice(0, 40)}`;
|
|
191
212
|
}
|
|
213
|
+
function pgBossOrgQueueName(organizationId) {
|
|
214
|
+
const name = `${DEFAULT_QUEUE_NAME}_org_${organizationId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
215
|
+
if (name.length <= 50) return name;
|
|
216
|
+
return `${DEFAULT_QUEUE_NAME}_org_${createHash("sha1").update(organizationId).digest("hex").slice(0, 36)}`;
|
|
217
|
+
}
|
|
192
218
|
function buildPgBossJobQueue(boss, options) {
|
|
193
219
|
const queueName = options.queueName ?? "keystroke";
|
|
194
|
-
const cancelChannel = createPgCancelChannel(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.projectId));
|
|
220
|
+
const cancelChannel = createPgCancelChannel(options.connectionString, pgCancelChannelName(options.cancelScope ?? "platform", options.cancelScope === "organization" ? options.organizationId : options.projectId));
|
|
195
221
|
return {
|
|
196
222
|
async enqueue(input) {
|
|
197
|
-
const jobId = await boss.send(queueName,
|
|
223
|
+
const jobId = await boss.send(queueName, {
|
|
224
|
+
...input,
|
|
225
|
+
payload: input.payload
|
|
226
|
+
}, {
|
|
198
227
|
retryLimit: (input.maxAttempts ?? 3) - 1,
|
|
199
228
|
retryDelay: Math.ceil(retryDelayMs(1) / 1e3),
|
|
200
229
|
startAfter: input.scheduledAt,
|
|
@@ -228,7 +257,8 @@ function buildPgBossJobQueue(boss, options) {
|
|
|
228
257
|
attempt,
|
|
229
258
|
maxAttempts,
|
|
230
259
|
exhaustedRetries: attempt >= maxAttempts,
|
|
231
|
-
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : /* @__PURE__ */ new Date()
|
|
260
|
+
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : /* @__PURE__ */ new Date(),
|
|
261
|
+
projectId: data.projectId
|
|
232
262
|
});
|
|
233
263
|
});
|
|
234
264
|
return async () => {
|
|
@@ -256,8 +286,9 @@ async function createPgBossQueue(options) {
|
|
|
256
286
|
stopBossOnWorkerStop: true,
|
|
257
287
|
queueName,
|
|
258
288
|
connectionString: options.connectionString,
|
|
259
|
-
cancelScope: "project",
|
|
260
|
-
projectId: options.projectId
|
|
289
|
+
cancelScope: options.cancelScope ?? "project",
|
|
290
|
+
projectId: options.projectId,
|
|
291
|
+
organizationId: options.organizationId
|
|
261
292
|
});
|
|
262
293
|
}
|
|
263
294
|
async function createSharedPgBossJobQueue(boss, options) {
|
|
@@ -274,7 +305,7 @@ async function createSharedPgBossJobQueue(boss, options) {
|
|
|
274
305
|
function resolveUrl(ctx) {
|
|
275
306
|
return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;
|
|
276
307
|
}
|
|
277
|
-
/** Postgres pg-boss backend. Platform scope shares one queue;
|
|
308
|
+
/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */
|
|
278
309
|
function pgBossSchedulerPlugin() {
|
|
279
310
|
return defineSchedulerPlugin({
|
|
280
311
|
name: "pg-boss",
|
|
@@ -284,6 +315,16 @@ function pgBossSchedulerPlugin() {
|
|
|
284
315
|
await startPgBoss(url);
|
|
285
316
|
return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });
|
|
286
317
|
}
|
|
318
|
+
if (ctx.scope === "organization") {
|
|
319
|
+
const organizationId = ctx.organizationId;
|
|
320
|
+
if (!organizationId) throw new Error("organizationId is required for organization-scoped pg-boss queue");
|
|
321
|
+
return createPgBossQueue({
|
|
322
|
+
connectionString: url,
|
|
323
|
+
queueName: pgBossOrgQueueName(organizationId),
|
|
324
|
+
cancelScope: "organization",
|
|
325
|
+
organizationId
|
|
326
|
+
});
|
|
327
|
+
}
|
|
287
328
|
const projectId = ctx.projectId ?? getProjectScopeId();
|
|
288
329
|
return createPgBossQueue({
|
|
289
330
|
connectionString: url,
|
|
@@ -314,18 +355,18 @@ function defaultSchedulerPlugin() {
|
|
|
314
355
|
}
|
|
315
356
|
//#endregion
|
|
316
357
|
//#region src/resolve-schedule.ts
|
|
317
|
-
function resolveTriggerSchedule(
|
|
318
|
-
return resolveCronSchedule(
|
|
358
|
+
function resolveTriggerSchedule(triggerSlug, schedule, overrides) {
|
|
359
|
+
return resolveCronSchedule(triggerSlug, schedule, {
|
|
319
360
|
cronScheduleOverride: overrides?.global,
|
|
320
|
-
attachmentScheduleOverrides: overrides?.
|
|
361
|
+
attachmentScheduleOverrides: overrides?.byTrigger
|
|
321
362
|
});
|
|
322
363
|
}
|
|
323
364
|
//#endregion
|
|
324
365
|
//#region src/sync-trigger-schedules.ts
|
|
325
366
|
async function syncTriggerSchedules(options) {
|
|
326
367
|
const now = /* @__PURE__ */ new Date();
|
|
327
|
-
const projectSlugs = options.schedules.map((schedule) => schedule.
|
|
328
|
-
const ephemeralSlugs = await
|
|
368
|
+
const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);
|
|
369
|
+
const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();
|
|
329
370
|
const slugs = [...projectSlugs, ...ephemeralSlugs];
|
|
330
371
|
if (slugs.length === 0) {
|
|
331
372
|
await disableAllTriggerSchedules(now);
|
|
@@ -333,16 +374,16 @@ async function syncTriggerSchedules(options) {
|
|
|
333
374
|
}
|
|
334
375
|
await disableTriggerSchedulesNotInSlugs(slugs, now);
|
|
335
376
|
for (const spec of options.schedules) {
|
|
336
|
-
const schedule = resolveTriggerSchedule(spec.
|
|
337
|
-
const existing = await
|
|
338
|
-
|
|
339
|
-
const nextRunAt = existing
|
|
340
|
-
await
|
|
341
|
-
|
|
342
|
-
|
|
377
|
+
const schedule = resolveTriggerSchedule(spec.triggerSlug, spec.schedule, options.scheduleOverrides);
|
|
378
|
+
const existing = await selectTriggerBySlug(spec.triggerSlug);
|
|
379
|
+
if (!existing) continue;
|
|
380
|
+
const nextRunAt = !(existing.schedule !== schedule) && existing.enabled === 1 ? existing.nextRunAt : nextTriggerRunAt(schedule, now);
|
|
381
|
+
const enabled = (await listExecutableAttachmentsByTriggerIds([existing.id])).length > 0;
|
|
382
|
+
await upsertTriggerScheduleFields({
|
|
383
|
+
triggerSlug: spec.triggerSlug,
|
|
343
384
|
schedule,
|
|
344
|
-
nextRunAt,
|
|
345
|
-
enabled
|
|
385
|
+
nextRunAt: nextRunAt ?? now,
|
|
386
|
+
enabled,
|
|
346
387
|
updatedAt: now
|
|
347
388
|
});
|
|
348
389
|
}
|
|
@@ -359,8 +400,11 @@ async function createUnderlyingJobQueue(options = {}) {
|
|
|
359
400
|
organizationId: options.organizationId
|
|
360
401
|
});
|
|
361
402
|
}
|
|
362
|
-
function wrapScheduler(jobQueue) {
|
|
363
|
-
const ticker = createScheduleTicker({
|
|
403
|
+
function wrapScheduler(jobQueue, scope) {
|
|
404
|
+
const ticker = createScheduleTicker({
|
|
405
|
+
jobQueue,
|
|
406
|
+
scope
|
|
407
|
+
});
|
|
364
408
|
return {
|
|
365
409
|
enqueue: (input) => jobQueue.enqueue(input),
|
|
366
410
|
startWorker: (handler, options) => jobQueue.startWorker(handler, options),
|
|
@@ -372,13 +416,13 @@ function wrapScheduler(jobQueue) {
|
|
|
372
416
|
};
|
|
373
417
|
}
|
|
374
418
|
async function createScheduler(options = {}) {
|
|
375
|
-
return wrapScheduler(await createUnderlyingJobQueue(options));
|
|
419
|
+
return wrapScheduler(await createUnderlyingJobQueue(options), options.scope === "organization" ? "organization" : "project");
|
|
376
420
|
}
|
|
377
421
|
async function createJobQueue(options = {}) {
|
|
378
422
|
return createUnderlyingJobQueue(options);
|
|
379
423
|
}
|
|
380
|
-
function wrapJobQueueAsScheduler(jobQueue) {
|
|
381
|
-
return wrapScheduler(jobQueue);
|
|
424
|
+
function wrapJobQueueAsScheduler(jobQueue, scope) {
|
|
425
|
+
return wrapScheduler(jobQueue, scope);
|
|
382
426
|
}
|
|
383
427
|
//#endregion
|
|
384
428
|
//#region src/memory.ts
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["sleep","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { 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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["sleep","syncTriggerScheduleRows","sharedJobQueue"],"sources":["../src/schedule-ticker.ts","../src/cancel-channel.ts","../src/database-queue.ts","../src/pg-boss-client.ts","../src/pg-cancel-channel.ts","../src/pg-boss-queue.ts","../src/plugin.ts","../src/resolve-schedule.ts","../src/sync-trigger-schedules.ts","../src/create-scheduler.ts","../src/memory.ts","../src/shared-pgboss-queue.ts","../src/shared-scheduler.ts"],"sourcesContent":["import { claimDueTriggers, claimDueTriggersForOrg } from \"@keystrokehq/database\";\nimport { getProjectScopeId } from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { EnqueueInput, JobQueue, ScheduleTickerOptions, StopFn } from \"./types\";\n\nexport type ScheduleTickerContext = {\n jobQueue: JobQueue;\n pollIntervalMs?: number;\n batchSize?: number;\n scope?: \"project\" | \"organization\";\n};\n\nfunction enqueueTriggerJob(\n jobQueue: JobQueue,\n row: { slug: string; kind: \"cron\" | \"poll\"; projectId: string },\n scheduledAt?: Date,\n): Promise<string> {\n const input: EnqueueInput = {\n kind: \"trigger\",\n targetId: row.slug,\n runId: crypto.randomUUID(),\n trigger: row.kind,\n payload: {},\n projectId: row.projectId,\n };\n\n if (scheduledAt) {\n input.scheduledAt = scheduledAt;\n }\n\n return jobQueue.enqueue(input);\n}\n\nasync function claimDue(\n scope: \"project\" | \"organization\",\n asOf: Date,\n resolveNextRunAt: (schedule: string) => Date,\n limit: number,\n) {\n if (scope === \"organization\") {\n return claimDueTriggersForOrg(asOf, resolveNextRunAt, limit);\n }\n\n return claimDueTriggers(asOf, resolveNextRunAt, limit).then((rows) =>\n rows.map((row) => ({ ...row, projectId: getProjectScopeId() })),\n );\n}\n\nexport function createScheduleTicker(ctx: ScheduleTickerContext) {\n const pollIntervalMs = ctx.pollIntervalMs ?? 1_000;\n const batchSize = ctx.batchSize ?? 10;\n\n async function fireDueSchedules(asOf = new Date()): Promise<number> {\n const scope = ctx.scope ?? \"project\";\n const claimed = await claimDue(\n scope,\n asOf,\n (schedule) => nextTriggerRunAt(schedule, asOf),\n batchSize,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(\n ctx.jobQueue,\n {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n },\n asOf,\n );\n }\n\n return claimed.length;\n }\n\n async function startScheduleTicker(options: ScheduleTickerOptions = {}): Promise<StopFn> {\n const intervalMs = options.pollIntervalMs ?? pollIntervalMs;\n const limit = options.batchSize ?? batchSize;\n const scope = options.scope ?? ctx.scope ?? \"project\";\n let running = true;\n\n const loop = async () => {\n while (running) {\n try {\n const claimed = await claimDue(\n scope,\n new Date(),\n (schedule) => nextTriggerRunAt(schedule, new Date()),\n limit,\n );\n\n for (const row of claimed) {\n if (row.kind !== \"cron\" && row.kind !== \"poll\") {\n continue;\n }\n await enqueueTriggerJob(ctx.jobQueue, {\n slug: row.slug,\n kind: row.kind,\n projectId: row.projectId,\n });\n }\n } catch {\n // keep ticking\n }\n\n await sleep(intervalMs);\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n };\n }\n\n return { fireDueSchedules, startScheduleTicker };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { EventEmitter } from \"node:events\";\n\nimport type { CancelHandler, StopFn } from \"./types\";\n\n/** In-process cancel pub/sub for single-process queues (memory, db polling). */\nexport function createInProcessCancelChannel(): {\n publishCancel(runId: string): Promise<void>;\n subscribeCancel(handler: CancelHandler): Promise<StopFn>;\n} {\n const emitter = new EventEmitter();\n return {\n async publishCancel(runId) {\n emitter.emit(\"cancel\", runId);\n },\n\n async subscribeCancel(handler) {\n const listener = (runId: string) => {\n void handler(runId);\n };\n emitter.on(\"cancel\", listener);\n return async () => {\n emitter.off(\"cancel\", listener);\n };\n },\n };\n}\n","import {\n claimNextJob,\n enqueueJob,\n failWorkflowRun,\n markJobComplete,\n markJobFailed,\n requeueExpiredLeases,\n scheduleJobRetry,\n} from \"@keystrokehq/database\";\nimport type { ClaimedJob } from \"@keystrokehq/database\";\nimport { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nfunction toJobPayload(job: ClaimedJob): Parameters<JobHandler>[0] {\n return {\n jobId: job.id,\n kind: job.kind,\n targetId: job.targetId,\n runId: job.runId,\n trigger: job.trigger,\n payload: job.payload,\n attempt: job.attempt,\n maxAttempts: job.maxAttempts,\n scheduledAt: job.scheduledAt,\n };\n}\n\nexport function createDatabaseJobQueue(): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n\n return {\n async enqueue(input) {\n return enqueueJob(input);\n },\n\n async startWorker(handler: JobHandler, options: WorkerOptions = {}): Promise<StopFn> {\n const workerId = options.workerId ?? crypto.randomUUID();\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n const leaseSweepIntervalMs = options.leaseSweepIntervalMs ?? 30_000;\n let running = true;\n\n const leaseTimer = setInterval(() => {\n void requeueExpiredLeases();\n }, leaseSweepIntervalMs);\n\n const loop = async () => {\n while (running) {\n try {\n const job = await claimNextJob(workerId);\n if (!job) {\n await sleep(pollIntervalMs);\n continue;\n }\n\n try {\n await handler(toJobPayload(job));\n await markJobComplete(job.id);\n } catch (error) {\n if (job.attempt < job.maxAttempts) {\n await scheduleJobRetry(job.id, job.attempt + 1, retryDelayMs(job.attempt));\n } else {\n await markJobFailed(job.id, error);\n if (job.kind === \"workflow\") {\n await failWorkflowRun(job.runId, error);\n }\n }\n }\n } catch {\n await sleep(pollIntervalMs);\n }\n }\n };\n\n void loop();\n\n return async () => {\n running = false;\n clearInterval(leaseTimer);\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { PgBoss } from \"pg-boss\";\n\nlet boss: PgBoss | undefined;\n\nfunction resolveDatabaseUrl(url?: string): string {\n const resolved = url ?? resolvePostgresUrlFromEnv(process.env);\n if (!resolved) {\n throw new Error(\n \"DATABASE_URL or POSTGRES_HOST/POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB is required\",\n );\n }\n return resolved;\n}\n\nexport async function startPgBoss(url?: string): Promise<PgBoss> {\n if (boss) {\n return boss;\n }\n\n const next = new PgBoss({\n connectionString: resolveDatabaseUrl(url),\n schema: \"pgboss\",\n });\n next.on(\"error\", (error) => {\n console.error(\"[pg-boss]\", error);\n });\n\n await next.start();\n boss = next;\n return next;\n}\n\nexport function getPgBoss(): PgBoss {\n if (!boss) {\n throw new Error(\"PgBoss not started. Call startPgBoss() first.\");\n }\n return boss;\n}\n\nexport async function stopPgBoss(): Promise<void> {\n if (!boss) {\n return;\n }\n\n await boss.stop();\n boss = undefined;\n}\n","export { createPostgresCancelChannel as createPgCancelChannel } from \"@keystrokehq/database\";\n\n/** Postgres NOTIFY channel — lowercase identifier (project ids are UUIDs, well under 63 chars). */\nexport function pgCancelChannelName(\n scope: \"platform\" | \"project\" | \"organization\",\n id?: string,\n): string {\n if (scope === \"platform\") {\n return \"keystroke_cancel_platform\";\n }\n\n if (scope === \"organization\") {\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_org_${sanitized}`;\n }\n\n const sanitized = (id ?? \"default\").replace(/[^a-z0-9_]/gi, \"_\").toLowerCase();\n return `keystroke_cancel_${sanitized}`;\n}\n","import { createHash } from \"node:crypto\";\nimport { PgBoss, type JobWithMetadata } from \"pg-boss\";\nimport { createPgCancelChannel, pgCancelChannelName } from \"./pg-cancel-channel\";\nimport type { JobHandler, JobQueue, StopFn } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\n/** Shared queue name for the platform control-plane scope. */\nexport const DEFAULT_QUEUE_NAME = \"keystroke\";\nconst PGBOSS_SCHEMA = \"pgboss\";\n\n/**\n * Derive a per-project pg-boss queue name. pg-boss queue names must be <= 50\n * chars, contain only [A-Za-z0-9_], and not start with a digit. Project ids are\n * usually UUIDs (hyphens, may start with a digit), so we sanitize, and fall back\n * to a hash when the sanitized name would exceed the length limit.\n */\nexport function pgBossProjectQueueName(projectId: string): string {\n const sanitized = projectId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_${createHash(\"sha1\").update(projectId).digest(\"hex\").slice(0, 40)}`;\n}\n\nexport function pgBossOrgQueueName(organizationId: string): string {\n const sanitized = organizationId.replace(/[^a-zA-Z0-9_]/g, \"_\");\n const name = `${DEFAULT_QUEUE_NAME}_org_${sanitized}`;\n if (name.length <= 50) {\n return name;\n }\n return `${DEFAULT_QUEUE_NAME}_org_${createHash(\"sha1\").update(organizationId).digest(\"hex\").slice(0, 36)}`;\n}\n\nexport type PgBossQueueOptions = {\n connectionString: string;\n queueName?: string;\n projectId?: string;\n organizationId?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n};\n\ntype PgBossJobData = {\n kind: \"workflow\" | \"agent\" | \"trigger\" | \"runtime\";\n targetId: string;\n runId: string;\n trigger: \"api\" | \"cron\" | \"webhook\" | \"poll\" | \"retry\" | \"prompt\";\n payload?: unknown;\n attempt?: number;\n maxAttempts?: number;\n scheduledAt?: string;\n projectId?: string;\n};\n\ntype BuildPgBossJobQueueOptions = {\n connectionString: string;\n stopBossOnWorkerStop?: boolean;\n queueName?: string;\n cancelScope?: \"platform\" | \"project\" | \"organization\";\n projectId?: string;\n organizationId?: string;\n};\n\nexport function buildPgBossJobQueue(boss: PgBoss, options: BuildPgBossJobQueueOptions): JobQueue {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const cancelChannel = createPgCancelChannel(\n options.connectionString,\n pgCancelChannelName(\n options.cancelScope ?? \"platform\",\n options.cancelScope === \"organization\" ? options.organizationId : options.projectId,\n ),\n );\n\n return {\n async enqueue(input) {\n const jobId = await boss.send(\n queueName,\n {\n ...input,\n payload: input.payload,\n },\n {\n retryLimit: (input.maxAttempts ?? 3) - 1,\n retryDelay: Math.ceil(retryDelayMs(1) / 1000),\n startAfter: input.scheduledAt,\n singletonKey: input.dedupeKey,\n },\n );\n\n if (!jobId) {\n if (input.dedupeKey) {\n return input.dedupeKey;\n }\n throw new Error(\"Failed to enqueue job\");\n }\n\n return jobId;\n },\n\n async startWorker(handler: JobHandler): Promise<StopFn> {\n let stopped = false;\n\n const workerId = await boss.work<PgBossJobData>(\n queueName,\n { batchSize: 1, includeMetadata: true },\n async (jobs: JobWithMetadata<PgBossJobData>[]) => {\n if (stopped) {\n return;\n }\n\n const job = jobs[0];\n if (!job) {\n return;\n }\n\n const data = job.data;\n const maxAttempts = data.maxAttempts ?? (job.retryLimit ?? 0) + 1;\n const attempt = (job.retryCount ?? 0) + 1;\n\n await handler({\n jobId: job.id,\n kind: data.kind,\n targetId: data.targetId,\n runId: data.runId,\n trigger: data.trigger,\n payload: data.payload ?? {},\n attempt,\n maxAttempts,\n exhaustedRetries: attempt >= maxAttempts,\n scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : new Date(),\n projectId: data.projectId,\n });\n },\n );\n\n return async () => {\n stopped = true;\n await boss.offWork(queueName, { id: workerId });\n if (options.stopBossOnWorkerStop) {\n await boss.stop({ graceful: true, timeout: 5_000 });\n }\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport async function createPgBossQueue(options: PgBossQueueOptions): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n const boss = new PgBoss({\n connectionString: options.connectionString,\n schema: PGBOSS_SCHEMA,\n });\n await boss.start();\n await boss.createQueue(queueName);\n\n return buildPgBossJobQueue(boss, {\n stopBossOnWorkerStop: true,\n queueName,\n connectionString: options.connectionString,\n cancelScope: options.cancelScope ?? \"project\",\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nexport async function createSharedPgBossJobQueue(\n boss: PgBoss,\n options: { connectionString: string; queueName?: string },\n): Promise<JobQueue> {\n const queueName = options.queueName ?? DEFAULT_QUEUE_NAME;\n await boss.createQueue(queueName);\n return buildPgBossJobQueue(boss, {\n queueName,\n connectionString: options.connectionString,\n cancelScope: \"platform\",\n });\n}\n","import {\n DEFAULT_DATABASE_URL,\n getProjectScopeId,\n inferDialect,\n resolveProjectDatabaseUrlFromEnv,\n} from \"@keystrokehq/database\";\n\nimport {\n defineSchedulerPlugin,\n type SchedulerPlugin,\n type SchedulerPluginContext,\n} from \"./contract\";\nimport { createDatabaseJobQueue } from \"./database-queue\";\nimport { getPgBoss, startPgBoss } from \"./pg-boss-client\";\nimport {\n createPgBossQueue,\n createSharedPgBossJobQueue,\n pgBossOrgQueueName,\n pgBossProjectQueueName,\n} from \"./pg-boss-queue\";\n\nexport type { SchedulerPlugin, SchedulerPluginContext, SchedulerScope } from \"./contract\";\nexport { defineSchedulerPlugin } from \"./contract\";\n\nfunction resolveUrl(ctx: SchedulerPluginContext): string {\n return ctx.url ?? resolveProjectDatabaseUrlFromEnv(process.env) ?? DEFAULT_DATABASE_URL;\n}\n\n/** Postgres pg-boss backend. Platform scope shares one queue; org scope gets one queue per org. */\nexport function pgBossSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"pg-boss\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n\n if (ctx.scope === \"platform\") {\n await startPgBoss(url);\n return createSharedPgBossJobQueue(getPgBoss(), { connectionString: url });\n }\n\n if (ctx.scope === \"organization\") {\n const organizationId = ctx.organizationId;\n if (!organizationId) {\n throw new Error(\"organizationId is required for organization-scoped pg-boss queue\");\n }\n\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossOrgQueueName(organizationId),\n cancelScope: \"organization\",\n organizationId,\n });\n }\n\n const projectId = ctx.projectId ?? getProjectScopeId();\n return createPgBossQueue({\n connectionString: url,\n queueName: pgBossProjectQueueName(projectId),\n projectId,\n });\n },\n });\n}\n\n/** DB-table queue (Drizzle `jobs`), for SQLite / self-host. */\nexport function pollingSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"polling\",\n async createJobQueue() {\n return createDatabaseJobQueue();\n },\n });\n}\n\n/** Auto-selects pg-boss (postgres) or polling (sqlite) by dialect. */\nexport function defaultSchedulerPlugin(): SchedulerPlugin {\n return defineSchedulerPlugin({\n name: \"default\",\n async createJobQueue(ctx) {\n const url = resolveUrl(ctx);\n const dialect = inferDialect(url, ctx.dialect);\n\n if (dialect === \"postgres\") {\n return pgBossSchedulerPlugin().createJobQueue(ctx);\n }\n\n return pollingSchedulerPlugin().createJobQueue(ctx);\n },\n });\n}\n","import { resolveCronSchedule } from \"@keystrokehq/trigger\";\n\nexport type ScheduleOverrideOptions = {\n global?: string;\n byTrigger?: Record<string, string>;\n};\n\nexport function resolveTriggerSchedule(\n triggerSlug: string,\n schedule: string,\n overrides?: ScheduleOverrideOptions,\n): string {\n return resolveCronSchedule(triggerSlug, schedule, {\n cronScheduleOverride: overrides?.global,\n attachmentScheduleOverrides: overrides?.byTrigger,\n });\n}\n","import {\n disableAllTriggerSchedules,\n disableTriggerSchedulesNotInSlugs,\n listExecutableAttachmentsByTriggerIds,\n selectActiveEphemeralScheduledTriggerSlugs,\n selectTriggerBySlug,\n upsertTriggerScheduleFields,\n} from \"@keystrokehq/database\";\nimport { nextTriggerRunAt } from \"@keystrokehq/trigger\";\nimport type { ScheduleSyncOptions } from \"./types\";\nimport { resolveTriggerSchedule } from \"./resolve-schedule\";\n\nexport async function syncTriggerSchedules(options: ScheduleSyncOptions): Promise<void> {\n const now = new Date();\n const projectSlugs = options.schedules.map((schedule) => schedule.triggerSlug);\n // Ephemeral cron/poll schedules are managed by set_trigger, not project discovery — keep them out\n // of the disable sweep so a deploy/restart does not wipe agent-created triggers.\n const ephemeralSlugs = await selectActiveEphemeralScheduledTriggerSlugs();\n const slugs = [...projectSlugs, ...ephemeralSlugs];\n\n if (slugs.length === 0) {\n await disableAllTriggerSchedules(now);\n return;\n }\n\n await disableTriggerSchedulesNotInSlugs(slugs, now);\n\n for (const spec of options.schedules) {\n const schedule = resolveTriggerSchedule(\n spec.triggerSlug,\n spec.schedule,\n options.scheduleOverrides,\n );\n const existing = await selectTriggerBySlug(spec.triggerSlug);\n if (!existing) {\n continue;\n }\n\n const scheduleChanged = existing.schedule !== schedule;\n const nextRunAt =\n !scheduleChanged && existing.enabled === 1\n ? existing.nextRunAt\n : nextTriggerRunAt(schedule, now);\n\n const executable = await listExecutableAttachmentsByTriggerIds([existing.id]);\n const enabled = executable.length > 0;\n\n await upsertTriggerScheduleFields({\n triggerSlug: spec.triggerSlug,\n schedule,\n nextRunAt: nextRunAt ?? now,\n enabled,\n updatedAt: now,\n });\n }\n}\n","import { createScheduleTicker } from \"./schedule-ticker\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport { syncTriggerSchedules as syncTriggerScheduleRows } from \"./sync-trigger-schedules\";\nimport type {\n CreateJobQueueOptions,\n CreateSchedulerOptions,\n JobQueue,\n ScheduleSyncOptions,\n ScheduleTickerOptions,\n Scheduler,\n StopFn,\n} from \"./types\";\n\nasync function createUnderlyingJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n if (options.adapter) {\n return options.adapter;\n }\n\n const plugin = options.plugin ?? defaultSchedulerPlugin();\n return plugin.createJobQueue({\n scope: options.scope ?? \"project\",\n url: options.url,\n dialect: options.dialect,\n projectId: options.projectId,\n organizationId: options.organizationId,\n });\n}\n\nfunction wrapScheduler(jobQueue: JobQueue, scope?: \"project\" | \"organization\"): Scheduler {\n const ticker = createScheduleTicker({ jobQueue, scope });\n\n return {\n enqueue: (input) => jobQueue.enqueue(input),\n startWorker: (handler, options) => jobQueue.startWorker(handler, options),\n publishCancel: (runId) => jobQueue.publishCancel(runId),\n subscribeCancel: (handler) => jobQueue.subscribeCancel(handler),\n syncTriggerSchedules: (options: ScheduleSyncOptions) => syncTriggerScheduleRows(options),\n startScheduleTicker: (options?: ScheduleTickerOptions) => ticker.startScheduleTicker(options),\n fireDueSchedules: (asOf?: Date) => ticker.fireDueSchedules(asOf),\n };\n}\n\nexport async function createScheduler(options: CreateSchedulerOptions = {}): Promise<Scheduler> {\n const jobQueue = await createUnderlyingJobQueue(options);\n const scope = options.scope === \"organization\" ? \"organization\" : \"project\";\n return wrapScheduler(jobQueue, scope);\n}\n\nexport async function createJobQueue(options: CreateJobQueueOptions = {}): Promise<JobQueue> {\n return createUnderlyingJobQueue(options);\n}\n\nexport function wrapJobQueueAsScheduler(\n jobQueue: JobQueue,\n scope?: \"project\" | \"organization\",\n): Scheduler {\n return wrapScheduler(jobQueue, scope);\n}\n\nexport type { StopFn };\n","import { createInProcessCancelChannel } from \"./cancel-channel\";\nimport type { EnqueueInput, JobHandler, JobQueue, StopFn, WorkerOptions } from \"./types\";\nimport { retryDelayMs } from \"./types\";\n\nexport type MemoryJobQueueOptions = {\n sync?: boolean;\n};\n\nexport function createMemoryJobQueue(options: MemoryJobQueueOptions = {}): JobQueue {\n const cancelChannel = createInProcessCancelChannel();\n const pending: Array<{ id: string; input: EnqueueInput; enqueuedAt: Date }> = [];\n let handler: JobHandler | undefined;\n let draining = false;\n\n async function drain(): Promise<void> {\n if (!handler || draining) {\n return;\n }\n\n draining = true;\n try {\n while (pending.length > 0) {\n const next = pending.shift();\n if (!next) {\n break;\n }\n\n const input = next.input;\n await handler({\n jobId: next.id,\n kind: input.kind,\n targetId: input.targetId,\n runId: input.runId,\n trigger: input.trigger,\n payload: input.payload ?? {},\n attempt: input.attempt ?? 1,\n maxAttempts: input.maxAttempts ?? 3,\n scheduledAt: input.scheduledAt ?? next.enqueuedAt,\n });\n }\n } finally {\n draining = false;\n }\n }\n\n return {\n async enqueue(input) {\n const id = crypto.randomUUID();\n pending.push({ id, input, enqueuedAt: new Date() });\n\n if (options.sync) {\n await drain();\n }\n\n return id;\n },\n\n async startWorker(nextHandler: JobHandler, _opts: WorkerOptions = {}): Promise<StopFn> {\n handler = nextHandler;\n\n if (!options.sync) {\n void drain();\n }\n\n return async () => {\n handler = undefined;\n };\n },\n\n publishCancel: (runId) => cancelChannel.publishCancel(runId),\n subscribeCancel: (handler) => cancelChannel.subscribeCancel(handler),\n };\n}\n\nexport { retryDelayMs };\n","import { resolvePostgresUrlFromEnv } from \"@keystrokehq/database\";\nimport { getPgBoss } from \"./pg-boss-client\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { createSharedPgBossJobQueue } from \"./pg-boss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet sharedJobQueue: JobQueue | undefined;\n\nexport async function createSharedPgBossScheduler(): Promise<Scheduler> {\n const jobQueue = await getSharedPgBossJobQueue();\n return wrapJobQueueAsScheduler(jobQueue);\n}\n\nexport async function getSharedPgBossJobQueue(): Promise<JobQueue> {\n if (sharedJobQueue) {\n return sharedJobQueue;\n }\n\n const connectionString = resolvePostgresUrlFromEnv(process.env);\n if (!connectionString) {\n throw new Error(\"Postgres connection string is required for shared pg-boss queue\");\n }\n\n sharedJobQueue = await createSharedPgBossJobQueue(getPgBoss(), { connectionString });\n return sharedJobQueue;\n}\n\nexport function resetSharedPgBossJobQueueForTests(): void {\n sharedJobQueue = undefined;\n}\n","import type { SchedulerPlugin } from \"./contract\";\nimport { wrapJobQueueAsScheduler } from \"./create-scheduler\";\nimport { defaultSchedulerPlugin } from \"./plugin\";\nimport {\n createSharedPgBossScheduler,\n resetSharedPgBossJobQueueForTests,\n} from \"./shared-pgboss-queue\";\nimport type { JobQueue, Scheduler } from \"./types\";\n\nlet configuredPlugin: SchedulerPlugin | undefined;\nlet sharedJobQueue: JobQueue | undefined;\n\nexport function configureSharedScheduler(plugin: SchedulerPlugin): void {\n configuredPlugin = plugin;\n sharedJobQueue = undefined;\n}\n\nexport async function createSharedScheduler(): Promise<Scheduler> {\n const plugin = configuredPlugin ?? defaultSchedulerPlugin();\n\n if (plugin.name === \"default\") {\n return createSharedPgBossScheduler();\n }\n\n if (!sharedJobQueue) {\n sharedJobQueue = await plugin.createJobQueue({ scope: \"platform\" });\n }\n\n return wrapJobQueueAsScheduler(sharedJobQueue);\n}\n\nexport function resetSharedSchedulerForTests(): void {\n configuredPlugin = undefined;\n sharedJobQueue = undefined;\n resetSharedPgBossJobQueueForTests();\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keystrokehq/scheduler",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"tsdown": "^0.22.0",
|
|
42
42
|
"typescript": "^6.0.3",
|
|
43
43
|
"vitest": "^4.1.7",
|
|
44
|
-
"@keystrokehq/database": "0.0.
|
|
44
|
+
"@keystrokehq/database": "0.0.129",
|
|
45
45
|
"@keystrokehq/oxlint-config": "0.0.4",
|
|
46
|
-
"@keystrokehq/trigger": "0.0.
|
|
46
|
+
"@keystrokehq/trigger": "0.0.155",
|
|
47
47
|
"@keystrokehq/tsconfig": "0.0.3",
|
|
48
48
|
"@keystrokehq/tsdown-config": "0.0.3",
|
|
49
49
|
"@keystrokehq/vitest-config": "0.0.5"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@keystrokehq/
|
|
53
|
-
"@keystrokehq/
|
|
52
|
+
"@keystrokehq/database": "0.0.129",
|
|
53
|
+
"@keystrokehq/trigger": "0.0.155"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"@keystrokehq/database": {
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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"}
|