@crewx/cron 0.1.10-rc.181 → 0.1.10-rc.183

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.
@@ -125,4 +125,4 @@ export interface CronRuntime {
125
125
  releaseFence(workspaceId: string, token: CronRuntimeFenceToken): CronRuntimeStatus;
126
126
  }
127
127
  export declare function createCronRuntime(options?: CronRuntimeOptions): CronRuntime;
128
- //# sourceMappingURL=runtime.d.ts.map
128
+
@@ -1,612 +1 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME = void 0;
37
- exports.createCronRuntime = createCronRuntime;
38
- const node_crypto_1 = require("node:crypto");
39
- const path = __importStar(require("node:path"));
40
- const sdk_1 = require("@crewx/sdk");
41
- const engine_1 = require("./engine");
42
- exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME = 'cron-runtime-process-lease.json';
43
- const DEFAULT_STOP_DEADLINE_MS = 5000;
44
- const localOwnerRegistry = new Map();
45
- function errorDetails(error) {
46
- const candidate = error;
47
- return {
48
- ...(typeof candidate?.code === 'string' ? { code: candidate.code } : {}),
49
- message: candidate?.message instanceof String
50
- ? candidate.message.toString()
51
- : candidate && typeof candidate.message === 'string'
52
- ? candidate.message
53
- : String(error),
54
- };
55
- }
56
- function cloneWorkspaceStatus(status) {
57
- return {
58
- ...status,
59
- ...(status.lastError ? { lastError: { ...status.lastError } } : {}),
60
- };
61
- }
62
- function localOwnerKey(leasePath, ownerId) {
63
- return `${leasePath}\u0000${ownerId}`;
64
- }
65
- function safeResolveLeasePath(leasePath) {
66
- try {
67
- return (0, sdk_1.resolveProcessLeasePath)(leasePath);
68
- }
69
- catch {
70
- return path.resolve(leasePath);
71
- }
72
- }
73
- function stopDeadline(deadlineMs) {
74
- if (deadlineMs === undefined)
75
- return DEFAULT_STOP_DEADLINE_MS;
76
- if (!Number.isFinite(deadlineMs))
77
- return DEFAULT_STOP_DEADLINE_MS;
78
- return Math.max(0, deadlineMs);
79
- }
80
- class CronRuntimeImpl {
81
- constructor(options = {}) {
82
- this.runtimeToken = Symbol('cron-runtime');
83
- this.enabled = false;
84
- this.draining = false;
85
- this.stopped = false;
86
- this.role = 'standby';
87
- this.ownerId = null;
88
- this.leaseId = null;
89
- this.workspaces = new Map();
90
- this.workspaceRevisions = new Map();
91
- this.workspaceFences = new Map();
92
- this.operations = new Set();
93
- this.reconcileQueue = Promise.resolve();
94
- this.requestedLeasePath = options.leasePath ?? exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME;
95
- this.resolvedLeasePath = safeResolveLeasePath(this.requestedLeasePath);
96
- this.configuredOwnerId = options.ownerId;
97
- this.leaseProvider = options.processLease
98
- ?? options.leaseProvider
99
- ?? { acquire: sdk_1.acquireProcessLease };
100
- this.storeFactory = options.storeFactory
101
- ?? ((workspace) => (0, engine_1.createScheduleStore)(workspace.path));
102
- this.executor = options.executeSchedule ?? options.executor ?? engine_1.executeSchedule;
103
- this.runPredicate = options.shouldRunNow ?? engine_1.shouldRunNow;
104
- this.testHooks = options.testHooks;
105
- }
106
- nextWorkspaceRevision(workspaceId) {
107
- const revision = (this.workspaceRevisions.get(workspaceId) ?? 0) + 1;
108
- this.workspaceRevisions.set(workspaceId, revision);
109
- return revision;
110
- }
111
- isWorkspaceFenced(workspaceId) {
112
- return (this.workspaceFences.get(workspaceId)?.size ?? 0) > 0;
113
- }
114
- setWorkspaceFenceStatus(workspaceId, fenced) {
115
- const entry = this.workspaces.get(workspaceId);
116
- if (!entry)
117
- return;
118
- entry.status = { ...entry.status, fenced };
119
- }
120
- register(workspace) {
121
- if (this.stopped || this.draining)
122
- return this.status();
123
- const previous = this.workspaces.get(workspace.id);
124
- const revision = previous && previous.workspace.path === workspace.path
125
- ? previous.revision
126
- : this.nextWorkspaceRevision(workspace.id);
127
- this.workspaces.set(workspace.id, {
128
- workspace: { ...workspace },
129
- revision,
130
- status: {
131
- id: workspace.id,
132
- path: workspace.path,
133
- state: 'registered',
134
- scheduleCount: 0,
135
- revision,
136
- fenced: this.isWorkspaceFenced(workspace.id),
137
- },
138
- });
139
- return this.status();
140
- }
141
- unregister(workspaceId) {
142
- if (this.workspaces.delete(workspaceId))
143
- this.nextWorkspaceRevision(workspaceId);
144
- this.workspaceFences.delete(workspaceId);
145
- return this.status();
146
- }
147
- fence(workspaceId) {
148
- const token = (0, node_crypto_1.randomUUID)();
149
- const tokens = this.workspaceFences.get(workspaceId) ?? new Set();
150
- tokens.add(token);
151
- this.workspaceFences.set(workspaceId, tokens);
152
- this.setWorkspaceFenceStatus(workspaceId, true);
153
- return {
154
- token,
155
- status: this.status(workspaceId),
156
- };
157
- }
158
- releaseFence(workspaceId, token) {
159
- const tokens = this.workspaceFences.get(workspaceId);
160
- if (tokens?.delete(token)) {
161
- if (tokens.size === 0)
162
- this.workspaceFences.delete(workspaceId);
163
- this.setWorkspaceFenceStatus(workspaceId, this.isWorkspaceFenced(workspaceId));
164
- }
165
- return this.status(workspaceId);
166
- }
167
- reconcile(snapshot) {
168
- const operation = this.reconcileQueue.then(() => this.applyReconcile(snapshot));
169
- this.reconcileQueue = operation.then(() => undefined, () => undefined);
170
- return this.track(operation);
171
- }
172
- async tick(now = new Date()) {
173
- if (this.activeTickPromise) {
174
- return this.skippedTick('reentrant', now, true);
175
- }
176
- const tickPromise = this.track(this.performTick(new Date(now.getTime())));
177
- this.activeTickPromise = tickPromise;
178
- void tickPromise.then(() => this.clearActiveTick(tickPromise), () => this.clearActiveTick(tickPromise));
179
- return tickPromise;
180
- }
181
- beginDrain() {
182
- if (this.stopped)
183
- return;
184
- this.draining = true;
185
- this.role = 'draining';
186
- }
187
- stop(options = {}) {
188
- if (this.stopped && this.stopPromise)
189
- return this.stopPromise;
190
- if (this.stopPromise)
191
- return this.stopPromise;
192
- this.beginDrain();
193
- const pending = this.waitForDrain(stopDeadline(options.deadlineMs)).then((drained) => {
194
- if (!drained) {
195
- return {
196
- status: 'timeout',
197
- timedOut: true,
198
- drained: false,
199
- leaseReleased: false,
200
- role: this.publicRole(),
201
- activeTick: this.activeTickPromise !== undefined,
202
- };
203
- }
204
- this.stopped = true;
205
- const leaseReleased = this.releaseLease();
206
- this.role = 'stopped';
207
- return {
208
- status: 'stopped',
209
- timedOut: false,
210
- drained: true,
211
- leaseReleased,
212
- role: this.publicRole(),
213
- activeTick: this.activeTickPromise !== undefined,
214
- };
215
- });
216
- this.stopPromise = pending;
217
- void pending.then((result) => {
218
- if (result.status === 'timeout')
219
- this.stopPromise = undefined;
220
- });
221
- return pending;
222
- }
223
- status(workspaceId) {
224
- const workspaces = Array.from(this.workspaces.values(), ({ status }) => cloneWorkspaceStatus(status));
225
- const workspace = workspaceId === undefined
226
- ? undefined
227
- : workspaces.find(item => item.id === workspaceId);
228
- return {
229
- enabled: this.enabled,
230
- role: this.publicRole(),
231
- activeTick: this.activeTickPromise !== undefined,
232
- draining: this.draining,
233
- stopped: this.stopped,
234
- ownerId: this.ownerId,
235
- leaseId: this.leaseId,
236
- leasePath: this.resolvedLeasePath,
237
- ...(this.standbyReason ? { standbyReason: this.standbyReason } : {}),
238
- workspaces,
239
- ...(workspace ? { workspace } : {}),
240
- };
241
- }
242
- async applyReconcile(snapshot) {
243
- if (this.stopped || this.draining)
244
- return this.status();
245
- this.enabled = snapshot.enabled;
246
- const snapshotIds = new Set(snapshot.workspaces.map(workspace => workspace.id));
247
- for (const id of this.workspaces.keys()) {
248
- if (!snapshotIds.has(id))
249
- this.nextWorkspaceRevision(id);
250
- }
251
- const next = new Map();
252
- for (const workspace of snapshot.workspaces) {
253
- const previous = this.workspaces.get(workspace.id);
254
- const revision = previous && previous.workspace.path === workspace.path
255
- ? previous.revision
256
- : this.nextWorkspaceRevision(workspace.id);
257
- next.set(workspace.id, {
258
- workspace: { ...workspace },
259
- revision,
260
- status: previous && previous.workspace.path === workspace.path
261
- ? {
262
- ...previous.status,
263
- path: workspace.path,
264
- revision,
265
- fenced: this.isWorkspaceFenced(workspace.id),
266
- }
267
- : {
268
- id: workspace.id,
269
- path: workspace.path,
270
- state: 'registered',
271
- scheduleCount: 0,
272
- revision,
273
- fenced: this.isWorkspaceFenced(workspace.id),
274
- },
275
- });
276
- }
277
- this.workspaces.clear();
278
- for (const [id, entry] of next)
279
- this.workspaces.set(id, entry);
280
- await Promise.all(Array.from(this.workspaces.values(), entry => this.inspectWorkspace(entry)));
281
- if (this.enabled && !this.draining && !this.stopped) {
282
- this.ensureOwner();
283
- }
284
- else if (!this.enabled) {
285
- this.role = 'disabled';
286
- }
287
- return this.status();
288
- }
289
- inspectWorkspace(entry) {
290
- try {
291
- const schedules = this.storeFactory(entry.workspace).list();
292
- entry.status = {
293
- id: entry.workspace.id,
294
- path: entry.workspace.path,
295
- state: 'ready',
296
- scheduleCount: schedules.length,
297
- revision: entry.revision,
298
- fenced: this.isWorkspaceFenced(entry.workspace.id),
299
- };
300
- }
301
- catch (error) {
302
- entry.status = {
303
- id: entry.workspace.id,
304
- path: entry.workspace.path,
305
- state: 'degraded',
306
- scheduleCount: 0,
307
- revision: entry.revision,
308
- fenced: this.isWorkspaceFenced(entry.workspace.id),
309
- lastError: errorDetails(error),
310
- };
311
- }
312
- }
313
- async performTick(now) {
314
- if (this.stopped)
315
- return this.skippedTick('stopped', now, false);
316
- if (this.draining)
317
- return this.skippedTick('draining', now, false);
318
- if (!this.enabled)
319
- return this.skippedTick('disabled', now, false);
320
- if (!this.ensureOwner())
321
- return this.skippedTick('standby', now, false);
322
- const entries = Array.from(this.workspaces.values());
323
- const workspaces = await Promise.all(entries.map(entry => this.tickWorkspace(entry, now)));
324
- return {
325
- status: 'completed',
326
- skipped: false,
327
- role: this.publicRole(),
328
- now: now.toISOString(),
329
- activeTick: true,
330
- dispatched: workspaces.reduce((total, item) => total + item.dispatched, 0),
331
- workspaces,
332
- };
333
- }
334
- async tickWorkspace(entry, now) {
335
- if (this.draining || this.stopped) {
336
- return {
337
- id: entry.workspace.id,
338
- path: entry.workspace.path,
339
- status: 'skipped',
340
- scheduleCount: entry.status.scheduleCount,
341
- dueCount: 0,
342
- dispatched: 0,
343
- schedules: [],
344
- };
345
- }
346
- let schedules;
347
- try {
348
- schedules = this.storeFactory(entry.workspace).list();
349
- entry.status = {
350
- id: entry.workspace.id,
351
- path: entry.workspace.path,
352
- state: 'ready',
353
- scheduleCount: schedules.length,
354
- revision: entry.revision,
355
- fenced: this.isWorkspaceFenced(entry.workspace.id),
356
- };
357
- }
358
- catch (error) {
359
- const details = errorDetails(error);
360
- entry.status = {
361
- id: entry.workspace.id,
362
- path: entry.workspace.path,
363
- state: 'degraded',
364
- scheduleCount: 0,
365
- revision: entry.revision,
366
- fenced: this.isWorkspaceFenced(entry.workspace.id),
367
- lastError: details,
368
- };
369
- return {
370
- id: entry.workspace.id,
371
- path: entry.workspace.path,
372
- status: 'degraded',
373
- scheduleCount: 0,
374
- dueCount: 0,
375
- dispatched: 0,
376
- schedules: [],
377
- error: details,
378
- };
379
- }
380
- const dueSchedules = [];
381
- for (const schedule of schedules) {
382
- if (!schedule.enabled)
383
- continue;
384
- try {
385
- if (!this.runPredicate(schedule.cron, now, schedule.timezone))
386
- continue;
387
- const minuteKey = (0, engine_1.getLocalMinuteKey)(now, schedule.timezone);
388
- dueSchedules.push({
389
- schedule,
390
- occurrenceKey: (0, engine_1.buildOccurrenceKey)(schedule.cron, minuteKey),
391
- });
392
- }
393
- catch {
394
- }
395
- }
396
- const scheduleResults = await Promise.all(dueSchedules.map(item => this.admitSchedule(entry.workspace, entry.revision, item.schedule, item.occurrenceKey)));
397
- return {
398
- id: entry.workspace.id,
399
- path: entry.workspace.path,
400
- status: 'completed',
401
- scheduleCount: schedules.length,
402
- dueCount: dueSchedules.length,
403
- dispatched: scheduleResults.filter(result => result.accepted).length,
404
- schedules: scheduleResults,
405
- };
406
- }
407
- async admitSchedule(workspace, revision, schedule, occurrenceKey) {
408
- if (this.draining || this.stopped) {
409
- return {
410
- scheduleId: schedule.id,
411
- occurrenceKey,
412
- status: 'skipped',
413
- accepted: false,
414
- reason: this.stopped ? 'stopped' : 'draining',
415
- };
416
- }
417
- const beforeExecutorAdmission = this.testHooks?.beforeExecutorAdmission;
418
- if (beforeExecutorAdmission) {
419
- await beforeExecutorAdmission({ workspace, schedule, occurrenceKey, revision });
420
- }
421
- if (this.draining || this.stopped) {
422
- return {
423
- scheduleId: schedule.id,
424
- occurrenceKey,
425
- status: 'skipped',
426
- accepted: false,
427
- reason: this.stopped ? 'stopped' : 'draining',
428
- };
429
- }
430
- const current = this.workspaces.get(workspace.id);
431
- if (!current || current.workspace.path !== workspace.path || current.revision !== revision) {
432
- return {
433
- scheduleId: schedule.id,
434
- occurrenceKey,
435
- status: 'skipped',
436
- accepted: false,
437
- reason: 'workspace-unregistered',
438
- };
439
- }
440
- if (this.isWorkspaceFenced(workspace.id)) {
441
- return {
442
- scheduleId: schedule.id,
443
- occurrenceKey,
444
- status: 'skipped',
445
- accepted: false,
446
- reason: 'workspace-fenced',
447
- };
448
- }
449
- try {
450
- const outcome = await this.executor(schedule, {
451
- dir: workspace.path,
452
- trigger: 'cron',
453
- occurrenceKey,
454
- });
455
- return {
456
- scheduleId: schedule.id,
457
- occurrenceKey,
458
- status: outcome.status,
459
- accepted: outcome.status === 'running',
460
- ...('taskId' in outcome ? { taskId: outcome.taskId } : {}),
461
- ...('reason' in outcome && outcome.reason ? { reason: outcome.reason } : {}),
462
- };
463
- }
464
- catch (error) {
465
- return {
466
- scheduleId: schedule.id,
467
- occurrenceKey,
468
- status: 'error',
469
- accepted: false,
470
- error: errorDetails(error).message,
471
- };
472
- }
473
- }
474
- ensureOwner() {
475
- if (this.lease) {
476
- try {
477
- if (this.lease.renew && !this.lease.renew()) {
478
- this.forgetLease();
479
- }
480
- else {
481
- this.role = 'owner';
482
- this.standbyReason = undefined;
483
- return true;
484
- }
485
- }
486
- catch {
487
- this.forgetLease();
488
- }
489
- }
490
- if (this.stopped || this.draining || !this.enabled)
491
- return false;
492
- let acquired;
493
- try {
494
- const options = {
495
- leasePath: this.requestedLeasePath,
496
- ...(this.configuredOwnerId ? { ownerId: this.configuredOwnerId } : {}),
497
- };
498
- acquired = this.leaseProvider.acquire(options);
499
- }
500
- catch (error) {
501
- this.role = 'standby';
502
- this.standbyReason = `lease-error:${errorDetails(error).message}`;
503
- return false;
504
- }
505
- if (!acquired.acquired) {
506
- this.role = 'standby';
507
- this.standbyReason = acquired.reason;
508
- this.ownerId = acquired.record?.ownerId ?? null;
509
- this.leaseId = acquired.record?.leaseId ?? null;
510
- return false;
511
- }
512
- const key = localOwnerKey(this.resolvedLeasePath, acquired.ownerId);
513
- const existing = localOwnerRegistry.get(key);
514
- if (existing && existing.runtimeToken !== this.runtimeToken) {
515
- this.role = 'standby';
516
- this.standbyReason = 'process-local-owner';
517
- this.ownerId = acquired.ownerId;
518
- this.leaseId = acquired.leaseId;
519
- return false;
520
- }
521
- this.lease = acquired.lease;
522
- this.leaseKey = key;
523
- this.ownerId = acquired.ownerId;
524
- this.leaseId = acquired.leaseId;
525
- localOwnerRegistry.set(key, {
526
- runtimeToken: this.runtimeToken,
527
- ownerId: acquired.ownerId,
528
- lease: acquired.lease,
529
- });
530
- this.role = 'owner';
531
- this.standbyReason = undefined;
532
- return true;
533
- }
534
- forgetLease() {
535
- if (this.leaseKey) {
536
- const current = localOwnerRegistry.get(this.leaseKey);
537
- if (current?.runtimeToken === this.runtimeToken)
538
- localOwnerRegistry.delete(this.leaseKey);
539
- }
540
- this.lease = undefined;
541
- this.leaseKey = undefined;
542
- this.ownerId = null;
543
- this.leaseId = null;
544
- this.role = 'standby';
545
- this.standbyReason = 'lease-lost';
546
- }
547
- releaseLease() {
548
- if (!this.lease)
549
- return true;
550
- let released = false;
551
- try {
552
- released = this.lease.release();
553
- }
554
- catch {
555
- released = false;
556
- }
557
- if (released)
558
- this.forgetLease();
559
- return released;
560
- }
561
- publicRole() {
562
- if (this.stopped)
563
- return 'stopped';
564
- if (this.draining)
565
- return 'draining';
566
- if (!this.enabled)
567
- return 'disabled';
568
- return this.role;
569
- }
570
- skippedTick(reason, now, reentrant) {
571
- return {
572
- status: 'skipped',
573
- skipped: true,
574
- reason,
575
- role: this.publicRole(),
576
- now: now.toISOString(),
577
- activeTick: reentrant || this.activeTickPromise !== undefined,
578
- dispatched: 0,
579
- workspaces: [],
580
- };
581
- }
582
- clearActiveTick(promise) {
583
- if (this.activeTickPromise === promise)
584
- this.activeTickPromise = undefined;
585
- }
586
- track(promise) {
587
- let tracked;
588
- tracked = promise.finally(() => this.operations.delete(tracked));
589
- this.operations.add(tracked);
590
- return tracked;
591
- }
592
- async waitForDrain(deadlineMs) {
593
- const pending = Array.from(this.operations);
594
- if (pending.length === 0)
595
- return true;
596
- const drain = Promise.allSettled(pending).then(() => true);
597
- if (deadlineMs <= 0)
598
- return false;
599
- let timer;
600
- const timeout = new Promise(resolve => {
601
- timer = setTimeout(() => resolve(false), deadlineMs);
602
- });
603
- const drained = await Promise.race([drain, timeout]);
604
- if (timer !== undefined)
605
- clearTimeout(timer);
606
- return drained;
607
- }
608
- }
609
- function createCronRuntime(options = {}) {
610
- return new CronRuntimeImpl(options);
611
- }
612
- //# sourceMappingURL=runtime.js.map
1
+ "use strict";var p=exports&&exports.__createBinding||(Object.create?(function(r,e,s,i){if(i===void 0)i=s;var t=Object.getOwnPropertyDescriptor(e,s);if(!t||("get"in t?!e.__esModule:t.writable||t.configurable)){t={enumerable:true,get:function(){return e[s]}}}Object.defineProperty(r,i,t)}):(function(r,e,s,i){if(i===void 0)i=s;r[i]=e[s]}));var f=exports&&exports.__setModuleDefault||(Object.create?(function(r,e){Object.defineProperty(r,"default",{enumerable:true,value:e})}):function(r,e){r["default"]=e});var k=exports&&exports.__importStar||(function(){var r=function(e){r=Object.getOwnPropertyNames||function(s){var i=[];for(var t in s)if(Object.prototype.hasOwnProperty.call(s,t))i[i.length]=t;return i};return r(e)};return function(e){if(e&&e.__esModule)return e;var s={};if(e!=null){for(var i=r(e),t=0;t<i.length;t++)if(i[t]!=="default")p(s,e,i[t])}f(s,e);return s}})();Object.defineProperty(exports,"__esModule",{value:true});exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME=void 0;exports.createCronRuntime=T;const w=require("node:crypto");const g=k(require("node:path"));const u=require("@crewx/sdk");const c=require("./engine");exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME="cron-runtime-process-lease.json";const l=5e3;const d=new Map;function h(r){const e=r;return{...typeof e?.code==="string"?{code:e.code}:{},message:e?.message instanceof String?e.message.toString():e&&typeof e.message==="string"?e.message:String(r)}}function m(r){return{...r,...r.lastError?{lastError:{...r.lastError}}:{}}}function v(r,e){return`${r}\0${e}`}function b(r){try{return(0,u.resolveProcessLeasePath)(r)}catch{return g.resolve(r)}}function P(r){if(r===void 0)return l;if(!Number.isFinite(r))return l;return Math.max(0,r)}class R{constructor(e={}){this.runtimeToken=Symbol("cron-runtime");this.enabled=false;this.draining=false;this.stopped=false;this.role="standby";this.ownerId=null;this.leaseId=null;this.workspaces=new Map;this.workspaceRevisions=new Map;this.workspaceFences=new Map;this.operations=new Set;this.reconcileQueue=Promise.resolve();this.requestedLeasePath=e.leasePath??exports.CRON_RUNTIME_PROCESS_LEASE_FILENAME;this.resolvedLeasePath=b(this.requestedLeasePath);this.configuredOwnerId=e.ownerId;this.leaseProvider=e.processLease??e.leaseProvider??{acquire:u.acquireProcessLease};this.storeFactory=e.storeFactory??(s=>(0,c.createScheduleStore)(s.path));this.executor=e.executeSchedule??e.executor??c.executeSchedule;this.runPredicate=e.shouldRunNow??c.shouldRunNow;this.testHooks=e.testHooks}nextWorkspaceRevision(e){const s=(this.workspaceRevisions.get(e)??0)+1;this.workspaceRevisions.set(e,s);return s}isWorkspaceFenced(e){return(this.workspaceFences.get(e)?.size??0)>0}setWorkspaceFenceStatus(e,s){const i=this.workspaces.get(e);if(!i)return;i.status={...i.status,fenced:s}}register(e){if(this.stopped||this.draining)return this.status();const s=this.workspaces.get(e.id);const i=s&&s.workspace.path===e.path?s.revision:this.nextWorkspaceRevision(e.id);this.workspaces.set(e.id,{workspace:{...e},revision:i,status:{id:e.id,path:e.path,state:"registered",scheduleCount:0,revision:i,fenced:this.isWorkspaceFenced(e.id)}});return this.status()}unregister(e){if(this.workspaces.delete(e))this.nextWorkspaceRevision(e);this.workspaceFences.delete(e);return this.status()}fence(e){const s=(0,w.randomUUID)();const i=this.workspaceFences.get(e)??new Set;i.add(s);this.workspaceFences.set(e,i);this.setWorkspaceFenceStatus(e,true);return{token:s,status:this.status(e)}}releaseFence(e,s){const i=this.workspaceFences.get(e);if(i?.delete(s)){if(i.size===0)this.workspaceFences.delete(e);this.setWorkspaceFenceStatus(e,this.isWorkspaceFenced(e))}return this.status(e)}reconcile(e){const s=this.reconcileQueue.then(()=>this.applyReconcile(e));this.reconcileQueue=s.then(()=>void 0,()=>void 0);return this.track(s)}async tick(e=new Date){if(this.activeTickPromise){return this.skippedTick("reentrant",e,true)}const s=this.track(this.performTick(new Date(e.getTime())));this.activeTickPromise=s;void s.then(()=>this.clearActiveTick(s),()=>this.clearActiveTick(s));return s}beginDrain(){if(this.stopped)return;this.draining=true;this.role="draining"}stop(e={}){if(this.stopped&&this.stopPromise)return this.stopPromise;if(this.stopPromise)return this.stopPromise;this.beginDrain();const s=this.waitForDrain(P(e.deadlineMs)).then(i=>{if(!i){return{status:"timeout",timedOut:true,drained:false,leaseReleased:false,role:this.publicRole(),activeTick:this.activeTickPromise!==void 0}}this.stopped=true;const t=this.releaseLease();this.role="stopped";return{status:"stopped",timedOut:false,drained:true,leaseReleased:t,role:this.publicRole(),activeTick:this.activeTickPromise!==void 0}});this.stopPromise=s;void s.then(i=>{if(i.status==="timeout")this.stopPromise=void 0});return s}status(e){const s=Array.from(this.workspaces.values(),({status:t})=>m(t));const i=e===void 0?void 0:s.find(t=>t.id===e);return{enabled:this.enabled,role:this.publicRole(),activeTick:this.activeTickPromise!==void 0,draining:this.draining,stopped:this.stopped,ownerId:this.ownerId,leaseId:this.leaseId,leasePath:this.resolvedLeasePath,...this.standbyReason?{standbyReason:this.standbyReason}:{},workspaces:s,...i?{workspace:i}:{}}}async applyReconcile(e){if(this.stopped||this.draining)return this.status();this.enabled=e.enabled;const s=new Set(e.workspaces.map(t=>t.id));for(const t of this.workspaces.keys()){if(!s.has(t))this.nextWorkspaceRevision(t)}const i=new Map;for(const t of e.workspaces){const a=this.workspaces.get(t.id);const n=a&&a.workspace.path===t.path?a.revision:this.nextWorkspaceRevision(t.id);i.set(t.id,{workspace:{...t},revision:n,status:a&&a.workspace.path===t.path?{...a.status,path:t.path,revision:n,fenced:this.isWorkspaceFenced(t.id)}:{id:t.id,path:t.path,state:"registered",scheduleCount:0,revision:n,fenced:this.isWorkspaceFenced(t.id)}})}this.workspaces.clear();for(const[t,a]of i)this.workspaces.set(t,a);await Promise.all(Array.from(this.workspaces.values(),t=>this.inspectWorkspace(t)));if(this.enabled&&!this.draining&&!this.stopped){this.ensureOwner()}else if(!this.enabled){this.role="disabled"}return this.status()}inspectWorkspace(e){try{const s=this.storeFactory(e.workspace).list();e.status={id:e.workspace.id,path:e.workspace.path,state:"ready",scheduleCount:s.length,revision:e.revision,fenced:this.isWorkspaceFenced(e.workspace.id)}}catch(s){e.status={id:e.workspace.id,path:e.workspace.path,state:"degraded",scheduleCount:0,revision:e.revision,fenced:this.isWorkspaceFenced(e.workspace.id),lastError:h(s)}}}async performTick(e){if(this.stopped)return this.skippedTick("stopped",e,false);if(this.draining)return this.skippedTick("draining",e,false);if(!this.enabled)return this.skippedTick("disabled",e,false);if(!this.ensureOwner())return this.skippedTick("standby",e,false);const s=Array.from(this.workspaces.values());const i=await Promise.all(s.map(t=>this.tickWorkspace(t,e)));return{status:"completed",skipped:false,role:this.publicRole(),now:e.toISOString(),activeTick:true,dispatched:i.reduce((t,a)=>t+a.dispatched,0),workspaces:i}}async tickWorkspace(e,s){if(this.draining||this.stopped){return{id:e.workspace.id,path:e.workspace.path,status:"skipped",scheduleCount:e.status.scheduleCount,dueCount:0,dispatched:0,schedules:[]}}let i;try{i=this.storeFactory(e.workspace).list();e.status={id:e.workspace.id,path:e.workspace.path,state:"ready",scheduleCount:i.length,revision:e.revision,fenced:this.isWorkspaceFenced(e.workspace.id)}}catch(n){const o=h(n);e.status={id:e.workspace.id,path:e.workspace.path,state:"degraded",scheduleCount:0,revision:e.revision,fenced:this.isWorkspaceFenced(e.workspace.id),lastError:o};return{id:e.workspace.id,path:e.workspace.path,status:"degraded",scheduleCount:0,dueCount:0,dispatched:0,schedules:[],error:o}}const t=[];for(const n of i){if(!n.enabled)continue;try{if(!this.runPredicate(n.cron,s,n.timezone))continue;const o=(0,c.getLocalMinuteKey)(s,n.timezone);t.push({schedule:n,occurrenceKey:(0,c.buildOccurrenceKey)(n.cron,o)})}catch{}}const a=await Promise.all(t.map(n=>this.admitSchedule(e.workspace,e.revision,n.schedule,n.occurrenceKey)));return{id:e.workspace.id,path:e.workspace.path,status:"completed",scheduleCount:i.length,dueCount:t.length,dispatched:a.filter(n=>n.accepted).length,schedules:a}}async admitSchedule(e,s,i,t){if(this.draining||this.stopped){return{scheduleId:i.id,occurrenceKey:t,status:"skipped",accepted:false,reason:this.stopped?"stopped":"draining"}}const a=this.testHooks?.beforeExecutorAdmission;if(a){await a({workspace:e,schedule:i,occurrenceKey:t,revision:s})}if(this.draining||this.stopped){return{scheduleId:i.id,occurrenceKey:t,status:"skipped",accepted:false,reason:this.stopped?"stopped":"draining"}}const n=this.workspaces.get(e.id);if(!n||n.workspace.path!==e.path||n.revision!==s){return{scheduleId:i.id,occurrenceKey:t,status:"skipped",accepted:false,reason:"workspace-unregistered"}}if(this.isWorkspaceFenced(e.id)){return{scheduleId:i.id,occurrenceKey:t,status:"skipped",accepted:false,reason:"workspace-fenced"}}try{const o=await this.executor(i,{dir:e.path,trigger:"cron",occurrenceKey:t});return{scheduleId:i.id,occurrenceKey:t,status:o.status,accepted:o.status==="running",..."taskId"in o?{taskId:o.taskId}:{},..."reason"in o&&o.reason?{reason:o.reason}:{}}}catch(o){return{scheduleId:i.id,occurrenceKey:t,status:"error",accepted:false,error:h(o).message}}}ensureOwner(){if(this.lease){try{if(this.lease.renew&&!this.lease.renew()){this.forgetLease()}else{this.role="owner";this.standbyReason=void 0;return true}}catch{this.forgetLease()}}if(this.stopped||this.draining||!this.enabled)return false;let e;try{const t={leasePath:this.requestedLeasePath,...this.configuredOwnerId?{ownerId:this.configuredOwnerId}:{}};e=this.leaseProvider.acquire(t)}catch(t){this.role="standby";this.standbyReason=`lease-error:${h(t).message}`;return false}if(!e.acquired){this.role="standby";this.standbyReason=e.reason;this.ownerId=e.record?.ownerId??null;this.leaseId=e.record?.leaseId??null;return false}const s=v(this.resolvedLeasePath,e.ownerId);const i=d.get(s);if(i&&i.runtimeToken!==this.runtimeToken){this.role="standby";this.standbyReason="process-local-owner";this.ownerId=e.ownerId;this.leaseId=e.leaseId;return false}this.lease=e.lease;this.leaseKey=s;this.ownerId=e.ownerId;this.leaseId=e.leaseId;d.set(s,{runtimeToken:this.runtimeToken,ownerId:e.ownerId,lease:e.lease});this.role="owner";this.standbyReason=void 0;return true}forgetLease(){if(this.leaseKey){const e=d.get(this.leaseKey);if(e?.runtimeToken===this.runtimeToken)d.delete(this.leaseKey)}this.lease=void 0;this.leaseKey=void 0;this.ownerId=null;this.leaseId=null;this.role="standby";this.standbyReason="lease-lost"}releaseLease(){if(!this.lease)return true;let e=false;try{e=this.lease.release()}catch{e=false}if(e)this.forgetLease();return e}publicRole(){if(this.stopped)return"stopped";if(this.draining)return"draining";if(!this.enabled)return"disabled";return this.role}skippedTick(e,s,i){return{status:"skipped",skipped:true,reason:e,role:this.publicRole(),now:s.toISOString(),activeTick:i||this.activeTickPromise!==void 0,dispatched:0,workspaces:[]}}clearActiveTick(e){if(this.activeTickPromise===e)this.activeTickPromise=void 0}track(e){let s;s=e.finally(()=>this.operations.delete(s));this.operations.add(s);return s}async waitForDrain(e){const s=Array.from(this.operations);if(s.length===0)return true;const i=Promise.allSettled(s).then(()=>true);if(e<=0)return false;let t;const a=new Promise(o=>{t=setTimeout(()=>o(false),e)});const n=await Promise.race([i,a]);if(t!==void 0)clearTimeout(t);return n}}function T(r={}){return new R(r)}
@@ -134,4 +134,4 @@ export interface UpdateOptions {
134
134
  model?: string | null;
135
135
  provider?: string | null;
136
136
  }
