@basaltkit/scheduler 1.0.0 → 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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Machize Contributors
3
+ Copyright (c) 2026 Basalt Contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -184,11 +184,23 @@ console.log(runs) // 1
184
184
 
185
185
  In tests with the plugin, pass `autostart: false` so the timer doesn't start.
186
186
 
187
+ ## CLI commands (`basalt schedule:*`)
188
+
189
+ ```bash
190
+ basalt schedule:list # every scheduled task + its cron (from metadata)
191
+ basalt schedule:run reconcile-billing # run one task NOW, ignoring its cron
192
+ basalt schedule:run --due # run everything due at this instant (a manual tick)
193
+ ```
194
+
195
+ `schedule:list` reads the `schedule:entries` metadata bucket; `schedule:run` is
196
+ registered by `schedulerPlugin` and executes against the live `Scheduler`, so it
197
+ respects each entry's overlap guard and `onFailure` handler.
198
+
187
199
  ## API reference
188
200
 
189
201
  ### `schedulerPlugin(options?: SchedulerPluginOptions)`
190
202
 
191
- Registers a `Scheduler` (singleton) under the `SCHEDULER` token; on `boot` it calls `define`, publishes the entries to the container's metadata (key `schedule:entries`, consumed by the `basalt schedule:list` CLI) and starts the timer; on `shutdown` it calls `stop()`.
203
+ Registers a `Scheduler` (singleton) under the `SCHEDULER` token; on `boot` it calls `define`, publishes the entries to the container's metadata (key `schedule:entries`, consumed by `basalt schedule:list`), registers the `schedule:run` command, and starts the timer; on `shutdown` it calls `stop()`.
192
204
 
193
205
  | Option | Type | Required? | Default | Description |
194
206
  |---|---|---|---|---|
package/dist/index.d.ts CHANGED
@@ -89,6 +89,14 @@ declare class Scheduler {
89
89
  cron: string;
90
90
  timezone: string;
91
91
  }[];
92
+ /** Names of every scheduled entry — for CLI validation/listing. */
93
+ names(): string[];
94
+ /**
95
+ * Runs a single entry by name on demand, ignoring its cron (for `schedule:run`
96
+ * and manual triggers). Returns false if no entry has that name. The entry's
97
+ * own overlap guard and failure handler still apply.
98
+ */
99
+ runNow(name: string): Promise<boolean>;
92
100
  /**
93
101
  * Runs the entries due at the given instant. Deterministic — this is what
94
102
  * the tests call directly and what the timer calls every minute.
package/dist/index.js CHANGED
@@ -205,6 +205,21 @@ var Scheduler = class {
205
205
  list() {
206
206
  return this.entries.map((entry) => entry.describe());
207
207
  }
208
+ /** Names of every scheduled entry — for CLI validation/listing. */
209
+ names() {
210
+ return this.entries.map((entry) => entry.name);
211
+ }
212
+ /**
213
+ * Runs a single entry by name on demand, ignoring its cron (for `schedule:run`
214
+ * and manual triggers). Returns false if no entry has that name. The entry's
215
+ * own overlap guard and failure handler still apply.
216
+ */
217
+ async runNow(name) {
218
+ const entry = this.entries.find((candidate) => candidate.name === name);
219
+ if (!entry) return false;
220
+ await entry.run();
221
+ return true;
222
+ }
208
223
  /**
209
224
  * Runs the entries due at the given instant. Deterministic — this is what
210
225
  * the tests call directly and what the timer calls every minute.
@@ -260,6 +275,7 @@ function schedulerPlugin(options = {}) {
260
275
  name: "basalt:scheduler",
261
276
  register({ container }) {
262
277
  container.singleton(SCHEDULER, () => new Scheduler());
278
+ registerScheduleRunCommand(container);
263
279
  },
264
280
  boot({ container }) {
265
281
  const scheduler = container.get(SCHEDULER);
@@ -273,6 +289,36 @@ function schedulerPlugin(options = {}) {
273
289
  }
274
290
  });
275
291
  }
292
+ function registerScheduleRunCommand(container) {
293
+ ensureMetadata(container).add("commands", {
294
+ name: "schedule:run",
295
+ description: "Run a scheduled task on demand (by name), or --due for all due now",
296
+ async handle({
297
+ io,
298
+ args,
299
+ flags
300
+ }) {
301
+ const scheduler = container.get(SCHEDULER);
302
+ if (flags["due"] === true) {
303
+ await scheduler.tick();
304
+ io.log("Ran all due scheduled tasks.");
305
+ return;
306
+ }
307
+ const name = args[0];
308
+ if (!name) {
309
+ io.error("Usage: basalt schedule:run <name> | --due");
310
+ return 1;
311
+ }
312
+ const ran = await scheduler.runNow(name);
313
+ if (!ran) {
314
+ const available = scheduler.names().join(", ") || "(none)";
315
+ io.error(`Unknown scheduled task "${name}". Available: ${available}.`);
316
+ return 1;
317
+ }
318
+ io.log(`Ran scheduled task "${name}".`);
319
+ }
320
+ });
321
+ }
276
322
  export {
277
323
  CronParseError,
278
324
  SCHEDULER,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/scheduler",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Fluent Basalt scheduler: schedule.job(X).daily().at('03:00'), timezones, withoutOverlapping and @basaltkit/queue integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "@basaltkit/core": "^1.0.0",
18
- "@basaltkit/queue": "^1.0.0"
18
+ "@basaltkit/queue": "^1.2.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^22.15.0",