alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
697 lines (696 loc) • 26.1 kB
JavaScript
import { $atom, $hook, $inject, $module, $pipeline, $state, Alepha, KIND, Primitive, createPrimitive, z } from "alepha";
import { $lock, AlephaLock, LockAcquireError } from "alepha/lock";
import { DateTimeProvider } from "alepha/datetime";
import { $logger } from "alepha/logger";
//#region ../../../../node_modules/cron-schedule/dist/utils.js
function extractDateElements(date) {
return {
second: date.getSeconds(),
minute: date.getMinutes(),
hour: date.getHours(),
day: date.getDate(),
month: date.getMonth(),
weekday: date.getDay(),
year: date.getFullYear()
};
}
function getDaysInMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}
function getDaysBetweenWeekdays(weekday1, weekday2) {
if (weekday1 <= weekday2) return weekday2 - weekday1;
return 6 - weekday1 + weekday2 + 1;
}
//#endregion
//#region ../../../../node_modules/cron-schedule/dist/cron.js
var Cron = class {
constructor({ seconds, minutes, hours, days, months, weekdays }) {
if (!seconds || seconds.size === 0) throw new Error("There must be at least one allowed second.");
if (!minutes || minutes.size === 0) throw new Error("There must be at least one allowed minute.");
if (!hours || hours.size === 0) throw new Error("There must be at least one allowed hour.");
if (!months || months.size === 0) throw new Error("There must be at least one allowed month.");
if ((!weekdays || weekdays.size === 0) && (!days || days.size === 0)) throw new Error("There must be at least one allowed day or weekday.");
this.seconds = Array.from(seconds).sort((a, b) => a - b);
this.minutes = Array.from(minutes).sort((a, b) => a - b);
this.hours = Array.from(hours).sort((a, b) => a - b);
this.days = Array.from(days).sort((a, b) => a - b);
this.months = Array.from(months).sort((a, b) => a - b);
this.weekdays = Array.from(weekdays).sort((a, b) => a - b);
const validateData = (name, data, constraint) => {
if (data.some((x) => typeof x !== "number" || x % 1 !== 0 || x < constraint.min || x > constraint.max)) throw new Error(`${name} must only consist of integers which are within the range of ${constraint.min} and ${constraint.max}`);
};
validateData("seconds", this.seconds, {
min: 0,
max: 59
});
validateData("minutes", this.minutes, {
min: 0,
max: 59
});
validateData("hours", this.hours, {
min: 0,
max: 23
});
validateData("days", this.days, {
min: 1,
max: 31
});
validateData("months", this.months, {
min: 0,
max: 11
});
validateData("weekdays", this.weekdays, {
min: 0,
max: 6
});
this.reversed = {
seconds: this.seconds.map((x) => x).reverse(),
minutes: this.minutes.map((x) => x).reverse(),
hours: this.hours.map((x) => x).reverse(),
days: this.days.map((x) => x).reverse(),
months: this.months.map((x) => x).reverse(),
weekdays: this.weekdays.map((x) => x).reverse()
};
}
/**
* Find the next or previous hour, starting from the given start hour that matches the hour constraint.
* startHour itself might also be allowed.
*/
findAllowedHour(dir, startHour) {
return dir === "next" ? this.hours.find((x) => x >= startHour) : this.reversed.hours.find((x) => x <= startHour);
}
/**
* Find the next or previous minute, starting from the given start minute that matches the minute constraint.
* startMinute itself might also be allowed.
*/
findAllowedMinute(dir, startMinute) {
return dir === "next" ? this.minutes.find((x) => x >= startMinute) : this.reversed.minutes.find((x) => x <= startMinute);
}
/**
* Find the next or previous second, starting from the given start second that matches the second constraint.
* startSecond itself IS NOT allowed.
*/
findAllowedSecond(dir, startSecond) {
return dir === "next" ? this.seconds.find((x) => x > startSecond) : this.reversed.seconds.find((x) => x < startSecond);
}
/**
* Find the next or previous time, starting from the given start time that matches the hour, minute
* and second constraints. startTime itself might also be allowed.
*/
findAllowedTime(dir, startTime) {
let hour = this.findAllowedHour(dir, startTime.hour);
if (hour !== void 0) if (hour === startTime.hour) {
let minute = this.findAllowedMinute(dir, startTime.minute);
if (minute !== void 0) if (minute === startTime.minute) {
const second = this.findAllowedSecond(dir, startTime.second);
if (second !== void 0) return {
hour,
minute,
second
};
minute = this.findAllowedMinute(dir, dir === "next" ? startTime.minute + 1 : startTime.minute - 1);
if (minute !== void 0) return {
hour,
minute,
second: dir === "next" ? this.seconds[0] : this.reversed.seconds[0]
};
} else return {
hour,
minute,
second: dir === "next" ? this.seconds[0] : this.reversed.seconds[0]
};
hour = this.findAllowedHour(dir, dir === "next" ? startTime.hour + 1 : startTime.hour - 1);
if (hour !== void 0) return {
hour,
minute: dir === "next" ? this.minutes[0] : this.reversed.minutes[0],
second: dir === "next" ? this.seconds[0] : this.reversed.seconds[0]
};
} else return {
hour,
minute: dir === "next" ? this.minutes[0] : this.reversed.minutes[0],
second: dir === "next" ? this.seconds[0] : this.reversed.seconds[0]
};
}
/**
* Find the next or previous day in the given month, starting from the given startDay
* that matches either the day or the weekday constraint. startDay itself might also be allowed.
*/
findAllowedDayInMonth(dir, year, month, startDay) {
var _a, _b;
if (startDay < 1) throw new Error("startDay must not be smaller than 1.");
const daysInMonth = getDaysInMonth(year, month);
const daysRestricted = this.days.length !== 31;
const weekdaysRestricted = this.weekdays.length !== 7;
if (!daysRestricted && !weekdaysRestricted) {
if (startDay > daysInMonth) return dir === "next" ? void 0 : daysInMonth;
return startDay;
}
let allowedDayByDays;
if (daysRestricted) {
allowedDayByDays = dir === "next" ? this.days.find((x) => x >= startDay) : this.reversed.days.find((x) => x <= startDay);
if (allowedDayByDays !== void 0 && allowedDayByDays > daysInMonth) allowedDayByDays = void 0;
}
let allowedDayByWeekdays;
if (weekdaysRestricted) {
const startWeekday = new Date(year, month, startDay).getDay();
const nearestAllowedWeekday = dir === "next" ? (_a = this.weekdays.find((x) => x >= startWeekday)) !== null && _a !== void 0 ? _a : this.weekdays[0] : (_b = this.reversed.weekdays.find((x) => x <= startWeekday)) !== null && _b !== void 0 ? _b : this.reversed.weekdays[0];
if (nearestAllowedWeekday !== void 0) {
const daysBetweenWeekdays = dir === "next" ? getDaysBetweenWeekdays(startWeekday, nearestAllowedWeekday) : getDaysBetweenWeekdays(nearestAllowedWeekday, startWeekday);
allowedDayByWeekdays = dir === "next" ? startDay + daysBetweenWeekdays : startDay - daysBetweenWeekdays;
if (allowedDayByWeekdays > daysInMonth || allowedDayByWeekdays < 1) allowedDayByWeekdays = void 0;
}
}
if (allowedDayByDays !== void 0 && allowedDayByWeekdays !== void 0) return dir === "next" ? Math.min(allowedDayByDays, allowedDayByWeekdays) : Math.max(allowedDayByDays, allowedDayByWeekdays);
if (allowedDayByDays !== void 0) return allowedDayByDays;
if (allowedDayByWeekdays !== void 0) return allowedDayByWeekdays;
}
/** Gets the next date starting from the given start date or now. */
getNextDate(startDate = /* @__PURE__ */ new Date()) {
const startDateElements = extractDateElements(startDate);
let minYear = startDateElements.year;
let startIndexMonth = this.months.findIndex((x) => x >= startDateElements.month);
if (startIndexMonth === -1) {
startIndexMonth = 0;
minYear++;
}
const maxIterations = this.months.length * 5;
for (let i = 0; i < maxIterations; i++) {
const year = minYear + Math.floor((startIndexMonth + i) / this.months.length);
const month = this.months[(startIndexMonth + i) % this.months.length];
const isStartMonth = year === startDateElements.year && month === startDateElements.month;
let day = this.findAllowedDayInMonth("next", year, month, isStartMonth ? startDateElements.day : 1);
let isStartDay = isStartMonth && day === startDateElements.day;
if (day !== void 0 && isStartDay) {
const nextTime = this.findAllowedTime("next", startDateElements);
if (nextTime !== void 0) return new Date(year, month, day, nextTime.hour, nextTime.minute, nextTime.second);
day = this.findAllowedDayInMonth("next", year, month, day + 1);
isStartDay = false;
}
if (day !== void 0 && !isStartDay) return new Date(year, month, day, this.hours[0], this.minutes[0], this.seconds[0]);
}
throw new Error("No valid next date was found.");
}
/** Gets the specified amount of future dates starting from the given start date or now. */
getNextDates(amount, startDate) {
const dates = [];
let nextDate;
for (let i = 0; i < amount; i++) {
nextDate = this.getNextDate(nextDate !== null && nextDate !== void 0 ? nextDate : startDate);
dates.push(nextDate);
}
return dates;
}
/**
* Get an ES6 compatible iterator which iterates over the next dates starting from startDate or now.
* The iterator runs until the optional endDate is reached or forever.
*/
*getNextDatesIterator(startDate, endDate) {
let nextDate;
while (true) {
nextDate = this.getNextDate(nextDate !== null && nextDate !== void 0 ? nextDate : startDate);
if (endDate && endDate.getTime() < nextDate.getTime()) return;
yield nextDate;
}
}
/** Gets the previous date starting from the given start date or now. */
getPrevDate(startDate = /* @__PURE__ */ new Date()) {
const startDateElements = extractDateElements(startDate);
let maxYear = startDateElements.year;
let startIndexMonth = this.reversed.months.findIndex((x) => x <= startDateElements.month);
if (startIndexMonth === -1) {
startIndexMonth = 0;
maxYear--;
}
const maxIterations = this.reversed.months.length * 5;
for (let i = 0; i < maxIterations; i++) {
const year = maxYear - Math.floor((startIndexMonth + i) / this.reversed.months.length);
const month = this.reversed.months[(startIndexMonth + i) % this.reversed.months.length];
const isStartMonth = year === startDateElements.year && month === startDateElements.month;
let day = this.findAllowedDayInMonth("prev", year, month, isStartMonth ? startDateElements.day : getDaysInMonth(year, month));
let isStartDay = isStartMonth && day === startDateElements.day;
if (day !== void 0 && isStartDay) {
const prevTime = this.findAllowedTime("prev", startDateElements);
if (prevTime !== void 0) return new Date(year, month, day, prevTime.hour, prevTime.minute, prevTime.second);
if (day > 1) {
day = this.findAllowedDayInMonth("prev", year, month, day - 1);
isStartDay = false;
}
}
if (day !== void 0 && !isStartDay) return new Date(year, month, day, this.reversed.hours[0], this.reversed.minutes[0], this.reversed.seconds[0]);
}
throw new Error("No valid previous date was found.");
}
/** Gets the specified amount of previous dates starting from the given start date or now. */
getPrevDates(amount, startDate) {
const dates = [];
let prevDate;
for (let i = 0; i < amount; i++) {
prevDate = this.getPrevDate(prevDate !== null && prevDate !== void 0 ? prevDate : startDate);
dates.push(prevDate);
}
return dates;
}
/**
* Get an ES6 compatible iterator which iterates over the previous dates starting from startDate or now.
* The iterator runs until the optional endDate is reached or forever.
*/
*getPrevDatesIterator(startDate, endDate) {
let prevDate;
while (true) {
prevDate = this.getPrevDate(prevDate !== null && prevDate !== void 0 ? prevDate : startDate);
if (endDate && endDate.getTime() > prevDate.getTime()) return;
yield prevDate;
}
}
/** Returns true when there is a cron date at the given date. */
matchDate(date) {
const { second, minute, hour, day, month, weekday } = extractDateElements(date);
if (this.seconds.indexOf(second) === -1 || this.minutes.indexOf(minute) === -1 || this.hours.indexOf(hour) === -1 || this.months.indexOf(month) === -1) return false;
if (this.days.length !== 31 && this.weekdays.length !== 7) return this.days.indexOf(day) !== -1 || this.weekdays.indexOf(weekday) !== -1;
return this.days.indexOf(day) !== -1 && this.weekdays.indexOf(weekday) !== -1;
}
};
//#endregion
//#region ../../../../node_modules/cron-schedule/dist/cron-parser.js
const secondConstraint = {
min: 0,
max: 59
};
const minuteConstraint = {
min: 0,
max: 59
};
const hourConstraint = {
min: 0,
max: 23
};
const dayConstraint = {
min: 1,
max: 31
};
const monthConstraint = {
min: 1,
max: 12,
aliases: {
jan: "1",
feb: "2",
mar: "3",
apr: "4",
may: "5",
jun: "6",
jul: "7",
aug: "8",
sep: "9",
oct: "10",
nov: "11",
dec: "12"
}
};
const weekdayConstraint = {
min: 0,
max: 7,
aliases: {
mon: "1",
tue: "2",
wed: "3",
thu: "4",
fri: "5",
sat: "6",
sun: "7"
}
};
const timeNicknames = {
"@yearly": "0 0 1 1 *",
"@annually": "0 0 1 1 *",
"@monthly": "0 0 1 * *",
"@weekly": "0 0 * * 0",
"@daily": "0 0 * * *",
"@hourly": "0 * * * *",
"@minutely": "* * * * *"
};
function parseElement(element, constraint) {
const result = /* @__PURE__ */ new Set();
if (element === "*") {
for (let i = constraint.min; i <= constraint.max; i = i + 1) result.add(i);
return result;
}
const listElements = element.split(",");
if (listElements.length > 1) {
for (const listElement of listElements) {
const parsedListElement = parseElement(listElement, constraint);
for (const x of parsedListElement) result.add(x);
}
return result;
}
const parseSingleElement = (singleElement) => {
var _a, _b;
singleElement = (_b = (_a = constraint.aliases) === null || _a === void 0 ? void 0 : _a[singleElement.toLowerCase()]) !== null && _b !== void 0 ? _b : singleElement;
const parsedElement = Number.parseInt(singleElement, 10);
if (Number.isNaN(parsedElement)) throw new Error(`Failed to parse ${element}: ${singleElement} is NaN.`);
if (parsedElement < constraint.min || parsedElement > constraint.max) throw new Error(`Failed to parse ${element}: ${singleElement} is outside of constraint range of ${constraint.min} - ${constraint.max}.`);
return parsedElement;
};
const rangeSegments = /^(([0-9a-zA-Z]+)-([0-9a-zA-Z]+)|\*)(\/([0-9]+))?$/.exec(element);
if (rangeSegments === null) {
result.add(parseSingleElement(element));
return result;
}
let parsedStart = rangeSegments[1] === "*" ? constraint.min : parseSingleElement(rangeSegments[2]);
const parsedEnd = rangeSegments[1] === "*" ? constraint.max : parseSingleElement(rangeSegments[3]);
if (constraint === weekdayConstraint && parsedStart === 7 && parsedEnd !== 7) parsedStart = 0;
if (parsedStart > parsedEnd) throw new Error(`Failed to parse ${element}: Invalid range (start: ${parsedStart}, end: ${parsedEnd}).`);
const step = rangeSegments[5];
let parsedStep = 1;
if (step !== void 0) {
parsedStep = Number.parseInt(step, 10);
if (Number.isNaN(parsedStep)) throw new Error(`Failed to parse step: ${step} is NaN.`);
if (parsedStep < 1) throw new Error(`Failed to parse step: Expected ${step} to be greater than 0.`);
}
for (let i = parsedStart; i <= parsedEnd; i = i + parsedStep) result.add(i);
return result;
}
/** Parses a cron expression into a Cron instance. */
function parseCronExpression(cronExpression) {
var _a;
if (typeof cronExpression !== "string") throw new TypeError("Invalid cron expression: must be of type string.");
cronExpression = (_a = timeNicknames[cronExpression.toLowerCase()]) !== null && _a !== void 0 ? _a : cronExpression;
const elements = cronExpression.split(" ").filter((elem) => elem.length > 0);
if (elements.length < 5 || elements.length > 6) throw new Error("Invalid cron expression: expected 5 or 6 elements.");
const rawSeconds = elements.length === 6 ? elements[0] : "0";
const rawMinutes = elements.length === 6 ? elements[1] : elements[0];
const rawHours = elements.length === 6 ? elements[2] : elements[1];
const rawDays = elements.length === 6 ? elements[3] : elements[2];
const rawMonths = elements.length === 6 ? elements[4] : elements[3];
const rawWeekdays = elements.length === 6 ? elements[5] : elements[4];
return new Cron({
seconds: parseElement(rawSeconds, secondConstraint),
minutes: parseElement(rawMinutes, minuteConstraint),
hours: parseElement(rawHours, hourConstraint),
days: parseElement(rawDays, dayConstraint),
months: new Set(Array.from(parseElement(rawMonths, monthConstraint)).map((x) => x - 1)),
weekdays: new Set(Array.from(parseElement(rawWeekdays, weekdayConstraint)).map((x) => x % 7))
});
}
//#endregion
//#region ../../src/scheduler/providers/CronProvider.ts
var CronProvider = class {
dt = $inject(DateTimeProvider);
alepha = $inject(Alepha);
log = $logger();
cronJobs = [];
getCronJobs() {
return this.cronJobs;
}
start = $hook({
on: "start",
handler: () => {
if (this.alepha.isServerless()) {
this.log.info("Ignoring cron jobs in serverless environment");
return;
}
for (const cron of this.cronJobs) if (!cron.running) {
cron.running = true;
this.log.debug(`Starting cron task '${cron.name}' with '${cron.expression}'`);
this.run(cron);
}
}
});
stop = $hook({
on: "stop",
handler: () => {
for (const cron of this.cronJobs) this.abort(cron);
}
});
/**
* Generic serverless cron trigger. Vercel's platform-emitted entry
* point fires `serverless:cron` with the job name; we run the matching
* job in-process. On long-running runtimes this listener is harmless
* (no one fires the event).
*/
onServerlessCron = $hook({
on: "serverless:cron",
handler: async ({ name }) => {
await this.trigger(name);
}
});
boot(name) {
const cron = typeof name === "string" ? this.cronJobs.find((c) => c.name === name) : name;
if (!cron) return;
cron.running = true;
this.log.debug(`Starting cron task '${cron.name}' with '${cron.expression}'`);
this.run(cron);
}
abort(name) {
const cron = typeof name === "string" ? this.cronJobs.find((c) => c.name === name) : name;
if (!cron?.running) return;
cron.running = false;
cron.abort?.abort();
this.log.debug(`Cron task '${cron.name}' stopped`);
}
/**
* Registers a cron job.
*
* It's automatically done when using the `$scheduler` primitive but can also be used manually.
*/
createCronJob(name, expression, handler, start) {
const cron = {
name,
cron: parseCronExpression(expression),
expression,
handler,
loop: true,
abort: new AbortController()
};
this.cronJobs.push(cron);
if (start && this.alepha.isStarted()) this.boot(cron);
}
run(task, now = this.dt.now()) {
if (!task.running) return;
const [next] = task.cron.getNextDates(1, now.toDate());
if (!next) return;
const duration = next.getTime() - now.toDate().getTime();
const abort = new AbortController();
task.abort = abort;
this.dt.wait(duration, {
now: now.valueOf(),
signal: abort.signal
}).then(() => {
if (!task.running) {
this.log.trace("Cron task stopped before execution");
return;
}
this.log.trace("Running cron task");
if (task.executing) {
this.log.warn(`Cron task '${task.name}' is still running, skipping this invocation`);
if (task.loop) this.run(task, this.dt.of(next));
return;
}
task.executing = true;
task.handler({ now: this.dt.of(next) }).catch((err) => {
if (task.onError) task.onError(err);
else this.log.error("Error in cron task:", err);
}).finally(() => {
task.executing = false;
});
if (task.loop) this.run(task, this.dt.of(next));
}).catch((err) => {
this.log.warn("Issue during cron waiting timer", err);
});
}
/**
* Trigger a specific cron job by name.
*/
async trigger(name) {
const job = this.cronJobs.find((j) => j.name === name);
if (!job) {
this.log.warn(`Cron job '${name}' not found`);
return;
}
await this.runJobs([job], this.dt.now());
}
/**
* Trigger all registered cron jobs.
*/
async triggerAll() {
await this.runJobs(this.cronJobs, this.dt.now());
}
/**
* Run multiple cron jobs in parallel.
*/
async runJobs(jobs, now) {
const failures = (await Promise.allSettled(jobs.map(async (job) => {
this.log.debug(`Running cron job '${job.name}'`);
try {
await job.handler({ now });
this.log.debug(`Cron job '${job.name}' completed`);
} catch (error) {
this.log.error(`Cron job '${job.name}' failed`, error);
throw error;
}
}))).filter((r) => r.status === "rejected");
if (failures.length > 0) this.log.error(`${failures.length}/${jobs.length} cron jobs failed`);
}
};
//#endregion
//#region ../../src/scheduler/primitives/$scheduler.ts
/**
* Scheduler primitive.
*/
const $scheduler = (options) => {
return createPrimitive(SchedulerPrimitive, options);
};
/**
* Scheduler configuration atom.
*/
const schedulerOptions = $atom({
name: "alepha.scheduler.options",
schema: z.object({ prefix: z.text({ description: "Prefix for scheduler lock keys." }).optional() }),
default: {}
});
var SchedulerPrimitive = class extends Primitive {
log = $logger();
settings = $state(schedulerOptions);
alepha = $inject(Alepha);
dateTimeProvider = $inject(DateTimeProvider);
cronProvider = $inject(CronProvider);
get name() {
return this.options.name ?? `${this.config.service.name}.${this.config.propertyKey}`;
}
onInit() {
if (this.options.interval) this.dateTimeProvider.createInterval(() => this.trigger(), this.options.interval);
if (this.options.cron) this.cronProvider.createCronJob(this.name, this.options.cron, () => this.trigger());
}
async trigger() {
if (!this.alepha.isStarted()) return;
const context = this.alepha.context.createContextId();
await this.alepha.context.run(async () => {
try {
const now = this.dateTimeProvider.now();
await this.alepha.events.emit("scheduler:begin", {
name: this.name,
now,
context
});
if (this.options.lock !== false) try {
await this.schedulerLock.run({ now });
} catch (error) {
if (!(error instanceof LockAcquireError)) throw error;
this.log.debug(`Scheduler '${this.name}' tick skipped — lock held by another runner`);
}
else await this.options.handler({ now });
await this.alepha.events.emit("scheduler:success", {
name: this.name,
context
}, { catch: true });
} catch (error) {
await this.alepha.events.emit("scheduler:error", {
name: this.name,
error,
context
}, { catch: true });
this.log.error("Error running scheduler:", error);
}
await this.alepha.events.emit("scheduler:end", {
name: this.name,
context
}, { catch: true });
}, { context });
}
schedulerLock = $pipeline({
use: [$lock({ name: () => {
return `${this.settings.prefix ? `${this.settings.prefix}:` : ""}scheduler:${this.name}`;
} })],
handler: async (args) => {
await this.options.handler(args);
}
});
};
$scheduler[KIND] = SchedulerPrimitive;
//#endregion
//#region ../../src/scheduler/providers/WorkerdCronProvider.ts
/**
* Cloudflare Workers cron provider.
*
* This provider handles scheduled events from Cloudflare Workers Cron Triggers.
* Unlike the Node.js CronProvider, this doesn't use intervals/timeouts - instead,
* it reacts to scheduled events triggered by Cloudflare.
*
* **Usage:**
* 1. Define schedulers with `$scheduler({ cron: "0 * * * *", handler: ... })`
* 2. Build your app with `alepha build` - cron triggers are automatically added to `wrangler.jsonc`
* 3. Deploy to Cloudflare Workers
*
* **How it works:**
* - During build, all registered `$scheduler` cron expressions are collected
* - The build generates `wrangler.jsonc` with `triggers.crons` automatically filled
* - When Cloudflare fires a cron trigger, the `scheduled` handler emits `cloudflare:scheduled`
* - This provider listens to that event and runs matching schedulers
*
* @see https://developers.cloudflare.com/workers/configuration/cron-triggers/
*/
var WorkerdCronProvider = class extends CronProvider {
/**
* Override to avoid creating AbortController in global scope.
* Cloudflare Workers doesn't allow this during initialization.
*/
createCronJob(name, expression, handler) {
this.cronJobs.push({
name,
cron: parseCronExpression(expression),
expression,
handler,
loop: false
});
}
/**
* Handle a scheduled event from Cloudflare Workers.
*/
onScheduledEvent = $hook({
on: "cloudflare:scheduled",
handler: async (event) => {
const now = this.dt.of(event.scheduledTime);
this.log.info("Received scheduled event", {
cron: event.cron,
scheduledTime: now.format()
});
const matchingJobs = this.cronJobs.filter((job) => job.expression === event.cron);
if (matchingJobs.length === 0) {
const matchingByTime = this.cronJobs.filter((job) => job.cron.matchDate(now.toDate()));
if (matchingByTime.length > 0) {
this.log.debug(`No exact cron match for '${event.cron}', found ${matchingByTime.length} jobs matching by time`);
await this.runJobs(matchingByTime, now);
return;
}
this.log.warn(`No cron jobs found for expression '${event.cron}'`);
return;
}
await this.runJobs(matchingJobs, now);
}
});
};
//#endregion
//#region ../../src/scheduler/constants/CRON.ts
const CRON = {
EVERY_MINUTE: "* * * * *",
EVERY_5_MINUTES: "*/5 * * * *",
EVERY_15_MINUTES: "*/15 * * * *",
EVERY_30_MINUTES: "*/30 * * * *",
EVERY_HOUR: "0 * * * *",
EVERY_DAY_AT_MIDNIGHT: "0 0 * * *"
};
//#endregion
//#region ../../src/scheduler/index.workerd.ts
const AlephaScheduler = $module({
name: "alepha.scheduler",
primitives: [$scheduler],
imports: [AlephaLock],
services: [CronProvider],
variants: [WorkerdCronProvider],
register: (alepha) => alepha.with({
provide: CronProvider,
use: WorkerdCronProvider
})
});
//#endregion
export { $scheduler, AlephaScheduler, CRON, CronProvider, SchedulerPrimitive, WorkerdCronProvider, schedulerOptions };
//# sourceMappingURL=index.workerd.js.map