@karmaniverous/jeeves-runner 0.1.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/LICENSE +28 -0
- package/README.md +401 -0
- package/dist/cli/jeeves-runner/index.js +880 -0
- package/dist/db/migrations/001-initial.sql +61 -0
- package/dist/index.d.ts +270 -0
- package/dist/mjs/index.js +917 -0
- package/package.json +141 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
name TEXT NOT NULL,
|
|
4
|
+
schedule TEXT NOT NULL,
|
|
5
|
+
script TEXT NOT NULL,
|
|
6
|
+
type TEXT DEFAULT 'script',
|
|
7
|
+
description TEXT,
|
|
8
|
+
enabled INTEGER DEFAULT 1,
|
|
9
|
+
timeout_ms INTEGER,
|
|
10
|
+
overlap_policy TEXT DEFAULT 'skip',
|
|
11
|
+
on_failure TEXT,
|
|
12
|
+
on_success TEXT,
|
|
13
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
14
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
18
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19
|
+
job_id TEXT NOT NULL REFERENCES jobs(id),
|
|
20
|
+
status TEXT NOT NULL,
|
|
21
|
+
started_at TEXT,
|
|
22
|
+
finished_at TEXT,
|
|
23
|
+
duration_ms INTEGER,
|
|
24
|
+
exit_code INTEGER,
|
|
25
|
+
tokens INTEGER,
|
|
26
|
+
result_meta TEXT,
|
|
27
|
+
error TEXT,
|
|
28
|
+
stdout_tail TEXT,
|
|
29
|
+
stderr_tail TEXT,
|
|
30
|
+
trigger TEXT DEFAULT 'schedule'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_runs_job_started ON runs(job_id, started_at DESC);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS cursors (
|
|
37
|
+
namespace TEXT NOT NULL,
|
|
38
|
+
key TEXT NOT NULL,
|
|
39
|
+
value TEXT,
|
|
40
|
+
expires_at TEXT,
|
|
41
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
42
|
+
PRIMARY KEY (namespace, key)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_cursors_expires ON cursors(expires_at) WHERE expires_at IS NOT NULL;
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS queues (
|
|
48
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
+
queue TEXT NOT NULL,
|
|
50
|
+
payload TEXT NOT NULL,
|
|
51
|
+
status TEXT DEFAULT 'pending',
|
|
52
|
+
priority INTEGER DEFAULT 0,
|
|
53
|
+
attempts INTEGER DEFAULT 0,
|
|
54
|
+
max_attempts INTEGER DEFAULT 1,
|
|
55
|
+
error TEXT,
|
|
56
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
57
|
+
claimed_at TEXT,
|
|
58
|
+
finished_at TEXT
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_queues_poll ON queues(queue, status, priority DESC, created_at);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
+
import { Logger } from 'pino';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runner configuration schema and types.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Full runner configuration schema. Validates and provides defaults. */
|
|
12
|
+
declare const runnerConfigSchema: z.ZodObject<{
|
|
13
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
14
|
+
dbPath: z.ZodDefault<z.ZodString>;
|
|
15
|
+
maxConcurrency: z.ZodDefault<z.ZodNumber>;
|
|
16
|
+
runRetentionDays: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
cursorCleanupIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
18
|
+
shutdownGraceMs: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
notifications: z.ZodDefault<z.ZodObject<{
|
|
20
|
+
slackTokenPath: z.ZodOptional<z.ZodString>;
|
|
21
|
+
defaultOnFailure: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
22
|
+
defaultOnSuccess: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
log: z.ZodDefault<z.ZodObject<{
|
|
25
|
+
level: z.ZodDefault<z.ZodEnum<{
|
|
26
|
+
trace: "trace";
|
|
27
|
+
debug: "debug";
|
|
28
|
+
info: "info";
|
|
29
|
+
warn: "warn";
|
|
30
|
+
error: "error";
|
|
31
|
+
fatal: "fatal";
|
|
32
|
+
}>>;
|
|
33
|
+
file: z.ZodOptional<z.ZodString>;
|
|
34
|
+
}, z.core.$strip>>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
/** Inferred runner configuration type. */
|
|
37
|
+
type RunnerConfig = z.infer<typeof runnerConfigSchema>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Job definition schema and types.
|
|
41
|
+
*
|
|
42
|
+
* @module
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
declare const jobSchema: z.ZodObject<{
|
|
46
|
+
id: z.ZodString;
|
|
47
|
+
name: z.ZodString;
|
|
48
|
+
schedule: z.ZodString;
|
|
49
|
+
script: z.ZodString;
|
|
50
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
51
|
+
script: "script";
|
|
52
|
+
session: "session";
|
|
53
|
+
}>>;
|
|
54
|
+
description: z.ZodOptional<z.ZodString>;
|
|
55
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
56
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
57
|
+
overlapPolicy: z.ZodDefault<z.ZodEnum<{
|
|
58
|
+
skip: "skip";
|
|
59
|
+
queue: "queue";
|
|
60
|
+
allow: "allow";
|
|
61
|
+
}>>;
|
|
62
|
+
onFailure: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
63
|
+
onSuccess: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
type Job = z.infer<typeof jobSchema>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Run record schema and types.
|
|
69
|
+
*
|
|
70
|
+
* @module
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
declare const runStatusSchema: z.ZodEnum<{
|
|
74
|
+
error: "error";
|
|
75
|
+
pending: "pending";
|
|
76
|
+
running: "running";
|
|
77
|
+
ok: "ok";
|
|
78
|
+
timeout: "timeout";
|
|
79
|
+
skipped: "skipped";
|
|
80
|
+
}>;
|
|
81
|
+
declare const runTriggerSchema: z.ZodEnum<{
|
|
82
|
+
schedule: "schedule";
|
|
83
|
+
manual: "manual";
|
|
84
|
+
retry: "retry";
|
|
85
|
+
}>;
|
|
86
|
+
declare const runSchema: z.ZodObject<{
|
|
87
|
+
id: z.ZodNumber;
|
|
88
|
+
jobId: z.ZodString;
|
|
89
|
+
status: z.ZodEnum<{
|
|
90
|
+
error: "error";
|
|
91
|
+
pending: "pending";
|
|
92
|
+
running: "running";
|
|
93
|
+
ok: "ok";
|
|
94
|
+
timeout: "timeout";
|
|
95
|
+
skipped: "skipped";
|
|
96
|
+
}>;
|
|
97
|
+
startedAt: z.ZodOptional<z.ZodString>;
|
|
98
|
+
finishedAt: z.ZodOptional<z.ZodString>;
|
|
99
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
exitCode: z.ZodOptional<z.ZodNumber>;
|
|
101
|
+
tokens: z.ZodOptional<z.ZodNumber>;
|
|
102
|
+
resultMeta: z.ZodOptional<z.ZodString>;
|
|
103
|
+
error: z.ZodOptional<z.ZodString>;
|
|
104
|
+
stdoutTail: z.ZodOptional<z.ZodString>;
|
|
105
|
+
stderrTail: z.ZodOptional<z.ZodString>;
|
|
106
|
+
trigger: z.ZodDefault<z.ZodEnum<{
|
|
107
|
+
schedule: "schedule";
|
|
108
|
+
manual: "manual";
|
|
109
|
+
retry: "retry";
|
|
110
|
+
}>>;
|
|
111
|
+
}, z.core.$strip>;
|
|
112
|
+
type Run = z.infer<typeof runSchema>;
|
|
113
|
+
type RunStatus = z.infer<typeof runStatusSchema>;
|
|
114
|
+
type RunTrigger = z.infer<typeof runTriggerSchema>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Main runner orchestrator. Wires up database, scheduler, API server, and handles graceful shutdown on SIGTERM/SIGINT.
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/** Runner interface. */
|
|
121
|
+
interface Runner {
|
|
122
|
+
start(): Promise<void>;
|
|
123
|
+
stop(): Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create the runner. Initializes database, scheduler, API server, and sets up graceful shutdown.
|
|
127
|
+
*/
|
|
128
|
+
declare function createRunner(config: RunnerConfig): Runner;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Job client library for runner jobs. Provides cursor (state) and queue operations. Opens its own DB connection via JR_DB_PATH env var.
|
|
132
|
+
*/
|
|
133
|
+
/** Client interface for job scripts to interact with runner state and queues. */
|
|
134
|
+
interface RunnerClient {
|
|
135
|
+
getCursor(namespace: string, key: string): string | null;
|
|
136
|
+
setCursor(namespace: string, key: string, value: string, options?: {
|
|
137
|
+
ttl?: string;
|
|
138
|
+
}): void;
|
|
139
|
+
deleteCursor(namespace: string, key: string): void;
|
|
140
|
+
enqueue(queue: string, payload: unknown, options?: {
|
|
141
|
+
priority?: number;
|
|
142
|
+
maxAttempts?: number;
|
|
143
|
+
}): number;
|
|
144
|
+
dequeue(queue: string, count?: number): Array<{
|
|
145
|
+
id: number;
|
|
146
|
+
payload: unknown;
|
|
147
|
+
}>;
|
|
148
|
+
done(queueItemId: number): void;
|
|
149
|
+
fail(queueItemId: number, error?: string): void;
|
|
150
|
+
close(): void;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Create a runner client for job scripts. Opens its own DB connection.
|
|
154
|
+
*/
|
|
155
|
+
declare function createClient(dbPath?: string): RunnerClient;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Job executor. Spawns job scripts as child processes, captures output, parses result metadata, enforces timeouts.
|
|
159
|
+
*/
|
|
160
|
+
/** Result of a job execution. */
|
|
161
|
+
interface ExecutionResult {
|
|
162
|
+
status: 'ok' | 'error' | 'timeout';
|
|
163
|
+
exitCode: number | null;
|
|
164
|
+
durationMs: number;
|
|
165
|
+
tokens: number | null;
|
|
166
|
+
resultMeta: string | null;
|
|
167
|
+
stdoutTail: string;
|
|
168
|
+
stderrTail: string;
|
|
169
|
+
error: string | null;
|
|
170
|
+
}
|
|
171
|
+
/** Options for executing a job script. */
|
|
172
|
+
interface ExecutionOptions {
|
|
173
|
+
script: string;
|
|
174
|
+
dbPath: string;
|
|
175
|
+
jobId: string;
|
|
176
|
+
runId: number;
|
|
177
|
+
timeoutMs?: number;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Execute a job script as a child process. Captures output, parses metadata, enforces timeout.
|
|
181
|
+
*/
|
|
182
|
+
declare function executeJob(options: ExecutionOptions): Promise<ExecutionResult>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Slack notification module. Sends job completion/failure messages via Slack Web API (chat.postMessage). Falls back gracefully if no token.
|
|
186
|
+
*/
|
|
187
|
+
/** Notification configuration. */
|
|
188
|
+
interface NotifyConfig {
|
|
189
|
+
slackToken: string | null;
|
|
190
|
+
}
|
|
191
|
+
/** Notifier interface for job completion events. */
|
|
192
|
+
interface Notifier {
|
|
193
|
+
notifySuccess(jobName: string, durationMs: number, channel: string): Promise<void>;
|
|
194
|
+
notifyFailure(jobName: string, durationMs: number, error: string | null, channel: string): Promise<void>;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create a notifier that sends Slack messages for job events. If no token, logs warning and returns silently.
|
|
198
|
+
*/
|
|
199
|
+
declare function createNotifier(config: NotifyConfig): Notifier;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Croner-based job scheduler. Loads enabled jobs, creates cron instances, manages execution, respects overlap policies and concurrency limits.
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
/** Scheduler dependencies. */
|
|
206
|
+
interface SchedulerDeps {
|
|
207
|
+
db: DatabaseSync;
|
|
208
|
+
executor: typeof executeJob;
|
|
209
|
+
notifier: Notifier;
|
|
210
|
+
config: RunnerConfig;
|
|
211
|
+
logger: Logger;
|
|
212
|
+
}
|
|
213
|
+
/** Scheduler interface. */
|
|
214
|
+
interface Scheduler {
|
|
215
|
+
start(): void;
|
|
216
|
+
stop(): void;
|
|
217
|
+
triggerJob(jobId: string): Promise<ExecutionResult>;
|
|
218
|
+
getRunningJobs(): string[];
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Create the job scheduler. Manages cron schedules, job execution, overlap policies, and notifications.
|
|
222
|
+
*/
|
|
223
|
+
declare function createScheduler(deps: SchedulerDeps): Scheduler;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* SQLite connection manager. Creates DB file with parent directories, enables WAL mode for concurrency.
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create and configure a SQLite database connection.
|
|
231
|
+
* Ensures parent directories exist and enables WAL mode for better concurrency.
|
|
232
|
+
*/
|
|
233
|
+
declare function createConnection(dbPath: string): DatabaseSync;
|
|
234
|
+
/**
|
|
235
|
+
* Close a database connection cleanly.
|
|
236
|
+
*/
|
|
237
|
+
declare function closeConnection(db: DatabaseSync): void;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Database maintenance tasks: run retention pruning and expired cursor cleanup.
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
/** Configuration for maintenance tasks. */
|
|
244
|
+
interface MaintenanceConfig {
|
|
245
|
+
runRetentionDays: number;
|
|
246
|
+
cursorCleanupIntervalMs: number;
|
|
247
|
+
}
|
|
248
|
+
/** Maintenance controller with start/stop lifecycle. */
|
|
249
|
+
interface Maintenance {
|
|
250
|
+
start(): void;
|
|
251
|
+
stop(): void;
|
|
252
|
+
/** Run all maintenance tasks immediately (useful for testing and startup). */
|
|
253
|
+
runNow(): void;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Create the maintenance controller. Runs cleanup tasks on startup and at configured intervals.
|
|
257
|
+
*/
|
|
258
|
+
declare function createMaintenance(db: DatabaseSync, config: MaintenanceConfig, logger: Logger): Maintenance;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Schema migration runner. Tracks applied migrations via schema_version table, applies pending migrations idempotently.
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Run all pending migrations. Creates schema_version table if needed, applies migrations in order.
|
|
266
|
+
*/
|
|
267
|
+
declare function runMigrations(db: DatabaseSync): void;
|
|
268
|
+
|
|
269
|
+
export { closeConnection, createClient, createConnection, createMaintenance, createNotifier, createRunner, createScheduler, executeJob, jobSchema, runMigrations, runSchema, runStatusSchema, runTriggerSchema, runnerConfigSchema };
|
|
270
|
+
export type { ExecutionOptions, ExecutionResult, Job, Maintenance, MaintenanceConfig, Notifier, NotifyConfig, Run, RunStatus, RunTrigger, Runner, RunnerClient, RunnerConfig, Scheduler, SchedulerDeps };
|