@hardlydifficult/daemon 1.0.0 → 1.0.2

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.md CHANGED
@@ -1,123 +1,99 @@
1
- # @hardlydifficult/teardown
1
+ # @hardlydifficult/daemon
2
2
 
3
- Idempotent resource teardown with signal trapping. Register cleanup functions once at resource creation time — all exit paths call a single `run()`.
3
+ Opinionated utilities for long-running Node.js services:
4
+
5
+ - `createTeardown()` for idempotent cleanup with signal trapping
6
+ - `runContinuousLoop()` for daemon-style cycle execution
4
7
 
5
8
  ## Installation
6
9
 
7
10
  ```bash
8
- npm install @hardlydifficult/teardown
11
+ npm install @hardlydifficult/daemon
9
12
  ```
10
13
 
11
- ## Quick Start
14
+ ## Teardown management
15
+
16
+ Use `createTeardown()` to register cleanup functions once and execute them from
17
+ every exit path.
12
18
 
13
19
  ```typescript
14
- import { createTeardown } from "@hardlydifficult/teardown";
20
+ import { createTeardown } from "@hardlydifficult/daemon";
15
21
 
16
22
  const teardown = createTeardown();
17
23
  teardown.add(() => server.stop());
18
- teardown.add(() => db.close());
24
+ teardown.add(async () => {
25
+ await db.close();
26
+ });
19
27
  teardown.trapSignals();
20
28
 
21
- // Any manual exit path:
22
29
  await teardown.run();
23
30
  ```
24
31
 
25
- ## Creating a Teardown Registry
32
+ Behavior:
26
33
 
27
- Use `createTeardown()` to create a new teardown registry that manages cleanup functions.
28
-
29
- ```typescript
30
- import { createTeardown } from "@hardlydifficult/teardown";
31
-
32
- const teardown = createTeardown();
33
- ```
34
+ - LIFO execution (last added, first run)
35
+ - Idempotent `run()` (safe to call multiple times)
36
+ - Per-function error isolation (one failing teardown does not block others)
37
+ - `add()` returns an unregister function
34
38
 
35
- ## Registering Cleanup Functions
39
+ ## Continuous loop execution
36
40
 
37
- Call `add()` to register a cleanup function. Functions run in LIFO order (last added runs first). The `add()` method returns an unregister function for selective cleanup.
41
+ Use `runContinuousLoop()` to run work cycles with graceful shutdown, dynamic
42
+ delay control, and configurable error policy.
38
43
 
39
44
  ```typescript
40
- const teardown = createTeardown();
41
-
42
- // Register sync cleanup
43
- teardown.add(() => {
44
- console.log("Closing server");
45
- server.stop();
46
- });
47
-
48
- // Register async cleanup
49
- teardown.add(async () => {
50
- console.log("Closing database");
51
- await db.close();
52
- });
53
-
54
- // Unregister a specific function
55
- const unregister = teardown.add(() => {
56
- console.log("This won't run");
45
+ import { runContinuousLoop } from "@hardlydifficult/daemon";
46
+
47
+ await runContinuousLoop({
48
+ intervalSeconds: 30,
49
+ async runCycle(isShutdownRequested) {
50
+ if (isShutdownRequested()) {
51
+ return { stop: true };
52
+ }
53
+
54
+ const didWork = await syncQueue();
55
+ if (!didWork) {
56
+ return 60_000; // ms
57
+ }
58
+
59
+ return "immediate";
60
+ },
61
+ onCycleError(error, context) {
62
+ notifyOps(error, { cycleNumber: context.cycleNumber });
63
+ return "continue"; // or "stop"
64
+ },
57
65
  });
58
- unregister();
59
-
60
- await teardown.run();
61
- // Output:
62
- // Closing database
63
- // Closing server
64
66
  ```
65
67
 
66
- ## Running Teardown
68
+ ### Cycle return contract
67
69
 
68
- Call `run()` to execute all registered cleanup functions in LIFO order. The method is idempotent — subsequent calls are no-ops.
70
+ `runCycle()` can return:
69
71
 
70
- ```typescript
71
- const teardown = createTeardown();
72
- teardown.add(() => console.log("cleanup"));
73
-
74
- await teardown.run();
75
- // Output: cleanup
76
-
77
- await teardown.run();
78
- // No output (idempotent)
79
- ```
80
-
81
- ## Signal Trapping
72
+ - any value/`undefined`: use default `intervalSeconds`
73
+ - `number`: use that delay in milliseconds
74
+ - `"immediate"`: run the next cycle without sleeping
75
+ - `{ stop: true }`: stop gracefully after current cycle
76
+ - `{ nextDelayMs: number | "immediate", stop?: true }`: explicit control object
82
77
 
