@fluojs/cron 1.0.0-beta.1

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +183 -0
  3. package/README.md +183 -0
  4. package/dist/decorators.d.ts +41 -0
  5. package/dist/decorators.d.ts.map +1 -0
  6. package/dist/decorators.js +116 -0
  7. package/dist/distributed-lock-manager.d.ts +38 -0
  8. package/dist/distributed-lock-manager.d.ts.map +1 -0
  9. package/dist/distributed-lock-manager.js +181 -0
  10. package/dist/expressions.d.ts +13 -0
  11. package/dist/expressions.d.ts.map +1 -0
  12. package/dist/expressions.js +12 -0
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +7 -0
  16. package/dist/metadata.d.ts +17 -0
  17. package/dist/metadata.d.ts.map +1 -0
  18. package/dist/metadata.js +78 -0
  19. package/dist/module.d.ts +31 -0
  20. package/dist/module.d.ts.map +1 -0
  21. package/dist/module.js +95 -0
  22. package/dist/scheduler.d.ts +3 -0
  23. package/dist/scheduler.d.ts.map +1 -0
  24. package/dist/scheduler.js +8 -0
  25. package/dist/service.d.ts +118 -0
  26. package/dist/service.d.ts.map +1 -0
  27. package/dist/service.js +528 -0
  28. package/dist/status.d.ts +32 -0
  29. package/dist/status.d.ts.map +1 -0
  30. package/dist/status.js +107 -0
  31. package/dist/task-discovery.d.ts +8 -0
  32. package/dist/task-discovery.d.ts.map +1 -0
  33. package/dist/task-discovery.js +104 -0
  34. package/dist/task-runner.d.ts +15 -0
  35. package/dist/task-runner.d.ts.map +1 -0
  36. package/dist/task-runner.js +87 -0
  37. package/dist/tokens.d.ts +7 -0
  38. package/dist/tokens.d.ts.map +1 -0
  39. package/dist/tokens.js +4 -0
  40. package/dist/types.d.ts +206 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +1 -0
  43. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fluo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,183 @@
