@aigne/afs-scheduler 1.12.0-beta.5

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.
Files changed (75) hide show
  1. package/LICENSE.md +26 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/cron-parser.cjs +202 -0
  5. package/dist/cron-parser.d.cts +65 -0
  6. package/dist/cron-parser.d.cts.map +1 -0
  7. package/dist/cron-parser.d.mts +65 -0
  8. package/dist/cron-parser.d.mts.map +1 -0
  9. package/dist/cron-parser.mjs +200 -0
  10. package/dist/cron-parser.mjs.map +1 -0
  11. package/dist/do-executor.cjs +59 -0
  12. package/dist/do-executor.d.cts +30 -0
  13. package/dist/do-executor.d.cts.map +1 -0
  14. package/dist/do-executor.d.mts +30 -0
  15. package/dist/do-executor.d.mts.map +1 -0
  16. package/dist/do-executor.mjs +59 -0
  17. package/dist/do-executor.mjs.map +1 -0
  18. package/dist/do-job-executor.cjs +53 -0
  19. package/dist/do-job-executor.d.cts +28 -0
  20. package/dist/do-job-executor.d.cts.map +1 -0
  21. package/dist/do-job-executor.d.mts +28 -0
  22. package/dist/do-job-executor.d.mts.map +1 -0
  23. package/dist/do-job-executor.mjs +53 -0
  24. package/dist/do-job-executor.mjs.map +1 -0
  25. package/dist/do-types.d.cts +66 -0
  26. package/dist/do-types.d.cts.map +1 -0
  27. package/dist/do-types.d.mts +66 -0
  28. package/dist/do-types.d.mts.map +1 -0
  29. package/dist/index.cjs +34 -0
  30. package/dist/index.d.cts +11 -0
  31. package/dist/index.d.mts +11 -0
  32. package/dist/index.mjs +11 -0
  33. package/dist/job-coordinator.cjs +216 -0
  34. package/dist/job-coordinator.d.cts +8 -0
  35. package/dist/job-coordinator.d.cts.map +1 -0
  36. package/dist/job-coordinator.d.mts +8 -0
  37. package/dist/job-coordinator.d.mts.map +1 -0
  38. package/dist/job-coordinator.mjs +216 -0
  39. package/dist/job-coordinator.mjs.map +1 -0
  40. package/dist/local-executor.cjs +25 -0
  41. package/dist/local-executor.d.cts +12 -0
  42. package/dist/local-executor.d.cts.map +1 -0
  43. package/dist/local-executor.d.mts +12 -0
  44. package/dist/local-executor.d.mts.map +1 -0
  45. package/dist/local-executor.mjs +25 -0
  46. package/dist/local-executor.mjs.map +1 -0
  47. package/dist/scheduler-provider.cjs +1236 -0
  48. package/dist/scheduler-provider.d.cts +95 -0
  49. package/dist/scheduler-provider.d.cts.map +1 -0
  50. package/dist/scheduler-provider.d.mts +95 -0
  51. package/dist/scheduler-provider.d.mts.map +1 -0
  52. package/dist/scheduler-provider.mjs +1237 -0
  53. package/dist/scheduler-provider.mjs.map +1 -0
  54. package/dist/task-runner.cjs +70 -0
  55. package/dist/task-runner.d.cts +25 -0
  56. package/dist/task-runner.d.cts.map +1 -0
  57. package/dist/task-runner.d.mts +25 -0
  58. package/dist/task-runner.d.mts.map +1 -0
  59. package/dist/task-runner.mjs +70 -0
  60. package/dist/task-runner.mjs.map +1 -0
  61. package/dist/task-store.cjs +186 -0
  62. package/dist/task-store.d.cts +60 -0
  63. package/dist/task-store.d.cts.map +1 -0
  64. package/dist/task-store.d.mts +60 -0
  65. package/dist/task-store.d.mts.map +1 -0
  66. package/dist/task-store.mjs +187 -0
  67. package/dist/task-store.mjs.map +1 -0
  68. package/dist/types.cjs +185 -0
  69. package/dist/types.d.cts +384 -0
  70. package/dist/types.d.cts.map +1 -0
  71. package/dist/types.d.mts +384 -0
  72. package/dist/types.d.mts.map +1 -0
  73. package/dist/types.mjs +176 -0
  74. package/dist/types.mjs.map +1 -0
  75. package/package.json +59 -0
