@voyantjs/workflows-orchestrator-node 0.30.1 → 0.30.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"node-standalone-driver.d.ts","sourceRoot":"","sources":["../src/node-standalone-driver.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EACV,aAAa,EAOd,MAAM,4BAA4B,CAAA;AAInC,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,WAAW,EACjB,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAWxD,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAIpC,MAAM,WAAW,2BAA2B;IAC1C,4EAA4E;IAC5E,EAAE,EAAE,EAAE,CAAA;IACN,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;IAC7D,wDAAwD;IACxD,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;IACpC,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,aAAa,CA8R3F"}
1
+ {"version":3,"file":"node-standalone-driver.d.ts","sourceRoot":"","sources":["../src/node-standalone-driver.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EACV,aAAa,EAOd,MAAM,4BAA4B,CAAA;AAInC,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,WAAW,EACjB,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAWxD,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAIpC,MAAM,WAAW,2BAA2B;IAC1C,4EAA4E;IAC5E,EAAE,EAAE,EAAE,CAAA;IACN,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;IAC7D,wDAAwD;IACxD,UAAU,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;IACpC,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,aAAa,CA+R3F"}
@@ -138,6 +138,7 @@ export function createNodeStandaloneDriver(opts) {
138
138
  tags: triggerOpts?.tags,
139
139
  idempotencyKey: triggerOpts?.idempotencyKey,
140
140
  delay: triggerOpts?.delay,
141
+ priority: triggerOpts?.priority,
141
142
  }, { store: runStore, handler, now });
142
143
  // Sync wakeup row so the time-wheel can resume DATETIME-parked runs.
143
144
  // No-op if the run completed inline (status !== "waiting").
@@ -272,6 +272,23 @@ export declare const wakeupsTable: import("drizzle-orm/pg-core").PgTableWithColu
272
272
  identity: undefined;
273
273
  generated: undefined;
274
274
  }, {}, {}>;
