@fluojs/cron
Advanced tools
| import type { Container } from '@fluojs/di'; | ||
| import { type ApplicationLogger, type CompiledModule, type OnApplicationBootstrap, type OnApplicationShutdown, type OnModuleDestroy } from '@fluojs/runtime'; | ||
| import type { ApplicationLogger, CompiledModule, OnApplicationBootstrap, OnApplicationShutdown, OnModuleDestroy } from '@fluojs/runtime'; | ||
| import type { CronTaskOptions, IntervalTaskOptions, NormalizedCronModuleOptions, SchedulingRegistry, SchedulingTaskCallback, SchedulingTaskDescriptor, TimeoutTaskOptions } from './types.js'; | ||
@@ -109,4 +109,6 @@ /** | ||
| private scheduleTask; | ||
| private createScheduledHandle; | ||
| private completeTimeoutTask; | ||
| private unscheduleTask; | ||
| private stopScheduledHandle; | ||
| private handleTaskTick; | ||
@@ -113,0 +115,0 @@ private runTaskTick; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"} |
+48
-25
@@ -179,6 +179,13 @@ let _initClass; | ||
| } | ||
| task.enabled = true; | ||
| if (this.started) { | ||
| this.scheduleTask(task); | ||
| task.enabled = true; | ||
| try { | ||
| this.scheduleTask(task); | ||
| } catch (error) { | ||
| task.enabled = false; | ||
| throw error; | ||
| } | ||
| return true; | ||
| } | ||
| task.enabled = true; | ||
| return true; | ||
@@ -241,8 +248,20 @@ } | ||
| } | ||
| task.descriptor.expression = expression; | ||
| if (!task.enabled || !this.started) { | ||
| task.descriptor.expression = expression; | ||
| return; | ||
| } | ||
| this.unscheduleTask(task); | ||
| this.scheduleTask(task); | ||
| const previousExpression = task.descriptor.expression; | ||
| const previousHandle = task.scheduledHandle; | ||
| task.descriptor.expression = expression; | ||
| try { | ||
| const nextHandle = this.createScheduledHandle(task); | ||
| task.scheduledHandle = nextHandle; | ||
| if (previousHandle) { | ||
| this.stopScheduledHandle(previousHandle); | ||
| } | ||
| } catch (error) { | ||
| task.descriptor.expression = previousExpression; | ||
| task.scheduledHandle = previousHandle; | ||
| throw error; | ||
| } | ||
| } | ||
@@ -370,6 +389,6 @@ async onApplicationBootstrap() { | ||
| }; | ||
| this.tasks.set(descriptor.taskName, task); | ||
| if (this.started) { | ||
| this.scheduleTask(task); | ||
| task.scheduledHandle = this.createScheduledHandle(task); | ||
| } | ||
| this.tasks.set(descriptor.taskName, task); | ||
| } | ||
@@ -392,26 +411,28 @@ assertTaskNameAvailable(taskName) { | ||
| } | ||
| task.scheduledHandle = this.createScheduledHandle(task); | ||
| } | ||
| createScheduledHandle(task) { | ||
| const taskName = task.descriptor.taskName; | ||
| if (task.descriptor.kind === 'cron') { | ||
| const expression = task.descriptor.expression; | ||
| if (!expression) { | ||
| throw new Error(`Cron task "${task.descriptor.taskName}" is missing a cron expression.`); | ||
| throw new Error(`Cron task "${taskName}" is missing a cron expression.`); | ||
| } | ||
| const scheduled = this.options.scheduler(expression, { | ||
| name: task.descriptor.taskName, | ||
| return this.options.scheduler(expression, { | ||
| name: taskName, | ||
| protect: true, | ||
| timezone: task.descriptor.timezone | ||
| }, async () => { | ||
| await this.handleTaskTick(task.descriptor.taskName); | ||
| await this.handleTaskTick(taskName); | ||
| }); | ||
| task.scheduledHandle = scheduled; | ||
| return; | ||
| } | ||
| const ms = task.descriptor.ms; | ||
| if (!ms) { | ||
| throw new Error(`${task.descriptor.kind} task "${task.descriptor.taskName}" is missing interval duration.`); | ||
| throw new Error(`${task.descriptor.kind} task "${taskName}" is missing interval duration.`); | ||
| } | ||
| if (task.descriptor.kind === 'interval') { | ||
| const timer = setInterval(() => { | ||
| void this.handleTaskTick(task.descriptor.taskName); | ||
| void this.handleTaskTick(taskName); | ||
| }, ms); | ||
| task.scheduledHandle = { | ||
| return { | ||
| stop: () => { | ||
@@ -421,10 +442,9 @@ clearInterval(timer); | ||
| }; | ||
| return; | ||
| } | ||
| const timer = setTimeout(() => { | ||
| void this.handleTaskTick(task.descriptor.taskName).finally(() => { | ||
| this.completeTimeoutTask(task.descriptor.taskName); | ||
| void this.handleTaskTick(taskName).finally(() => { | ||
| this.completeTimeoutTask(taskName); | ||
| }); | ||
| }, ms); | ||
| task.scheduledHandle = { | ||
| return { | ||
| stop: () => { | ||
@@ -447,8 +467,11 @@ clearTimeout(timer); | ||
| } | ||
| const scheduledHandle = task.scheduledHandle; | ||
| task.scheduledHandle = undefined; | ||
| this.stopScheduledHandle(scheduledHandle); | ||
| } | ||
| stopScheduledHandle(scheduledHandle) { | ||
| try { | ||
| task.scheduledHandle.stop(); | ||
| scheduledHandle.stop(); | ||
| } catch (error) { | ||
| this.logger.error('Failed to stop scheduled task during shutdown.', error, 'CronLifecycleService'); | ||
| } finally { | ||
| task.scheduledHandle = undefined; | ||
| this.logger.error('Failed to stop scheduled task.', error, 'CronLifecycleService'); | ||
| } | ||
@@ -458,3 +481,3 @@ } | ||
| const taskState = this.tasks.get(taskName); | ||
| if (!taskState || !taskState.enabled || taskState.running) { | ||
| if (!taskState?.enabled || taskState.running) { | ||
| return; | ||
@@ -461,0 +484,0 @@ } |
+5
-5
@@ -12,3 +12,3 @@ { | ||
| ], | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "private": false, | ||
@@ -41,6 +41,6 @@ "license": "MIT", | ||
| "croner": "^8.1.2", | ||
| "@fluojs/core": "^1.0.0", | ||
| "@fluojs/di": "^1.0.0", | ||
| "@fluojs/runtime": "^1.0.0", | ||
| "@fluojs/redis": "^1.0.0" | ||
| "@fluojs/core": "^1.0.1", | ||
| "@fluojs/di": "^1.0.1", | ||
| "@fluojs/redis": "^1.0.0", | ||
| "@fluojs/runtime": "^1.0.1" | ||
| }, | ||
@@ -47,0 +47,0 @@ "devDependencies": { |
+5
-0
@@ -26,2 +26,4 @@ # @fluojs/cron | ||
| `croner`는 `@fluojs/cron`이 사용하는 scheduler engine입니다. 애플리케이션과 배포 감사에서 runtime scheduler dependency ownership이 명확히 보이도록 패키지와 함께 설치하세요. | ||
| ## 사용 시점 | ||
@@ -145,2 +147,4 @@ | ||
| 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으로 실행되지 않습니다. | ||
| ### 제한된 종료 | ||
@@ -182,2 +186,3 @@ | ||
| - `createCronPlatformStatusSnapshot(...)`: health/readiness 통합을 위한 status snapshot을 생성합니다. | ||
| - 공개 타입: `CronModuleOptions`, `CronDistributedOptions`, `CronShutdownOptions`, `CronScheduleOptions`, `CronScheduler`, `CronScheduledJob`, `SchedulingRegistry`, `SchedulingTaskDescriptor`, `SchedulingTaskCallback`, `SchedulingTaskOptions`, `CronTaskOptions`, `IntervalTaskOptions`, `TimeoutTaskOptions`. | ||
| - 메타데이터 헬퍼와 심볼: `defineSchedulingTaskMetadata`, `defineCronTaskMetadata`, `getSchedulingTaskMetadata`, `getCronTaskMetadata`, `getSchedulingTaskMetadataEntries`, `getCronTaskMetadataEntries`, `schedulingMetadataSymbol`, `cronMetadataSymbol`. | ||
@@ -184,0 +189,0 @@ |
+5
-0
@@ -26,2 +26,4 @@ # @fluojs/cron | ||
| `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. | ||
| ## When to Use | ||
@@ -145,2 +147,4 @@ | ||
| 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. | ||
| ### Bounded Shutdown | ||
@@ -182,2 +186,3 @@ | ||
| - `createCronPlatformStatusSnapshot(...)`: Creates a status snapshot for health/readiness integrations. | ||
| - Public types: `CronModuleOptions`, `CronDistributedOptions`, `CronShutdownOptions`, `CronScheduleOptions`, `CronScheduler`, `CronScheduledJob`, `SchedulingRegistry`, `SchedulingTaskDescriptor`, `SchedulingTaskCallback`, `SchedulingTaskOptions`, `CronTaskOptions`, `IntervalTaskOptions`, and `TimeoutTaskOptions`. | ||
| - Metadata helpers and symbols: `defineSchedulingTaskMetadata`, `defineCronTaskMetadata`, `getSchedulingTaskMetadata`, `getCronTaskMetadata`, `getSchedulingTaskMetadataEntries`, `getCronTaskMetadataEntries`, `schedulingMetadataSymbol`, `cronMetadataSymbol`. | ||
@@ -184,0 +189,0 @@ |
110914
2.5%2050
1.23%200
2.56%Updated
Updated
Updated