package/LICENSE.md ADDED
@@ -0,0 +1,26 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. Unauthorized copying, modification, distribution, or use of
7
+ this Software, via any medium, is strictly prohibited.
8
+
9
+ The Software is provided for internal use only within ArcBlock, Inc. and its
10
+ authorized affiliates.
11
+
12
+ ## No License Granted
13
+
14
+ No license, express or implied, is granted to any party for any purpose.
15
+ All rights are reserved by ArcBlock, Inc.
16
+
17
+ ## Public Artifact Distribution
18
+
19
+ Portions of this Software may be released publicly under separate open-source
20
+ licenses (such as MIT License) through designated public repositories. Such
21
+ public releases are governed by their respective licenses and do not affect
22
+ the proprietary nature of this repository.
23
+
24
+ ## Contact
25
+
26
+ For licensing inquiries, contact: legal@arcblock.io
@@ -0,0 +1,11 @@
1
+
2
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
3
+ function __decorate(decorators, target, key, desc) {
4
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
5
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
6
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
7
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
8
+ }
9
+
10
+ //#endregion
11
+ exports.__decorate = __decorate;
@@ -0,0 +1,10 @@
1
+ //#region \0@oxc-project+runtime@0.108.0/helpers/decorate.js
2
+ function __decorate(decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ }
8
+
9
+ //#endregion
10
+ export { __decorate };
@@ -0,0 +1,202 @@
1
+
2
+ //#region src/cron-parser.ts
3
+ const FIELDS = [
4
+ {
5
+ min: 0,
6
+ max: 59,
7
+ name: "minute"
8
+ },
9
+ {
10
+ min: 0,
11
+ max: 23,
12
+ name: "hour"
13
+ },
14
+ {
15
+ min: 1,
16
+ max: 31,
17
+ name: "dayOfMonth"
18
+ },
19
+ {
20
+ min: 1,
21
+ max: 12,
22
+ name: "month"
23
+ },
24
+ {
25
+ min: 0,
26
+ max: 7,
27
+ name: "dayOfWeek"
28
+ }
29
+ ];
30
+ function parseField(field, constraint) {
31
+ return field.split(",").map((part) => parsePart(part.trim(), constraint));
32
+ }
33
+ function parsePart(part, constraint) {
34
+ if (part.includes("/")) {
35
+ const segments = part.split("/");
36
+ const baseStr = segments[0];
37
+ const stepStr = segments[1];
38
+ const step = parseIntStrict(stepStr, constraint.name);
39
+ if (step <= 0) throw new Error(`Invalid step value 0 in ${constraint.name}`);
40
+ let base;
41
+ if (baseStr === "*") base = { type: "wildcard" };
42
+ else if (baseStr.includes("-")) base = parseRange(baseStr, constraint);
43
+ else {
44
+ const val = parseIntStrict(baseStr, constraint.name);
45
+ validateValue(val, constraint);
46
+ base = {
47
+ type: "range",
48
+ from: val,
49
+ to: constraint.max
50
+ };
51
+ }
52
+ return {
53
+ type: "step",
54
+ base,
55
+ step
56
+ };
57
+ }
58
+ if (part.includes("-")) return parseRange(part, constraint);
59
+ if (part === "*") return { type: "wildcard" };
60
+ const value = parseIntStrict(part, constraint.name);
61
+ validateValue(value, constraint);
62
+ return {
63
+ type: "value",
64
+ value
65
+ };
66
+ }
67
+ function parseRange(part, constraint) {
68
+ const segments = part.split("-");
69
+ const from = parseIntStrict(segments[0], constraint.name);
70
+ const to = parseIntStrict(segments[1], constraint.name);
71
+ validateValue(from, constraint);
72
+ validateValue(to, constraint);
73
+ if (from > to) throw new Error(`Invalid range ${from}-${to} in ${constraint.name}: from > to`);
74
+ return {
75
+ type: "range",
76
+ from,
77
+ to
78
+ };
79
+ }
80
+ function parseIntStrict(str, fieldName) {
81
+ if (!/^\d+$/.test(str)) throw new Error(`Invalid value "${str}" in ${fieldName}`);
82
+ return Number.parseInt(str, 10);
83
+ }
84
+ function validateValue(value, constraint) {
85
+ if (value < constraint.min || value > constraint.max) throw new Error(`Value ${value} out of range [${constraint.min}-${constraint.max}] for ${constraint.name}`);
86
+ }
87
+ /**
88
+ * Parse a standard 5-field cron expression.
89
+ */
90
+ function parseCron(expr) {
91
+ const trimmed = expr.trim();
92
+ if (!trimmed) throw new Error("Empty cron expression");
93
+ const fields = trimmed.split(/\s+/);
94
+ if (fields.length !== 5) throw new Error(`Expected 5 fields in cron expression, got ${fields.length}`);
95
+ return {
96
+ minute: parseField(fields[0], FIELDS[0]),
97
+ hour: parseField(fields[1], FIELDS[1]),
98
+ dayOfMonth: parseField(fields[2], FIELDS[2]),
99
+ month: parseField(fields[3], FIELDS[3]),
100
+ dayOfWeek: parseField(fields[4], FIELDS[4])
101
+ };
102
+ }
103
+ function matchesPart(value, part) {
104
+ switch (part.type) {
105
+ case "wildcard": return true;
106
+ case "value": return value === part.value;
107
+ case "range": return value >= part.from && value <= part.to;
108
+ case "step": {
109
+ const { base, step } = part;
110
+ if (base.type === "wildcard") return value % step === 0;
111
+ if (value < base.from || value > base.to) return false;
112
+ return (value - base.from) % step === 0;
113
+ }
114
+ }
115
+ }
116
+ function matchesField(value, parts) {
117
+ return parts.some((part) => matchesPart(value, part));
118
+ }
119
+ /**
120
+ * Check if a cron expression matches a given time (evaluated in UTC).
121
+ */
122
+ function isDue(expr, now) {
123
+ const cron = parseCron(expr);
124
+ const minute = now.getUTCMinutes();
125
+ const hour = now.getUTCHours();
126
+ const dayOfMonth = now.getUTCDate();
127
+ const month = now.getUTCMonth() + 1;
128
+ const dayOfWeek = now.getUTCDay();
129
+ if (!matchesField(minute, cron.minute)) return false;
130
+ if (!matchesField(hour, cron.hour)) return false;
131
+ if (!matchesField(dayOfMonth, cron.dayOfMonth)) return false;
132
+ if (!matchesField(month, cron.month)) return false;
133
+ if (!matchesField(dayOfWeek, cron.dayOfWeek.map((part) => normalizeDow(part)))) return false;
134
+ return true;
135
+ }
136
+ /**
137
+ * Normalize day-of-week part: convert 7 to 0 (both mean Sunday).
138
+ */
139
+ function normalizeDow(part) {
140
+ switch (part.type) {
141
+ case "value": return part.value === 7 ? {
142
+ type: "value",
143
+ value: 0
144
+ } : part;
145
+ case "range": return {
146
+ type: "range",
147
+ from: part.from === 7 ? 0 : part.from,
148
+ to: part.to === 7 ? 0 : part.to
149
+ };
150
+ default: return part;
151
+ }
152
+ }
153
+ /**
154
+ * Calculate the next time a cron expression will fire after a given date (UTC).
155
+ * The `after` time itself is excluded — if it exactly matches, the next occurrence is returned.
156
+ *
157
+ * Throws if no match found within 4 years (guards against impossible expressions like Feb 30).
158
+ */
159
+ function nextRun(expr, after) {
160
+ const candidate = new Date(after.getTime());
161
+ candidate.setUTCSeconds(0, 0);
162
+ candidate.setUTCMinutes(candidate.getUTCMinutes() + 1);
163
+ const maxTime = after.getTime() + 4 * 365.25 * 24 * 60 * 60 * 1e3;
164
+ const cron = parseCron(expr);
165
+ const dowParts = cron.dayOfWeek.map((part) => normalizeDow(part));
166
+ while (candidate.getTime() <= maxTime) {
167
+ if (!matchesField(candidate.getUTCMonth() + 1, cron.month)) {
168
+ candidate.setUTCMonth(candidate.getUTCMonth() + 1, 1);
169
+ candidate.setUTCHours(0, 0, 0, 0);
170
+ continue;
171
+ }
172
+ const dayOfMonth = candidate.getUTCDate();
173
+ if (!matchesField(dayOfMonth, cron.dayOfMonth)) {
174
+ candidate.setUTCDate(dayOfMonth + 1);
175
+ candidate.setUTCHours(0, 0, 0, 0);
176
+ continue;
177
+ }
178
+ if (!matchesField(candidate.getUTCDay(), dowParts)) {
179
+ candidate.setUTCDate(candidate.getUTCDate() + 1);
180
+ candidate.setUTCHours(0, 0, 0, 0);
181
+ continue;
182
+ }
183
+ const hour = candidate.getUTCHours();
184
+ if (!matchesField(hour, cron.hour)) {
185
+ candidate.setUTCHours(hour + 1, 0, 0, 0);
186
+ continue;
187
+ }
188
+ const minute = candidate.getUTCMinutes();
189
+ if (!matchesField(minute, cron.minute)) {
190
+ candidate.setUTCMinutes(minute + 1);
191
+ candidate.setUTCSeconds(0, 0);
192
+ continue;
193
+ }
194
+ return new Date(candidate.getTime());
195
+ }
196
+ throw new Error(`No matching time found within 4 years for cron expression: ${expr}`);
197
+ }
198
+
199
+ //#endregion
200
+ exports.isDue = isDue;
201
+ exports.nextRun = nextRun;
202
+ exports.parseCron = parseCron;
@@ -0,0 +1,65 @@
1
+ //#region src/cron-parser.d.ts
2
+ /**
3
+ * Cron expression parser and evaluator.
4
+ *
5
+ * Supports standard 5-field cron: minute hour dayOfMonth month dayOfWeek
6
+ *
7
+ * Field ranges:
8
+ * minute: 0-59
9
+ * hour: 0-23
10
+ * dayOfMonth: 1-31
11
+ * month: 1-12
12
+ * dayOfWeek: 0-7 (0 and 7 both mean Sunday)
13
+ *
14
+ * Syntax per field:
15
+ * * wildcard (any value)
16
+ * N exact value
17
+ * N-M range (inclusive)
18
+ * N/S step from N
19
+ * * /S every S (step on wildcard)
20
+ * N-M/S step within range
21
+ * A,B,C comma-separated list of any of the above
22
+ */
23
+ interface CronWildcard {
24
+ type: "wildcard";
25
+ }
26
+ interface CronValue {
27
+ type: "value";
28
+ value: number;
29
+ }
30
+ interface CronRange {
31
+ type: "range";
32
+ from: number;
33
+ to: number;
34
+ }
35
+ interface CronStep {
36
+ type: "step";
37
+ base: CronWildcard | CronRange;
38
+ step: number;
39
+ }
40
+ type CronPart = CronWildcard | CronValue | CronRange | CronStep;
41
+ interface CronExpression {
42
+ minute: CronPart[];
43
+ hour: CronPart[];
44
+ dayOfMonth: CronPart[];
45
+ month: CronPart[];
46
+ dayOfWeek: CronPart[];
47
+ }
48
+ /**
49
+ * Parse a standard 5-field cron expression.
50
+ */
51
+ declare function parseCron(expr: string): CronExpression;
52
+ /**
53
+ * Check if a cron expression matches a given time (evaluated in UTC).
54
+ */
55
+ declare function isDue(expr: string, now: Date): boolean;
56
+ /**
57
+ * Calculate the next time a cron expression will fire after a given date (UTC).
58
+ * The `after` time itself is excluded — if it exactly matches, the next occurrence is returned.
59
+ *
60
+ * Throws if no match found within 4 years (guards against impossible expressions like Feb 30).
61
+ */
62
+ declare function nextRun(expr: string, after: Date): Date;
63
+ //#endregion
64
+ export { CronExpression, CronPart, isDue, nextRun, parseCron };
65
+ //# sourceMappingURL=cron-parser.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron-parser.d.cts","names":[],"sources":["../src/cron-parser.ts"],"mappings":";;AA0BA;;;;;AAIA;;;;;AAKA;;;;;;;;;AAMA;UAfiB,YAAA;EACf,IAAA;AAAA;AAAA,UAGe,SAAA;EACf,IAAA;EACA,KAAA;AAAA;AAAA,UAGe,SAAA;EACf,IAAA;EACA,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,QAAA;EACf,IAAA;EACA,IAAA,EAAM,YAAA,GAAe,SAAA;EACrB,IAAA;AAAA;AAAA,KAGU,QAAA,GAAW,YAAA,GAAe,SAAA,GAAY,SAAA,GAAY,QAAA;AAAA,UAE7C,cAAA;EACf,MAAA,EAAQ,QAAA;EACR,IAAA,EAAM,QAAA;EACN,UAAA,EAAY,QAAA;EACZ,KAAA,EAAO,QAAA;EACP,SAAA,EAAW,QAAA;AAAA;AALb;;;AAAA,iBAqGgB,SAAA,CAAU,IAAA,WAAe,cAAA;;;;iBAqDzB,KAAA,CAAM,IAAA,UAAc,GAAA,EAAK,IAAA;;;;;;;iBAkDzB,OAAA,CAAQ,IAAA,UAAc,KAAA,EAAO,IAAA,GAAO,IAAA"}
@@ -0,0 +1,65 @@
1
+ //#region src/cron-parser.d.ts
2
+ /**
3
+ * Cron expression parser and evaluator.
4
+ *
5
+ * Supports standard 5-field cron: minute hour dayOfMonth month dayOfWeek
6
+ *
7
+ * Field ranges:
8
+ * minute: 0-59
9
+ * hour: 0-23
10
+ * dayOfMonth: 1-31
11
+ * month: 1-12
12
+ * dayOfWeek: 0-7 (0 and 7 both mean Sunday)
13
+ *
14
+ * Syntax per field:
15
+ * * wildcard (any value)
16
+ * N exact value
17
+ * N-M range (inclusive)
18
+ * N/S step from N
19
+ * * /S every S (step on wildcard)
20
+ * N-M/S step within range
21
+ * A,B,C comma-separated list of any of the above
22
+ */
23
+ interface CronWildcard {
24
+ type: "wildcard";
25
+ }
26
+ interface CronValue {
27
+ type: "value";
28
+ value: number;
29
+ }
30
+ interface CronRange {
31
+ type: "range";
32
+ from: number;
33
+ to: number;
34
+ }
35
+ interface CronStep {
36
+ type: "step";
37
+ base: CronWildcard | CronRange;
38
+ step: number;
39
+ }
40
+ type CronPart = CronWildcard | CronValue | CronRange | CronStep;
41
+ interface CronExpression {
42
+ minute: CronPart[];
43
+ hour: CronPart[];
44
+ dayOfMonth: CronPart[];
45
+ month: CronPart[];
46
+ dayOfWeek: CronPart[];
47
+ }
48
+ /**
49
+ * Parse a standard 5-field cron expression.
50
+ */
51
+ declare function parseCron(expr: string): CronExpression;
52
+ /**
53
+ * Check if a cron expression matches a given time (evaluated in UTC).
54
+ */
55
+ declare function isDue(expr: string, now: Date): boolean;
56
+ /**
57
+ * Calculate the next time a cron expression will fire after a given date (UTC).
58
+ * The `after` time itself is excluded — if it exactly matches, the next occurrence is returned.
59
+ *
60
+ * Throws if no match found within 4 years (guards against impossible expressions like Feb 30).
61
+ */
62
+ declare function nextRun(expr: string, after: Date): Date;
63
+ //#endregion
64
+ export { CronExpression, CronPart, isDue, nextRun, parseCron };
65
+ //# sourceMappingURL=cron-parser.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron-parser.d.mts","names":[],"sources":["../src/cron-parser.ts"],"mappings":";;AA0BA;;;;;AAIA;;;;;AAKA;;;;;;;;;AAMA;UAfiB,YAAA;EACf,IAAA;AAAA;AAAA,UAGe,SAAA;EACf,IAAA;EACA,KAAA;AAAA;AAAA,UAGe,SAAA;EACf,IAAA;EACA,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,QAAA;EACf,IAAA;EACA,IAAA,EAAM,YAAA,GAAe,SAAA;EACrB,IAAA;AAAA;AAAA,KAGU,QAAA,GAAW,YAAA,GAAe,SAAA,GAAY,SAAA,GAAY,QAAA;AAAA,UAE7C,cAAA;EACf,MAAA,EAAQ,QAAA;EACR,IAAA,EAAM,QAAA;EACN,UAAA,EAAY,QAAA;EACZ,KAAA,EAAO,QAAA;EACP,SAAA,EAAW,QAAA;AAAA;AALb;;;AAAA,iBAqGgB,SAAA,CAAU,IAAA,WAAe,cAAA;;;;iBAqDzB,KAAA,CAAM,IAAA,UAAc,GAAA,EAAK,IAAA;;;;;;;iBAkDzB,OAAA,CAAQ,IAAA,UAAc,KAAA,EAAO,IAAA,GAAO,IAAA"}
@@ -0,0 +1,200 @@
1
+ //#region src/cron-parser.ts
2
+ const FIELDS = [
3
+ {
4
+ min: 0,
5
+ max: 59,
6
+ name: "minute"
7
+ },
8
+ {
9
+ min: 0,
10
+ max: 23,
11
+ name: "hour"
12
+ },
13
+ {
14
+ min: 1,
15
+ max: 31,
16
+ name: "dayOfMonth"
17
+ },
18
+ {
19
+ min: 1,
20
+ max: 12,
21
+ name: "month"
22
+ },
23
+ {
24
+ min: 0,
25
+ max: 7,
26
+ name: "dayOfWeek"
27
+ }
28
+ ];
29
+ function parseField(field, constraint) {
30
+ return field.split(",").map((part) => parsePart(part.trim(), constraint));
31
+ }
32
+ function parsePart(part, constraint) {
33
+ if (part.includes("/")) {
34
+ const segments = part.split("/");
35
+ const baseStr = segments[0];
36
+ const stepStr = segments[1];
37
+ const step = parseIntStrict(stepStr, constraint.name);
38
+ if (step <= 0) throw new Error(`Invalid step value 0 in ${constraint.name}`);
39
+ let base;
40
+ if (baseStr === "*") base = { type: "wildcard" };
41
+ else if (baseStr.includes("-")) base = parseRange(baseStr, constraint);
42
+ else {
43
+ const val = parseIntStrict(baseStr, constraint.name);
44
+ validateValue(val, constraint);
45
+ base = {
46
+ type: "range",
47
+ from: val,
48
+ to: constraint.max
49
+ };
50
+ }
51
+ return {
52
+ type: "step",
53
+ base,
54
+ step
55
+ };
56
+ }
57
+ if (part.includes("-")) return parseRange(part, constraint);
58
+ if (part === "*") return { type: "wildcard" };
59
+ const value = parseIntStrict(part, constraint.name);
60
+ validateValue(value, constraint);
61
+ return {
62
+ type: "value",
63
+ value
64
+ };
65
+ }
66
+ function parseRange(part, constraint) {
67
+ const segments = part.split("-");
68
+ const from = parseIntStrict(segments[0], constraint.name);
69
+ const to = parseIntStrict(segments[1], constraint.name);
70
+ validateValue(from, constraint);
71
+ validateValue(to, constraint);
72
+ if (from > to) throw new Error(`Invalid range ${from}-${to} in ${constraint.name}: from > to`);
73
+ return {
74
+ type: "range",
75
+ from,
76
+ to
77
+ };
78
+ }
79
+ function parseIntStrict(str, fieldName) {
80
+ if (!/^\d+$/.test(str)) throw new Error(`Invalid value "${str}" in ${fieldName}`);
81
+ return Number.parseInt(str, 10);
82
+ }
83
+ function validateValue(value, constraint) {
84
+ if (value < constraint.min || value > constraint.max) throw new Error(`Value ${value} out of range [${constraint.min}-${constraint.max}] for ${constraint.name}`);
85
+ }
86
+ /**
87
+ * Parse a standard 5-field cron expression.
88
+ */
89
+ function parseCron(expr) {
90
+ const trimmed = expr.trim();
91
+ if (!trimmed) throw new Error("Empty cron expression");
92
+ const fields = trimmed.split(/\s+/);
93
+ if (fields.length !== 5) throw new Error(`Expected 5 fields in cron expression, got ${fields.length}`);
94
+ return {
95
+ minute: parseField(fields[0], FIELDS[0]),
96
+ hour: parseField(fields[1], FIELDS[1]),
97
+ dayOfMonth: parseField(fields[2], FIELDS[2]),
98
+ month: parseField(fields[3], FIELDS[3]),
99
+ dayOfWeek: parseField(fields[4], FIELDS[4])
100
+ };
101
+ }
102
+ function matchesPart(value, part) {
103
+ switch (part.type) {
104
+ case "wildcard": return true;
105
+ case "value": return value === part.value;
106
+ case "range": return value >= part.from && value <= part.to;
107
+ case "step": {
108
+ const { base, step } = part;
109
+ if (base.type === "wildcard") return value % step === 0;
110
+ if (value < base.from || value > base.to) return false;
111
+ return (value - base.from) % step === 0;
112
+ }
113
+ }
114
+ }
115
+ function matchesField(value, parts) {
116
+ return parts.some((part) => matchesPart(value, part));
117
+ }
118
+ /**
119
+ * Check if a cron expression matches a given time (evaluated in UTC).
120
+ */
121
+ function isDue(expr, now) {
122
+ const cron = parseCron(expr);
123
+ const minute = now.getUTCMinutes();
124
+ const hour = now.getUTCHours();
125
+ const dayOfMonth = now.getUTCDate();
126
+ const month = now.getUTCMonth() + 1;
127
+ const dayOfWeek = now.getUTCDay();
128
+ if (!matchesField(minute, cron.minute)) return false;
129
+ if (!matchesField(hour, cron.hour)) return false;
130
+ if (!matchesField(dayOfMonth, cron.dayOfMonth)) return false;
131
+ if (!matchesField(month, cron.month)) return false;
132
+ if (!matchesField(dayOfWeek, cron.dayOfWeek.map((part) => normalizeDow(part)))) return false;
133
+ return true;
134
+ }
135
+ /**
136
+ * Normalize day-of-week part: convert 7 to 0 (both mean Sunday).
137
+ */
138
+ function normalizeDow(part) {
139
+ switch (part.type) {
140
+ case "value": return part.value === 7 ? {
141
+ type: "value",
142
+ value: 0
143
+ } : part;
144
+ case "range": return {
145
+ type: "range",
146
+ from: part.from === 7 ? 0 : part.from,
147
+ to: part.to === 7 ? 0 : part.to
148
+ };
149
+ default: return part;
150
+ }
151
+ }
152
+ /**
153
+ * Calculate the next time a cron expression will fire after a given date (UTC).
154
+ * The `after` time itself is excluded — if it exactly matches, the next occurrence is returned.
155
+ *
156
+ * Throws if no match found within 4 years (guards against impossible expressions like Feb 30).
157
+ */
158
+ function nextRun(expr, after) {
159
+ const candidate = new Date(after.getTime());
160
+ candidate.setUTCSeconds(0, 0);
161
+ candidate.setUTCMinutes(candidate.getUTCMinutes() + 1);
162
+ const maxTime = after.getTime() + 4 * 365.25 * 24 * 60 * 60 * 1e3;
163
+ const cron = parseCron(expr);
164
+ const dowParts = cron.dayOfWeek.map((part) => normalizeDow(part));
165
+ while (candidate.getTime() <= maxTime) {
166
+ if (!matchesField(candidate.getUTCMonth() + 1, cron.month)) {
167
+ candidate.setUTCMonth(candidate.getUTCMonth() + 1, 1);
168
+ candidate.setUTCHours(0, 0, 0, 0);
169
+ continue;
170
+ }
171
+ const dayOfMonth = candidate.getUTCDate();
172
+ if (!matchesField(dayOfMonth, cron.dayOfMonth)) {
173
+ candidate.setUTCDate(dayOfMonth + 1);
174
+ candidate.setUTCHours(0, 0, 0, 0);
175
+ continue;
176
+ }
177
+ if (!matchesField(candidate.getUTCDay(), dowParts)) {
178
+ candidate.setUTCDate(candidate.getUTCDate() + 1);
179
+ candidate.setUTCHours(0, 0, 0, 0);
180
+ continue;
181
+ }
182
+ const hour = candidate.getUTCHours();
183
+ if (!matchesField(hour, cron.hour)) {
184
+ candidate.setUTCHours(hour + 1, 0, 0, 0);
185
+ continue;
186
+ }
187
+ const minute = candidate.getUTCMinutes();
188
+ if (!matchesField(minute, cron.minute)) {
189
+ candidate.setUTCMinutes(minute + 1);
190
+ candidate.setUTCSeconds(0, 0);
191
+ continue;
192
+ }
193
+ return new Date(candidate.getTime());
194
+ }
195
+ throw new Error(`No matching time found within 4 years for cron expression: ${expr}`);
196
+ }
197
+
198
+ //#endregion
199
+ export { isDue, nextRun, parseCron };
200
+ //# sourceMappingURL=cron-parser.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron-parser.mjs","names":[],"sources":["../src/cron-parser.ts"],"sourcesContent":["/**\n * Cron expression parser and evaluator.\n *\n * Supports standard 5-field cron: minute hour dayOfMonth month dayOfWeek\n *\n * Field ranges:\n * minute: 0-59\n * hour: 0-23\n * dayOfMonth: 1-31\n * month: 1-12\n * dayOfWeek: 0-7 (0 and 7 both mean Sunday)\n *\n * Syntax per field:\n * * wildcard (any value)\n * N exact value\n * N-M range (inclusive)\n * N/S step from N\n * * /S every S (step on wildcard)\n * N-M/S step within range\n * A,B,C comma-separated list of any of the above\n */\n\n// ═══════════════════════════════════════════════════════════════════\n// Types\n// ═══════════════════════════════════════════════════════════════════\n\nexport interface CronWildcard {\n type: \"wildcard\";\n}\n\nexport interface CronValue {\n type: \"value\";\n value: number;\n}\n\nexport interface CronRange {\n type: \"range\";\n from: number;\n to: number;\n}\n\nexport interface CronStep {\n type: \"step\";\n base: CronWildcard | CronRange;\n step: number;\n}\n\nexport type CronPart = CronWildcard | CronValue | CronRange | CronStep;\n\nexport interface CronExpression {\n minute: CronPart[];\n hour: CronPart[];\n dayOfMonth: CronPart[];\n month: CronPart[];\n dayOfWeek: CronPart[];\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// Field constraints\n// ═══════════════════════════════════════════════════════════════════\n\ninterface FieldConstraint {\n min: number;\n max: number;\n name: string;\n}\n\nconst FIELDS: FieldConstraint[] = [\n { min: 0, max: 59, name: \"minute\" },\n { min: 0, max: 23, name: \"hour\" },\n { min: 1, max: 31, name: \"dayOfMonth\" },\n { min: 1, max: 12, name: \"month\" },\n { min: 0, max: 7, name: \"dayOfWeek\" },\n];\n\n// ═══════════════════════════════════════════════════════════════════\n// Parser\n// ═══════════════════════════════════════════════════════════════════\n\nfunction parseField(field: string, constraint: FieldConstraint): CronPart[] {\n const parts = field.split(\",\");\n return parts.map((part) => parsePart(part.trim(), constraint));\n}\n\nfunction parsePart(part: string, constraint: FieldConstraint): CronPart {\n // Step: base/step\n if (part.includes(\"/\")) {\n const segments = part.split(\"/\");\n const baseStr = segments[0] as string;\n const stepStr = segments[1] as string;\n const step = parseIntStrict(stepStr, constraint.name);\n if (step <= 0) throw new Error(`Invalid step value 0 in ${constraint.name}`);\n\n let base: CronWildcard | CronRange;\n if (baseStr === \"*\") {\n base = { type: \"wildcard\" };\n } else if (baseStr.includes(\"-\")) {\n base = parseRange(baseStr, constraint);\n } else {\n // Treat single value as start of range to max\n const val = parseIntStrict(baseStr, constraint.name);\n validateValue(val, constraint);\n base = { type: \"range\", from: val, to: constraint.max };\n }\n\n return { type: \"step\", base, step };\n }\n\n // Range: from-to\n if (part.includes(\"-\")) {\n return parseRange(part, constraint);\n }\n\n // Wildcard\n if (part === \"*\") {\n return { type: \"wildcard\" };\n }\n\n // Single value\n const value = parseIntStrict(part, constraint.name);\n validateValue(value, constraint);\n return { type: \"value\", value };\n}\n\nfunction parseRange(part: string, constraint: FieldConstraint): CronRange {\n const segments = part.split(\"-\");\n const from = parseIntStrict(segments[0] as string, constraint.name);\n const to = parseIntStrict(segments[1] as string, constraint.name);\n validateValue(from, constraint);\n validateValue(to, constraint);\n if (from > to) throw new Error(`Invalid range ${from}-${to} in ${constraint.name}: from > to`);\n return { type: \"range\", from, to };\n}\n\nfunction parseIntStrict(str: string, fieldName: string): number {\n if (!/^\\d+$/.test(str)) throw new Error(`Invalid value \"${str}\" in ${fieldName}`);\n return Number.parseInt(str, 10);\n}\n\nfunction validateValue(value: number, constraint: FieldConstraint): void {\n if (value < constraint.min || value > constraint.max) {\n throw new Error(\n `Value ${value} out of range [${constraint.min}-${constraint.max}] for ${constraint.name}`,\n );\n }\n}\n\n/**\n * Parse a standard 5-field cron expression.\n */\nexport function parseCron(expr: string): CronExpression {\n const trimmed = expr.trim();\n if (!trimmed) throw new Error(\"Empty cron expression\");\n\n const fields = trimmed.split(/\\s+/);\n if (fields.length !== 5) {\n throw new Error(`Expected 5 fields in cron expression, got ${fields.length}`);\n }\n\n return {\n minute: parseField(fields[0] as string, FIELDS[0] as FieldConstraint),\n hour: parseField(fields[1] as string, FIELDS[1] as FieldConstraint),\n dayOfMonth: parseField(fields[2] as string, FIELDS[2] as FieldConstraint),\n month: parseField(fields[3] as string, FIELDS[3] as FieldConstraint),\n dayOfWeek: parseField(fields[4] as string, FIELDS[4] as FieldConstraint),\n };\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// Matcher — does a value match a cron part?\n// ═══════════════════════════════════════════════════════════════════\n\nfunction matchesPart(value: number, part: CronPart): boolean {\n switch (part.type) {\n case \"wildcard\":\n return true;\n case \"value\":\n return value === part.value;\n case \"range\":\n return value >= part.from && value <= part.to;\n case \"step\": {\n const { base, step } = part;\n if (base.type === \"wildcard\") {\n return value % step === 0;\n }\n // Range-based step\n if (value < base.from || value > base.to) return false;\n return (value - base.from) % step === 0;\n }\n }\n}\n\nfunction matchesField(value: number, parts: CronPart[]): boolean {\n return parts.some((part) => matchesPart(value, part));\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// isDue\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if a cron expression matches a given time (evaluated in UTC).\n */\nexport function isDue(expr: string, now: Date): boolean {\n const cron = parseCron(expr);\n\n const minute = now.getUTCMinutes();\n const hour = now.getUTCHours();\n const dayOfMonth = now.getUTCDate();\n const month = now.getUTCMonth() + 1; // 0-based → 1-based\n const dayOfWeek = now.getUTCDay(); // 0=Sunday\n\n // Check each field\n if (!matchesField(minute, cron.minute)) return false;\n if (!matchesField(hour, cron.hour)) return false;\n if (!matchesField(dayOfMonth, cron.dayOfMonth)) return false;\n if (!matchesField(month, cron.month)) return false;\n\n // dayOfWeek: normalize 7 → 0 in the cron parts for matching\n const dowParts = cron.dayOfWeek.map((part) => normalizeDow(part));\n if (!matchesField(dayOfWeek, dowParts)) return false;\n\n return true;\n}\n\n/**\n * Normalize day-of-week part: convert 7 to 0 (both mean Sunday).\n */\nfunction normalizeDow(part: CronPart): CronPart {\n switch (part.type) {\n case \"value\":\n return part.value === 7 ? { type: \"value\", value: 0 } : part;\n case \"range\":\n return {\n type: \"range\",\n from: part.from === 7 ? 0 : part.from,\n to: part.to === 7 ? 0 : part.to,\n };\n default:\n return part;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// nextRun\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Calculate the next time a cron expression will fire after a given date (UTC).\n * The `after` time itself is excluded — if it exactly matches, the next occurrence is returned.\n *\n * Throws if no match found within 4 years (guards against impossible expressions like Feb 30).\n */\nexport function nextRun(expr: string, after: Date): Date {\n // Start from the next minute after `after`\n const candidate = new Date(after.getTime());\n candidate.setUTCSeconds(0, 0);\n candidate.setUTCMinutes(candidate.getUTCMinutes() + 1);\n\n const maxTime = after.getTime() + 4 * 365.25 * 24 * 60 * 60 * 1000;\n\n const cron = parseCron(expr);\n const dowParts = cron.dayOfWeek.map((part) => normalizeDow(part));\n\n while (candidate.getTime() <= maxTime) {\n const month = candidate.getUTCMonth() + 1;\n if (!matchesField(month, cron.month)) {\n // Jump to first day of next month\n candidate.setUTCMonth(candidate.getUTCMonth() + 1, 1);\n candidate.setUTCHours(0, 0, 0, 0);\n continue;\n }\n\n const dayOfMonth = candidate.getUTCDate();\n if (!matchesField(dayOfMonth, cron.dayOfMonth)) {\n // Jump to next day\n candidate.setUTCDate(dayOfMonth + 1);\n candidate.setUTCHours(0, 0, 0, 0);\n continue;\n }\n\n const dayOfWeek = candidate.getUTCDay();\n if (!matchesField(dayOfWeek, dowParts)) {\n // Jump to next day\n candidate.setUTCDate(candidate.getUTCDate() + 1);\n candidate.setUTCHours(0, 0, 0, 0);\n continue;\n }\n\n const hour = candidate.getUTCHours();\n if (!matchesField(hour, cron.hour)) {\n // Jump to next hour\n candidate.setUTCHours(hour + 1, 0, 0, 0);\n continue;\n }\n\n const minute = candidate.getUTCMinutes();\n if (!matchesField(minute, cron.minute)) {\n // Jump to next minute\n candidate.setUTCMinutes(minute + 1);\n candidate.setUTCSeconds(0, 0);\n continue;\n }\n\n // All fields match\n return new Date(candidate.getTime());\n }\n\n throw new Error(`No matching time found within 4 years for cron expression: ${expr}`);\n}\n"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+
2
+ //#region src/do-executor.ts
3
+ /**
4
+ * TaskExecutor that delegates execution to a Cloudflare Durable Object.
5
+ *
6
+ * Each task gets its own DO instance (idFromName(taskId)), bypassing
7
+ * the Workers 30-second CPU time limit.
8
+ *
9
+ * Error behavior matches LocalExecutor:
10
+ * - Infrastructure failures (DO crash, network error, non-200) → throws
11
+ * → Scheduler marks task as "failed"
12
+ * - Application results (exec returns { success: false }) → returns TaskResult
13
+ * → Scheduler marks task as "done" with the result
14
+ *
15
+ * Usage:
16
+ * ```typescript
17
+ * new AFSScheduler({ executor: new DOExecutor(env.TASK_DO) })
18
+ * ```
19
+ */
20
+ var DOExecutor = class {
21
+ constructor(doNamespace) {
22
+ this.doNamespace = doNamespace;
23
+ }
24
+ async run(taskId, taskPath, args, context) {
25
+ const doId = this.doNamespace.idFromName(taskId);
26
+ const stub = this.doNamespace.get(doId);
27
+ const body = {
28
+ taskId,
29
+ taskPath,
30
+ args
31
+ };
32
+ if (context) body.context = context;
33
+ const response = await stub.fetch(new Request("https://task/run", {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify(body)
37
+ }));
38
+ if (!response.ok) {
39
+ const text = await response.text().catch(() => "");
40
+ throw new Error(`DO returned ${response.status}: ${text}`);
41
+ }
42
+ const result = await response.json();
43
+ return {
44
+ success: result.success,
45
+ data: result.data,
46
+ error: result.error
47
+ };
48
+ }
49
+ async cancel(taskId) {
50
+ const doId = this.doNamespace.idFromName(taskId);
51
+ const stub = this.doNamespace.get(doId);
52
+ try {
53
+ await stub.fetch(new Request("https://task/cancel", { method: "POST" }));
54
+ } catch {}
55
+ }
56
+ };
57
+
58
+ //#endregion
59
+ exports.DOExecutor = DOExecutor;