@softure-ai/jobs 0.1.1 → 0.1.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.
- package/CHANGELOG.md +39 -0
- package/README.md +63 -10
- package/dist/index.d.ts +13 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +23 -11
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +37 -4
- package/dist/options.js.map +1 -1
- package/dist/schema.d.ts +34 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -1
- package/dist/server/claim.d.ts +2 -1
- package/dist/server/claim.d.ts.map +1 -1
- package/dist/server/claim.js +22 -14
- package/dist/server/claim.js.map +1 -1
- package/dist/server/schedules.d.ts +13 -1
- package/dist/server/schedules.d.ts.map +1 -1
- package/dist/server/schedules.js +51 -4
- package/dist/server/schedules.js.map +1 -1
- package/dist/worker/health.d.ts +30 -0
- package/dist/worker/health.d.ts.map +1 -0
- package/dist/worker/health.js +53 -0
- package/dist/worker/health.js.map +1 -0
- package/dist/worker/index.d.ts +1 -0
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +3 -2
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/process.d.ts +8 -2
- package/dist/worker/process.d.ts.map +1 -1
- package/dist/worker/process.js +27 -3
- package/dist/worker/process.js.map +1 -1
- package/dist/worker/run-job-worker.d.ts +5 -0
- package/dist/worker/run-job-worker.d.ts.map +1 -1
- package/dist/worker/run-job-worker.js +9 -0
- package/dist/worker/run-job-worker.js.map +1 -1
- package/dist/zoned-time.d.ts +19 -0
- package/dist/zoned-time.d.ts.map +1 -0
- package/dist/zoned-time.js +61 -0
- package/dist/zoned-time.js.map +1 -0
- package/migrations/0003_add_schedule_failures.sql +8 -0
- package/module.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -1
- package/src/options.ts +55 -8
- package/src/schema.ts +2 -0
- package/src/server/claim.ts +20 -14
- package/src/server/schedules.ts +58 -5
- package/src/worker/health.ts +64 -0
- package/src/worker/index.ts +3 -2
- package/src/worker/process.ts +32 -4
- package/src/worker/run-job-worker.ts +13 -0
- package/src/zoned-time.ts +75 -0
|
@@ -7,22 +7,32 @@ import type { JobsContext } from "./context.js";
|
|
|
7
7
|
export declare const SCHEDULE_DEDUP_KEY = "schedule";
|
|
8
8
|
/** A schedule's health as `getQueueStats` reports it. */
|
|
9
9
|
export interface ScheduleState {
|
|
10
|
+
/** The period; for a local-time schedule the longest gap between two of its times of day (24h for one time). */
|
|
10
11
|
readonly everySeconds: number;
|
|
11
12
|
/** The start of the latest period a run was enqueued for; null before any worker saw the schedule. */
|
|
12
13
|
readonly lastEnqueuedAt: Date | null;
|
|
13
14
|
/** When a job of the schedule's queue last succeeded; null when none has yet. */
|
|
14
15
|
readonly lastSucceededAt: Date | null;
|
|
16
|
+
/** When a job of the schedule's queue last ended `failed` (attempts used up, or failed for good); null when none has. */
|
|
17
|
+
readonly lastFailedAt: Date | null;
|
|
18
|
+
/** That job's error code (the handler's code, `jobs.handler_threw`, `jobs.lease_expired`…); null when none failed. */
|
|
19
|
+
readonly lastFailureCode: string | null;
|
|
15
20
|
/**
|
|
16
21
|
* True when no run succeeded within two periods (counted from when a worker first saw the schedule), or no worker
|
|
17
22
|
* has seen it at all: no worker runs its queue, or its runs keep failing.
|
|
18
23
|
*/
|
|
19
24
|
readonly isLate: boolean;
|
|
25
|
+
/** True when the latest outcome is a failure: `lastFailedAt` is later than `lastSucceededAt`, or nothing succeeded. */
|
|
26
|
+
readonly isFailing: boolean;
|
|
20
27
|
}
|
|
21
28
|
export interface EnqueueDueSchedulesInput {
|
|
22
29
|
/** Only the schedules of these queues; default every schedule. */
|
|
23
30
|
readonly queues?: readonly string[];
|
|
24
31
|
}
|
|
25
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The slot `now` falls in: the start of its period, or the latest of the schedule's local times at or before `now`.
|
|
34
|
+
* Slots only move forward as `now` does, across DST changes too.
|
|
35
|
+
*/
|
|
26
36
|
export declare function getScheduleSlot(schedule: JobScheduleOptions, now: Date): Date;
|
|
27
37
|
/**
|
|
28
38
|
* Enqueues a run for every schedule whose current period has none yet, and returns their queue names. Missed periods
|
|
@@ -37,6 +47,8 @@ export declare function enqueueDueSchedules(ctx: JobsContext, input?: EnqueueDue
|
|
|
37
47
|
export declare function enqueueScheduledRun(ctx: JobsContext, name: string, slot: Date): Promise<boolean>;
|
|
38
48
|
/** Notes a succeeded job of a scheduled queue (any job of it, a run enqueued by hand too). */
|
|
39
49
|
export declare function recordScheduleSuccess(ctx: JobsContext, queue: string): Promise<void>;
|
|
50
|
+
/** Notes a failed job of a scheduled queue (terminal, not a retried attempt) with its error code. */
|
|
51
|
+
export declare function recordScheduleFailure(ctx: JobsContext, queue: string, code: string): Promise<void>;
|
|
40
52
|
/** The state of every configured schedule, by queue name. */
|
|
41
53
|
export declare function getScheduleStates(ctx: JobsContext): Promise<Map<string, ScheduleState>>;
|
|
42
54
|
/** The schedules among `queues` (default all), in configuration order. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schedules.d.ts","sourceRoot":"","sources":["../../src/server/schedules.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"schedules.d.ts","sourceRoot":"","sources":["../../src/server/schedules.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAiC,MAAM,eAAe,CAAC;AAG5H,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAE7C,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,gHAAgH;IAChH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,sGAAsG;IACtG,QAAQ,CAAC,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;IACrC,iFAAiF;IACjF,QAAQ,CAAC,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IACtC,yHAAyH;IACzH,QAAQ,CAAC,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IACnC,sHAAsH;IACtH,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uHAAuH;IACvH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI,CAI7E;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnH;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBtG;AAED,8FAA8F;AAC9F,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI1F;AAED,qGAAqG;AACrG,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxG;AAED,6DAA6D;AAC7D,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAmB7F;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAEtG;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAIlF"}
|
package/dist/server/schedules.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// Recurring schedules:
|
|
2
|
-
// computes the same
|
|
1
|
+
// Recurring schedules: a period schedule's runs start on whole periods counted from the Unix epoch, a local-time
|
|
2
|
+
// schedule's at its listed times of day in its time zone, so every worker computes the same slot. A worker takes a period with a conditional upsert of `jobs.schedules` and enqueues its run
|
|
3
3
|
// in the same transaction, so each period is enqueued once however many workers race for it.
|
|
4
4
|
import { eq, inArray, lt } from "drizzle-orm";
|
|
5
5
|
import { parseEverySeconds } from "../options.js";
|
|
6
6
|
import { schedules } from "../schema.js";
|
|
7
|
+
import { getWallClock, getZonedInstant } from "../zoned-time.js";
|
|
7
8
|
import { enqueueJob } from "./enqueue.js";
|
|
8
9
|
import { getJobsOptions } from "./options.js";
|
|
9
10
|
/**
|
|
@@ -11,8 +12,13 @@ import { getJobsOptions } from "./options.js";
|
|
|
11
12
|
* the next period joins it instead of piling up a second run behind it.
|
|
12
13
|
*/
|
|
13
14
|
export const SCHEDULE_DEDUP_KEY = "schedule";
|
|
14
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The slot `now` falls in: the start of its period, or the latest of the schedule's local times at or before `now`.
|
|
17
|
+
* Slots only move forward as `now` does, across DST changes too.
|
|
18
|
+
*/
|
|
15
19
|
export function getScheduleSlot(schedule, now) {
|
|
20
|
+
if ("at" in schedule)
|
|
21
|
+
return new Date(getLocalTimeSlot(schedule, now.getTime()));
|
|
16
22
|
const periodMs = getEverySeconds(schedule) * 1000;
|
|
17
23
|
return new Date(Math.floor(now.getTime() / periodMs) * periodMs);
|
|
18
24
|
}
|
|
@@ -61,6 +67,13 @@ export async function recordScheduleSuccess(ctx, queue) {
|
|
|
61
67
|
const now = ctx.clock.now();
|
|
62
68
|
await ctx.db.update(schedules).set({ lastSucceededAt: now, updatedAt: now }).where(eq(schedules.name, queue));
|
|
63
69
|
}
|
|
70
|
+
/** Notes a failed job of a scheduled queue (terminal, not a retried attempt) with its error code. */
|
|
71
|
+
export async function recordScheduleFailure(ctx, queue, code) {
|
|
72
|
+
if (!Object.hasOwn(getJobsOptions(ctx.config).schedules, queue))
|
|
73
|
+
return;
|
|
74
|
+
const now = ctx.clock.now();
|
|
75
|
+
await ctx.db.update(schedules).set({ lastFailedAt: now, lastFailureCode: code, updatedAt: now }).where(eq(schedules.name, queue));
|
|
76
|
+
}
|
|
64
77
|
/** The state of every configured schedule, by queue name. */
|
|
65
78
|
export async function getScheduleStates(ctx) {
|
|
66
79
|
const options = getJobsOptions(ctx.config);
|
|
@@ -74,7 +87,11 @@ export async function getScheduleStates(ctx) {
|
|
|
74
87
|
const row = rows.find((candidate) => candidate.name === name);
|
|
75
88
|
const since = row?.lastSucceededAt ?? row?.createdAt ?? null;
|
|
76
89
|
const isLate = since === null || now - since.getTime() > 2 * everySeconds * 1000;
|
|
77
|
-
|
|
90
|
+
const lastSucceededAt = row?.lastSucceededAt ?? null;
|
|
91
|
+
const lastFailedAt = row?.lastFailedAt ?? null;
|
|
92
|
+
const isFailing = lastFailedAt !== null && (lastSucceededAt === null || lastFailedAt > lastSucceededAt);
|
|
93
|
+
const lastFailureCode = row?.lastFailureCode ?? null;
|
|
94
|
+
return [name, { everySeconds, lastEnqueuedAt: row?.lastSlotAt ?? null, lastSucceededAt, lastFailedAt, lastFailureCode, isLate, isFailing }];
|
|
78
95
|
}));
|
|
79
96
|
}
|
|
80
97
|
/** The schedules among `queues` (default all), in configuration order. */
|
|
@@ -88,10 +105,40 @@ export function getSchedule(options, name) {
|
|
|
88
105
|
return schedule;
|
|
89
106
|
}
|
|
90
107
|
function getEverySeconds(schedule) {
|
|
108
|
+
if ("at" in schedule)
|
|
109
|
+
return getLongestGapSeconds(schedule.at);
|
|
91
110
|
const seconds = parseEverySeconds(schedule.every);
|
|
92
111
|
// The options schema refused any other value at startup.
|
|
93
112
|
if (seconds === null)
|
|
94
113
|
throw new Error(`@softure-ai/jobs: invalid schedule period "${schedule.every}"`);
|
|
95
114
|
return seconds;
|
|
96
115
|
}
|
|
116
|
+
function getLocalTimeSlot(schedule, now) {
|
|
117
|
+
const today = getWallClock(now, schedule.timeZone);
|
|
118
|
+
// The calendar day before today's local date; Date.UTC rolls day 0 over to the month before.
|
|
119
|
+
const yesterday = new Date(Date.UTC(today.year, today.month - 1, today.day - 1));
|
|
120
|
+
const days = [
|
|
121
|
+
{ year: yesterday.getUTCFullYear(), month: yesterday.getUTCMonth() + 1, day: yesterday.getUTCDate() },
|
|
122
|
+
{ year: today.year, month: today.month, day: today.day },
|
|
123
|
+
];
|
|
124
|
+
let slot = Number.NEGATIVE_INFINITY;
|
|
125
|
+
for (const day of days) {
|
|
126
|
+
for (const time of schedule.at) {
|
|
127
|
+
const [hour, minute] = time.split(":").map(Number);
|
|
128
|
+
const instant = getZonedInstant({ ...day, hour: hour ?? 0, minute: minute ?? 0 }, schedule.timeZone);
|
|
129
|
+
if (instant <= now && instant > slot)
|
|
130
|
+
slot = instant;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return slot;
|
|
134
|
+
}
|
|
135
|
+
/** The longest gap between two consecutive times of day, wrapping past midnight; `times` is sorted. */
|
|
136
|
+
function getLongestGapSeconds(times) {
|
|
137
|
+
const minutes = times.map((time) => {
|
|
138
|
+
const [hour, minute] = time.split(":").map(Number);
|
|
139
|
+
return (hour ?? 0) * 60 + (minute ?? 0);
|
|
140
|
+
});
|
|
141
|
+
const gaps = minutes.map((minute, index) => (minutes[(index + 1) % minutes.length] ?? minute) - minute + (index === minutes.length - 1 ? 24 * 60 : 0));
|
|
142
|
+
return Math.max(...gaps) * 60;
|
|
143
|
+
}
|
|
97
144
|
//# sourceMappingURL=schedules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schedules.js","sourceRoot":"","sources":["../../src/server/schedules.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"schedules.js","sourceRoot":"","sources":["../../src/server/schedules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SoftureConfig } from "@softure-ai/core";
|
|
2
|
+
import type { JobsLogger } from "../server/logger.js";
|
|
3
|
+
export type JobWorkerHealth = {
|
|
4
|
+
ok: true;
|
|
5
|
+
ageMs: number;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
reason: "missing";
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: "stale";
|
|
12
|
+
ageMs: number;
|
|
13
|
+
};
|
|
14
|
+
export interface CheckJobWorkerHealthInput {
|
|
15
|
+
readonly healthFile: string;
|
|
16
|
+
/** The oldest heartbeat that still counts; `getJobWorkerHealthMaxAgeMs(config)` by default in the process. */
|
|
17
|
+
readonly maxAgeMs: number;
|
|
18
|
+
/** Epoch ms to compare the file's modification time with; default `Date.now()`. */
|
|
19
|
+
readonly now?: number;
|
|
20
|
+
}
|
|
21
|
+
/** The default age limit: five poll intervals, at least 30 s. */
|
|
22
|
+
export declare function getJobWorkerHealthMaxAgeMs(config: SoftureConfig): number;
|
|
23
|
+
/** Whether the heartbeat file exists and is no older than `maxAgeMs`. Reads the file system only. */
|
|
24
|
+
export declare function checkJobWorkerHealth(input: CheckJobWorkerHealthInput): JobWorkerHealth;
|
|
25
|
+
/** Writes and removes the heartbeat file; a failure is logged once until a write succeeds again, never thrown. */
|
|
26
|
+
export declare function createHealthFile(healthFile: string, logger: JobsLogger): {
|
|
27
|
+
touch: () => void;
|
|
28
|
+
remove: () => void;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/worker/health.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAOtD,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7I,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8GAA8G;IAC9G,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,iEAAiE;AACjE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAExE;AAED,qGAAqG;AACrG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,eAAe,CAStF;AAED,kHAAkH;AAClH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,CAwBlH"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// The worker's heartbeat: a file the loop touches after each round whose claims reached the database, and the check
|
|
2
|
+
// an image runs (`node worker.mjs --health`) to tell a running loop from a process that only stays alive.
|
|
3
|
+
import { rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { getJobsOptions } from "../server/options.js";
|
|
5
|
+
/** The age limit never drops below this, so a slow round (an hourly prune) does not read as a stuck loop. */
|
|
6
|
+
const MIN_HEALTH_MAX_AGE_MS = 30_000;
|
|
7
|
+
const POLL_INTERVALS_PER_MAX_AGE = 5;
|
|
8
|
+
/** The default age limit: five poll intervals, at least 30 s. */
|
|
9
|
+
export function getJobWorkerHealthMaxAgeMs(config) {
|
|
10
|
+
return Math.max(MIN_HEALTH_MAX_AGE_MS, POLL_INTERVALS_PER_MAX_AGE * getJobsOptions(config).pollIntervalMs);
|
|
11
|
+
}
|
|
12
|
+
/** Whether the heartbeat file exists and is no older than `maxAgeMs`. Reads the file system only. */
|
|
13
|
+
export function checkJobWorkerHealth(input) {
|
|
14
|
+
let mtimeMs;
|
|
15
|
+
try {
|
|
16
|
+
mtimeMs = statSync(input.healthFile).mtimeMs;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { ok: false, reason: "missing" };
|
|
20
|
+
}
|
|
21
|
+
const ageMs = Math.max(0, Math.round((input.now ?? Date.now()) - mtimeMs));
|
|
22
|
+
return ageMs > input.maxAgeMs ? { ok: false, reason: "stale", ageMs } : { ok: true, ageMs };
|
|
23
|
+
}
|
|
24
|
+
/** Writes and removes the heartbeat file; a failure is logged once until a write succeeds again, never thrown. */
|
|
25
|
+
export function createHealthFile(healthFile, logger) {
|
|
26
|
+
let hasFailed = false;
|
|
27
|
+
const report = (operation, error) => {
|
|
28
|
+
if (hasFailed)
|
|
29
|
+
return;
|
|
30
|
+
hasFailed = true;
|
|
31
|
+
logger.error(`could not ${operation} the worker's health file`, { healthFile, error });
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
touch: () => {
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(healthFile, new Date().toISOString());
|
|
37
|
+
hasFailed = false;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
report("write", error);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
remove: () => {
|
|
44
|
+
try {
|
|
45
|
+
rmSync(healthFile, { force: true });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
report("remove", error);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/worker/health.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,0GAA0G;AAC1G,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,6GAA6G;AAC7G,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAYrC,iEAAiE;AACjE,MAAM,UAAU,0BAA0B,CAAC,MAAqB;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,qBAAqB,EAAE,0BAA0B,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;AAC7G,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAC3E,OAAO,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC9F,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,MAAkB;IACrE,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,MAAM,MAAM,GAAG,CAAC,SAAiB,EAAE,KAAc,EAAE,EAAE;QACnD,IAAI,SAAS;YAAE,OAAO;QACtB,SAAS,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,KAAK,CAAC,aAAa,SAAS,2BAA2B,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IACzF,CAAC,CAAC;IACF,OAAO;QACL,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,CAAC;gBACH,aAAa,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;gBACpD,SAAS,GAAG,KAAK,CAAC;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QACD,MAAM,EAAE,GAAG,EAAE;YACX,IAAI,CAAC;gBACH,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { checkJobWorkerHealth, getJobWorkerHealthMaxAgeMs, type CheckJobWorkerHealthInput, type JobWorkerHealth } from "./health.js";
|
|
1
2
|
export { runJobWorkerProcess, type RunJobWorkerProcessOptions } from "./process.js";
|
|
2
3
|
export { runJobWorker, type RunJobWorkerOptions } from "./run-job-worker.js";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,KAAK,yBAAyB,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACrI,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/worker/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// The worker of @softure-ai/jobs: the loop (`runJobWorker`)
|
|
2
|
-
// (`runJobWorkerProcess`).
|
|
1
|
+
// The worker of @softure-ai/jobs: the loop (`runJobWorker`), the process entry an app's `scripts/worker.ts` calls
|
|
2
|
+
// (`runJobWorkerProcess`) and the check of its heartbeat (`checkJobWorkerHealth`).
|
|
3
|
+
export { checkJobWorkerHealth, getJobWorkerHealthMaxAgeMs } from "./health.js";
|
|
3
4
|
export { runJobWorkerProcess } from "./process.js";
|
|
4
5
|
export { runJobWorker } from "./run-job-worker.js";
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
package/dist/worker/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,mFAAmF;AACnF,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAwD,MAAM,aAAa,CAAC;AACrI,OAAO,EAAE,mBAAmB,EAAmC,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,YAAY,EAA4B,MAAM,qBAAqB,CAAC"}
|
package/dist/worker/process.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Database } from "@softure-ai/db";
|
|
|
3
3
|
import type { JobHandlers } from "../handlers.js";
|
|
4
4
|
import { type JobsLogger } from "../server/logger.js";
|
|
5
5
|
import { type RunJobWorkerOptions } from "./run-job-worker.js";
|
|
6
|
-
export interface RunJobWorkerProcessOptions extends Partial<Pick<RunJobWorkerOptions, "queues" | "clock" | "workerId" | "random" | "heartbeatMs">> {
|
|
6
|
+
export interface RunJobWorkerProcessOptions extends Partial<Pick<RunJobWorkerOptions, "queues" | "clock" | "workerId" | "random" | "heartbeatMs" | "healthFile">> {
|
|
7
7
|
readonly config: SoftureConfig;
|
|
8
8
|
readonly handlers: JobHandlers;
|
|
9
9
|
readonly logger?: JobsLogger;
|
|
@@ -11,10 +11,16 @@ export interface RunJobWorkerProcessOptions extends Partial<Pick<RunJobWorkerOpt
|
|
|
11
11
|
readonly database?: Database;
|
|
12
12
|
/** Stops the worker instead of SIGTERM and SIGINT (tests). */
|
|
13
13
|
readonly signal?: AbortSignal;
|
|
14
|
+
/** The oldest heartbeat `--health` accepts; default `getJobWorkerHealthMaxAgeMs(config)` (five poll intervals, at least 30 s). */
|
|
15
|
+
readonly healthMaxAgeMs?: number;
|
|
16
|
+
/** The process arguments; default `process.argv.slice(2)`. `--health` checks `healthFile` instead of running. */
|
|
17
|
+
readonly argv?: readonly string[];
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Runs the worker until SIGTERM or SIGINT, then stops gracefully. Resolves to the exit code and sets
|
|
17
|
-
* `process.exitCode`: 0 after a clean stop, 1 when it could not start or the database could not be opened.
|
|
21
|
+
* `process.exitCode`: 0 after a clean stop, 1 when it could not start or the database could not be opened. Started
|
|
22
|
+
* with `--health`, it opens nothing and checks the running worker's `healthFile` instead: 0 when its loop ran within
|
|
23
|
+
* `healthMaxAgeMs`, else 1.
|
|
18
24
|
*
|
|
19
25
|
* // scripts/worker.ts
|
|
20
26
|
* import { runJobWorkerProcess } from "@softure-ai/jobs/worker";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/worker/process.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/worker/process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGzE,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAO7E,MAAM,WAAW,0BACf,SAAQ,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC;IACrH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,kIAAkI;IAClI,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,iHAAiH;IACjH,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9F"}
|
package/dist/worker/process.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// The worker as a process: what an app's `scripts/worker.ts` calls. It opens its own database pool, turns SIGTERM and
|
|
2
|
-
// SIGINT into a graceful stop, closes the pool and reports an exit code.
|
|
2
|
+
// SIGINT into a graceful stop, closes the pool and reports an exit code. With `--health` it checks the heartbeat file
|
|
3
|
+
// of a running worker instead.
|
|
3
4
|
import { errorLogLabel } from "@softure-ai/core";
|
|
4
5
|
import { openCommandDatabase } from "@softure-ai/db";
|
|
5
6
|
import { consoleJobsLogger } from "../server/logger.js";
|
|
6
7
|
import { getJobsOptions } from "../server/options.js";
|
|
8
|
+
import { checkJobWorkerHealth, getJobWorkerHealthMaxAgeMs } from "./health.js";
|
|
7
9
|
import { runJobWorker } from "./run-job-worker.js";
|
|
8
10
|
const EXIT_OK = 0;
|
|
9
11
|
const EXIT_FAILED = 1;
|
|
10
12
|
const STOP_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
13
|
+
const HEALTH_FLAG = "--health";
|
|
11
14
|
/**
|
|
12
15
|
* Runs the worker until SIGTERM or SIGINT, then stops gracefully. Resolves to the exit code and sets
|
|
13
|
-
* `process.exitCode`: 0 after a clean stop, 1 when it could not start or the database could not be opened.
|
|
16
|
+
* `process.exitCode`: 0 after a clean stop, 1 when it could not start or the database could not be opened. Started
|
|
17
|
+
* with `--health`, it opens nothing and checks the running worker's `healthFile` instead: 0 when its loop ran within
|
|
18
|
+
* `healthMaxAgeMs`, else 1.
|
|
14
19
|
*
|
|
15
20
|
* // scripts/worker.ts
|
|
16
21
|
* import { runJobWorkerProcess } from "@softure-ai/jobs/worker";
|
|
@@ -19,10 +24,29 @@ const STOP_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
|
19
24
|
* await runJobWorkerProcess({ config, handlers });
|
|
20
25
|
*/
|
|
21
26
|
export async function runJobWorkerProcess(options) {
|
|
22
|
-
const
|
|
27
|
+
const argv = options.argv ?? process.argv.slice(2);
|
|
28
|
+
const exitCode = argv.includes(HEALTH_FLAG) ? checkHealth(options) : await runProcess(options);
|
|
23
29
|
process.exitCode = exitCode;
|
|
24
30
|
return exitCode;
|
|
25
31
|
}
|
|
32
|
+
function checkHealth(options) {
|
|
33
|
+
const logger = options.logger ?? consoleJobsLogger;
|
|
34
|
+
if (options.healthFile === undefined) {
|
|
35
|
+
logger.error(`${HEALTH_FLAG} needs the worker's healthFile option; pass healthFile to runJobWorkerProcess`);
|
|
36
|
+
return EXIT_FAILED;
|
|
37
|
+
}
|
|
38
|
+
const maxAgeMs = options.healthMaxAgeMs ?? getJobWorkerHealthMaxAgeMs(options.config);
|
|
39
|
+
const health = checkJobWorkerHealth({ healthFile: options.healthFile, maxAgeMs });
|
|
40
|
+
if (health.ok) {
|
|
41
|
+
logger.info(`the worker's loop ran ${String(health.ageMs)} ms ago`);
|
|
42
|
+
return EXIT_OK;
|
|
43
|
+
}
|
|
44
|
+
if (health.reason === "missing")
|
|
45
|
+
logger.error(`the worker's health file is missing: ${options.healthFile}`);
|
|
46
|
+
else
|
|
47
|
+
logger.error(`the worker's loop last ran ${String(health.ageMs)} ms ago (limit ${String(maxAgeMs)} ms)`);
|
|
48
|
+
return EXIT_FAILED;
|
|
49
|
+
}
|
|
26
50
|
async function runProcess(options) {
|
|
27
51
|
const logger = options.logger ?? consoleJobsLogger;
|
|
28
52
|
const database = options.config.database;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/worker/process.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/worker/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,11 @@ export interface RunJobWorkerOptions {
|
|
|
19
19
|
readonly random?: () => number;
|
|
20
20
|
/** How often a running job's lease is extended; default a third of the queue's `leaseSeconds`. */
|
|
21
21
|
readonly heartbeatMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* A file the loop touches after each round whose claims reached the database (removed at start and after a clean
|
|
24
|
+
* stop), for a health check of the loop: `checkJobWorkerHealth`. Default none.
|
|
25
|
+
*/
|
|
26
|
+
readonly healthFile?: string;
|
|
22
27
|
}
|
|
23
28
|
/**
|
|
24
29
|
* Runs until `signal` aborts, then resolves once every job finished or was released. Throws before starting on a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-job-worker.d.ts","sourceRoot":"","sources":["../../src/worker/run-job-worker.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAIlD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-job-worker.d.ts","sourceRoot":"","sources":["../../src/worker/run-job-worker.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAIlD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAWzE,MAAM,WAAW,mBAAmB;IAClC,oHAAoH;IACpH,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,gGAAgG;IAChG,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,kGAAkG;IAClG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAQD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiG9E"}
|
|
@@ -10,6 +10,7 @@ import { pruneJobs } from "../server/maintenance.js";
|
|
|
10
10
|
import { getJobsOptions, getQueue } from "../server/options.js";
|
|
11
11
|
import { runClaimedJob } from "../server/process.js";
|
|
12
12
|
import { enqueueScheduledRun, getSchedule, getScheduleNames, getScheduleSlot } from "../server/schedules.js";
|
|
13
|
+
import { createHealthFile } from "./health.js";
|
|
13
14
|
const PRUNE_INTERVAL_MS = 60 * 60 * 1000;
|
|
14
15
|
/** A lease is extended three times per lease period, so one slow write does not lose it. */
|
|
15
16
|
const HEARTBEATS_PER_LEASE = 3;
|
|
@@ -38,6 +39,9 @@ export async function runJobWorker(options) {
|
|
|
38
39
|
if (idle.length > 0 && options.queues === undefined) {
|
|
39
40
|
logger.warn("configured queues without a handler are not run by this worker", { queues: idle.join(", ") });
|
|
40
41
|
}
|
|
42
|
+
const healthFile = options.healthFile === undefined ? undefined : createHealthFile(options.healthFile, logger);
|
|
43
|
+
// A file left by an earlier run of the same container must not pass the check before this loop ran a round.
|
|
44
|
+
healthFile?.remove();
|
|
41
45
|
logger.info("worker started", { workerId, queues: queues.join(", ") });
|
|
42
46
|
const start = (claimed) => {
|
|
43
47
|
const controller = new AbortController();
|
|
@@ -78,6 +82,7 @@ export async function runJobWorker(options) {
|
|
|
78
82
|
logger.error("could not enqueue a scheduled run; trying again next round", { queue: name, error });
|
|
79
83
|
}
|
|
80
84
|
}
|
|
85
|
+
let isClaimFailed = false;
|
|
81
86
|
for (const queue of queues) {
|
|
82
87
|
const slots = running.get(queue) ?? new Set();
|
|
83
88
|
const free = getQueue(jobsOptions, queue).concurrency - slots.size;
|
|
@@ -89,6 +94,7 @@ export async function runJobWorker(options) {
|
|
|
89
94
|
slots.add(start(job));
|
|
90
95
|
}
|
|
91
96
|
catch (error) {
|
|
97
|
+
isClaimFailed = true;
|
|
92
98
|
logger.error("could not claim jobs", { queue, error });
|
|
93
99
|
}
|
|
94
100
|
}
|
|
@@ -103,9 +109,12 @@ export async function runJobWorker(options) {
|
|
|
103
109
|
logger.error("could not prune finished jobs", { error });
|
|
104
110
|
}
|
|
105
111
|
}
|
|
112
|
+
if (!isClaimFailed)
|
|
113
|
+
healthFile?.touch();
|
|
106
114
|
await wakeUp.wait(jobsOptions.pollIntervalMs, options.signal);
|
|
107
115
|
}
|
|
108
116
|
await stop(running, { ctx, logger, graceMs: jobsOptions.shutdownGraceMs });
|
|
117
|
+
healthFile?.remove();
|
|
109
118
|
logger.info("worker stopped", { workerId });
|
|
110
119
|
}
|
|
111
120
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-job-worker.js","sourceRoot":"","sources":["../../src/worker/run-job-worker.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,sHAAsH;AACtH,sFAAsF;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,WAAW,EAAkC,MAAM,kBAAkB,CAAC;AAG/E,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAmB,MAAM,oBAAoB,CAAC;AAE5F,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAmB,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-job-worker.js","sourceRoot":"","sources":["../../src/worker/run-job-worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A date and a time of day as a clock in some time zone shows them; `month` counts from 1. */
|
|
2
|
+
export interface WallClock {
|
|
3
|
+
readonly year: number;
|
|
4
|
+
readonly month: number;
|
|
5
|
+
readonly day: number;
|
|
6
|
+
readonly hour: number;
|
|
7
|
+
readonly minute: number;
|
|
8
|
+
}
|
|
9
|
+
/** True when `timeZone` is an IANA zone the runtime knows, such as `Europe/Warsaw` or `UTC`. */
|
|
10
|
+
export declare function isTimeZone(timeZone: string): boolean;
|
|
11
|
+
/** The wall clock of `instant` in `timeZone`, to the minute. */
|
|
12
|
+
export declare function getWallClock(instant: number, timeZone: string): WallClock;
|
|
13
|
+
/**
|
|
14
|
+
* The instant a clock in `timeZone` shows `wallClock`. A time that occurs twice (the hour a DST change repeats) gives
|
|
15
|
+
* its first occurrence; a time that does not occur (the hour a DST change skips) gives the instant the same distance
|
|
16
|
+
* after the change, so 02:30 on a day that jumps from 02:00 to 03:00 gives 03:30.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getZonedInstant(wallClock: WallClock, timeZone: string): number;
|
|
19
|
+
//# sourceMappingURL=zoned-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zoned-time.d.ts","sourceRoot":"","sources":["../src/zoned-time.ts"],"names":[],"mappings":"AAKA,+FAA+F;AAC/F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,gGAAgG;AAChG,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CASzE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO9E"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Wall-clock time in an IANA time zone, with the platform's Intl data and no date library.
|
|
2
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
3
|
+
const formatters = new Map();
|
|
4
|
+
/** True when `timeZone` is an IANA zone the runtime knows, such as `Europe/Warsaw` or `UTC`. */
|
|
5
|
+
export function isTimeZone(timeZone) {
|
|
6
|
+
try {
|
|
7
|
+
getFormatter(timeZone);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** The wall clock of `instant` in `timeZone`, to the minute. */
|
|
15
|
+
export function getWallClock(instant, timeZone) {
|
|
16
|
+
const parts = Object.fromEntries(getFormatter(timeZone).formatToParts(new Date(instant)).map((part) => [part.type, part.value]));
|
|
17
|
+
return {
|
|
18
|
+
year: Number(parts.year),
|
|
19
|
+
month: Number(parts.month),
|
|
20
|
+
day: Number(parts.day),
|
|
21
|
+
hour: Number(parts.hour),
|
|
22
|
+
minute: Number(parts.minute),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The instant a clock in `timeZone` shows `wallClock`. A time that occurs twice (the hour a DST change repeats) gives
|
|
27
|
+
* its first occurrence; a time that does not occur (the hour a DST change skips) gives the instant the same distance
|
|
28
|
+
* after the change, so 02:30 on a day that jumps from 02:00 to 03:00 gives 03:30.
|
|
29
|
+
*/
|
|
30
|
+
export function getZonedInstant(wallClock, timeZone) {
|
|
31
|
+
const asUtc = toUtcMs(wallClock);
|
|
32
|
+
// One DST change a day at most: the offsets half a day before and after cover both sides of it.
|
|
33
|
+
const offsetBefore = getOffsetMs(asUtc - 12 * HOUR_MS, timeZone);
|
|
34
|
+
const offsetAfter = getOffsetMs(asUtc + 12 * HOUR_MS, timeZone);
|
|
35
|
+
const matching = [asUtc - offsetBefore, asUtc - offsetAfter].filter((instant) => toUtcMs(getWallClock(instant, timeZone)) === asUtc);
|
|
36
|
+
return matching.length > 0 ? Math.min(...matching) : asUtc - offsetBefore;
|
|
37
|
+
}
|
|
38
|
+
function getOffsetMs(instant, timeZone) {
|
|
39
|
+
const minute = Math.floor(instant / 60_000) * 60_000;
|
|
40
|
+
return toUtcMs(getWallClock(minute, timeZone)) - minute;
|
|
41
|
+
}
|
|
42
|
+
function toUtcMs(wallClock) {
|
|
43
|
+
return Date.UTC(wallClock.year, wallClock.month - 1, wallClock.day, wallClock.hour, wallClock.minute);
|
|
44
|
+
}
|
|
45
|
+
function getFormatter(timeZone) {
|
|
46
|
+
let formatter = formatters.get(timeZone);
|
|
47
|
+
if (formatter === undefined) {
|
|
48
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
49
|
+
timeZone,
|
|
50
|
+
hourCycle: "h23",
|
|
51
|
+
year: "numeric",
|
|
52
|
+
month: "numeric",
|
|
53
|
+
day: "numeric",
|
|
54
|
+
hour: "numeric",
|
|
55
|
+
minute: "numeric",
|
|
56
|
+
});
|
|
57
|
+
formatters.set(timeZone, formatter);
|
|
58
|
+
}
|
|
59
|
+
return formatter;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=zoned-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zoned-time.js","sourceRoot":"","sources":["../src/zoned-time.ts"],"names":[],"mappings":"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"}
|