83
- Call `trapSignals()` to automatically run teardown when the process receives SIGTERM or SIGINT signals. Returns an untrap function to remove signal handlers.
78
+ ### Optional delay resolver
84
79
 
85
- ```typescript
86
- const teardown = createTeardown();
87
- teardown.add(() => server.stop());
80
+ If your cycle returns domain data, derive schedule policy with
81
+ `getNextDelayMs(result, context)`.
88
82
 
89
- // Wire SIGTERM/SIGINT to run() then process.exit(0)
90
- const untrap = teardown.trapSignals();
83
+ ### Error handling
91
84
 
92
- // Later, if needed:
93
- untrap();
94
- ```
85
+ Use `onCycleError(error, context)` to route to Slack/Sentry and decide whether
86
+ to `"continue"` or `"stop"`. Without this hook, cycle errors are logged and the
87
+ loop continues.
95
88
 
96
- ## Error Resilience
89
+ ### Logger injection
97
90
 
98
- Each teardown function is wrapped in try/catch. Errors don't block remaining teardowns — all functions run regardless of failures.
91
+ By default, warnings and errors use `console.warn` and `console.error`. Pass
92
+ `logger` to integrate your own logging implementation:
99
93
 
100
94
  ```typescript
101
- const teardown = createTeardown();
102
-
103
- teardown.add(() => {
104
- throw new Error("First cleanup fails");
105
- });
106
- teardown.add(() => {
107
- console.log("Second cleanup still runs");
108
- });
109
-
110
- await teardown.run();
111
- // Output: Second cleanup still runs
95
+ const logger = {
96
+ warn: (message, context) => myLogger.warn(message, context),
97
+ error: (message, context) => myLogger.error(message, context),
98
+ };
112
99
  ```
113
-
114
- ## Behavior Reference
115
-
116
- | Behavior | Details |
117
- |----------|---------|
118
- | **LIFO order** | Teardowns run in reverse registration order (last added runs first) |
119
- | **Idempotent** | `run()` executes once; subsequent calls are no-ops |
120
- | **Error resilient** | Each function is wrapped in try/catch; failures don't block remaining teardowns |
121
- | **Safe unregister** | `add()` returns an unregister function; safe to call multiple times |
122
- | **Post-run add** | `add()` after `run()` is a silent no-op |
123
- | **Duplicate safe** | Same function added twice runs twice; unregister only removes its own registration |
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createTeardown, type Teardown } from "./createTeardown.js";
2
- export { runContinuousLoop, type ContinuousLoopOptions, } from "./runContinuousLoop.js";
2
+ export { runContinuousLoop, type ContinuousLoopCycleContext, type ContinuousLoopCycleControl, type ContinuousLoopDelay, type ContinuousLoopErrorAction, type ContinuousLoopErrorHandler, type ContinuousLoopErrorContext, type ContinuousLoopLogger, type ContinuousLoopOptions, type ContinuousLoopRunCycleResult, } from "./runContinuousLoop.js";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EACL,iBAAiB,EACjB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,4BAA4B,GAClC,MAAM,wBAAwB,CAAC"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAoE;AAA3D,mHAAA,cAAc,OAAA;AACvB,+DAGgC;AAF9B,yHAAA,iBAAiB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yDAAoE;AAA3D,mHAAA,cAAc,OAAA;AACvB,+DAWgC;AAV9B,yHAAA,iBAAiB,OAAA"}
@@ -4,19 +4,62 @@
4
4
  * This module provides interruptible sleep and continuous loop functionality with proper signal handling for
5
5
  * SIGINT/SIGTERM.
6
6
  */
