@fluojs/cron 1.0.3 → 1.1.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.
package/README.ko.md CHANGED
@@ -41,6 +41,8 @@ npm install @fluojs/cron croner
41
41
  애플리케이션 모듈의 스케줄링 등록은 `CronModule.forRoot(...)`로 구성합니다.
42
42
  Cron 표현식은 다섯 필드(`minute hour day month weekday`) 또는 여섯 필드(`second minute hour day month weekday`)를 사용할 수 있습니다. 내장 `CronExpression` preset은 sub-minute 정밀도가 필요할 때 여섯 필드 표현식을 사용합니다. Cron task는 application bootstrap 이후에만 시작되고, 이미 시작된 registry에 동적으로 등록한 cron task는 등록 시점에 시작되며, fluo는 `timezone`과 no-overlap 보호를 scheduler에 전달해 같은 task instance가 자기 자신과 겹쳐 실행되지 않게 합니다.
43
43
 
44
+ Scheduling decorator는 public instance method에만 적용됩니다. NestJS에서 사용하던 private scheduled method, static helper, legacy decorator metadata 가정 뒤에 숨은 method name을 그대로 옮기지 마세요. 공개 provider/controller method를 노출하고 private 구현 세부사항은 그 method 뒤에 두세요.
45
+
44
46
  ```typescript
45
47
  import { Module } from '@fluojs/core';
46
48
  import { CronModule, Cron, CronExpression, Interval, Timeout } from '@fluojs/cron';
@@ -137,15 +139,19 @@ class TaskManager {
137
139
  });
138
140
  }
139
141
 
142
+ speedUpPolling() {
143
+ this.registry.updateIntervalMs('inventory.poll', 5_000);
144
+ }
145
+
140
146
  stopTask() {
141
147
  this.registry.remove('dynamic-job');
142
148
  }
143
149
  }
144
150
  ```
145
151
 
146
- Registry는 `addCron`, `addInterval`, `addTimeout`, `remove`, `enable`, `disable`, `get`, `getAll`, `updateCronExpression`을 제공합니다. Timeout task는 한 번 실행된 뒤 비활성화되지만 registry에는 남아 있어 의도적으로 다시 활성화할 수 있습니다.
152
+ Registry는 `addCron`, `addInterval`, `addTimeout`, `remove`, `enable`, `disable`, `get`, `getAll`, `updateCronExpression`, `updateIntervalMs`를 제공합니다. 첫 번째 `name` 인자는 기본 registry key이며, `options.name`을 전달하면 dynamic task의 실제 registry key, scheduler metadata name, 기본 distributed lock key가 이를 사용해 decorator naming semantics와 일치합니다. `get`과 `getAll`은 live `CronJob` handle이 아니라 read-only `SchedulingTaskDescriptor` 값을 반환합니다. Timeout task는 한 번 실행된 뒤 비활성화되지만 registry에는 남아 있어 의도적으로 다시 활성화할 수 있습니다.
147
153
 
148
- Dynamic cron 등록은 scheduler startup과 원자적으로 처리됩니다. Scheduler가 새 cron job을 거부하면 registry는 half-registered task를 남기지 않습니다. 실행 중인 cron expression update도 rollback-safe합니다. Rescheduling이 실패하면 이전 expression scheduled job이 그대로 유지됩니다. Cron task는 scheduler-level no-overlap protection과 fluo의 in-process running guard를 함께 사용하므로 같은 task instance가 overlapping tick으로 실행되지 않습니다.
154
+ Dynamic cron 등록은 scheduler startup과 원자적으로 처리됩니다. Scheduler가 새 cron job을 거부하면 registry는 half-registered task를 남기지 않습니다. 실행 중인 cron expression 또는 interval cadence update도 rollback-safe합니다. Rescheduling이 실패하면 이전 expression 또는 interval milliseconds와 scheduled handle이 그대로 유지됩니다. Cron task는 scheduler-level no-overlap protection과 fluo의 in-process running guard를 함께 사용하므로 같은 task instance가 overlapping tick으로 실행되지 않습니다.
149
155
 
