@lunora/scheduler 0.0.0 → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +105 -0
- package/README.md +140 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/index.d.mts +785 -0
- package/dist/index.d.ts +785 -0
- package/dist/index.mjs +8 -0
- package/dist/packem_shared/SchedulerDO-BNzXNnS4.mjs +678 -0
- package/dist/packem_shared/assertValidCronExpression-BLfrDgmK.mjs +20 -0
- package/dist/packem_shared/compileCronSchedule-BaLlXJiN.mjs +123 -0
- package/dist/packem_shared/createCronTrigger-Cq9IBcWQ.mjs +27 -0
- package/dist/packem_shared/createQueueConsumer-DWahNPfz.mjs +59 -0
- package/dist/packem_shared/createScheduler-KCso4_at.mjs +63 -0
- package/dist/packem_shared/createWorkpool-D5cWPQmH.mjs +56 -0
- package/dist/packem_shared/isWorkflowReference-C9mQkMXt.mjs +3 -0
- package/package.json +40 -17
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
2
|
+
|
|
3
|
+
const isValidCronExpression = (schedule) => {
|
|
4
|
+
if (typeof schedule !== "string" || schedule.trim() === "") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
CronExpressionParser.parse(schedule.trim());
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const assertValidCronExpression = (schedule, context = "cron expression") => {
|
|
15
|
+
if (!isValidCronExpression(schedule)) {
|
|
16
|
+
throw new Error(`@lunora/scheduler: invalid ${context} "${schedule}" — expected a standard 5- or 6-field cron expression (e.g. "0 * * * *")`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { assertValidCronExpression, isValidCronExpression };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { isWorkflowReference } from './isWorkflowReference-C9mQkMXt.mjs';
|
|
2
|
+
import { assertValidCronExpression } from './assertValidCronExpression-BLfrDgmK.mjs';
|
|
3
|
+
|
|
4
|
+
const WEEKDAY_INDEX = {
|
|
5
|
+
friday: 5,
|
|
6
|
+
monday: 1,
|
|
7
|
+
saturday: 6,
|
|
8
|
+
sunday: 0,
|
|
9
|
+
thursday: 4,
|
|
10
|
+
tuesday: 2,
|
|
11
|
+
wednesday: 3
|
|
12
|
+
};
|
|
13
|
+
const field = (value, label, min, max) => {
|
|
14
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
15
|
+
throw new Error(`@lunora/scheduler: cronJobs ${label} must be an integer in [${min.toFixed(0)}, ${max.toFixed(0)}], got ${String(value)}`);
|
|
16
|
+
}
|
|
17
|
+
return value.toFixed(0);
|
|
18
|
+
};
|
|
19
|
+
const compileInterval = (schedule) => {
|
|
20
|
+
const units = ["seconds", "minutes", "hours"].filter((unit2) => schedule[unit2] !== void 0);
|
|
21
|
+
if (units.length !== 1) {
|
|
22
|
+
throw new Error(`@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }`);
|
|
23
|
+
}
|
|
24
|
+
const unit = units[0];
|
|
25
|
+
const value = schedule[unit];
|
|
26
|
+
if (unit === "seconds") {
|
|
27
|
+
return `*/${field(value, "interval.seconds", 1, 59)} * * * * *`;
|
|
28
|
+
}
|
|
29
|
+
if (unit === "minutes") {
|
|
30
|
+
return `*/${field(value, "interval.minutes", 1, 59)} * * * *`;
|
|
31
|
+
}
|
|
32
|
+
return `0 */${field(value, "interval.hours", 1, 23)} * * *`;
|
|
33
|
+
};
|
|
34
|
+
const compileDaily = (schedule) => {
|
|
35
|
+
const minute = field(schedule.minuteUTC, "daily.minuteUTC", 0, 59);
|
|
36
|
+
const hour = field(schedule.hourUTC, "daily.hourUTC", 0, 23);
|
|
37
|
+
return `${minute} ${hour} * * *`;
|
|
38
|
+
};
|
|
39
|
+
const compileWeekly = (schedule) => {
|
|
40
|
+
const index = WEEKDAY_INDEX[schedule.dayOfWeek];
|
|
41
|
+
if (index === void 0) {
|
|
42
|
+
throw new Error(`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${schedule.dayOfWeek}"`);
|
|
43
|
+
}
|
|
44
|
+
const minute = field(schedule.minuteUTC, "weekly.minuteUTC", 0, 59);
|
|
45
|
+
const hour = field(schedule.hourUTC, "weekly.hourUTC", 0, 23);
|
|
46
|
+
return `${minute} ${hour} * * ${index.toFixed(0)}`;
|
|
47
|
+
};
|
|
48
|
+
const compileMonthly = (schedule) => {
|
|
49
|
+
const day = field(schedule.day, "monthly.day", 1, 31);
|
|
50
|
+
const minute = field(schedule.minuteUTC, "monthly.minuteUTC", 0, 59);
|
|
51
|
+
const hour = field(schedule.hourUTC, "monthly.hourUTC", 0, 23);
|
|
52
|
+
return `${minute} ${hour} ${day} * *`;
|
|
53
|
+
};
|
|
54
|
+
const CRON_SCHEDULE_KINDS = /* @__PURE__ */ new Set(["daily", "interval", "monthly", "weekly"]);
|
|
55
|
+
const compileCronSchedule = (kind, schedule) => {
|
|
56
|
+
switch (kind) {
|
|
57
|
+
case "daily": {
|
|
58
|
+
return compileDaily(schedule);
|
|
59
|
+
}
|
|
60
|
+
case "interval": {
|
|
61
|
+
return compileInterval(schedule);
|
|
62
|
+
}
|
|
63
|
+
case "monthly": {
|
|
64
|
+
return compileMonthly(schedule);
|
|
65
|
+
}
|
|
66
|
+
case "weekly": {
|
|
67
|
+
return compileWeekly(schedule);
|
|
68
|
+
}
|
|
69
|
+
default: {
|
|
70
|
+
throw new Error(`@lunora/scheduler: unknown cron schedule kind "${String(kind)}"`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const cronJobs = () => {
|
|
75
|
+
const jobs = [];
|
|
76
|
+
const seen = /* @__PURE__ */ new Set();
|
|
77
|
+
const register = (name, cron, target, args) => {
|
|
78
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
79
|
+
throw new Error(`@lunora/scheduler: cron job name must be a non-empty string`);
|
|
80
|
+
}
|
|
81
|
+
if (seen.has(name)) {
|
|
82
|
+
throw new Error(`@lunora/scheduler: duplicate cron job name "${name}" — names must be unique within one cronJobs()`);
|
|
83
|
+
}
|
|
84
|
+
if (isWorkflowReference(target)) {
|
|
85
|
+
assertValidCronExpression(cron, `cron expression for job "${name}"`);
|
|
86
|
+
seen.add(name);
|
|
87
|
+
jobs.push({ args: args ?? {}, cron, name, workflow: typeof target.name === "string" ? target.name : "" });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!target || typeof target.__lunoraRef !== "string") {
|
|
91
|
+
throw new Error(`@lunora/scheduler: cron job "${name}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);
|
|
92
|
+
}
|
|
93
|
+
assertValidCronExpression(cron, `cron expression for job "${name}"`);
|
|
94
|
+
seen.add(name);
|
|
95
|
+
jobs.push({ args: args ?? {}, cron, functionPath: target.__lunoraRef, name });
|
|
96
|
+
};
|
|
97
|
+
const builder = {
|
|
98
|
+
cron(name, cronExpr, function_, args) {
|
|
99
|
+
register(name, cronExpr, function_, args);
|
|
100
|
+
return builder;
|
|
101
|
+
},
|
|
102
|
+
daily(name, schedule, function_, args) {
|
|
103
|
+
register(name, compileCronSchedule("daily", schedule), function_, args);
|
|
104
|
+
return builder;
|
|
105
|
+
},
|
|
106
|
+
interval(name, schedule, function_, args) {
|
|
107
|
+
register(name, compileCronSchedule("interval", schedule), function_, args);
|
|
108
|
+
return builder;
|
|
109
|
+
},
|
|
110
|
+
jobs: () => [...jobs],
|
|
111
|
+
monthly(name, schedule, function_, args) {
|
|
112
|
+
register(name, compileCronSchedule("monthly", schedule), function_, args);
|
|
113
|
+
return builder;
|
|
114
|
+
},
|
|
115
|
+
weekly(name, schedule, function_, args) {
|
|
116
|
+
register(name, compileCronSchedule("weekly", schedule), function_, args);
|
|
117
|
+
return builder;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return builder;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export { CRON_SCHEDULE_KINDS, compileCronSchedule, cronJobs };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { assertValidCronExpression } from './assertValidCronExpression-BLfrDgmK.mjs';
|
|
2
|
+
|
|
3
|
+
const createCronTrigger = (options) => {
|
|
4
|
+
if (!options.schedule || !options.fn) {
|
|
5
|
+
throw new Error("@lunora/scheduler: createCronTrigger() requires `schedule` and `fn`");
|
|
6
|
+
}
|
|
7
|
+
assertValidCronExpression(options.schedule);
|
|
8
|
+
const snippet = JSON.stringify(
|
|
9
|
+
{
|
|
10
|
+
triggers: {
|
|
11
|
+
crons: [options.schedule]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
void 0,
|
|
15
|
+
2
|
|
16
|
+
);
|
|
17
|
+
return {
|
|
18
|
+
crons: [options.schedule],
|
|
19
|
+
dispatcher: {
|
|
20
|
+
args: options.args ?? {},
|
|
21
|
+
functionPath: options.fn.__lunoraRef
|
|
22
|
+
},
|
|
23
|
+
wranglerJsonc: snippet
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { createCronTrigger };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const trimTrailingSlashes = (value) => {
|
|
2
|
+
let end = value.length;
|
|
3
|
+
while (end > 0 && value[end - 1] === "/") {
|
|
4
|
+
end -= 1;
|
|
5
|
+
}
|
|
6
|
+
return value.slice(0, end);
|
|
7
|
+
};
|
|
8
|
+
const createQueueWorkpool = (options) => {
|
|
9
|
+
if (!options.queue) {
|
|
10
|
+
throw new Error("@lunora/scheduler: `queue` (a Cloudflare Queue binding) is required");
|
|
11
|
+
}
|
|
12
|
+
const enqueue = async (function_, args, enqueueOptions = {}) => {
|
|
13
|
+
const job = { args, functionPath: function_.__lunoraRef, shardKey: enqueueOptions.shardKey };
|
|
14
|
+
const sendOptions = enqueueOptions.delaySeconds === void 0 ? void 0 : { delaySeconds: enqueueOptions.delaySeconds };
|
|
15
|
+
await options.queue.send(job, sendOptions);
|
|
16
|
+
};
|
|
17
|
+
const enqueueBatch = async (jobs, sendOptions) => {
|
|
18
|
+
const messages = jobs.map((job) => {
|
|
19
|
+
return { body: { args: job.args, functionPath: job.ref.__lunoraRef, shardKey: job.shardKey } };
|
|
20
|
+
});
|
|
21
|
+
await options.queue.sendBatch(messages, sendOptions);
|
|
22
|
+
};
|
|
23
|
+
return { enqueue, enqueueBatch };
|
|
24
|
+
};
|
|
25
|
+
const isQueueJob = (value) => typeof value === "object" && value !== null && typeof value.functionPath === "string";
|
|
26
|
+
const createQueueConsumer = (options) => async (batch) => {
|
|
27
|
+
await Promise.all(
|
|
28
|
+
batch.messages.map(async (message) => {
|
|
29
|
+
try {
|
|
30
|
+
if (!isQueueJob(message.body)) {
|
|
31
|
+
throw new Error("@lunora/scheduler: queue message body is not a QueueJob (missing functionPath)");
|
|
32
|
+
}
|
|
33
|
+
await options.dispatch(message.body);
|
|
34
|
+
message.ack();
|
|
35
|
+
} catch {
|
|
36
|
+
message.retry();
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
const httpDispatcher = (options) => {
|
|
42
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
43
|
+
if (typeof fetchImpl !== "function") {
|
|
44
|
+
throw new TypeError("@lunora/scheduler: no fetch implementation available — pass fetchImpl or run on a platform with global fetch");
|
|
45
|
+
}
|
|
46
|
+
const url = `${trimTrailingSlashes(options.originUrl)}/_lunora/scheduler/dispatch`;
|
|
47
|
+
return async (job) => {
|
|
48
|
+
const response = await fetchImpl(url, {
|
|
49
|
+
body: JSON.stringify({ args: job.args ?? {}, functionPath: job.functionPath, shardKey: job.shardKey }),
|
|
50
|
+
headers: { authorization: `Bearer ${options.adminToken}`, "content-type": "application/json" },
|
|
51
|
+
method: "POST"
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`@lunora/scheduler: queue dispatch failed (${response.status.toString()}): ${await response.text()}`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export { createQueueConsumer, createQueueWorkpool, httpDispatcher };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const callDO = async (options, path, body) => {
|
|
2
|
+
const stub = options.namespace.get(options.namespace.idFromName(options.instanceName ?? "default"));
|
|
3
|
+
const response = await stub.fetch(`https://scheduler.internal${path}`, {
|
|
4
|
+
body: JSON.stringify(body),
|
|
5
|
+
headers: { "content-type": "application/json" },
|
|
6
|
+
method: "POST"
|
|
7
|
+
});
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
const text = await response.text();
|
|
10
|
+
throw new Error(`@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
11
|
+
}
|
|
12
|
+
return await response.json();
|
|
13
|
+
};
|
|
14
|
+
const getDO = async (options, path) => {
|
|
15
|
+
const stub = options.namespace.get(options.namespace.idFromName(options.instanceName ?? "default"));
|
|
16
|
+
const response = await stub.fetch(`https://scheduler.internal${path}`, { method: "GET" });
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const text = await response.text();
|
|
19
|
+
throw new Error(`@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
20
|
+
}
|
|
21
|
+
return await response.json();
|
|
22
|
+
};
|
|
23
|
+
const createScheduler = (options) => {
|
|
24
|
+
if (!options.namespace) {
|
|
25
|
+
throw new Error("@lunora/scheduler: `namespace` (SchedulerDO binding) is required");
|
|
26
|
+
}
|
|
27
|
+
if (!options.originUrl) {
|
|
28
|
+
throw new Error("@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");
|
|
29
|
+
}
|
|
30
|
+
const runAt = async (date, function_, args, options_ = {}) => {
|
|
31
|
+
const scheduledFor = date instanceof Date ? date.getTime() : date;
|
|
32
|
+
return callDO(options, "/schedule", {
|
|
33
|
+
args,
|
|
34
|
+
functionPath: function_.__lunoraRef,
|
|
35
|
+
originUrl: options.originUrl,
|
|
36
|
+
// Optional workpool / retry-policy passthrough. Absent for ordinary
|
|
37
|
+
// `runAfter`/`runAt` calls, which keeps the wire payload (and the
|
|
38
|
+
// DO's behaviour) identical to before this feature.
|
|
39
|
+
pool: options_.pool,
|
|
40
|
+
retry: options_.retry,
|
|
41
|
+
scheduledFor,
|
|
42
|
+
shardKey: options_.shardKey
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const runAfter = async (delayMs, function_, args, options_ = {}) => {
|
|
46
|
+
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
47
|
+
throw new Error("@lunora/scheduler: `delayMs` must be a non-negative finite number");
|
|
48
|
+
}
|
|
49
|
+
return runAt(Date.now() + delayMs, function_, args, options_);
|
|
50
|
+
};
|
|
51
|
+
const cancel = async (id) => callDO(options, "/cancel", { id });
|
|
52
|
+
const list = async () => {
|
|
53
|
+
const body = await getDO(options, "/list");
|
|
54
|
+
return Array.isArray(body.records) ? body.records : [];
|
|
55
|
+
};
|
|
56
|
+
const get = async (id) => {
|
|
57
|
+
const body = await getDO(options, `/get?id=${encodeURIComponent(id)}`);
|
|
58
|
+
return body.record ?? null;
|
|
59
|
+
};
|
|
60
|
+
return { cancel, get, list, runAfter, runAt };
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { createScheduler as default };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const callDO = async (options, path, body) => {
|
|
2
|
+
const stub = options.namespace.get(options.namespace.idFromName(options.instanceName ?? "default"));
|
|
3
|
+
const response = await stub.fetch(`https://scheduler.internal${path}`, {
|
|
4
|
+
body: JSON.stringify(body),
|
|
5
|
+
headers: { "content-type": "application/json" },
|
|
6
|
+
method: "POST"
|
|
7
|
+
});
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
const text = await response.text();
|
|
10
|
+
throw new Error(`@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
11
|
+
}
|
|
12
|
+
return await response.json();
|
|
13
|
+
};
|
|
14
|
+
const getDO = async (options, path) => {
|
|
15
|
+
const stub = options.namespace.get(options.namespace.idFromName(options.instanceName ?? "default"));
|
|
16
|
+
const response = await stub.fetch(`https://scheduler.internal${path}`, { method: "GET" });
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const text = await response.text();
|
|
19
|
+
throw new Error(`@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
20
|
+
}
|
|
21
|
+
return await response.json();
|
|
22
|
+
};
|
|
23
|
+
const createWorkpool = (options) => {
|
|
24
|
+
if (!options.namespace) {
|
|
25
|
+
throw new Error("@lunora/scheduler: `namespace` (SchedulerDO binding) is required");
|
|
26
|
+
}
|
|
27
|
+
if (!options.originUrl) {
|
|
28
|
+
throw new Error("@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0) {
|
|
31
|
+
throw new Error("@lunora/scheduler: `maxConcurrency` must be a positive integer");
|
|
32
|
+
}
|
|
33
|
+
const name = typeof options.name === "string" && options.name.length > 0 ? options.name : "default";
|
|
34
|
+
const enqueue = async (function_, args, options_ = {}) => {
|
|
35
|
+
const delayMs = options_.delayMs ?? 0;
|
|
36
|
+
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
37
|
+
throw new Error("@lunora/scheduler: `delayMs` must be a non-negative finite number");
|
|
38
|
+
}
|
|
39
|
+
return callDO(options, "/schedule", {
|
|
40
|
+
args,
|
|
41
|
+
functionPath: function_.__lunoraRef,
|
|
42
|
+
instanceName: options.instanceName ?? "default",
|
|
43
|
+
maxConcurrency: options.maxConcurrency,
|
|
44
|
+
originUrl: options.originUrl,
|
|
45
|
+
pool: name,
|
|
46
|
+
retry: options_.retry,
|
|
47
|
+
scheduledFor: Date.now() + delayMs,
|
|
48
|
+
shardKey: options_.shardKey
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const cancel = async (id) => callDO(options, "/cancel", { id });
|
|
52
|
+
const status = async () => getDO(options, `/pool?name=${encodeURIComponent(name)}`);
|
|
53
|
+
return { cancel, enqueue, name, status };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { createWorkpool as default };
|
package/package.json
CHANGED
|
@@ -1,31 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/scheduler",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
4
|
"description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cloudflare",
|
|
7
|
+
"cron",
|
|
8
|
+
"durable-objects",
|
|
9
|
+
"lunora",
|
|
10
|
+
"queues",
|
|
11
|
+
"scheduler",
|
|
12
|
+
"workers",
|
|
13
|
+
"workpool"
|
|
14
|
+
],
|
|
6
15
|
"homepage": "https://lunora.sh",
|
|
16
|
+
"bugs": "https://github.com/anolilab/lunora/issues",
|
|
17
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Daniel Bannert",
|
|
20
|
+
"email": "d.bannert@anolilab.de"
|
|
21
|
+
},
|
|
7
22
|
"repository": {
|
|
8
23
|
"type": "git",
|
|
9
24
|
"url": "git+https://github.com/anolilab/lunora.git",
|
|
10
25
|
"directory": "packages/scheduler"
|
|
11
26
|
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"cloudflare",
|
|
18
|
-
"workers",
|
|
19
|
-
"durable-objects",
|
|
20
|
-
"scheduler",
|
|
21
|
-
"cron",
|
|
22
|
-
"queues",
|
|
23
|
-
"workpool"
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"__assets__",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE.md"
|
|
24
32
|
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"main": "./dist/index.mjs",
|
|
36
|
+
"module": "./dist/index.mjs",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
25
45
|
"publishConfig": {
|
|
26
46
|
"access": "public"
|
|
27
47
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"cron-parser": "5.5.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": "^22.15.0 || >=24.11.0"
|
|
53
|
+
}
|
|
31
54
|
}
|