7
+ /** Delay directive returned by runCycle/getNextDelayMs. */
8
+ export type ContinuousLoopDelay = number | "immediate";
9
+ /** Context provided to each cycle and delay resolver. */
10
+ export interface ContinuousLoopCycleContext {
11
+ /** 1-based cycle number for the current loop iteration */
12
+ cycleNumber: number;
13
+ /** Function to check if shutdown has been requested */
14
+ isShutdownRequested: () => boolean;
15
+ }
16
+ /** Context provided to cycle error handling callbacks. */
17
+ export type ContinuousLoopErrorContext = ContinuousLoopCycleContext;
18
+ /** Action returned by onCycleError to control loop behavior. */
19
+ export type ContinuousLoopErrorAction = "continue" | "stop";
20
+ type ContinuousLoopErrorDecision = ContinuousLoopErrorAction | undefined;
21
+ /** Callback used to process cycle errors and decide loop policy. */
22
+ export type ContinuousLoopErrorHandler = (error: unknown, context: ContinuousLoopErrorContext) => ContinuousLoopErrorDecision | Promise<ContinuousLoopErrorDecision>;
23
+ /** Optional control directives that can be returned from runCycle. */
24
+ export interface ContinuousLoopCycleControl {
25
+ /** Stop the loop after this cycle completes. */
26
+ stop?: boolean;
27
+ /** Override delay before the next cycle. */
28
+ nextDelayMs?: ContinuousLoopDelay;
29
+ }
30
+ /** Minimal logger interface compatible with @hardlydifficult/logger. */
31
+ export interface ContinuousLoopLogger {
32
+ warn(message: string, context?: Readonly<Record<string, unknown>>): void;
33
+ error(message: string, context?: Readonly<Record<string, unknown>>): void;
34
+ }
35
+ /** Supported return shape from runCycle. */
36
+ export type ContinuousLoopRunCycleResult<TResult = unknown> = TResult | ContinuousLoopDelay | ContinuousLoopCycleControl;
7
37
  /** Options for running a continuous loop */
