@hardlydifficult/daemon 1.0.3 → 1.0.4

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.
Files changed (2) hide show
  1. package/README.md +110 -60
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -13,29 +13,36 @@ npm install @hardlydifficult/daemon
13
13
 
14
14
  ## Quick Start
15
15
 
16
+ Create a daemon with signal-trapped teardown and a continuous loop:
17
+
16
18
  ```typescript
17
19
  import { createTeardown, runContinuousLoop } from "@hardlydifficult/daemon";
18
20
 
19
- // Graceful shutdown with LIFO cleanup
20
21
  const teardown = createTeardown();
21
- teardown.add(() => console.log("Cleaning up server"));
22
- teardown.add(() => console.log("Closing database connection"));
22
+
23
+ // Register cleanup for resources
24
+ teardown.add(() => console.log("Cleanup: closing server"));
25
+ teardown.add(() => console.log("Cleanup: disconnecting database"));
26
+
27
+ // Trap SIGINT/SIGTERM
23
28
  teardown.trapSignals();
24
29
 
25
- // Continuous background task with signal-aware sleep
30
+ // Run a continuous loop
26
31
  await runContinuousLoop({
27
32
  intervalSeconds: 5,
28
- runCycle: async (isShutdownRequested) => {
29
- console.log("Running task...");
33
+ async runCycle(isShutdownRequested) {
34
+ console.log("Running cycle...");
30
35
  if (isShutdownRequested()) {
31
36
  return { stop: true };
32
37
  }
33
- // Perform background work
34
- return "immediate"; // Run next cycle immediately
38
+ // Perform background task
39
+ await new Promise(resolve => setTimeout(resolve, 1000));
40
+ return { stop: true }; // Stop after first cycle for demo
35
41
  },
36
42
  onShutdown: async () => {
43
+ console.log("Shutdown complete");
37
44
  await teardown.run();
38
- },
45
+ }
39
46
  });
40
47
  ```
41
48
 