150
156
  ### 제한된 종료
151
157
 
package/README.md CHANGED
@@ -41,6 +41,8 @@ Register the `CronModule` and use decorators to schedule your methods.
41
41
  Use `CronModule.forRoot(...)` to register scheduling for an application module.
42
42
  Cron expressions may use either five fields (`minute hour day month weekday`) or six fields (`second minute hour day month weekday`). The built-in `CronExpression` presets use six-field expressions when sub-minute precision is needed. Cron tasks start only after application bootstrap, dynamically registered cron tasks start when added to a started registry, and fluo forwards `timezone` plus no-overlap protection to the scheduler so one task instance does not overlap itself.
43
43
 
44
+ Scheduling decorators apply to public instance methods only. Do not migrate NestJS private scheduled methods, static helpers, or method names that are hidden behind legacy decorator metadata assumptions as-is; expose a public provider/controller method and keep any private implementation details behind that method.
45
+
44
46
  ```typescript
45
47
  import { Module } from '@fluojs/core';
46
48
  import { CronModule, Cron, CronExpression, Interval, Timeout } from '@fluojs/cron';
@@ -137,15 +139,19 @@ class TaskManager {
137
139
  });
138
140
  }
139
141
 
142
+ speedUpPolling() {
143
+ this.registry.updateIntervalMs('inventory.poll', 5_000);
144
+ }
145
+
140
146
  stopTask() {
141
147
  this.registry.remove('dynamic-job');
142
148
  }
143
149
  }
144
150
  ```
145
151
 
146
- The registry exposes `addCron`, `addInterval`, `addTimeout`, `remove`, `enable`, `disable`, `get`, `getAll`, and `updateCronExpression`. Timeout tasks run once, then disable themselves while remaining in the registry so they can be re-enabled deliberately.
152
+ The registry exposes `addCron`, `addInterval`, `addTimeout`, `remove`, `enable`, `disable`, `get`, `getAll`, `updateCronExpression`, and `updateIntervalMs`. The first `name` argument is the default registry key; passing `options.name` overrides the actual registry key, scheduler metadata name, and default distributed lock key for dynamic tasks so dynamic registration matches decorator naming semantics. `get` and `getAll` return read-only `SchedulingTaskDescriptor` values, not live `CronJob` handles. Timeout tasks run once, then disable themselves while remaining in the registry so they can be re-enabled deliberately.
147
153
 
148
- Dynamic cron registration is atomic with scheduler startup: if the scheduler rejects a new cron job, the registry does not retain a half-registered task. Updating a running cron expression is also rollback-safe. If rescheduling fails, the previous expression and scheduled job remain active. Cron tasks use both scheduler-level no-overlap protection and fluo's in-process running guard, so the same task instance will not run overlapping ticks.
154
+ Dynamic cron registration is atomic with scheduler startup: if the scheduler rejects a new cron job, the registry does not retain a half-registered task. Updating a running cron expression or interval cadence is also rollback-safe. If rescheduling fails, the previous expression or interval milliseconds and scheduled handle remain active. Cron tasks use both scheduler-level no-overlap protection and fluo's in-process running guard, so the same task instance will not run overlapping ticks.
149
155
 
150
156
  ### Bounded Shutdown
151
157
 