137
- //# sourceMappingURL=types.d.ts.map
137
+
package/dist/src/types.js CHANGED
@@ -1,14 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CronStoreError = void 0;
4
- class CronStoreError extends Error {
5
- constructor(code, message, filePath) {
6
- super(message);
7
- this.name = 'CronStoreError';
8
- this.code = code;
9
- this.filePath = filePath;
10
- Object.setPrototypeOf(this, new.target.prototype);
11
- }
12
- }
13
- exports.CronStoreError = CronStoreError;
14
- //# sourceMappingURL=types.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.CronStoreError=void 0;class o extends Error{constructor(r,e,t){super(e);this.name="CronStoreError";this.code=r;this.filePath=t;Object.setPrototypeOf(this,new.target.prototype)}}exports.CronStoreError=o;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/cron",
3
- "version": "0.1.10-rc.181",
3
+ "version": "0.1.10-rc.183",
4
4
  "description": "Cron Scheduler for CrewX - Schedule and execute crewx commands automatically",
5
5
  "type": "commonjs",
6
6
  "main": "dist/src/engine.js",
@@ -33,8 +33,8 @@
33
33
  ],
34
34
  "license": "MIT",
35
35
  "dependencies": {
36
- "@crewx/sdk": "0.9.0-rc.146",
37
- "@crewx/shared": "0.0.6-rc.61"
36
+ "@crewx/sdk": "0.9.0-rc.148",
37
+ "@crewx/shared": "0.0.6-rc.63"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^20.0.0",
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAqDA,eAAO,MAAM,8BAA8B,mCAAmC,CAAC;AA2C/E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAGzD;AAuCD,wBAAsB,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgU9D"}