8
- export interface ContinuousLoopOptions {
38
+ export interface ContinuousLoopOptions<TResult = unknown> {
9
39
  /** Interval between cycles in seconds */
10
40
  intervalSeconds: number;
11
41
  /**
12
42
  * Callback to run on each cycle.
13
43
  *
14
44
  * @param isShutdownRequested - Function to check if shutdown has been requested during the cycle
15
- * @returns Promise that resolves when the cycle is complete (return value is ignored)
45
+ * @returns Promise resolving to cycle result and optional control directives
46
+ */
47
+ runCycle: (isShutdownRequested: () => boolean) => Promise<ContinuousLoopRunCycleResult<TResult>>;
48
+ /**
49
+ * Optional hook to derive the next delay from a runCycle result.
50
+ * Used only when runCycle does not directly return a delay directive.
51
+ */
52
+ getNextDelayMs?: (result: ContinuousLoopRunCycleResult<TResult>, context: ContinuousLoopCycleContext) => ContinuousLoopDelay | undefined;
53
+ /**
54
+ * Optional error callback for cycle failures.
55
+ *
56
+ * Return "stop" to end the loop, otherwise it will continue.
16
57
  */
17
- runCycle: (isShutdownRequested: () => boolean) => Promise<unknown>;
58
+ onCycleError?: ContinuousLoopErrorHandler;
18
59
  /** Optional callback for cleanup on shutdown */
19
- onShutdown?: () => Promise<void>;
60
+ onShutdown?: () => void | Promise<void>;
61
+ /** Optional logger (defaults to console warn/error) */
62
+ logger?: ContinuousLoopLogger;
20
63
  }
21
64
  /**
22
65
  * Run a function in a continuous loop with graceful shutdown support.
@@ -25,10 +68,13 @@ export interface ContinuousLoopOptions {
25
68
  *
26
69
  * - Interruptible sleep that responds immediately to SIGINT/SIGTERM
27
70
  * - Proper signal handler cleanup to prevent listener accumulation
28
- * - Continues to next cycle even if current cycle fails
71
+ * - Per-cycle delay control via return value or getNextDelayMs
72
+ * - Graceful stop signaling from runCycle ({ stop: true })
73
+ * - Configurable error policy via onCycleError
29
74
  * - Passes shutdown check callback to runCycle for in-cycle interruption
30
75
  *
31
76
  * @param options - Configuration for the continuous loop
32
77
  */
33
- export declare function runContinuousLoop(options: ContinuousLoopOptions): Promise<void>;
78
+ export declare function runContinuousLoop<TResult = unknown>(options: ContinuousLoopOptions<TResult>): Promise<void>;
79
+ export {};
34
80
  //# sourceMappingURL=runContinuousLoop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runContinuousLoop.d.ts","sourceRoot":"","sources":["../src/runContinuousLoop.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,yCAAyC;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,mBAAmB,EAAE,MAAM,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAqCD;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAuDf"}
1
+ {"version":3,"file":"runContinuousLoop.d.ts","sourceRoot":"","sources":["../src/runContinuousLoop.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,2DAA2D;AAC3D,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,WAAW,CAAC;AAEvD,yDAAyD;AACzD,MAAM,WAAW,0BAA0B;IACzC,0DAA0D;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,mBAAmB,EAAE,MAAM,OAAO,CAAC;CACpC;AAED,0DAA0D;AAC1D,MAAM,MAAM,0BAA0B,GAAG,0BAA0B,CAAC;AAEpE,gEAAgE;AAChE,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,MAAM,CAAC;AAE5D,KAAK,2BAA2B,GAAG,yBAAyB,GAAG,SAAS,CAAC;AAEzE,oEAAoE;AACpE,MAAM,MAAM,0BAA0B,GAAG,CACvC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,0BAA0B,KAChC,2BAA2B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;AAExE,sEAAsE;AACtE,MAAM,WAAW,0BAA0B;IACzC,gDAAgD;IAChD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4CAA4C;IAC5C,WAAW,CAAC,EAAE,mBAAmB,CAAC;CACnC;AAED,wEAAwE;AACxE,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IACzE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CAC3E;AAED,4CAA4C;AAC5C,MAAM,MAAM,4BAA4B,CAAC,OAAO,GAAG,OAAO,IACtD,OAAO,GACP,mBAAmB,GACnB,0BAA0B,CAAC;AAE/B,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB,CAAC,OAAO,GAAG,OAAO;IACtD,yCAAyC;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,EAAE,CACR,mBAAmB,EAAE,MAAM,OAAO,KAC/B,OAAO,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC,CAAC;IACpD;;;OAGG;IACH,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,4BAA4B,CAAC,OAAO,CAAC,EAC7C,OAAO,EAAE,0BAA0B,KAChC,mBAAmB,GAAG,SAAS,CAAC;IACrC;;;;OAIG;IACH,YAAY,CAAC,EAAE,0BAA0B,CAAC;IAC1C,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,uDAAuD;IACvD,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AA2HD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,GAAG,OAAO,EACvD,OAAO,EAAE,qBAAqB,CAAC,OAAO,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAwGf"}
@@ -7,6 +7,22 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.runContinuousLoop = runContinuousLoop;
10
+ const defaultLogger = {
11
+ warn(message, context) {
12
+ if (context === undefined) {
13
+ console.warn(message);
14
+ return;
15
+ }
16
+ console.warn(message, context);
17
+ },
18
+ error(message, context) {
19
+ if (context === undefined) {
20
+ console.error(message);
21
+ return;
22
+ }
23
+ console.error(message, context);
24
+ },
25
+ };
10
26
  /**
11
27
  * Creates an interruptible sleep that can be woken early by calling the returned cancel function.
12
28
  *
@@ -35,6 +51,57 @@ function createInterruptibleSleep(durationMs) {
35
51
  };
36
52
  return { promise, cancel };
37
53
  }
54
+ function normalizeDelayMs(delayMs, source) {
55
+ if (delayMs === "immediate") {
56
+ return delayMs;
57
+ }
58
+ if (!Number.isFinite(delayMs) || delayMs < 0) {
59
+ if (source === "intervalSeconds") {
60
+ throw new Error("intervalSeconds must be a non-negative finite number");
61
+ }
62
+ throw new Error(`${source} must return a non-negative finite number or "immediate"`);
63
+ }
64
+ return delayMs;
65
+ }
66
+ function getControlFromCycleResult(result) {
67
+ if (typeof result === "number" || result === "immediate") {
68
+ return { nextDelayMs: result };
69
+ }
70
+ if (typeof result !== "object" || result === null) {
71
+ return {};
72
+ }
73
+ const value = result;
74
+ const { nextDelayMs } = value;
75
+ return {
76
+ stop: value.stop === true,
77
+ nextDelayMs: typeof nextDelayMs === "number" || nextDelayMs === "immediate"
78
+ ? nextDelayMs
79
+ : undefined,
80
+ };
81
+ }
82
+ async function handleCycleError(error, context, onCycleError, logger) {
83
+ if (onCycleError !== undefined) {
84
+ try {
85
+ const action = await onCycleError(error, context);
86
+ return action === "stop" ? "stop" : "continue";
87
+ }
88
+ catch (handlerError) {
89
+ logger.error("onCycleError handler failed", {
90
+ cycleNumber: context.cycleNumber,
91
+ cycleError: error instanceof Error ? error.message : String(error),
92
+ handlerError: handlerError instanceof Error
93
+ ? handlerError.message
94
+ : String(handlerError),
95
+ });
96
+ return "continue";
97
+ }
98
+ }
99
+ logger.error("Cycle error", {
100
+ cycleNumber: context.cycleNumber,
101
+ error: error instanceof Error ? error.message : String(error),
102
+ });
103
+ return "continue";
104
+ }
38
105
  /**
39
106
  * Run a function in a continuous loop with graceful shutdown support.
40
107
  *
@@ -42,17 +109,21 @@ function createInterruptibleSleep(durationMs) {
42
109
  *
43
110
  * - Interruptible sleep that responds immediately to SIGINT/SIGTERM
44
111
  * - Proper signal handler cleanup to prevent listener accumulation
45
- * - Continues to next cycle even if current cycle fails
112
+ * - Per-cycle delay control via return value or getNextDelayMs
113
+ * - Graceful stop signaling from runCycle ({ stop: true })
114
+ * - Configurable error policy via onCycleError
46
115
  * - Passes shutdown check callback to runCycle for in-cycle interruption
47
116
  *
48
117
  * @param options - Configuration for the continuous loop
49
118
  */
50
119
  async function runContinuousLoop(options) {
51
- const { intervalSeconds, runCycle, onShutdown } = options;
120
+ const { intervalSeconds, runCycle, getNextDelayMs, onCycleError, onShutdown, logger = defaultLogger, } = options;
121
+ const defaultDelayMs = normalizeDelayMs(intervalSeconds * 1000, "intervalSeconds");
52
122
  let shutdownRequested = false;
53
123
  let cancelCurrentSleep = null;
124
+ let cycleNumber = 0;
54
125
  const handleShutdown = (signal) => {
55
- console.warn(`Received ${signal}, shutting down gracefully...`);
126
+ logger.warn(`Received ${signal}, shutting down gracefully...`);
56
127
  shutdownRequested = true;
57
128
  if (cancelCurrentSleep !== null) {
58
129
  cancelCurrentSleep();
@@ -71,17 +142,46 @@ async function runContinuousLoop(options) {
71
142
  const shouldContinue = () => !shutdownRequested;
72
143
  try {
73
144
  while (shouldContinue()) {
145
+ cycleNumber += 1;
146
+ const cycleContext = {
147
+ cycleNumber,
148
+ isShutdownRequested,
149
+ };
150
+ let nextDelayMs = defaultDelayMs;
74
151
  try {
75
- await runCycle(isShutdownRequested);
152
+ const cycleResult = await runCycle(isShutdownRequested);
153
+ if (!shouldContinue()) {
154
+ continue;
155
+ }
156
+ const cycleControl = getControlFromCycleResult(cycleResult);
157
+ if (cycleControl.stop === true) {
158
+ shutdownRequested = true;
159
+ continue;
160
+ }
161
+ if (cycleControl.nextDelayMs !== undefined) {
162
+ nextDelayMs = normalizeDelayMs(cycleControl.nextDelayMs, "runCycle");
163
+ }
164
+ else if (getNextDelayMs !== undefined) {
165
+ const derivedDelay = getNextDelayMs(cycleResult, cycleContext);
166
+ if (derivedDelay !== undefined) {
167
+ nextDelayMs = normalizeDelayMs(derivedDelay, "getNextDelayMs");
168
+ }
169
+ }
76
170
  }
77
171
  catch (error) {
78
- const errorMessage = error instanceof Error ? error.message : String(error);
79
- console.error(`Cycle error: ${errorMessage}`);
172
+ const action = await handleCycleError(error, cycleContext, onCycleError, logger);
173
+ if (action === "stop") {
174
+ shutdownRequested = true;
175
+ continue;
176
+ }
80
177
  }
81
178
  if (!shouldContinue()) {
82
179
  break;
83
180
  }
84
- const sleep = createInterruptibleSleep(intervalSeconds * 1000);
181
+ if (nextDelayMs === "immediate") {
182
+ continue;
183
+ }
184
+ const sleep = createInterruptibleSleep(nextDelayMs);
85
185
  cancelCurrentSleep = sleep.cancel;
86
186
  await sleep.promise;
87
187
  cancelCurrentSleep = null;
@@ -1 +1 @@
1
- {"version":3,"file":"runContinuousLoop.js","sourceRoot":"","sources":["../src/runContinuousLoop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runContinuousLoop.js","sourceRoot":"","sources":["../src/runContinuousLoop.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/daemon",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [