@portel/photon 1.23.1 → 1.24.0

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 (57) hide show
  1. package/README.md +66 -0
  2. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
  3. package/dist/auto-ui/streamable-http-transport.js +262 -18
  4. package/dist/auto-ui/streamable-http-transport.js.map +1 -1
  5. package/dist/beam.bundle.js +58287 -56177
  6. package/dist/beam.bundle.js.map +4 -4
  7. package/dist/capability-negotiator.d.ts +9 -0
  8. package/dist/capability-negotiator.d.ts.map +1 -1
  9. package/dist/capability-negotiator.js +14 -0
  10. package/dist/capability-negotiator.js.map +1 -1
  11. package/dist/cli/commands/claim.d.ts +17 -0
  12. package/dist/cli/commands/claim.d.ts.map +1 -0
  13. package/dist/cli/commands/claim.js +124 -0
  14. package/dist/cli/commands/claim.js.map +1 -0
  15. package/dist/cli/commands/run.d.ts.map +1 -1
  16. package/dist/cli/commands/run.js +2 -0
  17. package/dist/cli/commands/run.js.map +1 -1
  18. package/dist/cli/index.d.ts.map +1 -1
  19. package/dist/cli/index.js +2 -0
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/daemon/claims.d.ts +108 -0
  22. package/dist/daemon/claims.d.ts.map +1 -0
  23. package/dist/daemon/claims.js +245 -0
  24. package/dist/daemon/claims.js.map +1 -0
  25. package/dist/daemon/client.d.ts.map +1 -1
  26. package/dist/daemon/client.js +15 -29
  27. package/dist/daemon/client.js.map +1 -1
  28. package/dist/daemon/cron.d.ts +36 -0
  29. package/dist/daemon/cron.d.ts.map +1 -0
  30. package/dist/daemon/cron.js +216 -0
  31. package/dist/daemon/cron.js.map +1 -0
  32. package/dist/daemon/schedule-loader.d.ts +76 -0
  33. package/dist/daemon/schedule-loader.d.ts.map +1 -0
  34. package/dist/daemon/schedule-loader.js +124 -0
  35. package/dist/daemon/schedule-loader.js.map +1 -0
  36. package/dist/daemon/server.js +76 -226
  37. package/dist/daemon/server.js.map +1 -1
  38. package/dist/deploy/cloudflare.d.ts.map +1 -1
  39. package/dist/deploy/cloudflare.js +68 -3
  40. package/dist/deploy/cloudflare.js.map +1 -1
  41. package/dist/loader.d.ts +22 -1
  42. package/dist/loader.d.ts.map +1 -1
  43. package/dist/loader.js +162 -7
  44. package/dist/loader.js.map +1 -1
  45. package/dist/photon-cli-runner.d.ts.map +1 -1
  46. package/dist/photon-cli-runner.js +17 -0
  47. package/dist/photon-cli-runner.js.map +1 -1
  48. package/dist/server.d.ts +10 -0
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +50 -1
  51. package/dist/server.js.map +1 -1
  52. package/dist/shared/memory-sqlite.d.ts +37 -0
  53. package/dist/shared/memory-sqlite.d.ts.map +1 -0
  54. package/dist/shared/memory-sqlite.js +143 -0
  55. package/dist/shared/memory-sqlite.js.map +1 -0
  56. package/package.json +2 -2
  57. package/templates/cloudflare/worker.ts.template +44 -73
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Cron parser and scheduling helpers.
3
+ *
4
+ * Five-field POSIX-style cron (minute hour day-of-month month day-of-week).
5
+ * Supports wildcards (*), lists (a,b,c), ranges (a-b), steps (*\/n), and
6
+ * Sunday-as-7 normalization. Minute granularity only.
7
+ *
8
+ * Extracted from daemon/server.ts so it can be shared with the boot loader
9
+ * and exercised by focused tests.
10
+ */
11
+ export function parseCronField(field, min, max) {
12
+ if (field === '*') {
13
+ const values = [];
14
+ for (let i = min; i <= max; i++)
15
+ values.push(i);
16
+ return values;
17
+ }
18
+ // Comma-separated list
19
+ if (field.includes(',')) {
20
+ const values = new Set();
21
+ for (const part of field.split(',')) {
22
+ const partValues = parseCronField(part, min, max);
23
+ if (!partValues)
24
+ return null;
25
+ partValues.forEach((v) => values.add(v));
26
+ }
27
+ return Array.from(values).sort((a, b) => a - b);
28
+ }
29
+ // Step values: */n, start/n, start-end/n
30
+ if (field.includes('/')) {
31
+ const slashIdx = field.indexOf('/');
32
+ const range = field.slice(0, slashIdx);
33
+ const step = parseInt(field.slice(slashIdx + 1));
34
+ if (isNaN(step) || step <= 0)
35
+ return null;
36
+ let start = min;
37
+ let end = max;
38
+ if (range !== '*') {
39
+ if (range.includes('-')) {
40
+ const [s, e] = range.split('-').map(Number);
41
+ if (isNaN(s) || isNaN(e))
42
+ return null;
43
+ start = s;
44
+ end = e;
45
+ }
46
+ else {
47
+ start = parseInt(range);
48
+ if (isNaN(start))
49
+ return null;
50
+ }
51
+ }
52
+ const values = [];
53
+ for (let i = start; i <= end; i += step)
54
+ values.push(i);
55
+ return values;
56
+ }
57
+ // Range: n-m
58
+ if (field.includes('-')) {
59
+ const [s, e] = field.split('-').map(Number);
60
+ if (isNaN(s) || isNaN(e) || s < min || e > max)
61
+ return null;
62
+ const values = [];
63
+ for (let i = s; i <= e; i++)
64
+ values.push(i);
65
+ return values;
66
+ }
67
+ // Single value
68
+ const value = parseInt(field);
69
+ if (isNaN(value) || value < min || value > max)
70
+ return null;
71
+ return [value];
72
+ }
73
+ function parse(cron) {
74
+ const parts = cron.trim().split(/\s+/);
75
+ if (parts.length !== 5)
76
+ return null;
77
+ const [minuteField, hourField, domField, monthField, dowField] = parts;
78
+ const minutes = parseCronField(minuteField, 0, 59);
79
+ const hours = parseCronField(hourField, 0, 23);
80
+ const doms = parseCronField(domField, 1, 31);
81
+ const months = parseCronField(monthField, 1, 12);
82
+ const dows = parseCronField(dowField, 0, 7);
83
+ if (!minutes || !hours || !doms || !months || !dows)
84
+ return null;
85
+ return {
86
+ minuteSet: new Set(minutes),
87
+ hourSet: new Set(hours),
88
+ domSet: new Set(doms),
89
+ monthSet: new Set(months),
90
+ // Normalize: 7 maps to 0 (both mean Sunday)
91
+ dowSet: new Set(dows.map((d) => (d === 7 ? 0 : d))),
92
+ domIsWild: domField === '*',
93
+ dowIsWild: dowField === '*',
94
+ };
95
+ }
96
+ function matches(parsed, date) {
97
+ if (!parsed.monthSet.has(date.getMonth() + 1))
98
+ return false;
99
+ let dayMatch;
100
+ if (parsed.domIsWild && parsed.dowIsWild)
101
+ dayMatch = true;
102
+ else if (parsed.domIsWild)
103
+ dayMatch = parsed.dowSet.has(date.getDay());
104
+ else if (parsed.dowIsWild)
105
+ dayMatch = parsed.domSet.has(date.getDate());
106
+ else
107
+ dayMatch = parsed.domSet.has(date.getDate()) || parsed.dowSet.has(date.getDay());
108
+ if (!dayMatch)
109
+ return false;
110
+ if (!parsed.hourSet.has(date.getHours()))
111
+ return false;
112
+ return parsed.minuteSet.has(date.getMinutes());
113
+ }
114
+ /**
115
+ * Compute the next cron occurrence strictly after `fromTime` (defaults to
116
+ * now). Returns `{isValid: false, nextRun: 0}` on malformed cron or if no
117
+ * occurrence is found within four years.
118
+ */
119
+ export function parseCron(cron, fromTime) {
120
+ const parsed = parse(cron);
121
+ if (!parsed)
122
+ return { isValid: false, nextRun: 0 };
123
+ const base = fromTime !== undefined ? new Date(fromTime) : new Date();
124
+ const candidate = new Date(base);
125
+ candidate.setSeconds(0);
126
+ candidate.setMilliseconds(0);
127
+ candidate.setMinutes(candidate.getMinutes() + 1);
128
+ const limit = new Date(candidate);
129
+ limit.setFullYear(limit.getFullYear() + 4);
130
+ while (candidate < limit) {
131
+ if (!parsed.monthSet.has(candidate.getMonth() + 1)) {
132
+ candidate.setMonth(candidate.getMonth() + 1);
133
+ candidate.setDate(1);
134
+ candidate.setHours(0);
135
+ candidate.setMinutes(0);
136
+ continue;
137
+ }
138
+ let dayMatch;
139
+ if (parsed.domIsWild && parsed.dowIsWild)
140
+ dayMatch = true;
141
+ else if (parsed.domIsWild)
142
+ dayMatch = parsed.dowSet.has(candidate.getDay());
143
+ else if (parsed.dowIsWild)
144
+ dayMatch = parsed.domSet.has(candidate.getDate());
145
+ else
146
+ dayMatch = parsed.domSet.has(candidate.getDate()) || parsed.dowSet.has(candidate.getDay());
147
+ if (!dayMatch) {
148
+ candidate.setDate(candidate.getDate() + 1);
149
+ candidate.setHours(0);
150
+ candidate.setMinutes(0);
151
+ continue;
152
+ }
153
+ if (!parsed.hourSet.has(candidate.getHours())) {
154
+ const nextHour = [...parsed.hourSet].find((h) => h > candidate.getHours());
155
+ if (nextHour !== undefined) {
156
+ candidate.setHours(nextHour);
157
+ candidate.setMinutes(0);
158
+ }
159
+ else {
160
+ candidate.setDate(candidate.getDate() + 1);
161
+ candidate.setHours(0);
162
+ candidate.setMinutes(0);
163
+ }
164
+ continue;
165
+ }
166
+ if (!parsed.minuteSet.has(candidate.getMinutes())) {
167
+ const nextMinute = [...parsed.minuteSet].find((m) => m > candidate.getMinutes());
168
+ if (nextMinute !== undefined) {
169
+ candidate.setMinutes(nextMinute);
170
+ }
171
+ else {
172
+ candidate.setHours(candidate.getHours() + 1);
173
+ candidate.setMinutes(0);
174
+ }
175
+ continue;
176
+ }
177
+ return { isValid: true, nextRun: candidate.getTime() };
178
+ }
179
+ return { isValid: false, nextRun: 0 };
180
+ }
181
+ /**
182
+ * Find the most recent cron occurrence strictly after `lastRunAt` and at or
183
+ * before `now`. Returns `null` if no occurrence fits the window, the cron is
184
+ * malformed, or `lastRunAt` is not in the past.
185
+ *
186
+ * Used at daemon boot: if a schedule was supposed to fire while the daemon
187
+ * was down, this identifies the most recent missed occurrence so the caller
188
+ * can trigger one catch-up run. Older missed occurrences are intentionally
189
+ * dropped to avoid a flood of invocations after a long outage.
190
+ *
191
+ * Complexity: iterates backward minute-by-minute from `now` to `lastRunAt`,
192
+ * bounded at 14 days. For typical schedules the first matching minute is
193
+ * found within a few hundred iterations.
194
+ */
195
+ export function computeMissedRun(cron, lastRunAt, now) {
196
+ if (!Number.isFinite(lastRunAt) || lastRunAt <= 0 || lastRunAt >= now)
197
+ return null;
198
+ const parsed = parse(cron);
199
+ if (!parsed)
200
+ return null;
201
+ const FOURTEEN_DAYS_MINUTES = 14 * 24 * 60;
202
+ const windowMinutes = Math.ceil((now - lastRunAt) / 60_000) + 1;
203
+ const maxIter = Math.min(windowMinutes, FOURTEEN_DAYS_MINUTES);
204
+ const candidate = new Date(now);
205
+ candidate.setSeconds(0);
206
+ candidate.setMilliseconds(0);
207
+ for (let i = 0; i < maxIter; i++) {
208
+ if (candidate.getTime() <= lastRunAt)
209
+ return null;
210
+ if (matches(parsed, candidate))
211
+ return candidate.getTime();
212
+ candidate.setMinutes(candidate.getMinutes() - 1);
213
+ }
214
+ return null;
215
+ }
216
+ //# sourceMappingURL=cron.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron.js","sourceRoot":"","sources":["../../src/daemon/cron.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Boot-time persisted-schedule loader.
3
+ *
4
+ * Extracted from server.ts so the logic can be imported by tests
5
+ * without triggering the daemon's bootstrap IIFE. The loader handles
6
+ * BOTH on-disk formats:
7
+ *
8
+ * - IPC (`source: 'ipc'`): written by `photon ps enable` and similar
9
+ * CLI hooks. Carries `photonName`, `args`, and `workingDir` on the
10
+ * task itself. TTL-swept after 30 days of inactivity.
11
+ * - ScheduleProvider: written by `this.schedule.create()` at runtime
12
+ * from photon code. Carries `params` (mapped to args) and `status`.
13
+ * The photon name is inferred from the directory path and passed
14
+ * in as `photonNameHint`. NOT TTL-swept — lifecycle is owned by
15
+ * the photon that created it.
16
+ *
17
+ * Before this unification, the boot scanner only handled IPC and every
18
+ * ScheduleProvider file was silently skipped (`source !== 'ipc'`). That
19
+ * caused schedules to stay dormant across daemon restarts until the
20
+ * owning photon happened to be invoked and triggered the lazy
21
+ * `autoRegisterFromMetadata` path.
22
+ */
23
+ export interface PersistedScheduleJob {
24
+ id: string;
25
+ method: string;
26
+ args: Record<string, unknown>;
27
+ cron: string;
28
+ runCount: number;
29
+ createdAt: number;
30
+ createdBy: string;
31
+ photonName: string;
32
+ workingDir?: string;
33
+ photonPath?: string;
34
+ /**
35
+ * Absolute path of the backing JSON file this job was loaded from.
36
+ * The fire handler uses it to drop phantom registrations whose
37
+ * backing file has been unlinked (e.g. via
38
+ * `this.schedule.cancel()`) out from under the in-memory cron map.
39
+ */
40
+ sourceFile?: string;
41
+ /**
42
+ * Epoch-ms of the last successful execution, read from the persisted
43
+ * `lastExecutionAt` field. Used by the daemon boot path to detect fire
44
+ * windows that elapsed while the daemon was down and schedule one
45
+ * catch-up run for the most recent missed occurrence.
46
+ */
47
+ lastRun?: number;
48
+ }
49
+ export interface LoadScheduleCallbacks {
50
+ /** Called for every valid job. Return `true` if the engine accepted it. */
51
+ register: (job: PersistedScheduleJob) => boolean;
52
+ /** True if the given job id is already known to the cron engine. */
53
+ alreadyRegistered: (jobId: string) => boolean;
54
+ /** Optional logger hooks — no-op by default. */
55
+ warn?: (msg: string, ctx?: Record<string, unknown>) => void;
56
+ info?: (msg: string, ctx?: Record<string, unknown>) => void;
57
+ }
58
+ /**
59
+ * Scan one directory of schedule JSON files and hand every valid job
60
+ * to `cb.register`. Returns counts of loaded + skipped for logging.
61
+ *
62
+ * @param schedulesPath Directory containing {taskId}.json files.
63
+ * @param ttlMs Age in ms above which untouched IPC jobs are deleted.
64
+ * ScheduleProvider jobs ignore TTL (photon owns them).
65
+ * @param photonNameHint Used when task doesn't carry its own
66
+ * `photonName` (ScheduleProvider case — photon
67
+ * name is the parent directory's basename).
68
+ * @param workingDirHint Fallback working dir when task lacks one
69
+ * (the base dir that owns this schedule tree).
70
+ * @param cb Engine hooks.
71
+ */
72
+ export declare function loadPersistedSchedulesFromDir(schedulesPath: string, ttlMs: number, photonNameHint: string | null, workingDirHint: string | undefined, cb: LoadScheduleCallbacks): {
73
+ loaded: number;
74
+ skipped: number;
75
+ };
76
+ //# sourceMappingURL=schedule-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule-loader.d.ts","sourceRoot":"","sources":["../../src/daemon/schedule-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC;IACjD,oEAAoE;IACpE,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,gDAAgD;IAChD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC5D,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC7D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,EAAE,EAAE,qBAAqB,GACxB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAyFrC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Boot-time persisted-schedule loader.
3
+ *
4
+ * Extracted from server.ts so the logic can be imported by tests
5
+ * without triggering the daemon's bootstrap IIFE. The loader handles
6
+ * BOTH on-disk formats:
7
+ *
8
+ * - IPC (`source: 'ipc'`): written by `photon ps enable` and similar
9
+ * CLI hooks. Carries `photonName`, `args`, and `workingDir` on the
10
+ * task itself. TTL-swept after 30 days of inactivity.
11
+ * - ScheduleProvider: written by `this.schedule.create()` at runtime
12
+ * from photon code. Carries `params` (mapped to args) and `status`.
13
+ * The photon name is inferred from the directory path and passed
14
+ * in as `photonNameHint`. NOT TTL-swept — lifecycle is owned by
15
+ * the photon that created it.
16
+ *
17
+ * Before this unification, the boot scanner only handled IPC and every
18
+ * ScheduleProvider file was silently skipped (`source !== 'ipc'`). That
19
+ * caused schedules to stay dormant across daemon restarts until the
20
+ * owning photon happened to be invoked and triggered the lazy
21
+ * `autoRegisterFromMetadata` path.
22
+ */
23
+ import fs from 'fs';
24
+ import path from 'path';
25
+ /**
26
+ * Scan one directory of schedule JSON files and hand every valid job
27
+ * to `cb.register`. Returns counts of loaded + skipped for logging.
28
+ *
29
+ * @param schedulesPath Directory containing {taskId}.json files.
30
+ * @param ttlMs Age in ms above which untouched IPC jobs are deleted.
31
+ * ScheduleProvider jobs ignore TTL (photon owns them).
32
+ * @param photonNameHint Used when task doesn't carry its own
33
+ * `photonName` (ScheduleProvider case — photon
34
+ * name is the parent directory's basename).
35
+ * @param workingDirHint Fallback working dir when task lacks one
36
+ * (the base dir that owns this schedule tree).
37
+ * @param cb Engine hooks.
38
+ */
39
+ export function loadPersistedSchedulesFromDir(schedulesPath, ttlMs, photonNameHint, workingDirHint, cb) {
40
+ let loaded = 0;
41
+ let skipped = 0;
42
+ let files;
43
+ try {
44
+ files = fs.readdirSync(schedulesPath).filter((f) => f.endsWith('.json'));
45
+ }
46
+ catch {
47
+ return { loaded, skipped };
48
+ }
49
+ for (const file of files) {
50
+ const filePath = path.join(schedulesPath, file);
51
+ try {
52
+ const content = fs.readFileSync(filePath, 'utf-8');
53
+ const task = JSON.parse(content);
54
+ const isIpc = task.source === 'ipc';
55
+ const photonName = task.photonName || photonNameHint;
56
+ if (!task.id || !task.method || !task.cron || !photonName) {
57
+ cb.warn?.('Skipping invalid persisted schedule', { file: filePath });
58
+ skipped++;
59
+ continue;
60
+ }
61
+ // Respect explicit pause/complete from ScheduleProvider tasks.
62
+ if (!isIpc && task.status && task.status !== 'active') {
63
+ skipped++;
64
+ continue;
65
+ }
66
+ // TTL applies only to IPC jobs — ScheduleProvider files are owned
67
+ // by the photon that created them and must persist until that
68
+ // photon removes them.
69
+ const lastExec = task.lastExecutionAt ? new Date(task.lastExecutionAt).getTime() : 0;
70
+ const created = task.createdAt ? new Date(task.createdAt).getTime() : 0;
71
+ const lastActivity = Math.max(lastExec, created);
72
+ if (isIpc && lastActivity > 0 && Date.now() - lastActivity > ttlMs) {
73
+ cb.info?.('Removing expired schedule (TTL)', {
74
+ jobId: task.id,
75
+ lastActivity: new Date(lastActivity).toISOString(),
76
+ });
77
+ try {
78
+ fs.unlinkSync(filePath);
79
+ }
80
+ catch {
81
+ /* ignore */
82
+ }
83
+ skipped++;
84
+ continue;
85
+ }
86
+ // ScheduleProvider ids are namespaced so they can't collide with
87
+ // raw IPC ids, and they read `params` where IPC reads `args`.
88
+ const jobId = isIpc ? task.id : `${photonName}:sched:${task.id}`;
89
+ const jobArgs = isIpc ? task.args || {} : task.params || {};
90
+ const jobWorkingDir = task.workingDir || workingDirHint;
91
+ if (cb.alreadyRegistered(jobId))
92
+ continue;
93
+ const job = {
94
+ id: jobId,
95
+ method: task.method,
96
+ args: jobArgs,
97
+ cron: task.cron,
98
+ runCount: task.executionCount || 0,
99
+ createdAt: created || Date.now(),
100
+ createdBy: task.createdBy || (isIpc ? 'ipc' : 'schedule-provider'),
101
+ photonName,
102
+ workingDir: jobWorkingDir,
103
+ sourceFile: filePath,
104
+ lastRun: lastExec > 0 ? lastExec : undefined,
105
+ };
106
+ if (cb.register(job)) {
107
+ loaded++;
108
+ }
109
+ else {
110
+ cb.warn?.('Failed to schedule persisted job (invalid cron?)', { jobId });
111
+ skipped++;
112
+ }
113
+ }
114
+ catch (err) {
115
+ cb.warn?.('Failed to load persisted schedule file', {
116
+ file: filePath,
117
+ error: err instanceof Error ? err.message : String(err),
118
+ });
119
+ skipped++;
120
+ }
121
+ }
122
+ return { loaded, skipped };
123
+ }
124
+ //# sourceMappingURL=schedule-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule-loader.js","sourceRoot":"","sources":["../../src/daemon/schedule-loader.ts"],"names":[],"mappings":"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"}