1
+ # @fluojs/cron
2
+
3
+ <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
+
5
+ fluo 애플리케이션을 위한 데코레이터 기반 스케줄링 패키지입니다. 앱 라이프사이클에 맞춰 시작/종료를 관리하고, Redis 기반 분산 락(Distributed Locking) 기능을 제공합니다.
6
+
7
+ ## 목차
8
+
9
+ - [설치](#설치)
10
+ - [사용 시점](#사용-시점)
11
+ - [빠른 시작](#빠른-시작)
12
+ - [공통 패턴](#공통-패턴)
13
+ - [분산 락 사용하기](#분산-락-사용하기)
14
+ - [동적 스케줄링](#동적-스케줄링)
15
+ - [제한된 종료](#제한된-종료)
16
+ - [공개 API 개요](#공개-api-개요)
17
+ - [관련 패키지](#관련-패키지)
18
+ - [예제 소스](#예제-소스)
19
+
20
+ ## 설치
21
+
22
+ ```bash
23
+ npm install @fluojs/cron croner
24
+ ```
25
+
26
+ ## 사용 시점
27
+
28
+ - 정기적인 백그라운드 작업(예: 데이터베이스 정리, 리포트 생성)이 필요할 때 사용합니다.
29
+ - 표준 Cron 표현식을 사용하여 작업을 예약하고 싶을 때 적합합니다.
30
+ - 다중 인스턴스 환경에서 특정 작업이 한 번에 하나의 인스턴스에서만 실행되도록 보장해야 할 때(분산 락) 사용합니다.
31
+ - 일회성 지연 작업(Timeout)이나 고정된 주기의 반복 작업(Interval)이 필요할 때 사용합니다.
32
+
33
+ ## 빠른 시작
34
+
35
+ `CronModule`을 등록하고 데코레이터를 사용하여 메서드를 스케줄링합니다.
36
+
37
+ 애플리케이션 모듈의 스케줄링 등록은 `CronModule.forRoot(...)`로 구성합니다.
38
+
39
+ ```typescript
40
+ import { Module } from '@fluojs/core';
41
+ import { CronModule, Cron, CronExpression, Interval, Timeout } from '@fluojs/cron';
42
+
43
+ class BillingService {
44
+ @Cron(CronExpression.EVERY_MINUTE, { name: 'billing.reconcile' })
45
+ async reconcilePendingInvoices() {
46
+ console.log('송장 정리 중...');
47
+ }
48
+
49
+ @Interval(15_000) // 15초마다
50
+ async pollStatus() {
51
+ console.log('상태 폴링 중...');
52
+ }
53
+
54
+ @Timeout(5_000) // 시작 5초 후 1회 실행
55
+ async initialSync() {
56
+ console.log('초기 동기화 실행 중...');
57
+ }
58
+ }
59
+
60
+ @Module({
61
+ imports: [CronModule.forRoot()],
62
+ providers: [BillingService],
63
+ })
64
+ class AppModule {}
65
+ ```
66
+
67
+ ## 공통 패턴
68
+
69
+ ### 분산 락 사용하기
70
+
71
+ 여러 서버 인스턴스에서 스케줄링된 작업이 동시에 실행되는 것을 방지하려면 분산 모드를 활성화하세요. 이 기능은 `@fluojs/redis`가 필요합니다.
72
+
73
+ ```typescript
74
+ import { Module } from '@fluojs/core';
75
+ import { CronModule } from '@fluojs/cron';
76
+ import { RedisModule } from '@fluojs/redis';
77
+
78
+ @Module({
79
+ imports: [
80
+ RedisModule.forRoot({ host: 'localhost', port: 6379 }),
81
+ CronModule.forRoot({
82
+ distributed: {
83
+ enabled: true,
84
+ keyPrefix: 'fluo:cron:lock',
85
+ lockTtlMs: 30_000,
86
+ },
87
+ }),
88
+ ],
89
+ })
90
+ class AppModule {}
91
+ ```
92
+
93
+ `distributed.clientName`을 생략하면 위의 기본 Redis 등록을 계속 사용합니다. 분산 락에 기본 Redis가 아닌 다른 연결을 쓰려면 `RedisModule.forRootNamed(...)`로 등록한 이름을 `distributed.clientName`에 지정하세요.
94
+
95
+ `distributed.lockTtlMs`는 `1_000ms` 이상이어야 합니다. fluo는 최소 지원 경계인 `1_000ms`를 포함해 TTL이 만료되기 전에 Redis 락을 갱신합니다.
96
+
97
+ ```typescript
98
+ @Module({
99
+ imports: [
100
+ RedisModule.forRoot({ host: 'localhost', port: 6379 }),
101
+ RedisModule.forRootNamed('locks', { host: 'localhost', port: 6380 }),
102
+ CronModule.forRoot({
103
+ distributed: {
104
+ clientName: 'locks',
105
+ enabled: true,
106
+ keyPrefix: 'fluo:cron:lock',
107
+ lockTtlMs: 30_000,
108
+ },
109
+ }),
110
+ ],
111
+ })
112
+ class MultiRedisCronModule {}
113
+ ```
114
+
115
+ ### 동적 스케줄링
116
+
117
+ `SCHEDULING_REGISTRY`를 사용하여 런타임에 작업을 관리할 수 있습니다.
118
+
119
+ ```typescript
120
+ import { Inject } from '@fluojs/core';
121
+ import { SCHEDULING_REGISTRY, type SchedulingRegistry } from '@fluojs/cron';
122
+
123
+ class TaskManager {
124
+ constructor(
125
+ @Inject(SCHEDULING_REGISTRY) private readonly registry: SchedulingRegistry
126
+ ) {}
127
+
128
+ addNewTask() {
129
+ this.registry.addCron('dynamic-job', '0 * * * *', () => {
130
+ console.log('동적 작업 실행 중!');
131
+ });
132
+ }
133
+
134
+ stopTask() {
135
+ this.registry.remove('dynamic-job');
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### 제한된 종료
141
+
142
+ `CronModule`은 애플리케이션 종료 시 실행 중인 작업을 drain하지만, 이제는 제한된 타임아웃 안에서만 기다립니다. 따라서 하나의 hung task 때문에 프로세스 종료가 영원히 막히지 않습니다.
143
+
144
+ 기본적으로 shutdown drain은 최대 `10_000ms` 동안 기다립니다. 이 시간이 지나면 스케줄러는 경고 로그를 남기고 hung task가 끝나기를 더 기다리지 않은 채 종료를 계속합니다.
145
+
146
+ ```typescript
147
+ @Module({
148
+ imports: [
149
+ CronModule.forRoot({
150
+ shutdown: {
151
+ timeoutMs: 5_000,
152
+ },
153
+ }),
154
+ ],
155
+ })
156
+ class AppModule {}
157
+ ```
158
+
159
+ ## 공개 API 개요
160
+
161
+ ### 모듈
162
+ - `CronModule.forRoot(options)`: 스케줄러를 설정하고 필요한 경우 분산 락을 활성화합니다.
163
+
164
+ ### 데코레이터
165
+ - `@Cron(expression, options?)`: Cron 표현식을 사용하여 메서드를 예약합니다.
166
+ - `@Interval(ms, options?)`: 고정된 주기로 메서드를 실행합니다.
167
+ - `@Timeout(ms, options?)`: 일정 시간 지연 후 메서드를 한 번 실행합니다.
168
+
169
+ ### 상수 및 토큰
170
+ - `CronExpression`: 공통 Cron 패턴(예: `EVERY_HOUR`, `EVERY_DAY_AT_MIDNIGHT`)을 담은 객체입니다.
171
+ - `SCHEDULING_REGISTRY`: `SchedulingRegistry` 서비스를 위한 주입 토큰입니다.
172
+
173
+
174
+ ## 관련 패키지
175
+
176
+ - `@fluojs/redis`: 분산 락 기능을 위해 필요합니다.
177
+ - `@fluojs/core`: DI 및 모듈 관리를 위해 필요합니다.
178
+ - `croner`: 내부 스케줄링 엔진입니다.
179
+
180
+ ## 예제 소스
181
+
182
+ - `packages/cron/src/module.test.ts`: 데코레이터 및 모듈 라이프사이클에 대한 종합 테스트.
183
+ - `packages/cron/src/scheduler.ts`: 코어 스케줄링 로직의 구현 상세.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # @fluojs/cron
2
+
3
+ <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
+
5
+ Decorator-based scheduling for fluo applications with lifecycle-managed startup/shutdown and optional Redis distributed locking.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [When to Use](#when-to-use)
11
+ - [Quick Start](#quick-start)
12
+ - [Common Patterns](#common-patterns)
13
+ - [Distributed Locking](#distributed-locking)
14
+ - [Dynamic Scheduling](#dynamic-scheduling)
15
+ - [Bounded Shutdown](#bounded-shutdown)
16
+ - [Public API Overview](#public-api-overview)
17
+ - [Related Packages](#related-packages)
18
+ - [Example Sources](#example-sources)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @fluojs/cron croner
24
+ ```
25
+
26
+ ## When to Use
27
+
28
+ - When you need to run periodic background tasks (e.g., database cleanup, report generation).
29
+ - When you want to schedule tasks using standard Cron expressions.
30
+ - When running in a multi-instance environment and you need to ensure a task runs only on one instance at a time (Distributed Locking).
31
+ - When you need simple one-off delayed tasks (Timeout) or fixed-rate intervals.
32
+
33
+ ## Quick Start
34
+
35
+ Register the `CronModule` and use decorators to schedule your methods.
36
+
37
+ Use `CronModule.forRoot(...)` to register scheduling for an application module.
38
+
39
+ ```typescript
40
+ import { Module } from '@fluojs/core';
41
+ import { CronModule, Cron, CronExpression, Interval, Timeout } from '@fluojs/cron';
42
+
43
+ class BillingService {
44
+ @Cron(CronExpression.EVERY_MINUTE, { name: 'billing.reconcile' })
45
+ async reconcilePendingInvoices() {
46
+ console.log('Reconciling invoices...');
47
+ }
48
+
49
+ @Interval(15_000) // 15 seconds
50
+ async pollStatus() {
51
+ console.log('Polling status...');
52
+ }
53
+
54
+ @Timeout(5_000) // 5 seconds after startup
55
+ async initialSync() {
56
+ console.log('Running initial sync...');
57
+ }
58
+ }
59
+
60
+ @Module({
61
+ imports: [CronModule.forRoot()],
62
+ providers: [BillingService],
63
+ })
64
+ class AppModule {}
65
+ ```
66
+
67
+ ## Common Patterns
68
+
69
+ ### Distributed Locking
70
+
71
+ To prevent scheduled tasks from running concurrently across multiple server instances, enable distributed mode. This requires `@fluojs/redis`.
72
+
73
+ ```typescript
74
+ import { Module } from '@fluojs/core';
75
+ import { CronModule } from '@fluojs/cron';
76
+ import { RedisModule } from '@fluojs/redis';
77
+
78
+ @Module({
79
+ imports: [
80
+ RedisModule.forRoot({ host: 'localhost', port: 6379 }),
81
+ CronModule.forRoot({
82
+ distributed: {
83
+ enabled: true,
84
+ keyPrefix: 'fluo:cron:lock',
85
+ lockTtlMs: 30_000,
86
+ },
87
+ }),
88
+ ],
89
+ })
90
+ class AppModule {}
91
+ ```
92
+
93
+ Leave `distributed.clientName` unset to keep using the default Redis registration above. To use a non-default Redis connection for distributed locks, set `distributed.clientName` to the name registered through `RedisModule.forRootNamed(...)`.
94
+
95
+ `distributed.lockTtlMs` must stay at or above `1_000ms`. fluo renews the Redis lock before that TTL expires, including the minimum supported `1_000ms` boundary.
96
+
97
+ ```typescript
98
+ @Module({
99
+ imports: [
100
+ RedisModule.forRoot({ host: 'localhost', port: 6379 }),
101
+ RedisModule.forRootNamed('locks', { host: 'localhost', port: 6380 }),
102
+ CronModule.forRoot({
103
+ distributed: {
104
+ clientName: 'locks',
105
+ enabled: true,
106
+ keyPrefix: 'fluo:cron:lock',
107
+ lockTtlMs: 30_000,
108
+ },
109
+ }),
110
+ ],
111
+ })
112
+ class MultiRedisCronModule {}
113
+ ```
114
+
115
+ ### Dynamic Scheduling
116
+
117
+ You can manage tasks at runtime using the `SCHEDULING_REGISTRY`.
118
+
119
+ ```typescript
120
+ import { Inject } from '@fluojs/core';
121
+ import { SCHEDULING_REGISTRY, type SchedulingRegistry } from '@fluojs/cron';
122
+
123
+ class TaskManager {
124
+ constructor(
125
+ @Inject(SCHEDULING_REGISTRY) private readonly registry: SchedulingRegistry
126
+ ) {}
127
+
128
+ addNewTask() {
129
+ this.registry.addCron('dynamic-job', '0 * * * *', () => {
130
+ console.log('Dynamic job running!');
131
+ });
132
+ }
133
+
134
+ stopTask() {
135
+ this.registry.remove('dynamic-job');
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### Bounded Shutdown
141
+
142
+ `CronModule` drains active task executions during application shutdown, but it now does so with a bounded timeout so one hung task cannot block process termination forever.
143
+
144
+ By default the shutdown drain waits up to `10_000ms`. If that timeout expires, the scheduler logs a warning and continues shutdown without waiting for the hung task to settle.
145
+
146
+ ```typescript
147
+ @Module({
148
+ imports: [
149
+ CronModule.forRoot({
150
+ shutdown: {
151
+ timeoutMs: 5_000,
152
+ },
153
+ }),
154
+ ],
155
+ })
156
+ class AppModule {}
157
+ ```
158
+
159
+ ## Public API Overview
160
+
161
+ ### Modules
162
+ - `CronModule.forRoot(options)`: Configures the scheduler and enables distributed locking if requested.
163
+
164
+ ### Decorators
165
+ - `@Cron(expression, options?)`: Schedules a method using a cron expression.
166
+ - `@Interval(ms, options?)`: Schedules a method to run at a fixed interval.
167
+ - `@Timeout(ms, options?)`: Schedules a method to run once after a delay.
168
+
169
+ ### Constants & Tokens
170
+ - `CronExpression`: Enum-like object with common cron patterns (e.g., `EVERY_HOUR`, `EVERY_DAY_AT_MIDNIGHT`).
171
+ - `SCHEDULING_REGISTRY`: Injection token for the `SchedulingRegistry` service.
172
+
173
+
174
+ ## Related Packages
175
+
176
+ - `@fluojs/redis`: Required for distributed locking functionality.
177
+ - `@fluojs/core`: Required for DI and Module management.
178
+ - `croner`: The underlying scheduling engine.
179
+
180
+ ## Example Sources
181
+
182
+ - `packages/cron/src/module.test.ts`: Comprehensive tests for decorators and module lifecycle.
183
+ - `packages/cron/src/scheduler.ts`: Implementation details of the core scheduling logic.
@@ -0,0 +1,41 @@
1
+ import type { CronTaskOptions, IntervalTaskOptions, TimeoutTaskOptions } from './types.js';
2
+ type StandardMethodDecoratorFn = (value: Function, context: ClassMethodDecoratorContext) => void;
3
+ type MethodDecoratorLike = StandardMethodDecoratorFn;
4
+ /**
5
+ * Schedules a public instance method using a cron expression.
6
+ *
7
+ * @param expression Cron expression validated during decorator evaluation.
8
+ * @param options Optional task name, lock settings, hooks, and timezone.
9
+ * @returns A method decorator that stores cron metadata for bootstrap discovery.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Cron, CronExpression } from '@fluojs/cron';
14
+ *
15
+ * class BillingService {
16
+ * @Cron(CronExpression.EVERY_MINUTE, { name: 'billing.reconcile' })
17
+ * async reconcilePendingInvoices() {
18
+ * await reconcileInvoices();
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export declare function Cron(expression: string, options?: CronTaskOptions): MethodDecoratorLike;
24
+ /**
25
+ * Schedules a public instance method to run repeatedly after a fixed delay.
26
+ *
27
+ * @param ms Positive interval in milliseconds.
28
+ * @param options Optional task name, lock settings, and lifecycle hooks.
29
+ * @returns A method decorator that stores interval metadata for bootstrap discovery.
30
+ */
31
+ export declare function Interval(ms: number, options?: IntervalTaskOptions): MethodDecoratorLike;
32
+ /**
33
+ * Schedules a public instance method to run once after application startup.
34
+ *
35
+ * @param ms Positive delay in milliseconds before the task runs.
36
+ * @param options Optional task name, lock settings, and lifecycle hooks.
37
+ * @returns A method decorator that stores timeout metadata for bootstrap discovery.
38
+ */
39
+ export declare function Timeout(ms: number, options?: TimeoutTaskOptions): MethodDecoratorLike;
40
+ export {};
41
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,eAAe,EAEf,mBAAmB,EAEnB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAGpB,KAAK,yBAAyB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,2BAA2B,KAAK,IAAI,CAAC;AACjG,KAAK,mBAAmB,GAAG,yBAAyB,CAAC;AAoCrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,mBAAmB,CAoB3F;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,mBAAmB,CAgB3F;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,mBAAmB,CAgBzF"}
@@ -0,0 +1,116 @@
1
+ import { metadataSymbol } from '@fluojs/core/internal';
2
+ import { Cron as CronValidator } from 'croner';
3
+ import { schedulingMetadataSymbol } from './metadata.js';
4
+ function getStandardMetadataBag(metadata) {
5
+ void metadataSymbol;
6
+ return metadata;
7
+ }
8
+ function defineStandardSchedulingMetadata(metadata, propertyKey, taskMetadata) {
9
+ const bag = getStandardMetadataBag(metadata);
10
+ const current = bag[schedulingMetadataSymbol];
11
+ const map = current ?? new Map();
12
+ map.set(propertyKey, {
13
+ ...taskMetadata,
14
+ options: {
15
+ ...taskMetadata.options
16
+ }
17
+ });
18
+ bag[schedulingMetadataSymbol] = map;
19
+ }
20
+ function assertValidIntervalMs(ms, decoratorName) {
21
+ if (!Number.isFinite(ms) || !Number.isInteger(ms) || ms <= 0) {
22
+ throw new Error(`${decoratorName}(): ms must be a positive integer.`);
23
+ }
24
+ }
25
+ function assertMethodIsPublic(context, decoratorName) {
26
+ if (context.private) {
27
+ throw new Error(`${decoratorName}() cannot be used on private methods.`);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Schedules a public instance method using a cron expression.
33
+ *
34
+ * @param expression Cron expression validated during decorator evaluation.
35
+ * @param options Optional task name, lock settings, hooks, and timezone.
36
+ * @returns A method decorator that stores cron metadata for bootstrap discovery.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { Cron, CronExpression } from '@fluojs/cron';
41
+ *
42
+ * class BillingService {
43
+ * @Cron(CronExpression.EVERY_MINUTE, { name: 'billing.reconcile' })
44
+ * async reconcilePendingInvoices() {
45
+ * await reconcileInvoices();
46
+ * }
47
+ * }
48
+ * ```
49
+ */
50
+ export function Cron(expression, options = {}) {
51
+ try {
52
+ new CronValidator(expression, {
53
+ maxRuns: 0
54
+ });
55
+ } catch {
56
+ throw new Error(`@Cron(): invalid cron expression "${expression}".`);
57
+ }
58
+ const decorator = (_value, context) => {
59
+ assertMethodIsPublic(context, '@Cron');
60
+ const metadata = {
61
+ expression,
62
+ kind: 'cron',
63
+ options: {
64
+ ...options
65
+ }
66
+ };
67
+ defineStandardSchedulingMetadata(context.metadata, context.name, metadata);
68
+ };
69
+ return decorator;
70
+ }
71
+
72
+ /**
73
+ * Schedules a public instance method to run repeatedly after a fixed delay.
74
+ *
75
+ * @param ms Positive interval in milliseconds.
76
+ * @param options Optional task name, lock settings, and lifecycle hooks.
77
+ * @returns A method decorator that stores interval metadata for bootstrap discovery.
78
+ */
79
+ export function Interval(ms, options = {}) {
80
+ assertValidIntervalMs(ms, '@Interval');
81
+ const decorator = (_value, context) => {
82
+ assertMethodIsPublic(context, '@Interval');
83
+ const metadata = {
84
+ kind: 'interval',
85
+ ms,
86
+ options: {
87
+ ...options
88
+ }
89
+ };
90
+ defineStandardSchedulingMetadata(context.metadata, context.name, metadata);
91
+ };
92
+ return decorator;
93
+ }
94
+
95
+ /**
96
+ * Schedules a public instance method to run once after application startup.
97
+ *
98
+ * @param ms Positive delay in milliseconds before the task runs.
99
+ * @param options Optional task name, lock settings, and lifecycle hooks.
100
+ * @returns A method decorator that stores timeout metadata for bootstrap discovery.
101
+ */
102
+ export function Timeout(ms, options = {}) {
103
+ assertValidIntervalMs(ms, '@Timeout');
104
+ const decorator = (_value, context) => {
105
+ assertMethodIsPublic(context, '@Timeout');
106
+ const metadata = {
107
+ kind: 'timeout',
108
+ ms,
109
+ options: {
110
+ ...options
111
+ }
112
+ };
113
+ defineStandardSchedulingMetadata(context.metadata, context.name, metadata);
114
+ };
115
+ return decorator;
116
+ }
@@ -0,0 +1,38 @@
1
+ import type { Container } from '@fluojs/di';
2
+ import type { ApplicationLogger } from '@fluojs/runtime';
3
+ import type { CronTaskDescriptor, NormalizedCronModuleOptions } from './types.js';
4
+ export interface RedisLockClient {
5
+ eval(script: string, keysLength: number, ...keysAndArgs: string[]): Promise<unknown>;
6
+ set(key: string, value: string, mode: 'PX', ttl: number, existence: 'NX'): Promise<'OK' | null | undefined>;
7
+ }
8
+ export interface LockRenewalMonitor {
9
+ getPostRunError(): Promise<Error | undefined>;
10
+ stop(): void;
11
+ }
12
+ export declare class CronDistributedLockManager {
13
+ private readonly options;
14
+ private readonly runtimeContainer;
15
+ private readonly logger;
16
+ private readonly ownedLockKeys;
17
+ private redisClient;
18
+ private lockOwnershipLosses;
19
+ private lockRenewalFailures;
20
+ constructor(options: NormalizedCronModuleOptions, runtimeContainer: Container, logger: ApplicationLogger);
21
+ get resolvedClient(): RedisLockClient | undefined;
22
+ get ownedLocks(): number;
23
+ get ownershipLosses(): number;
24
+ get renewalFailures(): number;
25
+ resolveClient(): Promise<void>;
26
+ reset(): void;
27
+ tryAcquireLock(descriptor: CronTaskDescriptor): Promise<boolean>;
28
+ startLockRenewalMonitor(descriptor: CronTaskDescriptor): LockRenewalMonitor;
29
+ releaseLock(descriptor: CronTaskDescriptor): Promise<void>;
30
+ releaseOwnedLocks(): Promise<void>;
31
+ private createLockRenewalState;
32
+ private queueDueLockRenewalAttempts;
33
+ private runLockRenewalAttempt;
34
+ private toLockPostRunError;
35
+ private renewLock;
36
+ private releaseLockKey;
37
+ }
38
+ //# sourceMappingURL=distributed-lock-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distributed-lock-manager.d.ts","sourceRoot":"","sources":["../src/distributed-lock-manager.ts"],"names":[],"mappings":"AACA,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,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,MAAM,WAAW,kBAAkB;IACjC,eAAe,IAAI,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC9C,IAAI,IAAI,IAAI,CAAC;CACd;AAiBD,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;IAoBpC,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,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxC,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,2BAA2B;YAcrB,qBAAqB;IAqBnC,OAAO,CAAC,kBAAkB;YAYZ,SAAS;YAwCT,cAAc;CAgC7B"}