@apisurf/ngn-core 0.3.1 → 0.4.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/dist/core.d.ts CHANGED
@@ -774,6 +774,13 @@ type Options$1 = {
774
774
  /** Called when a scheduled run throws, with the run it was recorded as when there is one. */
775
775
  onRunError?: (path: string, error: unknown, runId: number | null) => void;
776
776
  };
777
+ /** What `runNow()` did: started a run, or why it did not. */
778
+ type RunNowOutcome = {
779
+ started: true;
780
+ } | {
781
+ started: false;
782
+ reason: string;
783
+ };
777
784
  /** What `start()` did with each task: scheduled on its pattern, or left out and why. */
778
785
  interface ScheduleReport {
779
786
  scheduled: {
@@ -793,6 +800,17 @@ declare class TaskLibraryScheduler {
793
800
  start(): Promise<ScheduleReport>;
794
801
  stop(): Promise<void>;
795
802
  scheduleTask(task: Task): Promise<void>;
803
+ /**
804
+ * Start one run of a scheduled task now, outside its schedule, the way a tick
805
+ * would: retries, budget, locks and hooks all apply, and errors go where a
806
+ * tick's go. It is not a tick, so the breaker does not hold it back. Resolves
807
+ * once the run is under way, not when it ends: a run can take longer than
808
+ * anyone waits on a button.
809
+ *
810
+ * A run still going is reported rather than recorded as a skip, since nothing
811
+ * was due: the person asking can simply wait.
812
+ */
813
+ runNow(taskPath: string): RunNowOutcome;
796
814
  unscheduleTask(task: Task): void;
797
815
  isCronPatternValid(cronPattern: string): boolean;
798
816
  }
@@ -824,4 +842,4 @@ declare function createControlsGenerator({ dbClient, env, report, }: {
824
842
  report?: (problem: RunProblem) => void;
825
843
  }): BuildCompiledTaskConfigFn;
826
844
 
827
- export { type AddJob, type Backoff, type ClaimOptions, Compiler, type DBInstance, type Duration, type InitDBOptions, type Job, type JobFilter, type JobStatus, LockBusyError, type LockOptions, type Migration, type OverlapPolicy, Queue, type QueueOptions, type QueueStats, type RunProblem, type ScheduleReport, Task, type TaskContext, TaskLibrary, TaskLibraryScheduler, TaskOnceRunner, type TaskRun, type TaskSqlite, type WorkHandler, type WorkOptions, type WorkSummary, type WorkerHandle, closeTaskDatabases, createControlsGenerator, executeEsm, failedRunId, getDbClient, isCronPatternValid, releaseTaskDatabases, runFailure, scheduleTask, setupDbClient };
845
+ export { type AddJob, type Backoff, type ClaimOptions, Compiler, type DBInstance, type Duration, type InitDBOptions, type Job, type JobFilter, type JobStatus, LockBusyError, type LockOptions, type Migration, type OverlapPolicy, Queue, type QueueOptions, type QueueStats, type RunNowOutcome, type RunProblem, type ScheduleReport, Task, type TaskContext, TaskLibrary, TaskLibraryScheduler, TaskOnceRunner, type TaskRun, type TaskSqlite, type WorkHandler, type WorkOptions, type WorkSummary, type WorkerHandle, closeTaskDatabases, createControlsGenerator, executeEsm, failedRunId, getDbClient, isCronPatternValid, releaseTaskDatabases, runFailure, scheduleTask, setupDbClient };
package/dist/core.js CHANGED
@@ -385,10 +385,17 @@ function deadlineSignal(deadline) {
385
385
  return { signal: controller.signal, dispose: () => {
386
386
  } };
387
387
  }
388
- const timer = setTimeout(abort, delay);
389
- timer.unref();
388
+ let timer;
389
+ const arm = () => {
390
+ const left = deadline - Date.now();
391
+ if (left <= 0) return abort();
392
+ timer = setTimeout(arm, Math.min(left, MAX_TIMER_MS));
393
+ timer.unref();
394
+ };
395
+ arm();
390
396
  return { signal: controller.signal, dispose: () => clearTimeout(timer) };
391
397
  }
398
+ var MAX_TIMER_MS = 2 ** 31 - 1;
392
399
 
393
400
  // src/runtime/locks.ts
394
401
  var LockBusyError = class extends Error {
@@ -1664,6 +1671,36 @@ var TaskLibraryScheduler = class {
1664
1671
  this.scheduledTasks.set(task.taskPath, scheduledTask);
1665
1672
  logTaskLibraryScheduler(`Scheduled: ${task.taskPath}`);
1666
1673
  }
1674
+ /**
1675
+ * Start one run of a scheduled task now, outside its schedule, the way a tick
1676
+ * would: retries, budget, locks and hooks all apply, and errors go where a
1677
+ * tick's go. It is not a tick, so the breaker does not hold it back. Resolves
1678
+ * once the run is under way, not when it ends: a run can take longer than
1679
+ * anyone waits on a button.
1680
+ *
1681
+ * A run still going is reported rather than recorded as a skip, since nothing
1682
+ * was due: the person asking can simply wait.
1683
+ */
1684
+ runNow(taskPath) {
1685
+ const task = this.taskLibrary.getTasks().find((t) => t.taskPath === taskPath);
1686
+ const cron = this.scheduledTasks.get(taskPath);
1687
+ if (!task || !cron) {
1688
+ return { started: false, reason: `${taskPath} is not scheduled by this ngn run` };
1689
+ }
1690
+ if (task.isRunning && task.overlapPolicy === "skip") {
1691
+ return { started: false, reason: "a run of it is still going" };
1692
+ }
1693
+ void tick(
1694
+ task,
1695
+ (error) => {
1696
+ logTaskLibrarySchedulerError(`Run failed: ${task.taskPath}`);
1697
+ logTaskLibrarySchedulerError(error);
1698
+ this.onRunError?.(task.taskPath, error, failedRunId(error));
1699
+ },
1700
+ { scheduledAt: null, nextAt: cron.nextRun()?.getTime() ?? null }
1701
+ );
1702
+ return { started: true };
1703
+ }
1667
1704
  unscheduleTask(task) {
1668
1705
  const existingTask = this.scheduledTasks.get(task.taskPath);
1669
1706
  if (!existingTask) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apisurf/ngn-core",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "ngn runtime: task compilation, scheduling and the run/log/timing services.",
5
5
  "type": "module",
6
6
  "author": "Luka Vidakovic",
@@ -38,9 +38,9 @@
38
38
  "esbuild": "^0.27.1",
39
39
  "tiny-invariant": "^1.3.3",
40
40
  "zod": "^3.24.2",
41
- "@apisurf/ngn-os": "0.3.1",
42
- "@apisurf/ngn-schema": "0.3.1",
43
- "@apisurf/ngn-persistence": "0.3.1"
41
+ "@apisurf/ngn-os": "0.4.0",
42
+ "@apisurf/ngn-persistence": "0.4.0",
43
+ "@apisurf/ngn-schema": "0.4.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^22.8.1",