275
+ priority: import("drizzle-orm/pg-core").PgColumn<{
276
+ name: "priority";
277
+ tableName: "voyant_wakeups";
278
+ dataType: "number";
279
+ columnType: "PgInteger";
280
+ data: number;
281
+ driverParam: string | number;
282
+ notNull: true;
283
+ hasDefault: true;
284
+ isPrimaryKey: false;
285
+ isAutoincrement: false;
286
+ hasRuntimeDefault: false;
287
+ enumValues: undefined;
288
+ baseColumn: never;
289
+ identity: undefined;
290
+ generated: undefined;
291
+ }, {}, {}>;
275
292
  leaseOwner: import("drizzle-orm/pg-core").PgColumn<{
276
293
  name: "lease_owner";
277
294
  tableName: "voyant_wakeups";
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-schema.d.ts","sourceRoot":"","sources":["../src/postgres-schema.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0C7B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAaxB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAelC,CAAA"}
1
+ {"version":3,"file":"postgres-schema.d.ts","sourceRoot":"","sources":["../src/postgres-schema.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0C7B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcxB,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAelC,CAAA"}
@@ -36,11 +36,12 @@ export const snapshotRunsTable = pgTable("voyant_snapshot_runs", {
36
36
  export const wakeupsTable = pgTable("voyant_wakeups", {
37
37
  runId: text("run_id").primaryKey(),
38
38
  wakeAt: bigint("wake_at", { mode: "number" }).notNull(),
39
+ priority: integer("priority").notNull().default(0),
39
40
  leaseOwner: text("lease_owner"),
40
41
  leaseExpiresAt: bigint("lease_expires_at", { mode: "number" }),
41
42
  updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
42
43
  }, (table) => ({
43
- dueIdx: index("voyant_wakeups_due_idx").on(table.wakeAt),
44
+ dueIdx: index("voyant_wakeups_due_idx").on(table.priority.desc(), table.wakeAt.asc()),
44
45
  leaseIdx: index("voyant_wakeups_lease_idx").on(table.leaseExpiresAt),
45
46
  }));
46
47
  /**
@@ -12,6 +12,7 @@ export declare function wakeupToRow(record: Omit<WakeupRecord, "updatedAt"> & {
12
12
  }, updatedAt: number): {
13
13
  runId: string;
14
14
  wakeAt: number;
15
+ priority: number;
15
16
  leaseOwner: string | null;
16
17
  leaseExpiresAt: number | null;
17
18
  updatedAt: number;
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-wakeup-store.d.ts","sourceRoot":"","sources":["../src/postgres-wakeup-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAElE,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAE1C,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,QAAQ,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,0BAA0B,GAAG,WAAW,CA0GvF;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC/D,SAAS,EAAE,MAAM;;;;;;EASlB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,YAAY,CAAC,YAAY,GAAG,YAAY,CAQrF"}
1
+ {"version":3,"file":"postgres-wakeup-store.d.ts","sourceRoot":"","sources":["../src/postgres-wakeup-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAElE,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAE1C,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,QAAQ,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,0BAA0B,GAAG,WAAW,CAkHvF;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC/D,SAAS,EAAE,MAAM;;;;;;;EAUlB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,YAAY,CAAC,YAAY,GAAG,YAAY,CASrF"}
@@ -49,7 +49,7 @@ export function createPostgresWakeupStore(opts) {
49
49
  OR lease_expires_at <= ${at}
50
50
  OR lease_owner = ${owner}
51
51
  )
52
- ORDER BY wake_at ASC
52
+ ORDER BY priority DESC, wake_at ASC
53
53
  LIMIT ${limit}
54
54
  FOR UPDATE SKIP LOCKED
55
55
  )
@@ -59,12 +59,19 @@ export function createPostgresWakeupStore(opts) {
59
59
  updated_at = ${at}
60
60
  FROM due
61
61
  WHERE wakeups.run_id = due.run_id
62
- RETURNING wakeups.run_id, wakeups.wake_at, wakeups.lease_owner, wakeups.lease_expires_at, wakeups.updated_at
62
+ RETURNING wakeups.run_id, wakeups.wake_at, wakeups.priority, wakeups.lease_owner, wakeups.lease_expires_at, wakeups.updated_at
63
63
  `);
64
64
  const rows = result.rows;
65
+ rows.sort((a, b) => {
66
+ const priorityDelta = toNumber(b.priority) - toNumber(a.priority);
67
+ if (priorityDelta !== 0)
68
+ return priorityDelta;
69
+ return toNumber(a.wake_at) - toNumber(b.wake_at);
70
+ });
65
71
  return rows.map((row) => ({
66
72
  runId: row.run_id,
67
73
  wakeAt: toNumber(row.wake_at),
74
+ priority: toNumber(row.priority),
68
75
  leaseOwner: row.lease_owner ?? undefined,
69
76
  leaseExpiresAt: row.lease_expires_at === null ? undefined : toNumber(row.lease_expires_at),
70
77
  updatedAt: toNumber(row.updated_at),
@@ -90,6 +97,7 @@ export function wakeupToRow(record, updatedAt) {
90
97
  return {
91
98
  runId: record.runId,
92
99
  wakeAt: record.wakeAt,
100
+ priority: record.priority ?? 0,
93
101
  leaseOwner: record.leaseOwner ?? null,
94
102
  leaseExpiresAt: record.leaseExpiresAt ?? null,
95
103
  updatedAt,
@@ -99,6 +107,7 @@ export function rowToWakeupRecord(row) {
99
107
  return {
100
108
  runId: row.runId,
101
109
  wakeAt: row.wakeAt,
110
+ priority: row.priority,
102
111
  leaseOwner: row.leaseOwner ?? undefined,
103
112
  leaseExpiresAt: row.leaseExpiresAt ?? undefined,
104
113
  updatedAt: row.updatedAt,
@@ -1,4 +1,5 @@
1
- import type { Duration, EnvironmentName, ScheduleDeclaration } from "@voyantjs/workflows";
1
+ import type { EnvironmentName, ScheduleDeclaration } from "@voyantjs/workflows";
2
+ export { type CronSpec, computeNextFire, nextCronFire, parseCron, toMs, } from "@voyantjs/workflows-orchestrator";
2
3
  export interface ScheduleSource {
3
4
  workflowId: string;
4
5
  decl: ScheduleDeclaration;
@@ -30,15 +31,4 @@ export interface SchedulerHandle {
30
31
  sourceCount: () => number;
31
32
  }
32
33
  export declare function createScheduler(deps: SchedulerDeps): SchedulerHandle;
33
- export declare function computeNextFire(decl: ScheduleDeclaration, fromMs: number): number;
34
- export interface CronSpec {
35
- minute: number[];
36
- hour: number[];
37
- day: number[];
38
- month: number[];
39
- dow: number[];
40
- }
41
- export declare function parseCron(expr: string): CronSpec;
42
- export declare function nextCronFire(spec: CronSpec, fromMs: number): number;
43
- export declare function toMs(duration: Duration): number;
44
34
  //# sourceMappingURL=scheduler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,mBAAmB,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,cAAc,EAAE,CAAA;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9F,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,WAAW,CAAA;IAChC,aAAa,CAAC,EAAE,OAAO,aAAa,CAAA;IACpC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,WAAW,EAAE,MAAM;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IACzF,WAAW,EAAE,MAAM,MAAM,CAAA;CAC1B;AASD,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,CAsGpE;AAWD,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CASjF;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,EAAE,MAAM,EAAE,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,EAAE,CAAA;CACd;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAYhD;AA8BD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBnE;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAqB/C"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAG/E,OAAO,EACL,KAAK,QAAQ,EACb,eAAe,EACf,YAAY,EACZ,SAAS,EACT,IAAI,GACL,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,mBAAmB,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,cAAc,EAAE,CAAA;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9F,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,WAAW,CAAA;IAChC,aAAa,CAAC,EAAE,OAAO,aAAa,CAAA;IACpC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,WAAW,EAAE,MAAM;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IACzF,WAAW,EAAE,MAAM,MAAM,CAAA;CAC1B;AASD,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,CAsGpE"}
package/dist/scheduler.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { computeNextFire } from "@voyantjs/workflows-orchestrator";
2
+ export { computeNextFire, nextCronFire, parseCron, toMs, } from "@voyantjs/workflows-orchestrator";
1
3
  export function createScheduler(deps) {
2
4
  const now = deps.now ?? (() => Date.now());
3
5
  const tickMs = deps.tickMs ?? 1_000;
@@ -107,101 +109,3 @@ async function resolveInput(input) {
107
109
  }
108
110
  return input;
109
111
  }
110
- export function computeNextFire(decl, fromMs) {
111
- if ("cron" in decl)
112
- return nextCronFire(parseCron(decl.cron), fromMs);
113
- if ("every" in decl)
114
- return fromMs + toMs(decl.every);
115
- if ("at" in decl) {
116
- const at = typeof decl.at === "string" ? Date.parse(decl.at) : decl.at.getTime();
117
- if (!Number.isFinite(at))
118
- throw new Error(`invalid "at" value: ${String(decl.at)}`);
119
- return at < fromMs ? Number.POSITIVE_INFINITY : at;
120
- }
121
- throw new Error(`schedule declaration missing one of cron/every/at`);
122
- }
123
- export function parseCron(expr) {
124
- const parts = expr.trim().split(/\s+/);
125
- if (parts.length !== 5) {
126
- throw new Error(`invalid cron "${expr}" — expected 5 fields (minute hour day month dow)`);
127
- }
128
- return {
129
- minute: parseField(parts[0], 0, 59, "minute"),
130
- hour: parseField(parts[1], 0, 23, "hour"),
131
- day: parseField(parts[2], 1, 31, "day"),
132
- month: parseField(parts[3], 1, 12, "month"),
133
- dow: parseField(parts[4], 0, 6, "dow"),
134
- };
135
- }
136
- function parseField(f, min, max, label) {
137
- const out = new Set();
138
- for (const part of f.split(",")) {
139
- const stepMatch = /^(.+)\/(\d+)$/.exec(part);
140
- const body = stepMatch ? stepMatch[1] : part;
141
- const step = stepMatch ? Number(stepMatch[2]) : 1;
142
- if (!(step >= 1))
143
- throw new Error(`cron ${label} step must be >=1 in "${f}"`);
144
- let lo;
145
- let hi;
146
- if (body === "*") {
147
- lo = min;
148
- hi = max;
149
- }
150
- else if (body.includes("-")) {
151
- const [a, b] = body.split("-");
152
- lo = Number(a);
153
- hi = Number(b);
154
- }
155
- else {
156
- lo = Number(body);
157
- hi = lo;
158
- }
159
- if (!Number.isFinite(lo) || !Number.isFinite(hi) || lo < min || hi > max || lo > hi) {
160
- throw new Error(`cron ${label} out of range [${min}..${max}] in "${f}"`);
161
- }
162
- for (let i = lo; i <= hi; i += step)
163
- out.add(i);
164
- }
165
- return [...out].sort((a, b) => a - b);
166
- }
167
- export function nextCronFire(spec, fromMs) {
168
- const date = new Date(fromMs);
169
- date.setUTCSeconds(0, 0);
170
- date.setUTCMinutes(date.getUTCMinutes() + 1);
171
- const maxIterations = 60 * 24 * 366 * 5;
172
- for (let i = 0; i < maxIterations; i++) {
173
- if (spec.minute.includes(date.getUTCMinutes()) &&
174
- spec.hour.includes(date.getUTCHours()) &&
175
- spec.day.includes(date.getUTCDate()) &&
176
- spec.month.includes(date.getUTCMonth() + 1) &&
177
- spec.dow.includes(date.getUTCDay())) {
178
- return date.getTime();
179
- }
180
- date.setUTCMinutes(date.getUTCMinutes() + 1);
181
- }
182
- throw new Error("cron search exceeded 5 years without finding a match");
183
- }
184
- export function toMs(duration) {
185
- if (typeof duration === "number")
186
- return duration;
187
- const m = /^(\d+)(ms|s|m|h|d|w)$/.exec(duration);
188
- if (!m)
189
- throw new Error(`invalid duration "${duration}"`);
190
- const n = Number(m[1]);
191
- switch (m[2]) {
192
- case "ms":
193
- return n;
194
- case "s":
195
- return n * 1_000;
196
- case "m":
197
- return n * 60_000;
198
- case "h":
199
- return n * 3_600_000;
200
- case "d":
201
- return n * 86_400_000;
202
- case "w":
203
- return n * 604_800_000;
204
- default:
205
- throw new Error(`invalid duration "${duration}"`);
206
- }
207
- }
@@ -2,6 +2,7 @@ import type { RunRecord } from "@voyantjs/workflows-orchestrator";
2
2
  export interface WakeupRecord {
3
3
  runId: string;
4
4
  wakeAt: number;
5
+ priority?: number;
5
6
  leaseOwner?: string;
6
7
  leaseExpiresAt?: number;
7
8
  updatedAt: number;
@@ -1 +1 @@
1
- {"version":3,"file":"wakeup-store.d.ts","sourceRoot":"","sources":["../src/wakeup-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAGjE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;IACzD,MAAM,EAAE,CACN,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAC7D,OAAO,CAAC,YAAY,CAAC,CAAA;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,EAAE,MAAM,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,KAAK,EAAE,MAAM,CAAA;QACb,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;IAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,oBAAyB,GAAG,WAAW,CA4EhF;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAY/F"}
1
+ {"version":3,"file":"wakeup-store.d.ts","sourceRoot":"","sources":["../src/wakeup-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAGjE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;IACzD,MAAM,EAAE,CACN,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAC7D,OAAO,CAAC,YAAY,CAAC,CAAA;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,EAAE,MAAM,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE;QACf,KAAK,EAAE,MAAM,CAAA;QACb,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;IAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,oBAAyB,GAAG,WAAW,CA4EhF;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAa/F"}
@@ -41,7 +41,7 @@ export function createFsWakeupStore(opts = {}) {
41
41
  return wakeups;
42
42
  },
43
43
  async leaseDue({ owner, now: at = now(), leaseMs, limit = 25 }) {
44
- const wakeups = await this.list();
44
+ const wakeups = (await this.list()).sort(compareWakeupClaimOrder);
45
45
  const leased = [];
46
46
  for (const wakeup of wakeups) {
47
47
  if (leased.length >= limit)
@@ -86,10 +86,17 @@ export async function syncWakeupFromRecord(store, record) {
86
86
  await store.upsert({
87
87
  runId: record.id,
88
88
  wakeAt,
89
+ priority: record.priority,
89
90
  leaseOwner: undefined,
90
91
  leaseExpiresAt: undefined,
91
92
  });
92
93
  }
94
+ function compareWakeupClaimOrder(a, b) {
95
+ const priorityDelta = (b.priority ?? 0) - (a.priority ?? 0);
96
+ if (priorityDelta !== 0)
97
+ return priorityDelta;
98
+ return a.wakeAt - b.wakeAt;
99
+ }
93
100
  async function safeReaddir(dir) {
94
101
  try {
95
102
  const entries = await readdir(dir);
@@ -0,0 +1,3 @@
1
+ ALTER TABLE "voyant_wakeups" ADD COLUMN "priority" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
2
+ DROP INDEX "voyant_wakeups_due_idx";--> statement-breakpoint
3
+ CREATE INDEX "voyant_wakeups_due_idx" ON "voyant_wakeups" USING btree ("priority" DESC,"wake_at" ASC);
@@ -36,6 +36,13 @@
36
36
  "when": 1779000001000,
37
37
  "tag": "0004_workflow_manifests",
38
38
  "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1779000002000,
44
+ "tag": "0005_wakeup_priority",
45
+ "breakpoints": true
39
46
  }
40
47
  ]
41
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/workflows-orchestrator-node",
3
- "version": "0.30.1",
3
+ "version": "0.30.3",
4
4
  "description": "Node/Docker runtime primitives for @voyantjs/workflows-orchestrator, including a file-backed run store and local scheduler.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "dependencies": {
30
30
  "drizzle-orm": "^0.45.2",
31
31
  "pg": "^8.20.0",
32
- "@voyantjs/workflows-orchestrator": "0.30.1",
33
- "@voyantjs/workflows": "0.30.1"
32
+ "@voyantjs/workflows-orchestrator": "0.30.3",
33
+ "@voyantjs/workflows": "0.30.3"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^20.12.0",
@@ -244,6 +244,7 @@ export function createNodeStandaloneDriver(opts: NodeStandaloneDriverOptions): D
244
244
  tags: triggerOpts?.tags,
245
245
  idempotencyKey: triggerOpts?.idempotencyKey,
246
246
  delay: triggerOpts?.delay,
247
+ priority: triggerOpts?.priority,
247
248
  },
248
249
  { store: runStore, handler, now },
249
250
  )
@@ -61,12 +61,13 @@ export const wakeupsTable = pgTable(
61
61
  {
62
62
  runId: text("run_id").primaryKey(),
63
63
  wakeAt: bigint("wake_at", { mode: "number" }).notNull(),
64
+ priority: integer("priority").notNull().default(0),
64
65
  leaseOwner: text("lease_owner"),
65
66
  leaseExpiresAt: bigint("lease_expires_at", { mode: "number" }),
66
67
  updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
67
68
  },
68
69
  (table) => ({
69
- dueIdx: index("voyant_wakeups_due_idx").on(table.wakeAt),
70
+ dueIdx: index("voyant_wakeups_due_idx").on(table.priority.desc(), table.wakeAt.asc()),
70
71
  leaseIdx: index("voyant_wakeups_lease_idx").on(table.leaseExpiresAt),
71
72
  }),
72
73
  )
@@ -62,6 +62,7 @@ export function createPostgresWakeupStore(opts: PostgresWakeupStoreOptions): Wak
62
62
  wake_at: number
63
63
  lease_owner: string | null
64
64
  lease_expires_at: number | null
65
+ priority: number
65
66
  updated_at: number
66
67
  }>(sql`
67
68
  WITH due AS (
@@ -73,7 +74,7 @@ export function createPostgresWakeupStore(opts: PostgresWakeupStoreOptions): Wak
73
74
  OR lease_expires_at <= ${at}
74
75
  OR lease_owner = ${owner}
75
76
  )
76
- ORDER BY wake_at ASC
77
+ ORDER BY priority DESC, wake_at ASC
77
78
  LIMIT ${limit}
78
79
  FOR UPDATE SKIP LOCKED
79
80
  )
@@ -83,18 +84,25 @@ export function createPostgresWakeupStore(opts: PostgresWakeupStoreOptions): Wak
83
84
  updated_at = ${at}
84
85
  FROM due
85
86
  WHERE wakeups.run_id = due.run_id
86
- RETURNING wakeups.run_id, wakeups.wake_at, wakeups.lease_owner, wakeups.lease_expires_at, wakeups.updated_at
87
+ RETURNING wakeups.run_id, wakeups.wake_at, wakeups.priority, wakeups.lease_owner, wakeups.lease_expires_at, wakeups.updated_at
87
88
  `)
88
89
  const rows = result.rows as Array<{
89
90
  run_id: string
90
91
  wake_at: number | string
92
+ priority: number | string
91
93
  lease_owner: string | null
92
94
  lease_expires_at: number | string | null
93
95
  updated_at: number | string
94
96
  }>
97
+ rows.sort((a, b) => {
98
+ const priorityDelta = toNumber(b.priority) - toNumber(a.priority)
99
+ if (priorityDelta !== 0) return priorityDelta
100
+ return toNumber(a.wake_at) - toNumber(b.wake_at)
101
+ })
95
102
  return rows.map((row) => ({
96
103
  runId: row.run_id,
97
104
  wakeAt: toNumber(row.wake_at),
105
+ priority: toNumber(row.priority),
98
106
  leaseOwner: row.lease_owner ?? undefined,
99
107
  leaseExpiresAt: row.lease_expires_at === null ? undefined : toNumber(row.lease_expires_at),
100
108
  updatedAt: toNumber(row.updated_at),
@@ -125,6 +133,7 @@ export function wakeupToRow(
125
133
  return {
126
134
  runId: record.runId,
127
135
  wakeAt: record.wakeAt,
136
+ priority: record.priority ?? 0,
128
137
  leaseOwner: record.leaseOwner ?? null,
129
138
  leaseExpiresAt: record.leaseExpiresAt ?? null,
130
139
  updatedAt,
@@ -135,6 +144,7 @@ export function rowToWakeupRecord(row: typeof wakeupsTable.$inferSelect): Wakeup
135
144
  return {
136
145
  runId: row.runId,
137
146
  wakeAt: row.wakeAt,
147
+ priority: row.priority,
138
148
  leaseOwner: row.leaseOwner ?? undefined,
139
149
  leaseExpiresAt: row.leaseExpiresAt ?? undefined,
140
150
  updatedAt: row.updatedAt,
package/src/scheduler.ts CHANGED
@@ -1,4 +1,13 @@
1
- import type { Duration, EnvironmentName, ScheduleDeclaration } from "@voyantjs/workflows"
1
+ import type { EnvironmentName, ScheduleDeclaration } from "@voyantjs/workflows"
2
+ import { computeNextFire } from "@voyantjs/workflows-orchestrator"
3
+
4
+ export {
5
+ type CronSpec,
6
+ computeNextFire,
7
+ nextCronFire,
8
+ parseCron,
9
+ toMs,
10
+ } from "@voyantjs/workflows-orchestrator"
2
11
 
3
12
  export interface ScheduleSource {
4
13
  workflowId: string
@@ -143,108 +152,3 @@ async function resolveInput(
143
152
  }
144
153
  return input
145
154
  }
146
-
147
- export function computeNextFire(decl: ScheduleDeclaration, fromMs: number): number {
148
- if ("cron" in decl) return nextCronFire(parseCron(decl.cron), fromMs)
149
- if ("every" in decl) return fromMs + toMs(decl.every)
150
- if ("at" in decl) {
151
- const at = typeof decl.at === "string" ? Date.parse(decl.at) : decl.at.getTime()
152
- if (!Number.isFinite(at)) throw new Error(`invalid "at" value: ${String(decl.at)}`)
153
- return at < fromMs ? Number.POSITIVE_INFINITY : at
154
- }
155
- throw new Error(`schedule declaration missing one of cron/every/at`)
156
- }
157
-
158
- export interface CronSpec {
159
- minute: number[]
160
- hour: number[]
161
- day: number[]
162
- month: number[]
163
- dow: number[]
164
- }
165
-
166
- export function parseCron(expr: string): CronSpec {
167
- const parts = expr.trim().split(/\s+/)
168
- if (parts.length !== 5) {
169
- throw new Error(`invalid cron "${expr}" — expected 5 fields (minute hour day month dow)`)
170
- }
171
- return {
172
- minute: parseField(parts[0]!, 0, 59, "minute"),
173
- hour: parseField(parts[1]!, 0, 23, "hour"),
174
- day: parseField(parts[2]!, 1, 31, "day"),
175
- month: parseField(parts[3]!, 1, 12, "month"),
176
- dow: parseField(parts[4]!, 0, 6, "dow"),
177
- }
178
- }
179
-
180
- function parseField(f: string, min: number, max: number, label: string): number[] {
181
- const out = new Set<number>()
182
- for (const part of f.split(",")) {
183
- const stepMatch = /^(.+)\/(\d+)$/.exec(part)
184
- const body = stepMatch ? stepMatch[1]! : part
185
- const step = stepMatch ? Number(stepMatch[2]) : 1
186
- if (!(step >= 1)) throw new Error(`cron ${label} step must be >=1 in "${f}"`)
187
- let lo: number
188
- let hi: number
189
- if (body === "*") {
190
- lo = min
191
- hi = max
192
- } else if (body.includes("-")) {
193
- const [a, b] = body.split("-")
194
- lo = Number(a)
195
- hi = Number(b)
196
- } else {
197
- lo = Number(body)
198
- hi = lo
199
- }
200
- if (!Number.isFinite(lo) || !Number.isFinite(hi) || lo < min || hi > max || lo > hi) {
201
- throw new Error(`cron ${label} out of range [${min}..${max}] in "${f}"`)
202
- }
203
- for (let i = lo; i <= hi; i += step) out.add(i)
204
- }
205
- return [...out].sort((a, b) => a - b)
206
- }
207
-
208
- export function nextCronFire(spec: CronSpec, fromMs: number): number {
209
- const date = new Date(fromMs)
210
- date.setUTCSeconds(0, 0)
211
- date.setUTCMinutes(date.getUTCMinutes() + 1)
212
-
213
- const maxIterations = 60 * 24 * 366 * 5
214
- for (let i = 0; i < maxIterations; i++) {
215
- if (
216
- spec.minute.includes(date.getUTCMinutes()) &&
217
- spec.hour.includes(date.getUTCHours()) &&
218
- spec.day.includes(date.getUTCDate()) &&
219
- spec.month.includes(date.getUTCMonth() + 1) &&
220
- spec.dow.includes(date.getUTCDay())
221
- ) {
222
- return date.getTime()
223
- }
224
- date.setUTCMinutes(date.getUTCMinutes() + 1)
225
- }
226
- throw new Error("cron search exceeded 5 years without finding a match")
227
- }
228
-
229
- export function toMs(duration: Duration): number {
230
- if (typeof duration === "number") return duration
231
- const m = /^(\d+)(ms|s|m|h|d|w)$/.exec(duration)
232
- if (!m) throw new Error(`invalid duration "${duration}"`)
233
- const n = Number(m[1])
234
- switch (m[2]) {
235
- case "ms":
236
- return n
237
- case "s":
238
- return n * 1_000
239
- case "m":
240
- return n * 60_000
241
- case "h":
242
- return n * 3_600_000
243
- case "d":
244
- return n * 86_400_000
245
- case "w":
246
- return n * 604_800_000
247
- default:
248
- throw new Error(`invalid duration "${duration}"`)
249
- }
250
- }
@@ -6,6 +6,7 @@ import { findEarliestWakeAt } from "./sleep-alarm-manager.js"
6
6
  export interface WakeupRecord {
7
7
  runId: string
8
8
  wakeAt: number
9
+ priority?: number
9
10
  leaseOwner?: string
10
11
  leaseExpiresAt?: number
11
12
  updatedAt: number
@@ -74,7 +75,7 @@ export function createFsWakeupStore(opts: FsWakeupStoreOptions = {}): WakeupStor
74
75
  },
75
76
 
76
77
  async leaseDue({ owner, now: at = now(), leaseMs, limit = 25 }) {
77
- const wakeups = await this.list()
78
+ const wakeups = (await this.list()).sort(compareWakeupClaimOrder)
78
79
  const leased: WakeupRecord[] = []
79
80
  for (const wakeup of wakeups) {
80
81
  if (leased.length >= limit) break
@@ -119,11 +120,18 @@ export async function syncWakeupFromRecord(store: WakeupStore, record: RunRecord
119
120
  await store.upsert({
120
121
  runId: record.id,
121
122
  wakeAt,
123
+ priority: record.priority,
122
124
  leaseOwner: undefined,
123
125
  leaseExpiresAt: undefined,
124
126
  })
125
127
  }
126
128
 
129
+ function compareWakeupClaimOrder(a: WakeupRecord, b: WakeupRecord): number {
130
+ const priorityDelta = (b.priority ?? 0) - (a.priority ?? 0)
131
+ if (priorityDelta !== 0) return priorityDelta
132
+ return a.wakeAt - b.wakeAt
133
+ }
134
+
127
135
  async function safeReaddir(dir: string): Promise<string[]> {
128
136
  try {
129
137
  const entries = await readdir(dir)