Feat/compt 75 typed job definitions ischeduler interface#2
Feat/compt 75 typed job definitions ischeduler interface#2saadmoumou wants to merge 2 commits intodevelopfrom
Conversation
- Add ScheduledJob type: name, handler, and one of cron | interval | timeout - Add IScheduler interface: schedule, unschedule, reschedule, list - Add ScheduledJobStatus type: name, cron, lastRun, nextRun, isRunning - Add DuplicateJobError thrown when registering a duplicate job name - Enforce cron/interval/timeout mutual exclusivity via discriminated union - Add CronExpression named constants for human-readable schedules - Add cron fluent builder: dailyAt, weeklyOn, monthlyOn, every().minutes() - Update tsconfig.build.json: CommonJS output, Task 1 files only - Update package.json: name @ciscode/scheduler-kit, version 0.0.0
There was a problem hiding this comment.
Pull request overview
This PR refocuses the package’s public API toward a scheduler “kit” surface by adding typed job definitions (IScheduler/ScheduledJob), a duplicate-job error type, and cron helper utilities (constants + a fluent cron builder), while adjusting build/package configuration to match the new exported entrypoints.
Changes:
- Added typed scheduler contracts (
IScheduler,ScheduledJob, discriminated schedule timing types) and aDuplicateJobError. - Introduced cron helpers:
CronExpressionconstants and a fluentcronbuilder with unit tests. - Updated build config/public exports and adjusted package metadata + git hooks.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.build.json | Narrows build entrypoints and sets CJS output settings for published artifacts. |
| src/interfaces/scheduler.interface.ts | Adds the public scheduler type contract (job + timing union + interface). |
| src/index.ts | Updates the package’s public exports to expose new interfaces/errors/helpers. |
| src/errors/duplicate-job.error.ts | Introduces a dedicated error type for duplicate job registration. |
| src/errors/duplicate-job.error.spec.ts | Adds unit tests for DuplicateJobError. |
| src/cron-expression.ts | Adds readable cron-expression constants for consumers. |
| src/cron-expression.spec.ts | Adds unit tests for cron-expression constants. |
| src/cron-builder.ts | Adds a fluent cron builder and internal time parsing helper. |
| src/cron-builder.spec.ts | Adds unit tests for the cron builder behavior. |
| package.json | Updates description/repo metadata and dependency declarations. |
| .husky/pre-push | Modifies the pre-push hook commands. |
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/CISCODE-MA/SchedulerKit.git" | ||
| "url": "git+https://github.com/CISCODE-MA/" |
There was a problem hiding this comment.
The repository URL was changed to the GitHub org root ("git+https://github.com/CISCODE-MA/") rather than the actual repository. This breaks npm metadata (and tooling that links back to source). Update it to the full repo URL (typically including the repo name and .git suffix).
| "url": "git+https://github.com/CISCODE-MA/" | |
| "url": "git+https://github.com/CISCODE-MA/scheduler-kit.git" |
| "@nestjs/testing": "^10.4.0", | ||
| "@types/jest": "^29.5.14", | ||
| "@types/node": "^22.10.7", | ||
| "cron": "^4.4.0", | ||
| "eslint": "^9.18.0", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-plugin-import": "^2.32.0", | ||
| "eslint-plugin-prettier": "^5.5.5", |
There was a problem hiding this comment.
The "cron" package is listed in devDependencies, but there are no imports/usages of it anywhere in the repo. If this kit doesn’t directly use the library, drop the dependency (or add the intended usage) to avoid unnecessary install surface area.
| npm run typecheck | ||
| npm run test:cov No newline at end of file |
There was a problem hiding this comment.
This Husky hook no longer has a shebang or Husky bootstrap (the removed lines). Without a shebang, Git may fail to execute the hook with an "exec format" error, and without husky.sh you lose Husky’s environment setup/opt-out behavior. Restore the standard Husky header at the top of this hook.
| * cron.every(5).minutes() // "* /5 * * * *" | ||
| * cron.every(2).hours() // "0 * /2 * * *" |
There was a problem hiding this comment.
The example outputs in this docblock have extra spaces (e.g. "* /5" and "0 * /2") which are not valid cron syntax and don’t match what the functions actually return ("*/5", "0 */2"). Please correct the example strings to avoid misleading consumers.
| * cron.every(5).minutes() // "* /5 * * * *" | |
| * cron.every(2).hours() // "0 * /2 * * *" | |
| * cron.every(5).minutes() // "*/5 * * * * *" | |
| * cron.every(2).hours() // "0 */2 * * * *" |
| const ampm = /^(\d{1,2})(?::(\d{2}))?(am|pm)$/i.exec(time.trim()); | ||
| if (ampm) { | ||
| let hour = parseInt(ampm[1] as string, 10); | ||
| const minute = ampm[2] ? parseInt(ampm[2], 10) : 0; | ||
| const period = (ampm[3] as string).toLowerCase(); | ||
| if (period === "pm" && hour !== 12) hour += 12; | ||
| if (period === "am" && hour === 12) hour = 0; | ||
| return { hour, minute }; | ||
| } | ||
| const h24 = /^(\d{1,2}):(\d{2})$/.exec(time.trim()); | ||
| if (h24) { | ||
| return { hour: parseInt(h24[1] as string, 10), minute: parseInt(h24[2] as string, 10) }; |
There was a problem hiding this comment.
parseTime() accepts out-of-range times like "99:99" or "25pm" because it only validates format, not numeric bounds. This can generate invalid cron expressions that fail at runtime. Add range checks (hour 0–23, minute 0–59; plus 1–12 for am/pm input) and throw a clear error when out of range.
| const ampm = /^(\d{1,2})(?::(\d{2}))?(am|pm)$/i.exec(time.trim()); | |
| if (ampm) { | |
| let hour = parseInt(ampm[1] as string, 10); | |
| const minute = ampm[2] ? parseInt(ampm[2], 10) : 0; | |
| const period = (ampm[3] as string).toLowerCase(); | |
| if (period === "pm" && hour !== 12) hour += 12; | |
| if (period === "am" && hour === 12) hour = 0; | |
| return { hour, minute }; | |
| } | |
| const h24 = /^(\d{1,2}):(\d{2})$/.exec(time.trim()); | |
| if (h24) { | |
| return { hour: parseInt(h24[1] as string, 10), minute: parseInt(h24[2] as string, 10) }; | |
| const trimmedTime = time.trim(); | |
| const ampm = /^(\d{1,2})(?::(\d{2}))?(am|pm)$/i.exec(trimmedTime); | |
| if (ampm) { | |
| const rawHour = parseInt(ampm[1] as string, 10); | |
| const minute = ampm[2] ? parseInt(ampm[2], 10) : 0; | |
| if (rawHour < 1 || rawHour > 12) { | |
| throw new Error(`Cannot parse time: "${time}". Hour must be between 1 and 12 for am/pm input.`); | |
| } | |
| if (minute < 0 || minute > 59) { | |
| throw new Error(`Cannot parse time: "${time}". Minute must be between 0 and 59.`); | |
| } | |
| let hour = rawHour; | |
| const period = (ampm[3] as string).toLowerCase(); | |
| if (period === "pm" && hour !== 12) hour += 12; | |
| if (period === "am" && hour === 12) hour = 0; | |
| return { hour, minute }; | |
| } | |
| const h24 = /^(\d{1,2}):(\d{2})$/.exec(trimmedTime); | |
| if (h24) { | |
| const hour = parseInt(h24[1] as string, 10); | |
| const minute = parseInt(h24[2] as string, 10); | |
| if (hour < 0 || hour > 23) { | |
| throw new Error(`Cannot parse time: "${time}". Hour must be between 0 and 23 for 24-hour input.`); | |
| } | |
| if (minute < 0 || minute > 59) { | |
| throw new Error(`Cannot parse time: "${time}". Minute must be between 0 and 59.`); | |
| } | |
| return { hour, minute }; |
| every(n: number) { | ||
| return { | ||
| minutes: (): string => `*/${n} * * * *`, | ||
| hours: (): string => `0 */${n} * * *`, | ||
| }; | ||
| }, |
There was a problem hiding this comment.
cron.every(n) and cron.monthlyOn(dayOfMonth, ...) don’t validate inputs (e.g., n <= 0 / non-integers, dayOfMonth outside 1–31). These produce invalid cron strings. Validate these arguments and throw an error for invalid values.
| "peerDependencies": { | ||
| "@nestjs/common": "^10 || ^11", | ||
| "@nestjs/core": "^10 || ^11", | ||
| "@nestjs/platform-express": "^10 || ^11", | ||
| "@nestjs/schedule": "^4 || ^5", | ||
| "reflect-metadata": "^0.2.2", | ||
| "rxjs": "^7" | ||
| }, | ||
| "dependencies": { | ||
| "class-transformer": "^0.5.1", | ||
| "class-validator": "^0.14.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@changesets/cli": "^2.27.7", | ||
| "@eslint/js": "^9.18.0", | ||
| "@nestjs/common": "^10.4.0", | ||
| "@nestjs/core": "^10.4.0", | ||
| "@nestjs/mapped-types": "^2.0.0", | ||
| "@nestjs/platform-express": "^10.4.0", | ||
| "@nestjs/schedule": "^6.1.1", | ||
| "@nestjs/testing": "^10.4.0", |
There was a problem hiding this comment.
@nestjs/schedule version ranges are inconsistent: peerDependencies restrict to "^4 || ^5", but devDependencies installs "^6.1.1". Align these (either bump the peer range to include the dev version, or downgrade devDependency) to avoid local builds passing while consumers hit peer/dependency conflicts.
| export type IntervalSchedule = { | ||
| interval: number; | ||
| cron?: never; | ||
| timeout?: never; | ||
| }; | ||
|
|
||
| export type TimeoutSchedule = { | ||
| timeout: number; | ||
| cron?: never; | ||
| interval?: never; | ||
| }; |
There was a problem hiding this comment.
The numeric timing fields (interval/timeout) don’t specify units (ms vs seconds) in their doc comments. Since these are part of the public types, please document the expected unit to prevent consumers from scheduling jobs at the wrong cadence.
Summary
Why
Checklist
npm run lintpassesnpm run typecheckpassesnpm testpassesnpm run buildpassesnpx changeset) if this affects consumersNotes