@browserlessapi/inferlo 0.3.0 → 0.4.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/README.md +4 -1
- package/dist/ai/index.d.ts +44 -2
- package/dist/ai/index.js +158 -35
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/providers.d.ts +56 -3
- package/dist/ai/providers.js +97 -7
- package/dist/ai/providers.js.map +1 -1
- package/dist/app.d.ts +15 -0
- package/dist/app.js +59 -5
- package/dist/app.js.map +1 -1
- package/dist/cli/main.js +55 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/client-ip.d.ts +7 -0
- package/dist/client-ip.js +27 -0
- package/dist/client-ip.js.map +1 -0
- package/dist/cron.d.ts +17 -0
- package/dist/cron.js +143 -0
- package/dist/cron.js.map +1 -0
- package/dist/db/migrate.d.ts +6 -3
- package/dist/db/migrate.js +126 -10
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/table.d.ts +52 -7
- package/dist/db/table.js +105 -8
- package/dist/db/table.js.map +1 -1
- package/dist/describe.d.ts +19 -1
- package/dist/describe.js +6 -1
- package/dist/describe.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +177 -0
- package/dist/jobs.js +374 -0
- package/dist/jobs.js.map +1 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +43 -38
- package/dist/mcp.js.map +1 -1
- package/dist/openapi.js +3 -0
- package/dist/openapi.js.map +1 -1
- package/dist/rate-limit.d.ts +45 -0
- package/dist/rate-limit.js +93 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/route.d.ts +6 -0
- package/dist/route.js.map +1 -1
- package/dist/schema.d.ts +10 -2
- package/dist/schema.js +13 -1
- package/dist/schema.js.map +1 -1
- package/dist/serve.js +5 -2
- package/dist/serve.js.map +1 -1
- package/package.json +1 -1
- package/templates/app/AGENTS.md +10 -8
- package/templates/app/README.md +2 -0
- package/templates/app/package.json.tpl +1 -0
- package/templates/app/src/routes/notes.ts +1 -1
- package/templates/app/src/server.ts +10 -0
package/dist/jobs.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background jobs: work that runs after the request, with retries.
|
|
3
|
+
*
|
|
4
|
+
* export const sendWelcome = job("send-welcome", { input: t.object({ userId: t.uuid() }) })
|
|
5
|
+
* .handle(async ({ input, db }) => { ... })
|
|
6
|
+
*
|
|
7
|
+
* createApp({ jobs: [jobs] }) // import * as jobs from "./jobs.ts"
|
|
8
|
+
* await ctx.jobs.enqueue(sendWelcome, { userId }) // in a route handler, validated
|
|
9
|
+
*
|
|
10
|
+
* Jobs are plain values like routes. The queue is kept by a JobStore; app.jobs.start() runs due
|
|
11
|
+
* jobs in this process, app.jobs.runDue() runs them once (tests).
|
|
12
|
+
*/
|
|
13
|
+
import type { AI } from "./ai/index.ts";
|
|
14
|
+
import { type CronDef } from "./cron.ts";
|
|
15
|
+
import type { Adapter } from "./db/types.ts";
|
|
16
|
+
import type { AppDb } from "./route.ts";
|
|
17
|
+
import { type AnySchema, type Infer, type InferInput } from "./schema.ts";
|
|
18
|
+
export interface JobOptions<I extends AnySchema> {
|
|
19
|
+
/** Schema of the job's input. Checked when the job is enqueued and again when it runs. */
|
|
20
|
+
input: I;
|
|
21
|
+
/** Extra attempts after a failure, with growing delays (10s, 20s, 40s, ... up to 1 hour). Default 3. */
|
|
22
|
+
retries?: number;
|
|
23
|
+
/** One line shown in describe. */
|
|
24
|
+
summary?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface JobContext<Input> {
|
|
27
|
+
/** The validated input. */
|
|
28
|
+
input: Input;
|
|
29
|
+
db: AppDb;
|
|
30
|
+
ai: AI;
|
|
31
|
+
/** Enqueue follow-up jobs. */
|
|
32
|
+
jobs: Jobs;
|
|
33
|
+
/** Job id. */
|
|
34
|
+
id: string;
|
|
35
|
+
/** 1 on the first run, 2 on the first retry, and so on. */
|
|
36
|
+
attempt: number;
|
|
37
|
+
}
|
|
38
|
+
export interface JobDef<Input = any, Output = any> {
|
|
39
|
+
readonly kind: "inferlo.job";
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly options: JobOptions<AnySchema>;
|
|
42
|
+
readonly handler: (ctx: JobContext<Output>) => unknown;
|
|
43
|
+
/** Type only: what enqueue accepts. */
|
|
44
|
+
readonly $input?: Input;
|
|
45
|
+
}
|
|
46
|
+
/** What enqueue accepts for a job. */
|
|
47
|
+
export type JobInput<J> = J extends JobDef<infer I, any> ? I : never;
|
|
48
|
+
export interface EnqueueOptions {
|
|
49
|
+
/** Run no earlier than this time. Default: now. */
|
|
50
|
+
runAt?: Date;
|
|
51
|
+
}
|
|
52
|
+
export type JobStatus = "pending" | "running" | "done" | "failed";
|
|
53
|
+
export interface JobRecord {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
/** The input as stored (JSON). */
|
|
57
|
+
input: unknown;
|
|
58
|
+
status: JobStatus;
|
|
59
|
+
/** Runs started so far. */
|
|
60
|
+
attempts: number;
|
|
61
|
+
/** 1 + retries. */
|
|
62
|
+
maxAttempts: number;
|
|
63
|
+
/** When the job runs next (pending) or last ran. */
|
|
64
|
+
runAt: Date;
|
|
65
|
+
/** Message of the last failure. */
|
|
66
|
+
lastError?: string;
|
|
67
|
+
createdAt: Date;
|
|
68
|
+
updatedAt: Date;
|
|
69
|
+
}
|
|
70
|
+
export interface Jobs {
|
|
71
|
+
/** Validate the input and queue the job. Returns the queued record. */
|
|
72
|
+
enqueue<J extends JobDef>(job: J, input: JobInput<J>, options?: EnqueueOptions): Promise<JobRecord>;
|
|
73
|
+
}
|
|
74
|
+
export interface JobQueueConfig {
|
|
75
|
+
/**
|
|
76
|
+
* Where queued jobs are kept. "memory" (default): in this process, for development and tests.
|
|
77
|
+
* "database": in the inferlo_jobs table of the app's Postgres database, shared by every instance.
|
|
78
|
+
*/
|
|
79
|
+
store?: "memory" | "database";
|
|
80
|
+
}
|
|
81
|
+
/** Where queued jobs are kept. */
|
|
82
|
+
export interface JobStore {
|
|
83
|
+
/** Add a record. A record whose id is already stored is ignored (cron ticks use fixed ids). */
|
|
84
|
+
add(record: JobRecord): Promise<void>;
|
|
85
|
+
/** Mark up to limit pending jobs named in names with runAt <= now as running (attempts + 1) and return them, oldest first. */
|
|
86
|
+
claim(now: Date, limit: number, names: readonly string[]): Promise<JobRecord[]>;
|
|
87
|
+
update(id: string, patch: Partial<Pick<JobRecord, "status" | "runAt" | "lastError">>): Promise<void>;
|
|
88
|
+
get(id: string): Promise<JobRecord | null>;
|
|
89
|
+
/** Record that a running job is still alive (database store), so it is not picked up again as stuck. */
|
|
90
|
+
touch?(id: string): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
export interface RunResult {
|
|
93
|
+
/** Jobs that finished. */
|
|
94
|
+
done: number;
|
|
95
|
+
/** Jobs that failed and will run again later. */
|
|
96
|
+
retried: number;
|
|
97
|
+
/** Jobs that failed for the last time. */
|
|
98
|
+
failed: number;
|
|
99
|
+
}
|
|
100
|
+
export interface Worker {
|
|
101
|
+
/** Stop polling and wait for running jobs to finish. */
|
|
102
|
+
stop(): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
/** app.jobs: enqueue, inspect and run jobs. */
|
|
105
|
+
export interface AppJobs extends Jobs {
|
|
106
|
+
/** The registered jobs. */
|
|
107
|
+
readonly list: readonly JobDef[];
|
|
108
|
+
/** The registered crons: cron(schedule, job) values in createApp({ jobs }). */
|
|
109
|
+
readonly crons: readonly CronDef[];
|
|
110
|
+
/** Where queued jobs are kept (createApp({ jobQueue: { store } })). */
|
|
111
|
+
readonly queue: "memory" | "database";
|
|
112
|
+
/** A queued job by id, or null. */
|
|
113
|
+
get(id: string): Promise<JobRecord | null>;
|
|
114
|
+
/** Run every job that is due now (or at options.now), one after another, including follow-ups they enqueue, then return. For tests and scripts. */
|
|
115
|
+
runDue(options?: {
|
|
116
|
+
now?: Date;
|
|
117
|
+
}): Promise<RunResult>;
|
|
118
|
+
/** Run due jobs in this process every pollMs (default 1000) until stop() is called. Also queues each cron's job at every tick after the start. */
|
|
119
|
+
start(options?: {
|
|
120
|
+
pollMs?: number;
|
|
121
|
+
}): Worker;
|
|
122
|
+
}
|
|
123
|
+
/** Delay before retry n (1-based): 10s, 20s, 40s, ... at most one hour. */
|
|
124
|
+
export declare function retryDelayMs(attempt: number): number;
|
|
125
|
+
export declare class JobBuilder<I extends AnySchema> {
|
|
126
|
+
readonly name: string;
|
|
127
|
+
readonly options: JobOptions<I>;
|
|
128
|
+
constructor(name: string, options: JobOptions<I>);
|
|
129
|
+
/** The job's work. Throw to fail the attempt; it is retried until retries run out. */
|
|
130
|
+
handle(handler: (ctx: JobContext<Infer<I>>) => unknown): JobDef<InferInput<I>, Infer<I>>;
|
|
131
|
+
}
|
|
132
|
+
/** Define a background job: job("send-email", { input: t.object({ to: t.email() }) }).handle(fn). */
|
|
133
|
+
export declare function job<I extends AnySchema>(name: string, options: JobOptions<I>): JobBuilder<I>;
|
|
134
|
+
export declare function isJob(value: unknown): value is JobDef;
|
|
135
|
+
/** Anything that contains jobs: a job, a cron, an array, or a module (import * as jobs from "./jobs.ts"). */
|
|
136
|
+
export type JobSource = JobDef | CronDef | readonly JobSource[] | {
|
|
137
|
+
readonly [name: string]: unknown;
|
|
138
|
+
};
|
|
139
|
+
/** The jobs and crons in createApp({ jobs }). A cron's job is registered with it. */
|
|
140
|
+
export declare function collectJobs(sources: readonly JobSource[] | undefined): JobDef[];
|
|
141
|
+
export declare function collectCrons(sources: readonly JobSource[] | undefined): CronDef[];
|
|
142
|
+
/** Jobs kept in this process. They are lost when it stops, so use it for development and tests. */
|
|
143
|
+
export declare function memoryJobStore(): JobStore;
|
|
144
|
+
/** A running job whose worker has not reported for this long is treated as stuck (its process stopped) and run again. */
|
|
145
|
+
export declare const STALE_AFTER_MS: number;
|
|
146
|
+
/** How often a worker reports that a running job is still alive. */
|
|
147
|
+
export declare const HEARTBEAT_MS = 30000;
|
|
148
|
+
export declare const STOPPED_MESSAGE = "The worker stopped while running this job (the process exited or lost its database connection).";
|
|
149
|
+
/**
|
|
150
|
+
* Jobs in the inferlo_jobs table (created on first use), shared by every instance. Due jobs are
|
|
151
|
+
* claimed with FOR UPDATE SKIP LOCKED, so each one runs in one place. A running job whose worker
|
|
152
|
+
* stopped reporting (see STALE_AFTER_MS) is claimed again, or marked failed if it has no attempts left.
|
|
153
|
+
*/
|
|
154
|
+
export declare function databaseJobStore(adapter: Adapter, options?: {
|
|
155
|
+
staleAfterMs?: number;
|
|
156
|
+
}): JobStore;
|
|
157
|
+
/** Pick the job store for createApp({ jobQueue }). Raises IL6011 for an unknown store, IL6012 for "database" without Postgres. */
|
|
158
|
+
export declare function jobStoreFor(config: JobQueueConfig | undefined, adapter: Adapter): JobStore;
|
|
159
|
+
export interface JobRuntime {
|
|
160
|
+
db: AppDb;
|
|
161
|
+
ai: AI;
|
|
162
|
+
store: JobStore;
|
|
163
|
+
/** Name of the store, shown as app.jobs.queue. Default "memory". */
|
|
164
|
+
queue?: "memory" | "database";
|
|
165
|
+
/** How often a running job is reported alive to stores that support it. Default HEARTBEAT_MS. */
|
|
166
|
+
heartbeatMs?: number;
|
|
167
|
+
logger: {
|
|
168
|
+
info(message: string, data?: unknown): void;
|
|
169
|
+
error(message: string, data?: unknown): void;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** app.jobs plus what the framework tests reach directly. */
|
|
173
|
+
export interface InternalJobs extends AppJobs {
|
|
174
|
+
/** Queue the job of each cron's latest tick in (from, to]. */
|
|
175
|
+
queueCrons(from: Date, to: Date): Promise<void>;
|
|
176
|
+
}
|
|
177
|
+
export declare function createJobs(list: readonly JobDef[], runtime: JobRuntime, crons?: readonly CronDef[]): InternalJobs;
|
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { cronTickId, isCron } from "./cron.js";
|
|
2
|
+
import { InferloError } from "./errors.js";
|
|
3
|
+
import { ValidationError } from "./schema.js";
|
|
4
|
+
const MAX_RETRIES = 25;
|
|
5
|
+
const BASE_DELAY_MS = 10_000;
|
|
6
|
+
const MAX_DELAY_MS = 3_600_000;
|
|
7
|
+
/** Delay before retry n (1-based): 10s, 20s, 40s, ... at most one hour. */
|
|
8
|
+
export function retryDelayMs(attempt) {
|
|
9
|
+
return Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
|
|
10
|
+
}
|
|
11
|
+
export class JobBuilder {
|
|
12
|
+
name;
|
|
13
|
+
options;
|
|
14
|
+
constructor(name, options) {
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
/** The job's work. Throw to fail the attempt; it is retried until retries run out. */
|
|
19
|
+
handle(handler) {
|
|
20
|
+
return { kind: "inferlo.job", name: this.name, options: this.options, handler: handler };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const JOB_NAME = /^[a-z][a-z0-9-]{0,62}$/;
|
|
24
|
+
/** Define a background job: job("send-email", { input: t.object({ to: t.email() }) }).handle(fn). */
|
|
25
|
+
export function job(name, options) {
|
|
26
|
+
checkJob(name, options);
|
|
27
|
+
return new JobBuilder(name, options);
|
|
28
|
+
}
|
|
29
|
+
function checkJob(name, options) {
|
|
30
|
+
const o = options;
|
|
31
|
+
const problems = [];
|
|
32
|
+
if (typeof name !== "string" || !JOB_NAME.test(name))
|
|
33
|
+
problems.push("the name must be lowercase letters, digits and dashes, starting with a letter");
|
|
34
|
+
if (!o || typeof o !== "object")
|
|
35
|
+
problems.push("options must be an object with an input schema");
|
|
36
|
+
else {
|
|
37
|
+
if (!o.input || typeof o.input.safeParse !== "function")
|
|
38
|
+
problems.push("input must be a schema such as t.object({ ... })");
|
|
39
|
+
if (o.retries !== undefined && (!Number.isInteger(o.retries) || o.retries < 0 || o.retries > MAX_RETRIES))
|
|
40
|
+
problems.push(`retries must be a whole number from 0 to ${MAX_RETRIES}`);
|
|
41
|
+
}
|
|
42
|
+
if (problems.length) {
|
|
43
|
+
throw new InferloError("IL6009", `Invalid job "${String(name)}": ${problems.join(", ")}.`, {
|
|
44
|
+
fix: 'Use job("send-email", { input: t.object({ to: t.email() }), retries: 3 }).handle(async ({ input }) => { ... }).',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function isJob(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && value.kind === "inferlo.job";
|
|
50
|
+
}
|
|
51
|
+
/** The jobs and crons in createApp({ jobs }). A cron's job is registered with it. */
|
|
52
|
+
export function collectJobs(sources) {
|
|
53
|
+
return collect(sources).jobs;
|
|
54
|
+
}
|
|
55
|
+
export function collectCrons(sources) {
|
|
56
|
+
return collect(sources).crons;
|
|
57
|
+
}
|
|
58
|
+
function collect(sources) {
|
|
59
|
+
const out = [];
|
|
60
|
+
const crons = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
const visit = (source) => {
|
|
63
|
+
if (!source || typeof source !== "object" || seen.has(source))
|
|
64
|
+
return;
|
|
65
|
+
seen.add(source);
|
|
66
|
+
if (isJob(source))
|
|
67
|
+
out.push(source);
|
|
68
|
+
else if (isCron(source)) {
|
|
69
|
+
crons.push(source);
|
|
70
|
+
visit(source.job);
|
|
71
|
+
}
|
|
72
|
+
else if (Array.isArray(source))
|
|
73
|
+
source.forEach(visit);
|
|
74
|
+
else
|
|
75
|
+
for (const value of Object.values(source))
|
|
76
|
+
if (isJob(value) || isCron(value) || Array.isArray(value))
|
|
77
|
+
visit(value);
|
|
78
|
+
};
|
|
79
|
+
visit(sources);
|
|
80
|
+
const names = new Map();
|
|
81
|
+
for (const j of out) {
|
|
82
|
+
if (names.has(j.name))
|
|
83
|
+
throw new InferloError("IL6009", `Two jobs are named "${j.name}".`, { fix: "Give each job a unique name." });
|
|
84
|
+
names.set(j.name, j);
|
|
85
|
+
}
|
|
86
|
+
return { jobs: out, crons };
|
|
87
|
+
}
|
|
88
|
+
/** Jobs kept in this process. They are lost when it stops, so use it for development and tests. */
|
|
89
|
+
export function memoryJobStore() {
|
|
90
|
+
const records = new Map();
|
|
91
|
+
const copy = (r) => ({ ...r, runAt: new Date(r.runAt), createdAt: new Date(r.createdAt), updatedAt: new Date(r.updatedAt) });
|
|
92
|
+
return {
|
|
93
|
+
async add(record) {
|
|
94
|
+
if (!records.has(record.id))
|
|
95
|
+
records.set(record.id, copy(record));
|
|
96
|
+
},
|
|
97
|
+
async claim(now, limit, names) {
|
|
98
|
+
const due = [...records.values()]
|
|
99
|
+
.filter((r) => r.status === "pending" && r.runAt.getTime() <= now.getTime() && names.includes(r.name))
|
|
100
|
+
.sort((a, b) => a.runAt.getTime() - b.runAt.getTime() || a.createdAt.getTime() - b.createdAt.getTime())
|
|
101
|
+
.slice(0, limit);
|
|
102
|
+
for (const r of due)
|
|
103
|
+
Object.assign(r, { status: "running", attempts: r.attempts + 1, updatedAt: new Date() });
|
|
104
|
+
return due.map(copy);
|
|
105
|
+
},
|
|
106
|
+
async update(id, patch) {
|
|
107
|
+
const r = records.get(id);
|
|
108
|
+
if (r)
|
|
109
|
+
Object.assign(r, patch, { updatedAt: new Date() });
|
|
110
|
+
},
|
|
111
|
+
async get(id) {
|
|
112
|
+
const r = records.get(id);
|
|
113
|
+
return r ? copy(r) : null;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** A running job whose worker has not reported for this long is treated as stuck (its process stopped) and run again. */
|
|
118
|
+
export const STALE_AFTER_MS = 5 * 60_000;
|
|
119
|
+
/** How often a worker reports that a running job is still alive. */
|
|
120
|
+
export const HEARTBEAT_MS = 30_000;
|
|
121
|
+
export const STOPPED_MESSAGE = "The worker stopped while running this job (the process exited or lost its database connection).";
|
|
122
|
+
const JOB_COLUMNS = `"id", "name", "input", "status", "attempts", "max_attempts", "run_at", "last_error", "created_at", "updated_at"`;
|
|
123
|
+
function fromRow(row) {
|
|
124
|
+
const record = {
|
|
125
|
+
id: String(row.id),
|
|
126
|
+
name: String(row.name),
|
|
127
|
+
input: typeof row.input === "string" ? JSON.parse(row.input) : row.input,
|
|
128
|
+
status: row.status,
|
|
129
|
+
attempts: Number(row.attempts),
|
|
130
|
+
maxAttempts: Number(row.max_attempts),
|
|
131
|
+
runAt: new Date(row.run_at),
|
|
132
|
+
createdAt: new Date(row.created_at),
|
|
133
|
+
updatedAt: new Date(row.updated_at),
|
|
134
|
+
};
|
|
135
|
+
if (row.last_error !== null && row.last_error !== undefined)
|
|
136
|
+
record.lastError = String(row.last_error);
|
|
137
|
+
return record;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Jobs in the inferlo_jobs table (created on first use), shared by every instance. Due jobs are
|
|
141
|
+
* claimed with FOR UPDATE SKIP LOCKED, so each one runs in one place. A running job whose worker
|
|
142
|
+
* stopped reporting (see STALE_AFTER_MS) is claimed again, or marked failed if it has no attempts left.
|
|
143
|
+
*/
|
|
144
|
+
export function databaseJobStore(adapter, options = {}) {
|
|
145
|
+
const sql = adapter.sql?.bind(adapter);
|
|
146
|
+
if (!sql) {
|
|
147
|
+
throw new InferloError("IL6012", `jobQueue store "database" needs a Postgres database, but the app uses the ${adapter.dialect} adapter.`, {
|
|
148
|
+
fix: 'Use postgres() from "@browserlessapi/inferlo/postgres" for createApp({ db }), or jobQueue: { store: "memory" }.',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const staleAfterMs = options.staleAfterMs ?? STALE_AFTER_MS;
|
|
152
|
+
let ready;
|
|
153
|
+
const create = async () => {
|
|
154
|
+
try {
|
|
155
|
+
await sql(`CREATE TABLE IF NOT EXISTS "inferlo_jobs" (
|
|
156
|
+
"id" uuid PRIMARY KEY, "name" text NOT NULL, "input" jsonb NOT NULL, "status" text NOT NULL,
|
|
157
|
+
"attempts" integer NOT NULL, "max_attempts" integer NOT NULL, "run_at" timestamptz NOT NULL,
|
|
158
|
+
"last_error" text, "created_at" timestamptz NOT NULL, "updated_at" timestamptz NOT NULL)`);
|
|
159
|
+
await sql('CREATE INDEX IF NOT EXISTS "inferlo_jobs_due_idx" ON "inferlo_jobs" ("status", "run_at")');
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Two instances creating the table at the same moment: the other one won.
|
|
163
|
+
await sql('SELECT 1 FROM "inferlo_jobs" LIMIT 1');
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const table = async () => {
|
|
167
|
+
ready ??= create().catch((error) => {
|
|
168
|
+
ready = undefined;
|
|
169
|
+
throw error;
|
|
170
|
+
});
|
|
171
|
+
await ready;
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
async add(r) {
|
|
175
|
+
await table();
|
|
176
|
+
await sql(`INSERT INTO "inferlo_jobs" (${JOB_COLUMNS}) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT ("id") DO NOTHING`, [
|
|
177
|
+
r.id, r.name, JSON.stringify(r.input), r.status, r.attempts, r.maxAttempts, r.runAt, r.lastError ?? null, r.createdAt, r.updatedAt,
|
|
178
|
+
]);
|
|
179
|
+
},
|
|
180
|
+
async claim(now, limit, names) {
|
|
181
|
+
await table();
|
|
182
|
+
if (!names.length)
|
|
183
|
+
return [];
|
|
184
|
+
const stale = `"status" = 'running' AND "updated_at" < now() - $1 * interval '1 millisecond'`;
|
|
185
|
+
// Stuck jobs without attempts left are failed, not run again.
|
|
186
|
+
await sql(`UPDATE "inferlo_jobs" SET "status" = 'failed', "last_error" = $2, "updated_at" = now() WHERE ${stale} AND "attempts" >= "max_attempts" AND "name" = ANY($3)`, [
|
|
187
|
+
staleAfterMs, STOPPED_MESSAGE, names,
|
|
188
|
+
]);
|
|
189
|
+
const rows = await sql(`WITH due AS (
|
|
190
|
+
SELECT "id" FROM "inferlo_jobs"
|
|
191
|
+
WHERE "name" = ANY($3) AND (("status" = 'pending' AND "run_at" <= $4) OR (${stale} AND "attempts" < "max_attempts"))
|
|
192
|
+
ORDER BY "run_at", "created_at" LIMIT $5 FOR UPDATE SKIP LOCKED)
|
|
193
|
+
UPDATE "inferlo_jobs" j SET "status" = 'running', "attempts" = j."attempts" + 1, "updated_at" = now(),
|
|
194
|
+
"last_error" = CASE WHEN j."status" = 'running' THEN $2 ELSE j."last_error" END
|
|
195
|
+
FROM due WHERE j."id" = due."id"
|
|
196
|
+
RETURNING j.*`, [staleAfterMs, STOPPED_MESSAGE, names, now, limit]);
|
|
197
|
+
return rows
|
|
198
|
+
.map(fromRow)
|
|
199
|
+
.sort((a, b) => a.runAt.getTime() - b.runAt.getTime() || a.createdAt.getTime() - b.createdAt.getTime());
|
|
200
|
+
},
|
|
201
|
+
async update(id, patch) {
|
|
202
|
+
await table();
|
|
203
|
+
const sets = ['"updated_at" = now()'];
|
|
204
|
+
const params = [id];
|
|
205
|
+
if (patch.status !== undefined)
|
|
206
|
+
sets.push(`"status" = $${params.push(patch.status)}`);
|
|
207
|
+
if (patch.runAt !== undefined)
|
|
208
|
+
sets.push(`"run_at" = $${params.push(patch.runAt)}`);
|
|
209
|
+
if (patch.lastError !== undefined)
|
|
210
|
+
sets.push(`"last_error" = $${params.push(patch.lastError)}`);
|
|
211
|
+
await sql(`UPDATE "inferlo_jobs" SET ${sets.join(", ")} WHERE "id" = $1`, params);
|
|
212
|
+
},
|
|
213
|
+
async get(id) {
|
|
214
|
+
await table();
|
|
215
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id))
|
|
216
|
+
return null;
|
|
217
|
+
const rows = await sql(`SELECT ${JOB_COLUMNS} FROM "inferlo_jobs" WHERE "id" = $1`, [id]);
|
|
218
|
+
return rows[0] ? fromRow(rows[0]) : null;
|
|
219
|
+
},
|
|
220
|
+
async touch(id) {
|
|
221
|
+
await table();
|
|
222
|
+
await sql(`UPDATE "inferlo_jobs" SET "updated_at" = now() WHERE "id" = $1 AND "status" = 'running'`, [id]);
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Pick the job store for createApp({ jobQueue }). Raises IL6011 for an unknown store, IL6012 for "database" without Postgres. */
|
|
227
|
+
export function jobStoreFor(config, adapter) {
|
|
228
|
+
const store = config?.store ?? "memory";
|
|
229
|
+
if (store === "memory")
|
|
230
|
+
return memoryJobStore();
|
|
231
|
+
if (store === "database")
|
|
232
|
+
return databaseJobStore(adapter);
|
|
233
|
+
throw new InferloError("IL6011", `Invalid jobQueue store "${String(store)}".`, { fix: 'Use createApp({ jobQueue: { store: "memory" } }) (the default) or { store: "database" }.' });
|
|
234
|
+
}
|
|
235
|
+
export function createJobs(list, runtime, crons = []) {
|
|
236
|
+
const byName = new Map(list.map((j) => [j.name, j]));
|
|
237
|
+
const names = list.map((j) => j.name);
|
|
238
|
+
const registered = (j) => {
|
|
239
|
+
const found = isJob(j) ? byName.get(j.name) : undefined;
|
|
240
|
+
if (!found) {
|
|
241
|
+
const name = isJob(j) ? `"${j.name}"` : "This value";
|
|
242
|
+
throw new InferloError("IL6010", `${name} is not a registered job.`, {
|
|
243
|
+
fix: "Pass a job defined with job(...).handle(...) and add it (or its module) to createApp({ jobs: [...] }).",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return found;
|
|
247
|
+
};
|
|
248
|
+
const parse = (j, input) => {
|
|
249
|
+
const result = j.options.input.safeParse(input);
|
|
250
|
+
if (!result.ok)
|
|
251
|
+
throw new ValidationError(result.issues, `input for job "${j.name}"`);
|
|
252
|
+
return result.value;
|
|
253
|
+
};
|
|
254
|
+
const record = (def, input, runAt, id) => {
|
|
255
|
+
// Stored as JSON, so it survives a restart with a database store; parsed again when it runs.
|
|
256
|
+
const stored = JSON.parse(JSON.stringify(parse(def, input) ?? null));
|
|
257
|
+
const now = new Date();
|
|
258
|
+
return {
|
|
259
|
+
id,
|
|
260
|
+
name: def.name,
|
|
261
|
+
input: stored,
|
|
262
|
+
status: "pending",
|
|
263
|
+
attempts: 0,
|
|
264
|
+
maxAttempts: 1 + (def.options.retries ?? 3),
|
|
265
|
+
runAt: runAt ?? now,
|
|
266
|
+
createdAt: now,
|
|
267
|
+
updatedAt: now,
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
/** Queue the job of every cron tick in (from, to]. Only the latest tick of each cron is queued, so a pause does not cause a burst. */
|
|
271
|
+
const queueCrons = async (from, to) => {
|
|
272
|
+
for (const c of crons) {
|
|
273
|
+
let tick = c.next(from);
|
|
274
|
+
if (tick.getTime() > to.getTime())
|
|
275
|
+
continue;
|
|
276
|
+
for (let later = c.next(tick); later.getTime() <= to.getTime(); later = c.next(later))
|
|
277
|
+
tick = later;
|
|
278
|
+
await runtime.store.add(record(c.job, {}, tick, await cronTickId(c, tick)));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const jobs = {
|
|
282
|
+
list,
|
|
283
|
+
crons,
|
|
284
|
+
queueCrons,
|
|
285
|
+
queue: runtime.queue ?? "memory",
|
|
286
|
+
async enqueue(j, input, options = {}) {
|
|
287
|
+
const def = registered(j);
|
|
288
|
+
if (options.runAt !== undefined && (!(options.runAt instanceof Date) || Number.isNaN(options.runAt.getTime()))) {
|
|
289
|
+
throw new InferloError("IL6009", `runAt for job "${def.name}" must be a valid Date.`, { fix: "Pass runAt: new Date(Date.now() + 60_000) or leave it out to run now." });
|
|
290
|
+
}
|
|
291
|
+
const queued = record(def, input, options.runAt, crypto.randomUUID());
|
|
292
|
+
await runtime.store.add(queued);
|
|
293
|
+
return queued;
|
|
294
|
+
},
|
|
295
|
+
get: (id) => runtime.store.get(id),
|
|
296
|
+
async runDue(options = {}) {
|
|
297
|
+
const result = { done: 0, retried: 0, failed: 0 };
|
|
298
|
+
while (true) {
|
|
299
|
+
// Without options.now, jobs enqueued by the jobs that run (follow-ups) run in the same call.
|
|
300
|
+
const now = options.now ?? new Date();
|
|
301
|
+
const [record] = await runtime.store.claim(now, 1, names);
|
|
302
|
+
if (!record)
|
|
303
|
+
return result;
|
|
304
|
+
result[await run(record, now)]++;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
start(options = {}) {
|
|
308
|
+
const pollMs = options.pollMs ?? 1000;
|
|
309
|
+
let stopped = false;
|
|
310
|
+
let timer;
|
|
311
|
+
let wake;
|
|
312
|
+
let cronFrom = new Date();
|
|
313
|
+
const loop = (async () => {
|
|
314
|
+
while (!stopped) {
|
|
315
|
+
try {
|
|
316
|
+
if (crons.length) {
|
|
317
|
+
const now = new Date();
|
|
318
|
+
await queueCrons(cronFrom, now);
|
|
319
|
+
cronFrom = now;
|
|
320
|
+
}
|
|
321
|
+
await jobs.runDue();
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
runtime.logger.error("job worker error", error);
|
|
325
|
+
}
|
|
326
|
+
if (stopped)
|
|
327
|
+
break;
|
|
328
|
+
await new Promise((resolve) => {
|
|
329
|
+
wake = resolve;
|
|
330
|
+
timer = setTimeout(resolve, pollMs);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
})();
|
|
334
|
+
return {
|
|
335
|
+
async stop() {
|
|
336
|
+
stopped = true;
|
|
337
|
+
if (timer)
|
|
338
|
+
clearTimeout(timer);
|
|
339
|
+
wake?.();
|
|
340
|
+
await loop;
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
async function run(record, now) {
|
|
346
|
+
const def = byName.get(record.name);
|
|
347
|
+
const touch = runtime.store.touch?.bind(runtime.store);
|
|
348
|
+
const heartbeat = touch ? setInterval(() => void touch(record.id).catch(() => { }), runtime.heartbeatMs ?? HEARTBEAT_MS) : undefined;
|
|
349
|
+
try {
|
|
350
|
+
if (!def)
|
|
351
|
+
throw new InferloError("IL6010", `"${record.name}" is not a registered job.`, { fix: "Add the job to createApp({ jobs: [...] }) in the process that runs jobs." });
|
|
352
|
+
await def.handler({ input: parse(def, record.input), db: runtime.db, ai: runtime.ai, jobs, id: record.id, attempt: record.attempts });
|
|
353
|
+
await runtime.store.update(record.id, { status: "done" });
|
|
354
|
+
return "done";
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
358
|
+
if (record.attempts < record.maxAttempts) {
|
|
359
|
+
await runtime.store.update(record.id, { status: "pending", lastError: message, runAt: new Date(now.getTime() + retryDelayMs(record.attempts)) });
|
|
360
|
+
runtime.logger.error(`job "${record.name}" failed (attempt ${record.attempts} of ${record.maxAttempts}), will retry`, { id: record.id, error: message });
|
|
361
|
+
return "retried";
|
|
362
|
+
}
|
|
363
|
+
await runtime.store.update(record.id, { status: "failed", lastError: message });
|
|
364
|
+
runtime.logger.error(`job "${record.name}" failed after ${record.attempts} attempt(s)`, { id: record.id, error: message });
|
|
365
|
+
return "failed";
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
if (heartbeat)
|
|
369
|
+
clearInterval(heartbeat);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return jobs;
|
|
373
|
+
}
|
|
374
|
+
//# sourceMappingURL=jobs.js.map
|
package/dist/jobs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../src/jobs.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -15,4 +15,8 @@ export interface ToolDefinition {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function toolName(route: RouteDef): string;
|
|
17
17
|
export declare function toolDefinitions(app: App<any>): ToolDefinition[];
|
|
18
|
+
/** The tool definition of one route: its name, description and a JSON Schema of params, query and body together. */
|
|
19
|
+
export declare function toolDefinition(route: RouteDef): ToolDefinition;
|
|
18
20
|
export declare function handleMcp(app: App<any>, req: Request, headers: Headers, testUsers: WeakMap<Request, unknown>): Promise<Response>;
|
|
21
|
+
/** Run a tool call as an HTTP request to its route, forwarding the caller's credentials, so auth, roles and validation apply. */
|
|
22
|
+
export declare function callTool(app: App<any>, tool: ToolDefinition, args: Record<string, unknown>, original: Request, testUsers: WeakMap<Request, unknown>): Promise<Response>;
|