@@ -17,19 +17,21 @@ export declare class CronDistributedLockManager {
17
17
  private readonly runtimeContainer;
18
18
  private readonly logger;
19
19
  private readonly ownedLockKeys;
20
+ private lockIoError;
20
21
  private redisClient;
21
22
  private lockOwnershipLosses;
22
23
  private lockRenewalFailures;
23
24
  constructor(options: NormalizedCronModuleOptions, runtimeContainer: Container, logger: ApplicationLogger);
24
25
  get resolvedClient(): RedisLockClient | undefined;
25
26
  get ownedLocks(): number;
27
+ get lockIoAvailable(): boolean;
26
28
  get ownershipLosses(): number;
27
29
  get renewalFailures(): number;
28
30
  resolveClient(): Promise<void>;
29
31
  reset(): void;
30
32
  tryAcquireLock(descriptor: CronTaskDescriptor): Promise<boolean>;
31
33
  startLockRenewalMonitor(descriptor: CronTaskDescriptor): LockRenewalMonitor;
32
- releaseLock(descriptor: CronTaskDescriptor): Promise<void>;
34
+ releaseLock(descriptor: CronTaskDescriptor): Promise<boolean>;
33
35
  releaseOwnedLocks(excludedLockKeys?: ReadonlySet<string>): Promise<void>;
34
36
  private createLockRenewalState;
35
37
  private queueDueLockRenewalAttempts;
@@ -37,5 +39,8 @@ export declare class CronDistributedLockManager {
37
39
  private toLockPostRunError;
38
40
  private renewLock;
39
41
  private releaseLockKey;
42
+ private verifyLockIoAvailability;
43
+ private markLockIoAvailable;
44
+ private markLockIoUnavailable;
40
45
  }
41
46
  //# sourceMappingURL=distributed-lock-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"distributed-lock-manager.d.ts","sourceRoot":"","sources":["../src/distributed-lock-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAElF,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAC7G;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,eAAe,IAAI,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC9C,IAAI,IAAI,IAAI,CAAC;CACd;AAoDD,yFAAyF;AACzF,qBAAa,0BAA0B;IAOnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IARzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,mBAAmB,CAAK;gBAGb,OAAO,EAAE,2BAA2B,EACpC,gBAAgB,EAAE,SAAS,EAC3B,MAAM,EAAE,iBAAiB;IAG5C,IAAI,cAAc,IAAI,eAAe,GAAG,SAAS,CAEhD;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;IAEK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBpC,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IA+BtE,uBAAuB,CAAC,UAAU,EAAE,kBAAkB,GAAG,kBAAkB;IA8BrE,WAAW,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,iBAAiB,CAAC,gBAAgB,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzF,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,2BAA2B;YAcrB,qBAAqB;IAqBnC,OAAO,CAAC,kBAAkB;YAYZ,SAAS;YAwCT,cAAc;CAgC7B"}
1
+ {"version":3,"file":"distributed-lock-manager.d.ts","sourceRoot":"","sources":["../src/distributed-lock-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAElF,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAC7G;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,eAAe,IAAI,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC9C,IAAI,IAAI,IAAI,CAAC;CACd;AAoDD,yFAAyF;AACzF,qBAAa,0BAA0B;IAQnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IATzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,mBAAmB,CAAK;gBAGb,OAAO,EAAE,2BAA2B,EACpC,gBAAgB,EAAE,SAAS,EAC3B,MAAM,EAAE,iBAAiB;IAG5C,IAAI,cAAc,IAAI,eAAe,GAAG,SAAS,CAEhD;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,eAAe,IAAI,OAAO,CAM7B;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;IAEK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBpC,KAAK,IAAI,IAAI;IAKP,cAAc,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAkCtE,uBAAuB,CAAC,UAAU,EAAE,kBAAkB,GAAG,kBAAkB;IA8BrE,WAAW,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,iBAAiB,CAAC,gBAAgB,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzF,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,2BAA2B;YAcrB,qBAAqB;IAqBnC,OAAO,CAAC,kBAAkB;YAYZ,SAAS;YA2CT,cAAc;YAsCd,wBAAwB;IAmBtC,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,qBAAqB;CAG9B"}
@@ -30,6 +30,7 @@ async function resolveRedisPeerModule() {
30
30
  /** Coordinates Redis lock acquisition, renewal, and release for scheduled cron tasks. */
31
31
  export class CronDistributedLockManager {
32
32
  ownedLockKeys = new Set();
33
+ lockIoError;
33
34
  redisClient;
34
35
  lockOwnershipLosses = 0;
35
36
  lockRenewalFailures = 0;
@@ -44,6 +45,12 @@ export class CronDistributedLockManager {
44
45
  get ownedLocks() {
45
46
  return this.ownedLockKeys.size;
46
47
  }
48
+ get lockIoAvailable() {
49
+ if (!this.options.distributed.enabled) {
50
+ return true;
51
+ }
52
+ return this.redisClient !== undefined && this.lockIoError === undefined;
53
+ }
47
54
  get ownershipLosses() {
48
55
  return this.lockOwnershipLosses;
49
56
  }
@@ -66,8 +73,10 @@ export class CronDistributedLockManager {
66
73
  throw new Error('Cron distributed mode requires the configured Redis client to implement set/eval lock operations.');
67
74
  }
68
75
  this.redisClient = redisClient;
76
+ await this.verifyLockIoAvailability();
69
77
  }
70
78
  reset() {
79
+ this.lockIoError = undefined;
71
80
  this.redisClient = undefined;
72
81
  }
73
82
  async tryAcquireLock(descriptor) {
@@ -77,11 +86,13 @@ export class CronDistributedLockManager {
77
86
  }
78
87
  try {
79
88
  const result = await redis.set(descriptor.lockKey, this.options.distributed.ownerId, 'PX', descriptor.lockTtlMs, 'NX');
89
+ this.markLockIoAvailable();
80
90
  if (result === 'OK') {
81
91
  this.ownedLockKeys.add(descriptor.lockKey);
82
92
  }
83
93
  return result === 'OK';
84
94
  } catch (error) {
95
+ this.markLockIoUnavailable(error);
85
96
  this.logger.error(`Failed to acquire distributed cron lock for ${descriptor.taskName}.`, error, 'CronLifecycleService');
86
97
  return false;
87
98
  }
@@ -113,7 +124,7 @@ export class CronDistributedLockManager {
113
124
  };
114
125
  }
115
126
  async releaseLock(descriptor) {
116
- await this.releaseLockKey(descriptor.lockKey, descriptor.taskName);
127
+ return await this.releaseLockKey(descriptor.lockKey, descriptor.taskName);
117
128
  }
118
129
  async releaseOwnedLocks(excludedLockKeys = new Set()) {
119
130
  if (!this.redisClient || this.ownedLockKeys.size === 0) {
@@ -176,12 +187,15 @@ export class CronDistributedLockManager {
176
187
  try {
177
188
  const result = await redis.eval(RENEW_LOCK_SCRIPT, 1, descriptor.lockKey, this.options.distributed.ownerId, String(descriptor.lockTtlMs));
178
189
  if (typeof result === 'number' && result <= 0) {
190
+ this.markLockIoAvailable();
179
191
  this.logger.warn(`Distributed cron lock ownership was lost for ${descriptor.taskName}.`, 'CronLifecycleService');
180
192
  return 'ownership-lost';
181
193
  }
194
+ this.markLockIoAvailable();
182
195
  this.logger.log(`Renewed distributed cron lock for ${descriptor.taskName}.`, 'CronLifecycleService');
183
196
  return 'renewed';
184
197
  } catch (error) {
198
+ this.markLockIoUnavailable(error);
185
199
  this.logger.error(`Failed to renew distributed cron lock for ${descriptor.taskName}.`, error, 'CronLifecycleService');
186
200
  return 'renewal-failed';
187
201
  }
@@ -189,21 +203,47 @@ export class CronDistributedLockManager {
189
203
  async releaseLockKey(lockKey, taskName) {
190
204
  const redis = this.redisClient;
191
205
  if (!redis) {
192
- return;
206
+ return true;
193
207
  }
194
208
  try {
195
209
  const result = await redis.eval(RELEASE_LOCK_SCRIPT, 1, lockKey, this.options.distributed.ownerId);
196
210
  if (typeof result === 'number' && result <= 0) {
211
+ this.markLockIoAvailable();
197
212
  this.logger.warn(`Distributed cron lock for ${taskName} was already released or owned by another node.`, 'CronLifecycleService');
198
213
  this.ownedLockKeys.delete(lockKey);
199
- return;
214
+ return true;
200
215
  }
216
+ this.markLockIoAvailable();
201
217
  this.logger.log(`Released distributed cron lock for ${taskName}.`, 'CronLifecycleService');
202
218
  this.ownedLockKeys.delete(lockKey);
219
+ return true;
203
220
  } catch (error) {
221
+ this.markLockIoUnavailable(error);
204
222
  this.logger.error(`Failed to release distributed cron lock for ${taskName}.`, error, 'CronLifecycleService');
223
+ return false;
205
224
  }
206
225
  }
226
+ async verifyLockIoAvailability() {
227
+ const redis = this.redisClient;
228
+ if (!redis) {
229
+ return;
230
+ }
231
+ const probeKey = `${this.options.distributed.keyPrefix}:__probe:${this.options.distributed.ownerId}`;
232
+ try {
233
+ await redis.set(probeKey, this.options.distributed.ownerId, 'PX', 1_000, 'NX');
234
+ await redis.eval(RELEASE_LOCK_SCRIPT, 1, probeKey, this.options.distributed.ownerId);
235
+ this.markLockIoAvailable();
236
+ } catch (error) {
237
+ this.markLockIoUnavailable(error);
238
+ throw new Error('Cron distributed mode requires Redis lock I/O to be available.');
239
+ }
240
+ }
241
+ markLockIoAvailable() {
242
+ this.lockIoError = undefined;
243
+ }
244
+ markLockIoUnavailable(error) {
245
+ this.lockIoError = error instanceof Error ? error : new Error('Redis lock I/O failed.');
246
+ }
207
247
  }
208
248
  function hasRedisLockClient(value) {
209
249
  if (typeof value !== 'object' || value === null) {
package/dist/service.d.ts CHANGED
@@ -90,12 +90,20 @@ export declare class CronLifecycleService implements SchedulingRegistry, OnAppli
90
90
  * @param expression New cron expression to validate and schedule.
91
91
  */
92
92
  updateCronExpression(name: string, expression: string): void;
93
+ /**
94
+ * Replaces the millisecond cadence of one existing interval task.
95
+ *
96
+ * @param name Name of the interval task to update.
97
+ * @param ms New positive interval in milliseconds.
98
+ */
99
+ updateIntervalMs(name: string, ms: number): void;
93
100
  onApplicationBootstrap(): Promise<void>;
94
101
  onApplicationShutdown(): Promise<void>;
95
102
  onModuleDestroy(): Promise<void>;
96
103
  createPlatformStatusSnapshot(): import("./status.js").CronPlatformStatusSnapshot;
97
104
  private toSchedulingTaskDescriptor;
98
105
  private shutdown;
106
+ private retryReleasedDistributedLocksAfterShutdown;
99
107
  private startLifecycle;
100
108
  private validateDistributedLockConfiguration;
101
109
  private handleStartupFailure;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/dist/service.js CHANGED
@@ -5,8 +5,8 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
5
5
  function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
6
  function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
7
  import { Inject } from '@fluojs/core';
8
- import { Cron as CronValidator } from 'croner';
9
8
  import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
9
+ import { Cron as CronValidator } from 'croner';
10
10
  import { CronDistributedLockManager } from './distributed-lock-manager.js';
11
11
  import { createCronPlatformStatusSnapshot } from './status.js';
12
12
  import { createLockKey, discoverCronTaskDescriptors } from './task-discovery.js';
@@ -22,6 +22,14 @@ function assertValidTaskName(name) {
22
22
  throw new Error('Scheduling task name must be a non-empty string.');
23
23
  }
24
24
  }
25
+ function resolveDynamicTaskName(name, optionName) {
26
+ assertValidTaskName(name);
27
+ if (optionName !== undefined) {
28
+ assertValidTaskName(optionName);
29
+ return optionName;
30
+ }
31
+ return name;
32
+ }
25
33
  function assertValidMs(ms, context) {
26
34
  if (!Number.isFinite(ms) || !Number.isInteger(ms) || ms <= 0) {
27
35
  throw new Error(`${context}: ms must be a positive integer.`);
@@ -85,8 +93,8 @@ class CronLifecycleService {
85
93
  * @param options Optional hooks, distributed lock overrides, and timezone.
86
94
  */
87
95
  addCron(name, expression, callback, options = {}) {
88
- assertValidTaskName(name);
89
96
  assertValidCronExpression(expression);
97
+ const taskName = resolveDynamicTaskName(name, options.name);
90
98
  this.registerTask({
91
99
  afterRun: options.afterRun,
92
100
  beforeRun: options.beforeRun,
@@ -94,11 +102,11 @@ class CronLifecycleService {
94
102
  distributed: options.distributed ?? true,
95
103
  expression,
96
104
  kind: 'cron',
97
- lockKey: createLockKey(this.options.distributed.keyPrefix, options.key ?? name),
105
+ lockKey: createLockKey(this.options.distributed.keyPrefix, options.key ?? taskName),
98
106
  lockTtlMs: options.lockTtlMs ?? this.options.distributed.lockTtlMs,
99
107
  onError: options.onError,
100
108
  onSuccess: options.onSuccess,
101
- taskName: name,
109
+ taskName,
102
110
  timezone: options.timezone
103
111
  }, 'dynamic');
104
112
  }
@@ -112,20 +120,20 @@ class CronLifecycleService {
112
120
  * @param options Optional hooks and distributed lock overrides.
113
121
  */
114
122
  addInterval(name, ms, callback, options = {}) {
115
- assertValidTaskName(name);
116
123
  assertValidMs(ms, 'scheduling registry');
124
+ const taskName = resolveDynamicTaskName(name, options.name);
117
125
  this.registerTask({
118
126
  afterRun: options.afterRun,
119
127
  beforeRun: options.beforeRun,
120
128
  callback,
121
129
  distributed: options.distributed ?? true,
122
130
  kind: 'interval',
123
- lockKey: createLockKey(this.options.distributed.keyPrefix, options.key ?? name),
131
+ lockKey: createLockKey(this.options.distributed.keyPrefix, options.key ?? taskName),
124
132
  lockTtlMs: options.lockTtlMs ?? this.options.distributed.lockTtlMs,
125
133
  ms,
126
134
  onError: options.onError,
127
135
  onSuccess: options.onSuccess,
128
- taskName: name
136
+ taskName
129
137
  }, 'dynamic');
130
138
  }
131
139
 
@@ -138,20 +146,20 @@ class CronLifecycleService {
138
146
  * @param options Optional hooks and distributed lock overrides.
139
147
  */
140
148
  addTimeout(name, ms, callback, options = {}) {
141
- assertValidTaskName(name);
142
149
  assertValidMs(ms, 'scheduling registry');
150
+ const taskName = resolveDynamicTaskName(name, options.name);
143
151
  this.registerTask({
144
152
  afterRun: options.afterRun,
145
153
  beforeRun: options.beforeRun,
146
154
  callback,
147
155
  distributed: options.distributed ?? true,
148
156
  kind: 'timeout',
149
- lockKey: createLockKey(this.options.distributed.keyPrefix, options.key ?? name),
157
+ lockKey: createLockKey(this.options.distributed.keyPrefix, options.key ?? taskName),
150
158
  lockTtlMs: options.lockTtlMs ?? this.options.distributed.lockTtlMs,
151
159
  ms,
152
160
  onError: options.onError,
153
161
  onSuccess: options.onSuccess,
154
- taskName: name
162
+ taskName
155
163
  }, 'dynamic');
156
164
  }
157
165
 
@@ -272,6 +280,41 @@ class CronLifecycleService {
272
280
  throw error;
273
281
  }
274
282
  }
283
+
284
+ /**
285
+ * Replaces the millisecond cadence of one existing interval task.
286
+ *
287
+ * @param name Name of the interval task to update.
288
+ * @param ms New positive interval in milliseconds.
289
+ */
290
+ updateIntervalMs(name, ms) {
291
+ assertValidMs(ms, 'scheduling registry');
292
+ const task = this.tasks.get(name);
293
+ if (!task) {
294
+ throw new Error(`Scheduling task "${name}" does not exist.`);
295
+ }
296
+ if (task.descriptor.kind !== 'interval') {
297
+ throw new Error(`updateIntervalMs() supports only interval tasks. Received ${task.descriptor.kind} task "${name}".`);
298
+ }
299
+ if (!task.enabled || !this.started) {
300
+ task.descriptor.ms = ms;
301
+ return;
302
+ }
303
+ const previousMs = task.descriptor.ms;
304
+ const previousHandle = task.scheduledHandle;
305
+ task.descriptor.ms = ms;
306
+ try {
307
+ const nextHandle = this.createScheduledHandle(task);
308
+ task.scheduledHandle = nextHandle;
309
+ if (previousHandle) {
310
+ this.stopScheduledHandle(previousHandle);
311
+ }
312
+ } catch (error) {
313
+ task.descriptor.ms = previousMs;
314
+ task.scheduledHandle = previousHandle;
315
+ throw error;
316
+ }
317
+ }
275
318
  async onApplicationBootstrap() {
276
319
  if (this.started) {
277
320
  return;
@@ -313,6 +356,7 @@ class CronLifecycleService {
313
356
  lockRenewalFailures: this.distributedLocks.renewalFailures,
314
357
  ownedLocks: this.distributedLocks.ownedLocks,
315
358
  redisDependencyResolved: this.distributedLocks.resolvedClient !== undefined,
359
+ redisLockIoAvailable: this.distributedLocks.lockIoAvailable,
316
360
  runningTasks,
317
361
  totalTasks: this.tasks.size
318
362
  });
@@ -337,11 +381,18 @@ class CronLifecycleService {
337
381
  async shutdown() {
338
382
  if (this.shutdownPromise) {
339
383
  await this.shutdownPromise;
384
+ await this.retryReleasedDistributedLocksAfterShutdown();
340
385
  return;
341
386
  }
342
387
  this.shutdownPromise = this.runShutdownLifecycle();
343
388
  await this.shutdownPromise;
344
389
  }
390
+ async retryReleasedDistributedLocksAfterShutdown() {
391
+ if (this.lifecycleState !== 'stopped' || this.activeTasks.size > 0) {
392
+ return;
393
+ }
394
+ await this.distributedLocks.releaseOwnedLocks();
395
+ }
345
396
  async startLifecycle() {
346
397
  await this.distributedLocks.resolveClient();
347
398
  this.validateDistributedLockConfiguration();
@@ -518,8 +569,11 @@ class CronLifecycleService {
518
569
  });
519
570
  } finally {
520
571
  lockRenewalMonitor.stop();
521
- await this.distributedLocks.releaseLock(descriptor);
522
572
  this.runningDistributedLockKeys.delete(descriptor.lockKey);
573
+ const released = await this.distributedLocks.releaseLock(descriptor);
574
+ if (!released && this.lifecycleState === 'stopped') {
575
+ await this.distributedLocks.releaseOwnedLocks();
576
+ }
523
577
  }
524
578
  }
525
579
  async waitForActiveTasks() {
package/dist/status.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface CronStatusAdapterInput {
12
12
  lockRenewalFailures: number;
13
13
  ownedLocks: number;
14
14
  redisDependencyResolved: boolean;
15
+ redisLockIoAvailable?: boolean;
15
16
  runningTasks: number;
16
17
  totalTasks: number;
17
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,qEAAqE;AACrE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEtG,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,kBAAkB,CAAC;IACnC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,OAAO,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qFAAqF;AACrF,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAoFD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,sBAAsB,GAAG,0BAA0B,CAsB1G"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,qEAAqE;AACrE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEtG,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,kBAAkB,CAAC;IACnC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,OAAO,CAAC;IACjC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qFAAqF;AACrF,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAuGD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,sBAAsB,GAAG,0BAA0B,CAyB1G"}
package/dist/status.js CHANGED
@@ -5,8 +5,9 @@
5
5
  /** Cron-specific platform snapshot returned to health and readiness integrations. */
6
6
 
7
7
  function createReadiness(input) {
8
+ const redisLockIoAvailable = resolveRedisLockIoAvailable(input);
8
9
  if (input.lifecycleState === 'ready') {
9
- if (input.distributedEnabled && !input.redisDependencyResolved) {
10
+ if (input.distributedEnabled && (!input.redisDependencyResolved || !redisLockIoAvailable)) {
10
11
  return {
11
12
  critical: true,
12
13
  reason: 'Distributed cron mode requires a ready Redis lock client.',
@@ -53,6 +54,7 @@ function createReadiness(input) {
53
54
  };
54
55
  }
55
56
  function createHealth(input) {
57
+ const redisLockIoAvailable = resolveRedisLockIoAvailable(input);
56
58
  if (input.lifecycleState === 'failed' || input.lifecycleState === 'stopped') {
57
59
  return {
58
60
  reason: 'Cron scheduler is unavailable.',
@@ -65,6 +67,12 @@ function createHealth(input) {
65
67
  status: 'degraded'
66
68
  };
67
69
  }
70
+ if (input.distributedEnabled && (!input.redisDependencyResolved || !redisLockIoAvailable)) {
71
+ return {
72
+ reason: 'Distributed cron Redis lock I/O is unavailable.',
73
+ status: 'unhealthy'
74
+ };
75
+ }
68
76
  if (input.lockRenewalFailures > 0 || input.lockOwnershipLosses > 0) {
69
77
  return {
70
78
  reason: 'Distributed cron lock renewal reported recoverable failures.',
@@ -75,6 +83,12 @@ function createHealth(input) {
75
83
  status: 'healthy'
76
84
  };
77
85
  }
86
+ function resolveRedisLockIoAvailable(input) {
87
+ if (!input.distributedEnabled) {
88
+ return true;
89
+ }
90
+ return input.redisLockIoAvailable ?? input.redisDependencyResolved;
91
+ }
78
92
 
79
93
  /**
80
94
  * Creates the cron platform snapshot consumed by status reporters.
@@ -83,6 +97,7 @@ function createHealth(input) {
83
97
  * @returns Readiness, health, ownership, and cron detail fields.
84
98
  */
85
99
  export function createCronPlatformStatusSnapshot(input) {
100
+ const redisLockIoAvailable = resolveRedisLockIoAvailable(input);
86
101
  return {
87
102
  details: {
88
103
  activeTicks: input.activeTicks,
@@ -94,6 +109,7 @@ export function createCronPlatformStatusSnapshot(input) {
94
109
  lockRenewalFailures: input.lockRenewalFailures,
95
110
  ownedLocks: input.ownedLocks,
96
111
  redisDependencyResolved: input.redisDependencyResolved,
112
+ redisLockIoAvailable,
97
113
  runningTasks: input.runningTasks,
98
114
  totalTasks: input.totalTasks
99
115
  },
package/dist/types.d.ts CHANGED
@@ -204,5 +204,12 @@ export interface SchedulingRegistry {
204
204
  * @param expression New cron expression validated before rescheduling.
205
205
  */
206
206
  updateCronExpression(name: string, expression: string): void;
207
+ /**
208
+ * Replaces the millisecond cadence for an existing interval task.
209
+ *
210
+ * @param name Task name to update.
211
+ * @param ms New positive interval in milliseconds.
212
+ */
213
+ updateIntervalMs(name: string, ms: number): void;
207
214
  }
208
215
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "timeout",
10
10
  "distributed-lock"
11
11
  ],
12
- "version": "1.0.3",
12
+ "version": "1.1.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -38,11 +38,11 @@
38
38
  "dependencies": {
39
39
  "croner": "^8.1.2",
40
40
  "@fluojs/core": "^1.0.3",
41
- "@fluojs/runtime": "^1.1.7",
42
- "@fluojs/di": "^1.1.0"
41
+ "@fluojs/di": "^1.1.0",
42
+ "@fluojs/runtime": "^1.1.8"
43
43
  },
44
44
  "peerDependencies": {
45
- "@fluojs/redis": "^1.0.1"
45
+ "@fluojs/redis": "^1.0.2"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@fluojs/redis": {
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "vitest": "^3.2.4",
54
- "@fluojs/redis": "^1.0.1"
54
+ "@fluojs/redis": "^1.0.2"
55
55
  },
56
56
  "scripts": {
57
57
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",