@fluojs/cron 1.0.1 → 1.0.3
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 +6 -2
- package/README.md +6 -2
- package/dist/distributed-lock-manager.d.ts.map +1 -1
- package/dist/distributed-lock-manager.js +25 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +11 -2
- package/package.json +14 -6
package/README.ko.md
CHANGED
|
@@ -25,6 +25,8 @@ npm install @fluojs/cron croner
|
|
|
25
25
|
|
|
26
26
|
`croner`는 `@fluojs/cron`이 사용하는 scheduler engine입니다. 애플리케이션과 배포 감사에서 runtime scheduler dependency ownership이 명확히 보이도록 패키지와 함께 설치하세요.
|
|
27
27
|
|
|
28
|
+
`@fluojs/redis`는 Redis distributed locking을 활성화할 때만 필요합니다. Non-distributed scheduling 경로는 package import, module registration, bootstrap, status snapshot 생성 중 Redis integration을 로드하지 않습니다.
|
|
29
|
+
|
|
28
30
|
## 사용 시점
|
|
29
31
|
|
|
30
32
|
- 정기적인 백그라운드 작업(예: 데이터베이스 정리, 리포트 생성)이 필요할 때 사용합니다.
|
|
@@ -71,7 +73,7 @@ class AppModule {}
|
|
|
71
73
|
|
|
72
74
|
### 분산 락 사용하기
|
|
73
75
|
|
|
74
|
-
여러 서버 인스턴스에서 스케줄링된 작업이 동시에 실행되는 것을 방지하려면 분산 모드를 활성화하세요. 이 기능은 `@fluojs/redis`가
|
|
76
|
+
여러 서버 인스턴스에서 스케줄링된 작업이 동시에 실행되는 것을 방지하려면 분산 모드를 활성화하세요. 이 기능은 `@fluojs/redis`가 필요하며, Redis peer는 `distributed.enabled`가 `true`일 때만 로드되고 resolve됩니다.
|
|
75
77
|
|
|
76
78
|
```typescript
|
|
77
79
|
import { Module } from '@fluojs/core';
|
|
@@ -181,7 +183,9 @@ singleton provider/controller만 스케줄링됩니다. Request-scoped 및 trans
|
|
|
181
183
|
- `SCHEDULING_REGISTRY`: `SchedulingRegistry` 서비스를 위한 주입 토큰입니다.
|
|
182
184
|
- `normalizeCronModuleOptions(...)`: module option과 기본값을 정규화합니다.
|
|
183
185
|
- `createCronPlatformStatusSnapshot(...)`: health/readiness 통합을 위한 status snapshot을 생성합니다.
|
|
184
|
-
- 공개 타입: `
|
|
186
|
+
- 공개 scheduling 타입: `SchedulingTaskKind`, `SchedulingTaskCallback`, `SchedulingTaskOptions`, `CronTaskOptions`, `IntervalTaskOptions`, `TimeoutTaskOptions`, `CronTaskMetadata`, `IntervalTaskMetadata`, `TimeoutTaskMetadata`, `SchedulingTaskMetadata`, `CronTaskDescriptor`, `SchedulingTaskDescriptor`, `SchedulingRegistry`.
|
|
187
|
+
- 공개 module 및 scheduler 타입: `CronModuleOptions`, `NormalizedCronModuleOptions`, `CronDistributedOptions`, `CronShutdownOptions`, `CronScheduleOptions`, `CronScheduler`, `CronScheduledJob`.
|
|
188
|
+
- 공개 status 타입: `CronLifecycleState`, `CronStatusAdapterInput`, `CronPlatformStatusSnapshot`.
|
|
185
189
|
- 메타데이터 헬퍼와 심볼: `defineSchedulingTaskMetadata`, `defineCronTaskMetadata`, `getSchedulingTaskMetadata`, `getCronTaskMetadata`, `getSchedulingTaskMetadataEntries`, `getCronTaskMetadataEntries`, `schedulingMetadataSymbol`, `cronMetadataSymbol`.
|
|
186
190
|
|
|
187
191
|
|
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ npm install @fluojs/cron croner
|
|
|
25
25
|
|
|
26
26
|
`croner` is the scheduler engine used by `@fluojs/cron`. Install it alongside the package so lockfiles make the runtime scheduler dependency explicit for applications and deployment audits.
|
|
27
27
|
|
|
28
|
+
`@fluojs/redis` is needed only when Redis distributed locking is enabled. Non-distributed scheduling paths do not load the Redis integration during package import, module registration, bootstrap, or status snapshot creation.
|
|
29
|
+
|
|
28
30
|
## When to Use
|
|
29
31
|
|
|
30
32
|
- When you need to run periodic background tasks (e.g., database cleanup, report generation).
|
|
@@ -71,7 +73,7 @@ class AppModule {}
|
|
|
71
73
|
|
|
72
74
|
### Distributed Locking
|
|
73
75
|
|
|
74
|
-
To prevent scheduled tasks from running concurrently across multiple server instances, enable distributed mode. This requires `@fluojs/redis`.
|
|
76
|
+
To prevent scheduled tasks from running concurrently across multiple server instances, enable distributed mode. This requires `@fluojs/redis`; the Redis peer is loaded and resolved only when `distributed.enabled` is `true`.
|
|
75
77
|
|
|
76
78
|
```typescript
|
|
77
79
|
import { Module } from '@fluojs/core';
|
|
@@ -181,7 +183,9 @@ Only singleton providers/controllers are scheduled. Request-scoped and transient
|
|
|
181
183
|
- `SCHEDULING_REGISTRY`: Injection token for the `SchedulingRegistry` service.
|
|
182
184
|
- `normalizeCronModuleOptions(...)`: Normalizes module options and defaults.
|
|
183
185
|
- `createCronPlatformStatusSnapshot(...)`: Creates a status snapshot for health/readiness integrations.
|
|
184
|
-
- Public types: `
|
|
186
|
+
- Public scheduling types: `SchedulingTaskKind`, `SchedulingTaskCallback`, `SchedulingTaskOptions`, `CronTaskOptions`, `IntervalTaskOptions`, `TimeoutTaskOptions`, `CronTaskMetadata`, `IntervalTaskMetadata`, `TimeoutTaskMetadata`, `SchedulingTaskMetadata`, `CronTaskDescriptor`, `SchedulingTaskDescriptor`, and `SchedulingRegistry`.
|
|
187
|
+
- Public module and scheduler types: `CronModuleOptions`, `NormalizedCronModuleOptions`, `CronDistributedOptions`, `CronShutdownOptions`, `CronScheduleOptions`, `CronScheduler`, and `CronScheduledJob`.
|
|
188
|
+
- Public status types: `CronLifecycleState`, `CronStatusAdapterInput`, and `CronPlatformStatusSnapshot`.
|
|
185
189
|
- Metadata helpers and symbols: `defineSchedulingTaskMetadata`, `defineCronTaskMetadata`, `getSchedulingTaskMetadata`, `getCronTaskMetadata`, `getSchedulingTaskMetadataEntries`, `getCronTaskMetadataEntries`, `schedulingMetadataSymbol`, `cronMetadataSymbol`.
|
|
186
190
|
|
|
187
191
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distributed-lock-manager.d.ts","sourceRoot":"","sources":["../src/distributed-lock-manager.ts"],"names":[],"mappings":"
|
|
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,11 +1,31 @@
|
|
|
1
|
-
import { getRedisClientToken } from '@fluojs/redis';
|
|
2
|
-
|
|
3
1
|
/** Minimal Redis command surface required for distributed cron locks. */
|
|
4
2
|
|
|
5
3
|
/** Tracks renewal state for one acquired distributed cron lock. */
|
|
6
4
|
|
|
7
5
|
const RELEASE_LOCK_SCRIPT = 'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end';
|
|
8
6
|
const RENEW_LOCK_SCRIPT = 'if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("PEXPIRE", KEYS[1], ARGV[2]) else return 0 end';
|
|
7
|
+
const REDIS_PEER_MODULE_SPECIFIER = '@fluojs/redis';
|
|
8
|
+
const loadRedisPeerModule = async () => import(REDIS_PEER_MODULE_SPECIFIER);
|
|
9
|
+
function isMissingRedisPeer(error) {
|
|
10
|
+
if (!(error instanceof Error)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const code = 'code' in error ? error.code : undefined;
|
|
14
|
+
return code === 'ERR_MODULE_NOT_FOUND' && error.message.includes(REDIS_PEER_MODULE_SPECIFIER);
|
|
15
|
+
}
|
|
16
|
+
function createRedisBootstrapError() {
|
|
17
|
+
return new Error(['Cron distributed mode requires @fluojs/redis to be installed and registered.', 'Install and import @fluojs/redis, or disable distributed locking with distributed.enabled: false.'].join(' '));
|
|
18
|
+
}
|
|
19
|
+
async function resolveRedisPeerModule() {
|
|
20
|
+
try {
|
|
21
|
+
return await loadRedisPeerModule();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (isMissingRedisPeer(error)) {
|
|
24
|
+
throw createRedisBootstrapError();
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
9
29
|
|
|
10
30
|
/** Coordinates Redis lock acquisition, renewal, and release for scheduled cron tasks. */
|
|
11
31
|
export class CronDistributedLockManager {
|
|
@@ -34,6 +54,9 @@ export class CronDistributedLockManager {
|
|
|
34
54
|
if (!this.options.distributed.enabled) {
|
|
35
55
|
return;
|
|
36
56
|
}
|
|
57
|
+
const {
|
|
58
|
+
getRedisClientToken
|
|
59
|
+
} = await resolveRedisPeerModule();
|
|
37
60
|
const redisToken = getRedisClientToken(this.options.distributed.clientName);
|
|
38
61
|
if (!this.runtimeContainer.has(redisToken)) {
|
|
39
62
|
throw new Error('Cron distributed mode requires the configured Redis client to be registered.');
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/service.js
CHANGED
|
@@ -5,7 +5,6 @@ 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 { getRedisComponentId } from '@fluojs/redis';
|
|
9
8
|
import { Cron as CronValidator } from 'croner';
|
|
10
9
|
import { APPLICATION_LOGGER, COMPILED_MODULES, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
11
10
|
import { CronDistributedLockManager } from './distributed-lock-manager.js';
|
|
@@ -37,6 +36,16 @@ function assertValidCronExpression(expression) {
|
|
|
37
36
|
throw new Error(`@Cron(): invalid cron expression "${expression}".`);
|
|
38
37
|
}
|
|
39
38
|
}
|
|
39
|
+
function createRedisDependencyId(name) {
|
|
40
|
+
if (name === undefined) {
|
|
41
|
+
return 'redis.default';
|
|
42
|
+
}
|
|
43
|
+
const normalizedName = name.trim();
|
|
44
|
+
if (normalizedName.length === 0) {
|
|
45
|
+
throw new Error('Redis client name must be a non-empty string.');
|
|
46
|
+
}
|
|
47
|
+
return `redis.${normalizedName}`;
|
|
48
|
+
}
|
|
40
49
|
|
|
41
50
|
/**
|
|
42
51
|
* Lifecycle-managed scheduler runtime for decorator-discovered and dynamic tasks.
|
|
@@ -296,7 +305,7 @@ class CronLifecycleService {
|
|
|
296
305
|
}
|
|
297
306
|
return createCronPlatformStatusSnapshot({
|
|
298
307
|
activeTicks: this.activeTasks.size,
|
|
299
|
-
dependencyId: this.options.distributed.enabled ?
|
|
308
|
+
dependencyId: this.options.distributed.enabled ? createRedisDependencyId(this.options.distributed.clientName) : undefined,
|
|
300
309
|
distributedEnabled: this.options.distributed.enabled,
|
|
301
310
|
enabledTasks,
|
|
302
311
|
lifecycleState: this.lifecycleState,
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"timeout",
|
|
10
10
|
"distributed-lock"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.
|
|
12
|
+
"version": "1.0.3",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -37,13 +37,21 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"croner": "^8.1.2",
|
|
40
|
-
"@fluojs/core": "^1.0.
|
|
41
|
-
"@fluojs/
|
|
42
|
-
"@fluojs/
|
|
43
|
-
|
|
40
|
+
"@fluojs/core": "^1.0.3",
|
|
41
|
+
"@fluojs/runtime": "^1.1.7",
|
|
42
|
+
"@fluojs/di": "^1.1.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@fluojs/redis": "^1.0.1"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@fluojs/redis": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
44
51
|
},
|
|
45
52
|
"devDependencies": {
|
|
46
|
-
"vitest": "^3.2.4"
|
|
53
|
+
"vitest": "^3.2.4",
|
|
54
|
+
"@fluojs/redis": "^1.0.1"
|
|
47
55
|
},
|
|
48
56
|
"scripts": {
|
|
49
57
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|