@gobing-ai/ts-infra 0.4.58 → 0.4.60
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 +14 -4
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +3 -0
- package/dist/application/types.d.ts +11 -2
- package/dist/application/types.d.ts.map +1 -1
- package/dist/application-node.d.ts.map +1 -1
- package/dist/application-node.js +34 -4
- package/dist/execution-policy.d.ts +76 -0
- package/dist/execution-policy.d.ts.map +1 -0
- package/dist/execution-policy.js +123 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/job-queue/db-job-queue.d.ts +14 -1
- package/dist/job-queue/db-job-queue.d.ts.map +1 -1
- package/dist/job-queue/db-job-queue.js +165 -26
- package/dist/job-queue/types.d.ts +47 -7
- package/dist/job-queue/types.d.ts.map +1 -1
- package/dist/scheduler/cloudflare.d.ts +4 -0
- package/dist/scheduler/cloudflare.d.ts.map +1 -1
- package/dist/scheduler/cloudflare.js +2 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/node.d.ts +10 -1
- package/dist/scheduler/node.d.ts.map +1 -1
- package/dist/scheduler/node.js +28 -6
- package/dist/scheduler/types.d.ts +20 -3
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/scheduler/wrap-handler.d.ts.map +1 -1
- package/dist/scheduler/wrap-handler.js +2 -2
- package/package.json +6 -6
- package/src/application/index.ts +3 -0
- package/src/application/types.ts +16 -3
- package/src/application-node.ts +40 -4
- package/src/execution-policy.ts +168 -0
- package/src/index.ts +3 -1
- package/src/job-queue/db-job-queue.ts +184 -25
- package/src/job-queue/types.ts +47 -7
- package/src/scheduler/cloudflare.ts +3 -1
- package/src/scheduler/index.ts +6 -1
- package/src/scheduler/node.ts +39 -6
- package/src/scheduler/types.ts +34 -5
- package/src/scheduler/wrap-handler.ts +3 -2
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ classDiagram
|
|
|
70
70
|
namespace scheduler {
|
|
71
71
|
class SchedulerAdapter {
|
|
72
72
|
<<interface>>
|
|
73
|
-
+register(cron, action) void
|
|
73
|
+
+register(cron, action, options?) void
|
|
74
74
|
+start() Promise~void~
|
|
75
75
|
+stop() Promise~void~
|
|
76
76
|
}
|
|
@@ -249,17 +249,25 @@ await queue.enqueue(
|
|
|
249
249
|
{ to: 'alice@example.com', subject: 'Welcome' },
|
|
250
250
|
// maxRetries is a total attempt budget, not retries after the first:
|
|
251
251
|
// 5 means up to 5 attempts, and `maxRetries: 1` runs the job once with no retry.
|
|
252
|
-
|
|
252
|
+
// timeoutMs is the per-job execution policy: positive ms, or `null` = unlimited.
|
|
253
|
+
{ maxRetries: 5, delay: 1_000, ttlMs: 86_400_000, timeoutMs: 60_000 },
|
|
253
254
|
);
|
|
254
255
|
|
|
255
256
|
const consumer = new DBQueueConsumer<{ to: string; subject: string }>(dao, {
|
|
256
257
|
batchSize: 10,
|
|
257
258
|
maxConcurrency: 4,
|
|
258
259
|
visibilityTimeout: 30_000,
|
|
260
|
+
// Execution policy: defaultTimeoutMs applies to jobs without their own
|
|
261
|
+
// timeoutMs (job timeoutMs wins; explicit null = unlimited). Handlers that
|
|
262
|
+
// overrun are failed with a deadline message; lease loss or cancel(jobId)
|
|
263
|
+
// aborts the handler context and fences the ack.
|
|
264
|
+
defaultTimeoutMs: 120_000,
|
|
265
|
+
drainPolicy: 'bounded',
|
|
259
266
|
});
|
|
260
267
|
|
|
261
|
-
consumer.register('send-email', async (job) => {
|
|
262
|
-
|
|
268
|
+
consumer.register('send-email', async (job, context) => {
|
|
269
|
+
// context.signal aborts on deadline expiry, lease loss, or cancel(jobId).
|
|
270
|
+
await sendEmail(job.payload.to, job.payload.subject, context.signal);
|
|
263
271
|
});
|
|
264
272
|
|
|
265
273
|
await consumer.start();
|
|
@@ -273,6 +281,8 @@ const processed = await consumer.processOnce();
|
|
|
273
281
|
|
|
274
282
|
The consumer claims ready jobs, resets stuck processing jobs after the visibility timeout, retries failed jobs with exponential backoff, and marks expired jobs failed through `QueueJobDao`. Corrupt payloads are failed individually without rejecting the batch. Poll-cycle errors are logged and retried on the next cycle — a single DAO hiccup will not crash the process.
|
|
275
283
|
|
|
284
|
+
**Execution deadlines (A21):** every attempt runs under one shared deadline clock from `@gobing-ai/ts-infra` (`resolveExecutionTimeoutMs`, `runWithExecutionDeadline`). Resolution is first-match-wins: job `timeoutMs` (explicit `null` = unlimited) → consumer `defaultTimeoutMs` → unlimited. A deadline aborts the handler's `context.signal` and fails the job; lease loss or `consumer.cancel(jobId)` reports `cancelled` and skips all acks (ownership fencing via attempt tokens).
|
|
285
|
+
|
|
276
286
|
`stop()` drains gracefully: it stops polling, then waits for work already in flight — including a poll cycle that has claimed nothing yet — before resolving. The wait is capped by `drainTimeoutMs` (default 30s) so a stuck handler cannot block shutdown; when the cap is hit, `queue.consumer.stopped` reports `drained: false` with the outstanding `inFlightAtStop` count.
|
|
277
287
|
|
|
278
288
|
**Queue identity.** Once a consumer is event-enabled (an `events` bus is configured), it **must** supply a non-empty, already-trimmed `queueName` — construction throws `Queue consumer queueName …` otherwise, so an observable consumer can never emit an anonymous `queue.consumer.started`/`queue.consumer.stopped` row. The name is validated, never normalized, and is emitted byte-for-byte as `queueName` on both lifecycle details. Silent consumers (no `events`) may omit identity entirely.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAO7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAiCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,cAAc,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EAC7F,OAAO,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1D,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CA6JlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,YAAY,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { attachDefaultObservers, createLifecycleBus } from '../event-bus/default-observers.js';
|
|
13
13
|
import { EventBus } from '../event-bus/event-bus.js';
|
|
14
14
|
import { attachFileObserver } from '../event-bus/file-observer.js';
|
|
15
|
+
import { resolveExecutionTimeoutMs } from '../execution-policy.js';
|
|
15
16
|
import { getLogger } from '../logger.js';
|
|
16
17
|
import { initScheduler } from '../scheduler/factory.js';
|
|
17
18
|
import { loggerPlugin, schedulerPlugin, telemetryPlugin, userCallbackPlugin } from './plugins/builtins.js';
|
|
@@ -95,6 +96,8 @@ export async function runApplication(options) {
|
|
|
95
96
|
enabled: schedOpts?.enabled ?? false,
|
|
96
97
|
autoStart: schedOpts?.autoStart ?? true,
|
|
97
98
|
jobs: schedOpts?.jobs ?? [],
|
|
99
|
+
// Bootstrap-level execution policy (A21): omitted resolves to unlimited.
|
|
100
|
+
timeoutMs: resolveExecutionTimeoutMs('bootstrap.scheduler', schedOpts?.timeoutMs),
|
|
98
101
|
};
|
|
99
102
|
const eventsEnabled = options.config?.events?.enabled ?? true;
|
|
100
103
|
const eventsLifecycle = options.config?.events?.lifecycle ?? true;
|
|
@@ -12,7 +12,7 @@ import type { FileObserverWriter } from '../event-bus/file-observer';
|
|
|
12
12
|
import type { BusLifecycleEvents, EventMap } from '../event-bus/types';
|
|
13
13
|
import type { InfraEvents } from '../events';
|
|
14
14
|
import type { Logger, LogLevel } from '../logger';
|
|
15
|
-
import type { SchedulerAdapter, SchedulerJobConfig } from '../scheduler/types';
|
|
15
|
+
import type { ScheduledAction, SchedulerAdapter, SchedulerJobConfig } from '../scheduler/types';
|
|
16
16
|
import type { PluginHost } from './plugins/host';
|
|
17
17
|
import type { Plugin } from './plugins/types';
|
|
18
18
|
/** Logging feature flags. */
|
|
@@ -74,7 +74,7 @@ export interface SchedulerOptions {
|
|
|
74
74
|
/** Injected adapter (skips noop default when provided). */
|
|
75
75
|
adapter?: SchedulerAdapter;
|
|
76
76
|
/** Cron entries to register: `[cron, action][]`. */
|
|
77
|
-
entries?: Array<[string,
|
|
77
|
+
entries?: Array<[string, ScheduledAction]>;
|
|
78
78
|
/** Start scheduler immediately after registration. Default `true` when enabled. */
|
|
79
79
|
autoStart?: boolean;
|
|
80
80
|
/**
|
|
@@ -84,6 +84,13 @@ export interface SchedulerOptions {
|
|
|
84
84
|
* job `command` itself.
|
|
85
85
|
*/
|
|
86
86
|
jobs?: readonly SchedulerJobConfig[];
|
|
87
|
+
/**
|
|
88
|
+
* Bootstrap-level execution policy (A21): a positive integer ms deadline
|
|
89
|
+
* or explicit `null` = unlimited; omitted resolves to unlimited. Per-job
|
|
90
|
+
* `timeoutMs` overrides this default through the shared first-match-wins
|
|
91
|
+
* resolution.
|
|
92
|
+
*/
|
|
93
|
+
timeoutMs?: number | null;
|
|
87
94
|
}
|
|
88
95
|
/**
|
|
89
96
|
* Fully-resolved bootstrap config (all optionals filled with defaults).
|
|
@@ -112,6 +119,8 @@ export interface ApplicationBootstrapConfig {
|
|
|
112
119
|
enabled: boolean;
|
|
113
120
|
autoStart: boolean;
|
|
114
121
|
jobs: readonly SchedulerJobConfig[];
|
|
122
|
+
/** Resolved bootstrap execution policy; `null` = unlimited. */
|
|
123
|
+
timeoutMs: number | null;
|
|
115
124
|
};
|
|
116
125
|
}
|
|
117
126
|
/** Services that may be pre-injected instead of created by the bootstrap. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/application/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/application/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EACnB,MAAM,qBAAqB,CAAC;AAS7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAwOD;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,6BAA6B,CAAC;QAC1C,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAiJlD"}
|
package/dist/application-node.js
CHANGED
|
@@ -20,6 +20,7 @@ import { createDbAdapter } from '@gobing-ai/ts-db';
|
|
|
20
20
|
import { createNodeFileSystem, interpolateTree, parseYamlObject } from '@gobing-ai/ts-runtime';
|
|
21
21
|
import { runApplication } from './application/index.js';
|
|
22
22
|
import { dbPlugin } from './application/plugins/builtins.js';
|
|
23
|
+
import { resolveExecutionTimeoutMs } from './execution-policy.js';
|
|
23
24
|
import { parseCronExpression } from './scheduler/cron.js';
|
|
24
25
|
import { NodeSchedulerAdapter } from './scheduler-node.js';
|
|
25
26
|
import { initNodeTelemetry, shutdownNodeTelemetry } from './telemetry/otel-node.js';
|
|
@@ -60,7 +61,20 @@ function validateAppConfig(validator, raw, section, filePath) {
|
|
|
60
61
|
}
|
|
61
62
|
throw new ConfigValidationError(`Unsupported validator shape for section "${section}"`);
|
|
62
63
|
}
|
|
63
|
-
// ── Scheduler job validation (task 0734)
|
|
64
|
+
// ── Scheduler job validation (task 0734) ────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Resolve and validate the bootstrap-level scheduler execution policy (A21),
|
|
67
|
+
* rethrowing invalid values as {@link ConfigValidationError} with the exact
|
|
68
|
+
* config path so startup aborts before the user `start` callback.
|
|
69
|
+
*/
|
|
70
|
+
function resolveBootstrapSchedulerTimeout(value) {
|
|
71
|
+
try {
|
|
72
|
+
return resolveExecutionTimeoutMs('bootstrap.scheduler', value);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new ConfigValidationError(`bootstrap.scheduler.timeoutMs must be a positive integer or null; received ${String(value)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
64
78
|
/** Max `intervalMinutes` so that `intervalMinutes * 60_000` fits in the platform timer maximum. */
|
|
65
79
|
const MAX_INTERVAL_MINUTES = Math.floor(MAX_TIMEOUT_MS / 60_000);
|
|
66
80
|
/**
|
|
@@ -92,6 +106,18 @@ function normalizeSchedulerJobs(raw) {
|
|
|
92
106
|
const command = typeof entry.command === 'string' ? entry.command.trim() : '';
|
|
93
107
|
const cron = typeof entry.cron === 'string' ? entry.cron.trim() : '';
|
|
94
108
|
const interval = entry.intervalMinutes;
|
|
109
|
+
// Per-job execution policy (A21): undefined inherits, explicit null is
|
|
110
|
+
// unlimited, otherwise a positive integer. Validated here so a bad
|
|
111
|
+
// value aborts startup with the exact job path.
|
|
112
|
+
const rawTimeout = entry.timeoutMs;
|
|
113
|
+
let timeoutMs;
|
|
114
|
+
if (rawTimeout !== undefined) {
|
|
115
|
+
if (rawTimeout !== null &&
|
|
116
|
+
(typeof rawTimeout !== 'number' || !Number.isInteger(rawTimeout) || rawTimeout <= 0)) {
|
|
117
|
+
throw new ConfigValidationError(`bootstrap.scheduler.jobs.${index}.timeoutMs must be a positive integer or null`);
|
|
118
|
+
}
|
|
119
|
+
timeoutMs = rawTimeout;
|
|
120
|
+
}
|
|
95
121
|
if (name === '') {
|
|
96
122
|
throw new ConfigValidationError(`bootstrap.scheduler.jobs.${index}.name must be a non-empty string`);
|
|
97
123
|
}
|
|
@@ -114,7 +140,7 @@ function normalizeSchedulerJobs(raw) {
|
|
|
114
140
|
interval > MAX_INTERVAL_MINUTES) {
|
|
115
141
|
throw new ConfigValidationError(`bootstrap.scheduler.jobs.${index}.intervalMinutes must be an integer in 1..${MAX_INTERVAL_MINUTES}`);
|
|
116
142
|
}
|
|
117
|
-
jobs.push({ name, command, intervalMinutes: interval });
|
|
143
|
+
jobs.push({ name, command, intervalMinutes: interval, timeoutMs });
|
|
118
144
|
}
|
|
119
145
|
else {
|
|
120
146
|
if (cron === '') {
|
|
@@ -127,7 +153,7 @@ function normalizeSchedulerJobs(raw) {
|
|
|
127
153
|
const detail = error instanceof Error ? error.message : String(error);
|
|
128
154
|
throw new ConfigValidationError(`bootstrap.scheduler.jobs.${index}.cron: ${detail}`);
|
|
129
155
|
}
|
|
130
|
-
jobs.push({ name, command, cron });
|
|
156
|
+
jobs.push({ name, command, cron, timeoutMs });
|
|
131
157
|
}
|
|
132
158
|
});
|
|
133
159
|
return jobs;
|
|
@@ -257,9 +283,13 @@ export async function runNodeApplication(options) {
|
|
|
257
283
|
enabled: schedulerOpts.enabled === true,
|
|
258
284
|
autoStart: schedulerOpts.autoStart,
|
|
259
285
|
jobs: normalizeSchedulerJobs(schedulerOpts.jobs),
|
|
286
|
+
// Bootstrap-level execution policy (A21): validated with the exact
|
|
287
|
+
// config path so a bad value aborts startup before the user callback.
|
|
288
|
+
timeoutMs: resolveBootstrapSchedulerTimeout(schedulerOpts.timeoutMs),
|
|
260
289
|
};
|
|
261
290
|
if (schedulerConfig.enabled) {
|
|
262
|
-
schedulerConfig.adapter =
|
|
291
|
+
schedulerConfig.adapter =
|
|
292
|
+
schedulerOpts.adapter ?? new NodeSchedulerAdapter({ timeoutMs: schedulerConfig.timeoutMs });
|
|
263
293
|
if (schedulerOpts.entries) {
|
|
264
294
|
schedulerConfig.entries = schedulerOpts.entries;
|
|
265
295
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared execution-deadline policy for background work (feature A21).
|
|
3
|
+
*
|
|
4
|
+
* One nullable policy (`number | null`) and one clock per execution: a finite
|
|
5
|
+
* deadline arms a single timer that requests cancellation through an
|
|
6
|
+
* `AbortController`; the executor always awaits the handler's settlement, even
|
|
7
|
+
* when the handler ignores the abort signal. Both the queue consumer and the
|
|
8
|
+
* Node scheduler run their work through this seam so deadline, cancellation,
|
|
9
|
+
* and timing semantics stay identical across surfaces.
|
|
10
|
+
*
|
|
11
|
+
* Resolution is first-match-wins down the scope chain: an explicit job value
|
|
12
|
+
* (including explicit `null` = unlimited) beats the consumer default; absence
|
|
13
|
+
* (`undefined`) inherits; an absent inherited value is unlimited. A scope
|
|
14
|
+
* never *negates* an inherited finite deadline by being omitted — only an
|
|
15
|
+
* explicit `null` opts out.
|
|
16
|
+
*/
|
|
17
|
+
/** Execution deadline policy: positive integer milliseconds, or `null` = unlimited. */
|
|
18
|
+
export type ExecutionDeadlineMs = number | null;
|
|
19
|
+
/** Cancellation context handed to job and scheduler handlers. */
|
|
20
|
+
export interface ExecutionContext {
|
|
21
|
+
/** Fires when the deadline expires or the caller's signal cancels first. */
|
|
22
|
+
readonly signal: AbortSignal;
|
|
23
|
+
/** The resolved policy for this execution; `null` means unlimited. */
|
|
24
|
+
readonly deadlineMs: ExecutionDeadlineMs;
|
|
25
|
+
/**
|
|
26
|
+
* Why cancellation was requested: `'timeout'` when the execution deadline
|
|
27
|
+
* expired, `'cancelled'` when the caller's signal fired first. `undefined`
|
|
28
|
+
* while the work is not (yet) being cancelled.
|
|
29
|
+
*/
|
|
30
|
+
readonly cancellationReason: 'timeout' | 'cancelled' | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Outcome of an execution run through {@link runWithExecutionDeadline}. */
|
|
33
|
+
export interface ExecutionOutcome {
|
|
34
|
+
/** `'timeout'` and `'cancelled'` still mean the handler settled (or was awaited to settlement). */
|
|
35
|
+
outcome: 'completed' | 'timeout' | 'cancelled' | 'error';
|
|
36
|
+
/** The handler's error when `outcome === 'error'`. */
|
|
37
|
+
error?: unknown;
|
|
38
|
+
elapsedMs: number;
|
|
39
|
+
/** True when the deadline expired, even if the handler ignored the abort and finished late. */
|
|
40
|
+
timedOut: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Options for {@link runWithExecutionDeadline}. */
|
|
43
|
+
export interface ExecutionDeadlineOptions {
|
|
44
|
+
/** Resolved deadline for this run; `undefined` is treated as unlimited. */
|
|
45
|
+
timeoutMs?: number | null;
|
|
46
|
+
/** Caller-controlled cancellation composed into the same controller. */
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the leaf execution policy for one scope.
|
|
51
|
+
*
|
|
52
|
+
* `value === undefined` inherits `inherited` (itself `null` when absent);
|
|
53
|
+
* `value === null` explicitly opts out of this scope's deadline; a positive
|
|
54
|
+
* integer wins outright. Anything else is a configuration error.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveExecutionTimeoutMs(scope: string, value: number | null | undefined, inherited?: number | null): number | null;
|
|
57
|
+
/**
|
|
58
|
+
* A reusable unlimited context for callers that execute actions without
|
|
59
|
+
* arming a ts-infra deadline — notably the Cloudflare adapter, whose ticks
|
|
60
|
+
* run under the Workers runtime's own limits (A21).
|
|
61
|
+
*/
|
|
62
|
+
export declare function unlimitedExecutionContext(): ExecutionContext;
|
|
63
|
+
/**
|
|
64
|
+
* Run `action` under one shared deadline clock.
|
|
65
|
+
*
|
|
66
|
+
* A finite `timeoutMs` arms exactly one timer that aborts the context's
|
|
67
|
+
* controller (`cancellationReason: 'timeout'`); an aborted caller signal
|
|
68
|
+
* aborts the same controller (`cancellationReason: 'cancelled'`). The handler
|
|
69
|
+
* is always awaited — including after abort, so an uncooperative handler that
|
|
70
|
+
* finishes late is still reported as timed out, never as a successful
|
|
71
|
+
* cancellation — and handler rejections surface as `{ outcome: 'error' }`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function runWithExecutionDeadline<T>(action: (context: ExecutionContext) => Promise<T>, options: ExecutionDeadlineOptions): Promise<ExecutionOutcome & {
|
|
74
|
+
result?: T;
|
|
75
|
+
}>;
|
|
76
|
+
//# sourceMappingURL=execution-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-policy.d.ts","sourceRoot":"","sources":["../src/execution-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,uFAAuF;AACvF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAAC;AAEhD,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;CACpE;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC7B,mGAAmG;IACnG,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;IACzD,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED,oDAAoD;AACpD,MAAM,WAAW,wBAAwB;IACrC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACrC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,MAAM,GAAG,IAAI,CAWf;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,gBAAgB,CAM5D;AAED;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,EAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,EACjD,OAAO,EAAE,wBAAwB,GAClC,OAAO,CAAC,gBAAgB,GAAG;IAAE,MAAM,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC,CAgE5C"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared execution-deadline policy for background work (feature A21).
|
|
3
|
+
*
|
|
4
|
+
* One nullable policy (`number | null`) and one clock per execution: a finite
|
|
5
|
+
* deadline arms a single timer that requests cancellation through an
|
|
6
|
+
* `AbortController`; the executor always awaits the handler's settlement, even
|
|
7
|
+
* when the handler ignores the abort signal. Both the queue consumer and the
|
|
8
|
+
* Node scheduler run their work through this seam so deadline, cancellation,
|
|
9
|
+
* and timing semantics stay identical across surfaces.
|
|
10
|
+
*
|
|
11
|
+
* Resolution is first-match-wins down the scope chain: an explicit job value
|
|
12
|
+
* (including explicit `null` = unlimited) beats the consumer default; absence
|
|
13
|
+
* (`undefined`) inherits; an absent inherited value is unlimited. A scope
|
|
14
|
+
* never *negates* an inherited finite deadline by being omitted — only an
|
|
15
|
+
* explicit `null` opts out.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the leaf execution policy for one scope.
|
|
19
|
+
*
|
|
20
|
+
* `value === undefined` inherits `inherited` (itself `null` when absent);
|
|
21
|
+
* `value === null` explicitly opts out of this scope's deadline; a positive
|
|
22
|
+
* integer wins outright. Anything else is a configuration error.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveExecutionTimeoutMs(scope, value, inherited) {
|
|
25
|
+
if (value === undefined) {
|
|
26
|
+
return inherited ?? null;
|
|
27
|
+
}
|
|
28
|
+
if (value === null) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
|
|
32
|
+
throw new RangeError(`${scope} timeoutMs must be a positive integer or null; received ${String(value)}`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A reusable unlimited context for callers that execute actions without
|
|
38
|
+
* arming a ts-infra deadline — notably the Cloudflare adapter, whose ticks
|
|
39
|
+
* run under the Workers runtime's own limits (A21).
|
|
40
|
+
*/
|
|
41
|
+
export function unlimitedExecutionContext() {
|
|
42
|
+
return {
|
|
43
|
+
signal: new AbortController().signal,
|
|
44
|
+
deadlineMs: null,
|
|
45
|
+
cancellationReason: undefined,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run `action` under one shared deadline clock.
|
|
50
|
+
*
|
|
51
|
+
* A finite `timeoutMs` arms exactly one timer that aborts the context's
|
|
52
|
+
* controller (`cancellationReason: 'timeout'`); an aborted caller signal
|
|
53
|
+
* aborts the same controller (`cancellationReason: 'cancelled'`). The handler
|
|
54
|
+
* is always awaited — including after abort, so an uncooperative handler that
|
|
55
|
+
* finishes late is still reported as timed out, never as a successful
|
|
56
|
+
* cancellation — and handler rejections surface as `{ outcome: 'error' }`.
|
|
57
|
+
*/
|
|
58
|
+
export async function runWithExecutionDeadline(action, options) {
|
|
59
|
+
const deadlineMs = options.timeoutMs ?? null;
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
const cancellation = { reason: undefined };
|
|
62
|
+
const context = {
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
deadlineMs,
|
|
65
|
+
get cancellationReason() {
|
|
66
|
+
return cancellation.reason;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
if (options.signal !== undefined) {
|
|
70
|
+
if (options.signal.aborted) {
|
|
71
|
+
cancellation.reason = 'cancelled';
|
|
72
|
+
controller.abort();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const abortFromCaller = () => {
|
|
76
|
+
if (cancellation.reason === undefined)
|
|
77
|
+
cancellation.reason = 'cancelled';
|
|
78
|
+
controller.abort();
|
|
79
|
+
};
|
|
80
|
+
options.signal.addEventListener('abort', abortFromCaller, { once: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const startMs = performance.now();
|
|
84
|
+
let timer;
|
|
85
|
+
if (deadlineMs !== null) {
|
|
86
|
+
timer = setTimeout(() => {
|
|
87
|
+
if (cancellation.reason === undefined)
|
|
88
|
+
cancellation.reason = 'timeout';
|
|
89
|
+
controller.abort();
|
|
90
|
+
}, deadlineMs);
|
|
91
|
+
}
|
|
92
|
+
let result;
|
|
93
|
+
let error;
|
|
94
|
+
let failed = false;
|
|
95
|
+
try {
|
|
96
|
+
result = await action(context);
|
|
97
|
+
}
|
|
98
|
+
catch (caught) {
|
|
99
|
+
error = caught;
|
|
100
|
+
failed = true;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
if (timer !== undefined)
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
const elapsedMs = performance.now() - startMs;
|
|
107
|
+
const timedOut = cancellation.reason === 'timeout';
|
|
108
|
+
if (failed) {
|
|
109
|
+
return { outcome: 'error', error, elapsedMs, timedOut };
|
|
110
|
+
}
|
|
111
|
+
if (timedOut) {
|
|
112
|
+
return {
|
|
113
|
+
outcome: 'timeout',
|
|
114
|
+
error: new Error(`execution deadline (${String(deadlineMs)}ms) expired`),
|
|
115
|
+
elapsedMs,
|
|
116
|
+
timedOut,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (cancellation.reason === 'cancelled') {
|
|
120
|
+
return { outcome: 'cancelled', elapsedMs, timedOut };
|
|
121
|
+
}
|
|
122
|
+
return { outcome: 'completed', result, elapsedMs, timedOut };
|
|
123
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { APIClient, type APIClientConfig, APIError, type RawHttpResponse, type RawRequestOptions, type RequestOptions, } from './api-client';
|
|
2
2
|
export { type AsyncEventJobPayload, attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, type BusLifecycleEvents, createLifecycleBus, EventBus, type EventMap, type FileObserverWriter, type SubscribeOptions, } from './event-bus/index';
|
|
3
3
|
export type { ApiClientEvents, ApiRequestErrorDetail, DbConnectionErrorDetail, DbEvents, EventSeverity, InfraEvents, QueueConsumerStartedDetail, QueueConsumerStoppedDetail, QueueEvents, QueueJobCompletedDetail, QueueJobEnqueuedDetail, QueueJobFailedDetail, QueueJobRef, QueueJobRetryingDetail, SchedulerEvents, SchedulerJobExecutedDetail, WithEventSeverity, } from './events';
|
|
4
|
+
export type { ExecutionContext, ExecutionDeadlineMs, ExecutionOutcome } from './execution-policy';
|
|
5
|
+
export { resolveExecutionTimeoutMs, runWithExecutionDeadline } from './execution-policy';
|
|
4
6
|
export type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig, QueueStats, } from './job-queue/index';
|
|
5
7
|
export { getLogger, type InitLoggerOptions, initializeLogger, type Logger, type LogLevel, setLoggerMuted, } from './logger';
|
|
6
8
|
export { ActionRegistry, type CreateDefaultRegistryOptions, createDefaultRegistry, HealthPingAction, type HealthPingWriter, initScheduler, LogAction, NoopSchedulerAdapter, QueueStatsAction, type QueueStatsDaoProvider, type ScheduledAction, type SchedulerAction, type SchedulerAdapter, toScheduledAction, wrapScheduledHandler, } from './scheduler/index';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,SAAS,EACT,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACH,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACxB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EACR,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,0BAA0B,EAC1B,0BAA0B,EAC1B,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,GACpB,MAAM,UAAU,CAAC;AAElB,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,SAAS,EACT,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,cAAc,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACH,cAAc,EACd,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,SAAS,EACT,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACH,KAAK,oBAAoB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACxB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EACR,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,QAAQ,EACR,aAAa,EACb,WAAW,EACX,0BAA0B,EAC1B,0BAA0B,EAC1B,WAAW,EACX,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,0BAA0B,EAC1B,iBAAiB,GACpB,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AACzF,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACH,SAAS,EACT,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,cAAc,GACjB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACH,cAAc,EACd,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,aAAa,EACb,SAAS,EACT,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { APIClient, APIError, } from './api-client.js';
|
|
3
3
|
// Event Bus
|
|
4
4
|
export { attachDefaultObservers, attachFileObserver, attachLogObserver, attachTelemetryObserver, createLifecycleBus, EventBus, } from './event-bus/index.js';
|
|
5
|
+
export { resolveExecutionTimeoutMs, runWithExecutionDeadline } from './execution-policy.js';
|
|
5
6
|
// Logger
|
|
6
7
|
export { getLogger, initializeLogger, setLoggerMuted, } from './logger.js';
|
|
7
8
|
// Scheduler
|
|
@@ -14,7 +14,7 @@ export declare class DBJobQueue<T = unknown> implements JobQueue<T> {
|
|
|
14
14
|
} & EnqueueOptions>): Promise<string[]>;
|
|
15
15
|
stats(): Promise<QueueStats>;
|
|
16
16
|
}
|
|
17
|
-
/** DB-backed queue consumer with polling, retry, and
|
|
17
|
+
/** DB-backed queue consumer with polling, retry, lease ownership, and execution-deadline handling. */
|
|
18
18
|
export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
19
19
|
private readonly dao;
|
|
20
20
|
private readonly handlers;
|
|
@@ -25,6 +25,11 @@ export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
25
25
|
private readonly baseDelay;
|
|
26
26
|
private readonly maxDelay;
|
|
27
27
|
private readonly drainTimeoutMs;
|
|
28
|
+
private readonly drainPolicy;
|
|
29
|
+
/** Default execution policy for jobs without their own persisted decision; `null` = unlimited. */
|
|
30
|
+
private readonly defaultTimeoutMs;
|
|
31
|
+
/** Live attempts owned by this consumer, keyed by job id — the target of `cancel()`. */
|
|
32
|
+
private readonly activeAttempts;
|
|
28
33
|
/**
|
|
29
34
|
* Validated event sink: the bus plus the non-empty queue name its lifecycle rows
|
|
30
35
|
* require (ADR-068). `undefined` for silent consumers — no `events`, no identity.
|
|
@@ -38,12 +43,20 @@ export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
|
|
|
38
43
|
register(type: string, handler: JobHandler<T>): void;
|
|
39
44
|
start(): Promise<void>;
|
|
40
45
|
stop(): Promise<void>;
|
|
46
|
+
cancel(jobId: string): Promise<boolean>;
|
|
41
47
|
stats(): Promise<QueueStats>;
|
|
42
48
|
/** Process one batch immediately. Useful for tests, schedulers, and manual drains. */
|
|
43
49
|
processOnce(): Promise<number>;
|
|
44
50
|
private schedule;
|
|
45
51
|
private poll;
|
|
46
52
|
private processJob;
|
|
53
|
+
/**
|
|
54
|
+
* Extend the attempt's lease on an interval derived from the visibility
|
|
55
|
+
* timeout, proving this consumer is still alive and working. A failed or
|
|
56
|
+
* erroring renewal means ownership was lost: abort the attempt so the
|
|
57
|
+
* handler can stop, and mark the attempt fenced.
|
|
58
|
+
*/
|
|
59
|
+
private renewUntilSettled;
|
|
47
60
|
private failOrRetry;
|
|
48
61
|
}
|
|
49
62
|
//# sourceMappingURL=db-job-queue.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db-job-queue.d.ts","sourceRoot":"","sources":["../../src/job-queue/db-job-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EACR,WAAW,EAKd,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"db-job-queue.d.ts","sourceRoot":"","sources":["../../src/job-queue/db-job-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EACR,WAAW,EAKd,MAAM,WAAW,CAAC;AAWnB,OAAO,KAAK,EAAE,cAAc,EAAO,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7G,kFAAkF;AAClF,qBAAa,UAAU,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAEnD,QAAQ,CAAC,GAAG,EAAE,WAAW;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADf,GAAG,EAAE,WAAW,EACR,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,YAAA;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAY3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAcD,sGAAsG;AACtG,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAyB7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAxBxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoC;IAChE,kGAAkG;IAClG,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgB;IACjD,wFAAwF;IACxF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2E;IAC1G;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgE;IAC1F,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAA8B;gBAG5B,GAAG,EAAE,WAAW,EACjC,MAAM,GAAE,mBAAwB;IAyBpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsDrB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQvC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAWF,IAAI;YAgBJ,UAAU;IAmHxB;;;;;OAKG;YACW,iBAAiB;YA2BjB,WAAW;CAsC5B"}
|