@lunora/scheduler 1.0.0-alpha.4 → 1.0.0-alpha.6
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 +6 -0
- package/dist/index.mjs +7 -7
- package/dist/packem_shared/{CRON_SCHEDULE_KINDS-BaLlXJiN.mjs → CRON_SCHEDULE_KINDS-BMVeCHOu.mjs} +15 -8
- package/dist/packem_shared/{SchedulerDO-CQfTGw7C.mjs → SchedulerDO-DVeJrNbs.mjs} +4 -8
- package/dist/packem_shared/{assertValidCronExpression-BLfrDgmK.mjs → assertValidCronExpression-B9m75qU0.mjs} +5 -1
- package/dist/packem_shared/{createCronTrigger-Cq9IBcWQ.mjs → createCronTrigger-Dh4VLxD9.mjs} +3 -2
- package/dist/packem_shared/{createQueueConsumer-DWahNPfz.mjs → createQueueConsumer-Cy-Mp-El.mjs} +5 -3
- package/dist/packem_shared/{createScheduler-CWMn70nv.mjs → createScheduler-CcD09oIm.mjs} +6 -5
- package/dist/packem_shared/{createWorkpool-CEnqCafM.mjs → createWorkpool-C28trhTA.mjs} +7 -6
- package/package.json +2 -1
package/LICENSE.md
CHANGED
|
@@ -103,3 +103,9 @@ Unless required by applicable law or agreed to in writing, software distributed
|
|
|
103
103
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
104
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
105
|
specific language governing permissions and limitations under the License.
|
|
106
|
+
|
|
107
|
+
<!-- DEPENDENCIES -->
|
|
108
|
+
<!-- /DEPENDENCIES -->
|
|
109
|
+
|
|
110
|
+
<!-- TYPE_DEPENDENCIES -->
|
|
111
|
+
<!-- /TYPE_DEPENDENCIES -->
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { default as createScheduler } from './packem_shared/createScheduler-
|
|
2
|
-
export { default as createWorkpool } from './packem_shared/createWorkpool-
|
|
3
|
-
export { createCronTrigger } from './packem_shared/createCronTrigger-
|
|
4
|
-
export { CRON_SCHEDULE_KINDS, compileCronSchedule, cronJobs } from './packem_shared/CRON_SCHEDULE_KINDS-
|
|
5
|
-
export { createQueueConsumer, createQueueWorkpool, httpDispatcher } from './packem_shared/createQueueConsumer-
|
|
6
|
-
export { SchedulerDO } from './packem_shared/SchedulerDO-
|
|
1
|
+
export { default as createScheduler } from './packem_shared/createScheduler-CcD09oIm.mjs';
|
|
2
|
+
export { default as createWorkpool } from './packem_shared/createWorkpool-C28trhTA.mjs';
|
|
3
|
+
export { createCronTrigger } from './packem_shared/createCronTrigger-Dh4VLxD9.mjs';
|
|
4
|
+
export { CRON_SCHEDULE_KINDS, compileCronSchedule, cronJobs } from './packem_shared/CRON_SCHEDULE_KINDS-BMVeCHOu.mjs';
|
|
5
|
+
export { createQueueConsumer, createQueueWorkpool, httpDispatcher } from './packem_shared/createQueueConsumer-Cy-Mp-El.mjs';
|
|
6
|
+
export { SchedulerDO } from './packem_shared/SchedulerDO-DVeJrNbs.mjs';
|
|
7
7
|
export { isWorkflowReference } from './packem_shared/isWorkflowReference-C9mQkMXt.mjs';
|
|
8
|
-
export { assertValidCronExpression, isValidCronExpression } from './packem_shared/assertValidCronExpression-
|
|
8
|
+
export { assertValidCronExpression, isValidCronExpression } from './packem_shared/assertValidCronExpression-B9m75qU0.mjs';
|
package/dist/packem_shared/{CRON_SCHEDULE_KINDS-BaLlXJiN.mjs → CRON_SCHEDULE_KINDS-BMVeCHOu.mjs}
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { isWorkflowReference } from './isWorkflowReference-C9mQkMXt.mjs';
|
|
2
|
-
import { assertValidCronExpression } from './assertValidCronExpression-
|
|
3
|
+
import { assertValidCronExpression } from './assertValidCronExpression-B9m75qU0.mjs';
|
|
3
4
|
|
|
4
5
|
const WEEKDAY_INDEX = {
|
|
5
6
|
friday: 5,
|
|
@@ -12,14 +13,17 @@ const WEEKDAY_INDEX = {
|
|
|
12
13
|
};
|
|
13
14
|
const field = (value, label, min, max) => {
|
|
14
15
|
if (!Number.isInteger(value) || value < min || value > max) {
|
|
15
|
-
throw new
|
|
16
|
+
throw new LunoraError(
|
|
17
|
+
"INTERNAL",
|
|
18
|
+
`@lunora/scheduler: cronJobs ${label} must be an integer in [${min.toFixed(0)}, ${max.toFixed(0)}], got ${String(value)}`
|
|
19
|
+
);
|
|
16
20
|
}
|
|
17
21
|
return value.toFixed(0);
|
|
18
22
|
};
|
|
19
23
|
const compileInterval = (schedule) => {
|
|
20
24
|
const units = ["seconds", "minutes", "hours"].filter((unit2) => schedule[unit2] !== void 0);
|
|
21
25
|
if (units.length !== 1) {
|
|
22
|
-
throw new
|
|
26
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }`);
|
|
23
27
|
}
|
|
24
28
|
const unit = units[0];
|
|
25
29
|
const value = schedule[unit];
|
|
@@ -39,7 +43,7 @@ const compileDaily = (schedule) => {
|
|
|
39
43
|
const compileWeekly = (schedule) => {
|
|
40
44
|
const index = WEEKDAY_INDEX[schedule.dayOfWeek];
|
|
41
45
|
if (index === void 0) {
|
|
42
|
-
throw new
|
|
46
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: weekly schedule has invalid dayOfWeek "${schedule.dayOfWeek}"`);
|
|
43
47
|
}
|
|
44
48
|
const minute = field(schedule.minuteUTC, "weekly.minuteUTC", 0, 59);
|
|
45
49
|
const hour = field(schedule.hourUTC, "weekly.hourUTC", 0, 23);
|
|
@@ -67,7 +71,7 @@ const compileCronSchedule = (kind, schedule) => {
|
|
|
67
71
|
return compileWeekly(schedule);
|
|
68
72
|
}
|
|
69
73
|
default: {
|
|
70
|
-
throw new
|
|
74
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: unknown cron schedule kind "${String(kind)}"`);
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
};
|
|
@@ -76,10 +80,10 @@ const cronJobs = () => {
|
|
|
76
80
|
const seen = /* @__PURE__ */ new Set();
|
|
77
81
|
const register = (name, cron, target, args) => {
|
|
78
82
|
if (typeof name !== "string" || name.trim() === "") {
|
|
79
|
-
throw new
|
|
83
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: cron job name must be a non-empty string`);
|
|
80
84
|
}
|
|
81
85
|
if (seen.has(name)) {
|
|
82
|
-
throw new
|
|
86
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: duplicate cron job name "${name}" — names must be unique within one cronJobs()`);
|
|
83
87
|
}
|
|
84
88
|
if (isWorkflowReference(target)) {
|
|
85
89
|
assertValidCronExpression(cron, `cron expression for job "${name}"`);
|
|
@@ -88,7 +92,10 @@ const cronJobs = () => {
|
|
|
88
92
|
return;
|
|
89
93
|
}
|
|
90
94
|
if (!target || typeof target.__lunoraRef !== "string") {
|
|
91
|
-
throw new
|
|
95
|
+
throw new LunoraError(
|
|
96
|
+
"INTERNAL",
|
|
97
|
+
`@lunora/scheduler: cron job "${name}" requires a function reference (e.g. internal.email.digest) or a workflow reference`
|
|
98
|
+
);
|
|
92
99
|
}
|
|
93
100
|
assertValidCronExpression(cron, `cron expression for job "${name}"`);
|
|
94
101
|
seen.add(name);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const jsonResponse = (body, status = 200, headers) => Response.json(body, { headers: { "content-type": "application/json", ...headers }, status });
|
|
2
|
+
|
|
1
3
|
const HEADER_PREFIX = "id:";
|
|
2
4
|
const RETRY_PREFIX = "retry:";
|
|
3
5
|
const DEAD_PREFIX = "dead:";
|
|
@@ -21,7 +23,7 @@ class SchedulerDO {
|
|
|
21
23
|
return `t:${padTime(scheduledFor)}:${id}`;
|
|
22
24
|
}
|
|
23
25
|
static json(body, status = 200) {
|
|
24
|
-
return
|
|
26
|
+
return jsonResponse(body, status);
|
|
25
27
|
}
|
|
26
28
|
static error(status, code, message) {
|
|
27
29
|
return SchedulerDO.json({ error: { code, message } }, status);
|
|
@@ -143,13 +145,7 @@ class SchedulerDO {
|
|
|
143
145
|
return this.handleSchedule(request);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
|
-
return
|
|
147
|
-
{ error: { code: "NOT_FOUND" } },
|
|
148
|
-
{
|
|
149
|
-
headers: { "content-type": "application/json" },
|
|
150
|
-
status: 404
|
|
151
|
-
}
|
|
152
|
-
);
|
|
148
|
+
return jsonResponse({ error: { code: "NOT_FOUND" } }, 404);
|
|
153
149
|
}
|
|
154
150
|
/** Called by the Workers runtime when the alarm previously set by `_rescheduleAlarm()` fires. */
|
|
155
151
|
async alarm() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { CronExpressionParser } from 'cron-parser';
|
|
2
3
|
|
|
3
4
|
const isValidCronExpression = (schedule) => {
|
|
@@ -13,7 +14,10 @@ const isValidCronExpression = (schedule) => {
|
|
|
13
14
|
};
|
|
14
15
|
const assertValidCronExpression = (schedule, context = "cron expression") => {
|
|
15
16
|
if (!isValidCronExpression(schedule)) {
|
|
16
|
-
throw new
|
|
17
|
+
throw new LunoraError(
|
|
18
|
+
"INTERNAL",
|
|
19
|
+
`@lunora/scheduler: invalid ${context} "${schedule}" — expected a standard 5- or 6-field cron expression (e.g. "0 * * * *")`
|
|
20
|
+
);
|
|
17
21
|
}
|
|
18
22
|
};
|
|
19
23
|
|
package/dist/packem_shared/{createCronTrigger-Cq9IBcWQ.mjs → createCronTrigger-Dh4VLxD9.mjs}
RENAMED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { assertValidCronExpression } from './assertValidCronExpression-B9m75qU0.mjs';
|
|
2
3
|
|
|
3
4
|
const createCronTrigger = (options) => {
|
|
4
5
|
if (!options.schedule || !options.fn) {
|
|
5
|
-
throw new
|
|
6
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: createCronTrigger() requires `schedule` and `fn`");
|
|
6
7
|
}
|
|
7
8
|
assertValidCronExpression(options.schedule);
|
|
8
9
|
const snippet = JSON.stringify(
|
package/dist/packem_shared/{createQueueConsumer-DWahNPfz.mjs → createQueueConsumer-Cy-Mp-El.mjs}
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const trimTrailingSlashes = (value) => {
|
|
2
4
|
let end = value.length;
|
|
3
5
|
while (end > 0 && value[end - 1] === "/") {
|
|
@@ -7,7 +9,7 @@ const trimTrailingSlashes = (value) => {
|
|
|
7
9
|
};
|
|
8
10
|
const createQueueWorkpool = (options) => {
|
|
9
11
|
if (!options.queue) {
|
|
10
|
-
throw new
|
|
12
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `queue` (a Cloudflare Queue binding) is required");
|
|
11
13
|
}
|
|
12
14
|
const enqueue = async (function_, args, enqueueOptions = {}) => {
|
|
13
15
|
const job = { args, functionPath: function_.__lunoraRef, shardKey: enqueueOptions.shardKey };
|
|
@@ -28,7 +30,7 @@ const createQueueConsumer = (options) => async (batch) => {
|
|
|
28
30
|
batch.messages.map(async (message) => {
|
|
29
31
|
try {
|
|
30
32
|
if (!isQueueJob(message.body)) {
|
|
31
|
-
throw new
|
|
33
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: queue message body is not a QueueJob (missing functionPath)");
|
|
32
34
|
}
|
|
33
35
|
await options.dispatch(message.body);
|
|
34
36
|
message.ack();
|
|
@@ -51,7 +53,7 @@ const httpDispatcher = (options) => {
|
|
|
51
53
|
method: "POST"
|
|
52
54
|
});
|
|
53
55
|
if (!response.ok) {
|
|
54
|
-
throw new
|
|
56
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: queue dispatch failed (${response.status.toString()}): ${await response.text()}`);
|
|
55
57
|
}
|
|
56
58
|
};
|
|
57
59
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { a as applyJurisdiction } from './jurisdiction-CR2zC3Et.mjs';
|
|
2
3
|
|
|
3
4
|
const schedulerStub = (options) => {
|
|
@@ -13,7 +14,7 @@ const callDO = async (options, path, body) => {
|
|
|
13
14
|
});
|
|
14
15
|
if (!response.ok) {
|
|
15
16
|
const text = await response.text();
|
|
16
|
-
throw new
|
|
17
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
17
18
|
}
|
|
18
19
|
return await response.json();
|
|
19
20
|
};
|
|
@@ -22,16 +23,16 @@ const getDO = async (options, path) => {
|
|
|
22
23
|
const response = await stub.fetch(`https://scheduler.internal${path}`, { method: "GET" });
|
|
23
24
|
if (!response.ok) {
|
|
24
25
|
const text = await response.text();
|
|
25
|
-
throw new
|
|
26
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
26
27
|
}
|
|
27
28
|
return await response.json();
|
|
28
29
|
};
|
|
29
30
|
const createScheduler = (options) => {
|
|
30
31
|
if (!options.namespace) {
|
|
31
|
-
throw new
|
|
32
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `namespace` (SchedulerDO binding) is required");
|
|
32
33
|
}
|
|
33
34
|
if (!options.originUrl) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");
|
|
35
36
|
}
|
|
36
37
|
const runAt = async (date, function_, args, options_ = {}) => {
|
|
37
38
|
const scheduledFor = date instanceof Date ? date.getTime() : date;
|
|
@@ -50,7 +51,7 @@ const createScheduler = (options) => {
|
|
|
50
51
|
};
|
|
51
52
|
const runAfter = async (delayMs, function_, args, options_ = {}) => {
|
|
52
53
|
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
53
|
-
throw new
|
|
54
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `delayMs` must be a non-negative finite number");
|
|
54
55
|
}
|
|
55
56
|
return runAt(Date.now() + delayMs, function_, args, options_);
|
|
56
57
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { a as applyJurisdiction } from './jurisdiction-CR2zC3Et.mjs';
|
|
2
3
|
|
|
3
4
|
const workpoolStub = (options) => {
|
|
@@ -13,7 +14,7 @@ const callDO = async (options, path, body) => {
|
|
|
13
14
|
});
|
|
14
15
|
if (!response.ok) {
|
|
15
16
|
const text = await response.text();
|
|
16
|
-
throw new
|
|
17
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
17
18
|
}
|
|
18
19
|
return await response.json();
|
|
19
20
|
};
|
|
@@ -22,25 +23,25 @@ const getDO = async (options, path) => {
|
|
|
22
23
|
const response = await stub.fetch(`https://scheduler.internal${path}`, { method: "GET" });
|
|
23
24
|
if (!response.ok) {
|
|
24
25
|
const text = await response.text();
|
|
25
|
-
throw new
|
|
26
|
+
throw new LunoraError("INTERNAL", `@lunora/scheduler: SchedulerDO ${path} failed (${String(response.status)}): ${text}`);
|
|
26
27
|
}
|
|
27
28
|
return await response.json();
|
|
28
29
|
};
|
|
29
30
|
const createWorkpool = (options) => {
|
|
30
31
|
if (!options.namespace) {
|
|
31
|
-
throw new
|
|
32
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `namespace` (SchedulerDO binding) is required");
|
|
32
33
|
}
|
|
33
34
|
if (!options.originUrl) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `originUrl` is required so the DO can dispatch back to the Worker");
|
|
35
36
|
}
|
|
36
37
|
if (!Number.isInteger(options.maxConcurrency) || options.maxConcurrency <= 0) {
|
|
37
|
-
throw new
|
|
38
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `maxConcurrency` must be a positive integer");
|
|
38
39
|
}
|
|
39
40
|
const name = typeof options.name === "string" && options.name.length > 0 ? options.name : "default";
|
|
40
41
|
const enqueue = async (function_, args, options_ = {}) => {
|
|
41
42
|
const delayMs = options_.delayMs ?? 0;
|
|
42
43
|
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
43
|
-
throw new
|
|
44
|
+
throw new LunoraError("INTERNAL", "@lunora/scheduler: `delayMs` must be a non-negative finite number");
|
|
44
45
|
}
|
|
45
46
|
return callDO(options, "/schedule", {
|
|
46
47
|
args,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/scheduler",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.6",
|
|
4
4
|
"description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.2",
|
|
49
50
|
"cron-parser": "5.6.1"
|
|
50
51
|
},
|
|
51
52
|
"engines": {
|