@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.
- package/README.md +110 -60
- 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
|
-
|
|
22
|
-
|
|
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
|
-
//
|
|
30
|
+
// Run a continuous loop
|
|
26
31
|
await runContinuousLoop({
|
|
27
32
|
intervalSeconds: 5,
|
|
28
|
-
|
|
29
|
-
console.log("Running
|
|
33
|
+
async runCycle(isShutdownRequested) {
|
|
34
|
+
console.log("Running cycle...");
|
|
30
35
|
if (isShutdownRequested()) {
|
|
31
36
|
return { stop: true };
|
|
32
37
|
}
|
|
33
|
-
// Perform background
|
|
34
|
-
|
|
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` |
|
|
182
|
-
| `runCycle` | `
|
|
183
|
-
| `getNextDelayMs?` | `
|
|
184
|
-
| `onCycleError?` | `
|
|
185
|
-
| `onShutdown?` | `
|
|
186
|
-
| `logger?` | `ContinuousLoopLogger` |
|
|
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
|
-
|
|
197
|
+
The return type supports:
|
|
198
|
+
- Raw delay (`number` or `"immediate"`)
|
|
199
|
+
- Control object: `{ stop?: boolean; nextDelayMs?: ContinuousLoopDelay }`
|
|
189
200
|
|
|
190
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
239
|
+
### `ContinuousLoopErrorContext`
|
|
212
240
|
|
|
213
|
-
|
|
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
|
-
###
|
|
243
|
+
### `ContinuousLoopErrorHandler`
|
|
221
244
|
|
|
222
|
-
|
|
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:
|
|
227
|
-
runCycle:
|
|
228
|
-
|
|
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.
|