@@ -176,72 +183,115 @@ const logger = {
176
183
 
177
184
  ### `runContinuousLoop()` Options
178
185
 
179
- | Option | Type | Description |
180
- |--------|------|-------------|
181
- | `intervalSeconds` | `number` | Base interval between cycles (converted to ms) |
182
- | `runCycle` | `(isShutdownRequested: () => boolean) => Promise<...>` | Cycle function with shutdown check |
183
- | `getNextDelayMs?` | `(...)` => `ContinuousLoopDelay \| undefined` | Derive delay from cycle result |
184
- | `onCycleError?` | `ContinuousLoopErrorHandler` | Handle cycle errors, return `"continue"` or `"stop"` |
185
- | `onShutdown?` | `() => void \| Promise<void>` | Cleanup called after shutdown completes |
186
- | `logger?` | `ContinuousLoopLogger` | Custom logger (defaults to `console`) |
186
+ | Option | Type | Default | Description |
187
+ |--------|------|---------|-------------|
188
+ | `intervalSeconds` | `number` | | Interval between cycles in seconds |
189
+ | `runCycle` | `Function` | | Callback for each cycle |
190
+ | `getNextDelayMs?` | `Function` | | Derive delay from cycle result |
191
+ | `onCycleError?` | `Function` | — | Handle cycle errors |
192
+ | `onShutdown?` | `Function` | | Cleanup on shutdown |
193
+ | `logger?` | `ContinuousLoopLogger` | `console` | Logger for warnings/errors |
194
+
195
+ ### `ContinuousLoopRunCycleResult`
187
196
 
188
- ### Return Values
197
+ The return type supports:
198
+ - Raw delay (`number` or `"immediate"`)
199
+ - Control object: `{ stop?: boolean; nextDelayMs?: ContinuousLoopDelay }`
189
200
 
190
- The `runCycle` function can return:
191
- - A delay value (`number` ms or `"immediate"`)
192
- - `{ stop: true }` to gracefully terminate
193
- - `{ nextDelayMs: ... }` to override delay
201
+ **Example:**
194
202
 
195
203
  ```typescript
196
- await runContinuousLoop({
197
- intervalSeconds: 5,
198
- runCycle: async () => {
199
- const data = await fetchData();
200
- if (!data) {
201
- return { nextDelayMs: "immediate" }; // Retry immediately
202
- }
203
- if (data.done) {
204
- return { stop: true }; // End loop
205
- }
206
- return 2000; // Wait 2 seconds
207
- },
208
- });
204
+ async runCycle() {
205
+ // Return raw delay
206
+ return 5000;
207
+
208
+ // Or return control directives
209
+ return { nextDelayMs: "immediate", stop: false };
210
+ }
211
+ ```
212
+
213
+ ### `ContinuousLoopDelay`
214
+
215
+ ```typescript
216
+ type ContinuousLoopDelay = number | "immediate"
217
+ ```
218
+
219
+ ### `ContinuousLoopCycleControl`
220
+
221
+ ```typescript
222
+ interface ContinuousLoopCycleControl {
223
+ stop?: boolean;
224
+ nextDelayMs?: ContinuousLoopDelay;
225
+ }
226
+ ```
227
+
228
+ ### `ContinuousLoopCycleContext`
229
+
230
+ Provides context to cycle and delay resolver functions.
231
+
232
+ ```typescript
233
+ interface ContinuousLoopCycleContext {
234
+ cycleNumber: number;
235
+ isShutdownRequested: () => boolean;
236
+ }
209
237
  ```
210
238
 
211
- #### Delay Directives
239
+ ### `ContinuousLoopErrorContext`
212
240
 
213
- | Directive | Description |
214
- |----------|-------------|
215
- | `number` | Milliseconds to wait before next cycle |
216
- | `"immediate"` | Run next cycle without delay |
217
- | `{ stop: true }` | Stop the loop after current cycle |
218
- | `{ nextDelayMs: ... }` | Override default or derived delay |
241
+ Same as `ContinuousLoopCycleContext`.
219
242
 
220
- ### Error Handling Example
243
+ ### `ContinuousLoopErrorHandler`
221
244
 
222
- Cycles errors are caught and handled according to the error policy.
245
+ Handles errors and returns `"stop"` or `"continue"`.
246
+
247
+ **Signature:**
248
+
249
+ ```typescript
250
+ type ContinuousLoopErrorHandler = (
251
+ error: unknown,
252
+ context: ContinuousLoopErrorContext
253
+ ) => ContinuousLoopErrorAction | Promise<ContinuousLoopErrorAction>
254
+ ```
255
+
256
+ **Example:**
257
+
258
+ ```typescript
259
+ onCycleError: async (error, context) => {
260
+ console.error(`Cycle ${context.cycleNumber} failed: ${error.message}`);
261
+ return "stop"; // or "continue"
262
+ }
263
+ ```
264
+
265
+ ### `ContinuousLoopErrorAction`
266
+
267
+ ```typescript
268
+ type ContinuousLoopErrorAction = "continue" | "stop"
269
+ ```
270
+
271
+ ### `ContinuousLoopLogger`
223
272
 
224
273
  ```typescript
274
+ interface ContinuousLoopLogger {
275
+ warn(message: string, context?: Readonly<Record<string, unknown>>): void;
276
+ error(message: string, context?: Readonly<Record<string, unknown>>): void;
277
+ }
278
+ ```
279
+
280
+ **Example:**
281
+
282
+ ```typescript
283
+ const logger = {
284
+ warn: (msg) => console.warn(`[WARN] ${msg}`),
285
+ error: (msg) => console.error(`[ERROR] ${msg}`)
286
+ };
287
+
225
288
  await runContinuousLoop({
226
- intervalSeconds: 1,
227
- runCycle: async () => {
228
- if (Math.random() > 0.8) {
229
- throw new Error("Network failure");
230
- }
231
- },
232
- onCycleError: async (error, context) => {
233
- console.error(`Cycle ${context.cycleNumber} failed:`, error.message);
234
- return "continue"; // Keep the loop running
235
- },
236
- logger: {
237
- warn: (msg, ctx) => console.log(`[WARN] ${msg}`, ctx),
238
- error: (msg, ctx) => console.error(`[ERROR] ${msg}`, ctx),
239
- },
289
+ intervalSeconds: 10,
290
+ runCycle: () => Promise.resolve(),
291
+ logger
240
292
  });
241
293
  ```
242
294
 
243
- If no `onCycleError` is provided, errors are logged and the loop continues.
244
-
245
295
  ## Shutdown
246
296
 
247
297
  Signal handlers are automatically registered for `SIGINT` and `SIGTERM`, and removed on completion.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/daemon",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [