@foundryprotocol/0gkit-jobs 0.5.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Foundry Protocol contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # `@foundryprotocol/0gkit-jobs`
2
+
3
+ Durable async job runner for long-running 0G operations (inference, multi-step
4
+ agents, batched uploads, DA publishes). Three swappable backends behind one
5
+ interface; zod-typed `jobs.define()`; HMAC-signed webhook delivery on state
6
+ transitions; graceful shutdown via `AbortSignal` (designed for Vercel Fluid
7
+ Compute and similar serverless runtimes).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @foundryprotocol/0gkit-jobs @foundryprotocol/0gkit-core zod
13
+
14
+ # redis backend only
15
+ pnpm add ioredis
16
+ ```
17
+
18
+ ## Quickstart
19
+
20
+ ```ts
21
+ import { JobRunner, jobs } from "@foundryprotocol/0gkit-jobs";
22
+ import { MemoryBackend } from "@foundryprotocol/0gkit-jobs/backends/memory";
23
+ import { z } from "zod";
24
+
25
+ const InferenceJob = jobs.define({
26
+ name: "inference",
27
+ input: z.object({ prompt: z.string(), model: z.string() }),
28
+ output: z.object({ text: z.string() }),
29
+ handler: async ({ input, signal }) => {
30
+ // Use `signal` for graceful shutdown; fail-fast in long handlers.
31
+ if (signal.aborted) throw new Error("shutting down");
32
+ return { text: "..." };
33
+ },
34
+ });
35
+
36
+ const runner = new JobRunner({
37
+ backend: new MemoryBackend(),
38
+ signer,
39
+ webhook: { url: process.env.WEBHOOK_URL!, secret: process.env.WEBHOOK_SECRET! },
40
+ });
41
+ runner.register(InferenceJob);
42
+ await runner.start({ concurrency: 4 });
43
+
44
+ const id = await runner.enqueue(InferenceJob, { prompt: "hi", model: "..." });
45
+ const final = await runner.waitFor(id);
46
+ ```
47
+
48
+ ## Backends
49
+
50
+ | Backend | Install | When to use |
51
+ | ------- | ------------------------------- | -------------------------------- |
52
+ | memory | (built-in) | dev, tests, ephemeral workflows |
53
+ | sqlite | (built-in via `better-sqlite3`) | single-node prod, no extra infra |
54
+ | redis | optional peer `ioredis` | multi-node prod, fan-out |
55
+
56
+ ## Webhook verification (server side)
57
+
58
+ ```ts
59
+ import { jobs } from "@foundryprotocol/0gkit-jobs";
60
+
61
+ app.post("/api/jobs/webhook", express.text({ type: "*/*" }), (req, res) => {
62
+ const ok = jobs.verifyWebhook({
63
+ body: req.body,
64
+ signature: req.header("x-0gkit-signature") ?? "",
65
+ secret: process.env.JOBS_SECRET!,
66
+ });
67
+ if (!ok) return res.status(401).send("bad signature");
68
+ // ... dedupe on (jobId + newState)
69
+ });
70
+ ```
71
+
72
+ ## Vercel Fluid Compute
73
+
74
+ Functions get a grace period on shutdown. Register a `beforeExit` hook that
75
+ calls `runner.stop({ drain: true, timeoutMs: 25_000 })` so in-flight jobs land
76
+ cleanly before the instance is reaped.
77
+
78
+ ## Error codes
79
+
80
+ - [`JOBS_BACKEND_UNREACHABLE`](https://0gkit.dev/errors/JOBS_BACKEND_UNREACHABLE)
81
+ - [`JOBS_JOB_NOT_FOUND`](https://0gkit.dev/errors/JOBS_JOB_NOT_FOUND)
82
+ - [`JOBS_HANDLER_THREW`](https://0gkit.dev/errors/JOBS_HANDLER_THREW)
83
+ - [`JOBS_WEBHOOK_BAD_SIGNATURE`](https://0gkit.dev/errors/JOBS_WEBHOOK_BAD_SIGNATURE)
84
+
85
+ ## At-least-once delivery
86
+
87
+ A worker that crashes between handler completion and `backend.complete()`
88
+ returning will retry on next claim. **Handlers must be idempotent on their
89
+ inputs** — use `jobId` as the idempotency key for any external side effect.
90
+ Webhook receivers should dedupe on `(jobId, newState)`.
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,17 @@
1
+ import { J as JobBackend, d as JobRecord } from '../types-CTdOcaj1.js';
2
+ import 'zod';
3
+ import '@foundryprotocol/0gkit-core';
4
+
5
+ declare class MemoryBackend implements JobBackend {
6
+ private store;
7
+ private order;
8
+ enqueue<I>(name: string, input: I): Promise<string>;
9
+ claim(): Promise<JobRecord | null>;
10
+ complete<O>(jobId: string, result: O): Promise<void>;
11
+ fail(jobId: string, error: string, retry: boolean): Promise<void>;
12
+ cancel(jobId: string): Promise<void>;
13
+ status(jobId: string): Promise<JobRecord | null>;
14
+ close(): Promise<void>;
15
+ }
16
+
17
+ export { MemoryBackend };
@@ -0,0 +1,92 @@
1
+ import { ZeroGError } from '@foundryprotocol/0gkit-core';
2
+
3
+ // src/backends/memory.ts
4
+ function makeId() {
5
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
6
+ }
7
+ var MemoryBackend = class {
8
+ store = /* @__PURE__ */ new Map();
9
+ order = [];
10
+ async enqueue(name, input) {
11
+ const jobId = makeId();
12
+ const rec = {
13
+ id: jobId,
14
+ name,
15
+ state: "queued",
16
+ input,
17
+ metadata: { attempts: 0, createdAt: Date.now() }
18
+ };
19
+ this.store.set(jobId, rec);
20
+ this.order.push(jobId);
21
+ return jobId;
22
+ }
23
+ async claim() {
24
+ for (const jobId of this.order) {
25
+ const rec = this.store.get(jobId);
26
+ if (rec && rec.state === "queued") {
27
+ rec.state = "running";
28
+ rec.metadata.attempts += 1;
29
+ rec.metadata.startedAt = Date.now();
30
+ return rec;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ async complete(jobId, result) {
36
+ const rec = this.store.get(jobId);
37
+ if (!rec) {
38
+ throw new ZeroGError(
39
+ "JOBS_JOB_NOT_FOUND",
40
+ `job ${jobId} not found`,
41
+ "verify the id returned by enqueue() and ensure the backend instance is the same one that enqueued it."
42
+ );
43
+ }
44
+ rec.state = "done";
45
+ rec.result = result;
46
+ rec.metadata.finishedAt = Date.now();
47
+ }
48
+ async fail(jobId, error, retry) {
49
+ const rec = this.store.get(jobId);
50
+ if (!rec) {
51
+ throw new ZeroGError(
52
+ "JOBS_JOB_NOT_FOUND",
53
+ `job ${jobId} not found`,
54
+ "verify the id"
55
+ );
56
+ }
57
+ rec.metadata.lastError = error;
58
+ rec.metadata.finishedAt = Date.now();
59
+ if (retry) {
60
+ rec.state = "queued";
61
+ rec.metadata.startedAt = void 0;
62
+ } else {
63
+ rec.state = "failed";
64
+ rec.error = error;
65
+ }
66
+ }
67
+ async cancel(jobId) {
68
+ const rec = this.store.get(jobId);
69
+ if (!rec) {
70
+ throw new ZeroGError(
71
+ "JOBS_JOB_NOT_FOUND",
72
+ `job ${jobId} not found`,
73
+ "verify the id"
74
+ );
75
+ }
76
+ if (rec.state === "running" || rec.state === "queued") {
77
+ rec.state = "cancelled";
78
+ rec.metadata.finishedAt = Date.now();
79
+ }
80
+ }
81
+ async status(jobId) {
82
+ return this.store.get(jobId) ?? null;
83
+ }
84
+ async close() {
85
+ this.store.clear();
86
+ this.order = [];
87
+ }
88
+ };
89
+
90
+ export { MemoryBackend };
91
+ //# sourceMappingURL=memory.js.map
92
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/backends/memory.ts"],"names":[],"mappings":"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","file":"memory.js","sourcesContent":["import { ZeroGError } from \"@foundryprotocol/0gkit-core\";\nimport type { JobBackend, JobRecord } from \"../types.js\";\n\nfunction makeId(): string {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport class MemoryBackend implements JobBackend {\n private store = new Map<string, JobRecord>();\n private order: string[] = [];\n\n async enqueue<I>(name: string, input: I): Promise<string> {\n const jobId = makeId();\n const rec: JobRecord<I, unknown> = {\n id: jobId,\n name,\n state: \"queued\",\n input,\n metadata: { attempts: 0, createdAt: Date.now() },\n };\n this.store.set(jobId, rec as JobRecord);\n this.order.push(jobId);\n return jobId;\n }\n\n async claim(): Promise<JobRecord | null> {\n for (const jobId of this.order) {\n const rec = this.store.get(jobId);\n if (rec && rec.state === \"queued\") {\n rec.state = \"running\";\n rec.metadata.attempts += 1;\n rec.metadata.startedAt = Date.now();\n return rec;\n }\n }\n return null;\n }\n\n async complete<O>(jobId: string, result: O): Promise<void> {\n const rec = this.store.get(jobId);\n if (!rec) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id returned by enqueue() and ensure the backend instance is the same one that enqueued it.\"\n );\n }\n rec.state = \"done\";\n rec.result = result;\n rec.metadata.finishedAt = Date.now();\n }\n\n async fail(jobId: string, error: string, retry: boolean): Promise<void> {\n const rec = this.store.get(jobId);\n if (!rec) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n rec.metadata.lastError = error;\n rec.metadata.finishedAt = Date.now();\n if (retry) {\n rec.state = \"queued\";\n rec.metadata.startedAt = undefined;\n } else {\n rec.state = \"failed\";\n rec.error = error;\n }\n }\n\n async cancel(jobId: string): Promise<void> {\n const rec = this.store.get(jobId);\n if (!rec) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n if (rec.state === \"running\" || rec.state === \"queued\") {\n rec.state = \"cancelled\";\n rec.metadata.finishedAt = Date.now();\n }\n }\n\n async status(jobId: string): Promise<JobRecord | null> {\n return this.store.get(jobId) ?? null;\n }\n\n async close(): Promise<void> {\n this.store.clear();\n this.order = [];\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { J as JobBackend, d as JobRecord } from '../types-CTdOcaj1.js';
2
+ import 'zod';
3
+ import '@foundryprotocol/0gkit-core';
4
+
5
+ interface RedisOpts {
6
+ url: string;
7
+ keyPrefix?: string;
8
+ }
9
+ declare class RedisBackend implements JobBackend {
10
+ private client;
11
+ private keyPrefix;
12
+ private ready;
13
+ constructor(opts: RedisOpts);
14
+ private k;
15
+ enqueue<I>(name: string, input: I): Promise<string>;
16
+ claim(): Promise<JobRecord | null>;
17
+ complete<O>(jobId: string, result: O): Promise<void>;
18
+ fail(jobId: string, error: string, retry: boolean): Promise<void>;
19
+ cancel(jobId: string): Promise<void>;
20
+ status(jobId: string): Promise<JobRecord | null>;
21
+ close(): Promise<void>;
22
+ }
23
+
24
+ export { RedisBackend };
@@ -0,0 +1,136 @@
1
+ import { ZeroGError } from '@foundryprotocol/0gkit-core';
2
+
3
+ // src/backends/redis.ts
4
+ function makeId() {
5
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
6
+ }
7
+ async function loadIoredis() {
8
+ try {
9
+ const mod = await import(["ioredis"].join("/"));
10
+ const Ctor = mod.default ?? mod.Redis;
11
+ if (!Ctor) {
12
+ throw new Error("ioredis loaded but no Redis constructor found");
13
+ }
14
+ return Ctor;
15
+ } catch {
16
+ throw new ZeroGError(
17
+ "JOBS_BACKEND_UNREACHABLE",
18
+ "ioredis is not installed \u2014 the redis JobBackend requires it as an optional peer.",
19
+ "run `pnpm add ioredis` (or `npm install ioredis`) in your project, then retry."
20
+ );
21
+ }
22
+ }
23
+ var RedisBackend = class {
24
+ client;
25
+ keyPrefix;
26
+ ready;
27
+ constructor(opts) {
28
+ this.keyPrefix = opts.keyPrefix ?? "0gkit:jobs";
29
+ this.ready = (async () => {
30
+ const Redis = await loadIoredis();
31
+ this.client = new Redis(opts.url);
32
+ })();
33
+ }
34
+ k(rest) {
35
+ return `${this.keyPrefix}:${rest}`;
36
+ }
37
+ async enqueue(name, input) {
38
+ await this.ready;
39
+ const jobId = makeId();
40
+ const rec = {
41
+ id: jobId,
42
+ name,
43
+ state: "queued",
44
+ input,
45
+ metadata: { attempts: 0, createdAt: Date.now() }
46
+ };
47
+ await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));
48
+ await this.client.rpush(this.k("q"), jobId);
49
+ return jobId;
50
+ }
51
+ async claim() {
52
+ await this.ready;
53
+ const jobId = await this.client.lpop(this.k("q"));
54
+ if (!jobId) return null;
55
+ const raw = await this.client.get(this.k(`rec:${jobId}`));
56
+ if (!raw) return null;
57
+ const rec = JSON.parse(raw);
58
+ if (rec.state !== "queued") return null;
59
+ rec.state = "running";
60
+ rec.metadata.attempts += 1;
61
+ rec.metadata.startedAt = Date.now();
62
+ await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));
63
+ return rec;
64
+ }
65
+ async complete(jobId, result) {
66
+ await this.ready;
67
+ const raw = await this.client.get(this.k(`rec:${jobId}`));
68
+ if (!raw) {
69
+ throw new ZeroGError(
70
+ "JOBS_JOB_NOT_FOUND",
71
+ `job ${jobId} not found`,
72
+ "verify the id"
73
+ );
74
+ }
75
+ const rec = JSON.parse(raw);
76
+ rec.state = "done";
77
+ rec.result = result;
78
+ rec.metadata.finishedAt = Date.now();
79
+ await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));
80
+ }
81
+ async fail(jobId, error, retry) {
82
+ await this.ready;
83
+ const raw = await this.client.get(this.k(`rec:${jobId}`));
84
+ if (!raw) {
85
+ throw new ZeroGError(
86
+ "JOBS_JOB_NOT_FOUND",
87
+ `job ${jobId} not found`,
88
+ "verify the id"
89
+ );
90
+ }
91
+ const rec = JSON.parse(raw);
92
+ rec.metadata.lastError = error;
93
+ rec.metadata.finishedAt = Date.now();
94
+ if (retry) {
95
+ rec.state = "queued";
96
+ rec.metadata.startedAt = void 0;
97
+ await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));
98
+ await this.client.rpush(this.k("q"), jobId);
99
+ } else {
100
+ rec.state = "failed";
101
+ rec.error = error;
102
+ await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));
103
+ }
104
+ }
105
+ async cancel(jobId) {
106
+ await this.ready;
107
+ const raw = await this.client.get(this.k(`rec:${jobId}`));
108
+ if (!raw) {
109
+ throw new ZeroGError(
110
+ "JOBS_JOB_NOT_FOUND",
111
+ `job ${jobId} not found`,
112
+ "verify the id"
113
+ );
114
+ }
115
+ const rec = JSON.parse(raw);
116
+ if (rec.state === "queued" || rec.state === "running") {
117
+ rec.state = "cancelled";
118
+ rec.metadata.finishedAt = Date.now();
119
+ await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));
120
+ await this.client.lrem(this.k("q"), 0, jobId);
121
+ }
122
+ }
123
+ async status(jobId) {
124
+ await this.ready;
125
+ const raw = await this.client.get(this.k(`rec:${jobId}`));
126
+ return raw ? JSON.parse(raw) : null;
127
+ }
128
+ async close() {
129
+ await this.ready;
130
+ await this.client.quit();
131
+ }
132
+ };
133
+
134
+ export { RedisBackend };
135
+ //# sourceMappingURL=redis.js.map
136
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/backends/redis.ts"],"names":[],"mappings":"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","file":"redis.js","sourcesContent":["import { ZeroGError } from \"@foundryprotocol/0gkit-core\";\nimport type { JobBackend, JobRecord } from \"../types.js\";\n\ninterface RedisOpts {\n url: string;\n keyPrefix?: string;\n}\n\nfunction makeId(): string {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\ntype RedisLike = {\n set: (key: string, value: string) => Promise<unknown>;\n get: (key: string) => Promise<string | null>;\n rpush: (key: string, value: string) => Promise<unknown>;\n lpop: (key: string) => Promise<string | null>;\n lrem: (key: string, count: number, value: string) => Promise<unknown>;\n quit: () => Promise<unknown>;\n};\n\nasync function loadIoredis(): Promise<new (url: string) => RedisLike> {\n try {\n // Computed specifier so the dep graph stays clean and the peer is truly optional.\n const mod = (await import([\"ioredis\"].join(\"/\"))) as {\n default?: new (url: string) => RedisLike;\n Redis?: new (url: string) => RedisLike;\n };\n const Ctor = mod.default ?? mod.Redis;\n if (!Ctor) {\n throw new Error(\"ioredis loaded but no Redis constructor found\");\n }\n return Ctor;\n } catch {\n throw new ZeroGError(\n \"JOBS_BACKEND_UNREACHABLE\",\n \"ioredis is not installed — the redis JobBackend requires it as an optional peer.\",\n \"run `pnpm add ioredis` (or `npm install ioredis`) in your project, then retry.\"\n );\n }\n}\n\nexport class RedisBackend implements JobBackend {\n private client!: RedisLike;\n private keyPrefix: string;\n private ready: Promise<void>;\n\n constructor(opts: RedisOpts) {\n this.keyPrefix = opts.keyPrefix ?? \"0gkit:jobs\";\n this.ready = (async () => {\n const Redis = await loadIoredis();\n this.client = new Redis(opts.url);\n })();\n }\n\n private k(rest: string): string {\n return `${this.keyPrefix}:${rest}`;\n }\n\n async enqueue<I>(name: string, input: I): Promise<string> {\n await this.ready;\n const jobId = makeId();\n const rec: JobRecord<I, unknown> = {\n id: jobId,\n name,\n state: \"queued\",\n input,\n metadata: { attempts: 0, createdAt: Date.now() },\n };\n await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));\n await this.client.rpush(this.k(\"q\"), jobId);\n return jobId;\n }\n\n async claim(): Promise<JobRecord | null> {\n await this.ready;\n const jobId = await this.client.lpop(this.k(\"q\"));\n if (!jobId) return null;\n const raw = await this.client.get(this.k(`rec:${jobId}`));\n if (!raw) return null;\n const rec = JSON.parse(raw) as JobRecord;\n if (rec.state !== \"queued\") return null;\n rec.state = \"running\";\n rec.metadata.attempts += 1;\n rec.metadata.startedAt = Date.now();\n await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));\n return rec;\n }\n\n async complete<O>(jobId: string, result: O): Promise<void> {\n await this.ready;\n const raw = await this.client.get(this.k(`rec:${jobId}`));\n if (!raw) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n const rec = JSON.parse(raw) as JobRecord;\n rec.state = \"done\";\n rec.result = result;\n rec.metadata.finishedAt = Date.now();\n await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));\n }\n\n async fail(jobId: string, error: string, retry: boolean): Promise<void> {\n await this.ready;\n const raw = await this.client.get(this.k(`rec:${jobId}`));\n if (!raw) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n const rec = JSON.parse(raw) as JobRecord;\n rec.metadata.lastError = error;\n rec.metadata.finishedAt = Date.now();\n if (retry) {\n rec.state = \"queued\";\n rec.metadata.startedAt = undefined;\n await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));\n await this.client.rpush(this.k(\"q\"), jobId);\n } else {\n rec.state = \"failed\";\n rec.error = error;\n await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));\n }\n }\n\n async cancel(jobId: string): Promise<void> {\n await this.ready;\n const raw = await this.client.get(this.k(`rec:${jobId}`));\n if (!raw) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n const rec = JSON.parse(raw) as JobRecord;\n if (rec.state === \"queued\" || rec.state === \"running\") {\n rec.state = \"cancelled\";\n rec.metadata.finishedAt = Date.now();\n await this.client.set(this.k(`rec:${jobId}`), JSON.stringify(rec));\n await this.client.lrem(this.k(\"q\"), 0, jobId);\n }\n }\n\n async status(jobId: string): Promise<JobRecord | null> {\n await this.ready;\n const raw = await this.client.get(this.k(`rec:${jobId}`));\n return raw ? (JSON.parse(raw) as JobRecord) : null;\n }\n\n async close(): Promise<void> {\n await this.ready;\n await this.client.quit();\n }\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import { J as JobBackend, d as JobRecord } from '../types-CTdOcaj1.js';
2
+ import 'zod';
3
+ import '@foundryprotocol/0gkit-core';
4
+
5
+ interface SqliteOpts {
6
+ path: string;
7
+ }
8
+ declare class SqliteBackend implements JobBackend {
9
+ private db;
10
+ constructor(opts: SqliteOpts);
11
+ enqueue<I>(name: string, input: I): Promise<string>;
12
+ claim(): Promise<JobRecord | null>;
13
+ complete<O>(jobId: string, result: O): Promise<void>;
14
+ fail(jobId: string, error: string, retry: boolean): Promise<void>;
15
+ cancel(jobId: string): Promise<void>;
16
+ status(jobId: string): Promise<JobRecord | null>;
17
+ close(): Promise<void>;
18
+ private rowToRecord;
19
+ }
20
+
21
+ export { SqliteBackend };
@@ -0,0 +1,113 @@
1
+ import Database from 'better-sqlite3';
2
+ import { ZeroGError } from '@foundryprotocol/0gkit-core';
3
+
4
+ // src/backends/sqlite.ts
5
+ function makeId() {
6
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
7
+ }
8
+ var SqliteBackend = class {
9
+ db;
10
+ constructor(opts) {
11
+ this.db = new Database(opts.path);
12
+ this.db.pragma("journal_mode = WAL");
13
+ this.db.exec(`
14
+ CREATE TABLE IF NOT EXISTS jobs (
15
+ id TEXT PRIMARY KEY,
16
+ name TEXT NOT NULL,
17
+ state TEXT NOT NULL,
18
+ input TEXT NOT NULL,
19
+ result TEXT,
20
+ error TEXT,
21
+ attempts INTEGER NOT NULL DEFAULT 0,
22
+ created_at INTEGER NOT NULL,
23
+ started_at INTEGER,
24
+ finished_at INTEGER,
25
+ last_error TEXT
26
+ );
27
+ CREATE INDEX IF NOT EXISTS idx_jobs_state_created ON jobs(state, created_at);
28
+ `);
29
+ }
30
+ async enqueue(name, input) {
31
+ const jobId = makeId();
32
+ this.db.prepare(
33
+ "INSERT INTO jobs (id, name, state, input, attempts, created_at) VALUES (?, ?, 'queued', ?, 0, ?)"
34
+ ).run(jobId, name, JSON.stringify(input), Date.now());
35
+ return jobId;
36
+ }
37
+ async claim() {
38
+ const row = this.db.prepare(
39
+ `UPDATE jobs SET state='running', attempts=attempts+1, started_at=?
40
+ WHERE id = (SELECT id FROM jobs WHERE state='queued' ORDER BY created_at LIMIT 1)
41
+ RETURNING *`
42
+ ).get(Date.now());
43
+ if (!row) return null;
44
+ return this.rowToRecord(row);
45
+ }
46
+ async complete(jobId, result) {
47
+ const res = this.db.prepare("UPDATE jobs SET state='done', result=?, finished_at=? WHERE id=?").run(JSON.stringify(result), Date.now(), jobId);
48
+ if (res.changes === 0) {
49
+ throw new ZeroGError(
50
+ "JOBS_JOB_NOT_FOUND",
51
+ `job ${jobId} not found`,
52
+ "verify the id"
53
+ );
54
+ }
55
+ }
56
+ async fail(jobId, error, retry) {
57
+ const next = retry ? "queued" : "failed";
58
+ const errorCol = retry ? null : error;
59
+ const res = this.db.prepare(
60
+ `UPDATE jobs
61
+ SET state=?, error=COALESCE(?, error), last_error=?, finished_at=?, started_at=NULL
62
+ WHERE id=?`
63
+ ).run(next, errorCol, error, Date.now(), jobId);
64
+ if (res.changes === 0) {
65
+ throw new ZeroGError(
66
+ "JOBS_JOB_NOT_FOUND",
67
+ `job ${jobId} not found`,
68
+ "verify the id"
69
+ );
70
+ }
71
+ }
72
+ async cancel(jobId) {
73
+ const row = this.db.prepare("SELECT * FROM jobs WHERE id=?").get(jobId);
74
+ if (!row) {
75
+ throw new ZeroGError(
76
+ "JOBS_JOB_NOT_FOUND",
77
+ `job ${jobId} not found`,
78
+ "verify the id"
79
+ );
80
+ }
81
+ this.db.prepare(
82
+ "UPDATE jobs SET state='cancelled', finished_at=? WHERE id=? AND state IN ('queued','running')"
83
+ ).run(Date.now(), jobId);
84
+ }
85
+ async status(jobId) {
86
+ const row = this.db.prepare("SELECT * FROM jobs WHERE id=?").get(jobId);
87
+ return row ? this.rowToRecord(row) : null;
88
+ }
89
+ async close() {
90
+ this.db.close();
91
+ }
92
+ rowToRecord(r) {
93
+ return {
94
+ id: r.id,
95
+ name: r.name,
96
+ state: r.state,
97
+ input: JSON.parse(r.input),
98
+ result: r.result ? JSON.parse(r.result) : void 0,
99
+ error: r.error ?? void 0,
100
+ metadata: {
101
+ attempts: r.attempts,
102
+ createdAt: r.created_at,
103
+ startedAt: r.started_at ?? void 0,
104
+ finishedAt: r.finished_at ?? void 0,
105
+ lastError: r.last_error ?? void 0
106
+ }
107
+ };
108
+ }
109
+ };
110
+
111
+ export { SqliteBackend };
112
+ //# sourceMappingURL=sqlite.js.map
113
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/backends/sqlite.ts"],"names":[],"mappings":"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","file":"sqlite.js","sourcesContent":["import Database from \"better-sqlite3\";\nimport { ZeroGError } from \"@foundryprotocol/0gkit-core\";\nimport type { JobBackend, JobRecord, JobState } from \"../types.js\";\n\ninterface SqliteOpts {\n path: string;\n}\n\ninterface JobRow {\n id: string;\n name: string;\n state: JobState;\n input: string;\n result: string | null;\n error: string | null;\n attempts: number;\n created_at: number;\n started_at: number | null;\n finished_at: number | null;\n last_error: string | null;\n}\n\nfunction makeId(): string {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport class SqliteBackend implements JobBackend {\n private db: Database.Database;\n\n constructor(opts: SqliteOpts) {\n this.db = new Database(opts.path);\n this.db.pragma(\"journal_mode = WAL\");\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n state TEXT NOT NULL,\n input TEXT NOT NULL,\n result TEXT,\n error TEXT,\n attempts INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n started_at INTEGER,\n finished_at INTEGER,\n last_error TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_jobs_state_created ON jobs(state, created_at);\n `);\n }\n\n async enqueue<I>(name: string, input: I): Promise<string> {\n const jobId = makeId();\n this.db\n .prepare(\n \"INSERT INTO jobs (id, name, state, input, attempts, created_at) VALUES (?, ?, 'queued', ?, 0, ?)\"\n )\n .run(jobId, name, JSON.stringify(input), Date.now());\n return jobId;\n }\n\n async claim(): Promise<JobRecord | null> {\n const row = this.db\n .prepare(\n `UPDATE jobs SET state='running', attempts=attempts+1, started_at=?\n WHERE id = (SELECT id FROM jobs WHERE state='queued' ORDER BY created_at LIMIT 1)\n RETURNING *`\n )\n .get(Date.now()) as JobRow | undefined;\n if (!row) return null;\n return this.rowToRecord(row);\n }\n\n async complete<O>(jobId: string, result: O): Promise<void> {\n const res = this.db\n .prepare(\"UPDATE jobs SET state='done', result=?, finished_at=? WHERE id=?\")\n .run(JSON.stringify(result), Date.now(), jobId);\n if (res.changes === 0) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n }\n\n async fail(jobId: string, error: string, retry: boolean): Promise<void> {\n const next = retry ? \"queued\" : \"failed\";\n const errorCol = retry ? null : error;\n const res = this.db\n .prepare(\n `UPDATE jobs\n SET state=?, error=COALESCE(?, error), last_error=?, finished_at=?, started_at=NULL\n WHERE id=?`\n )\n .run(next, errorCol, error, Date.now(), jobId);\n if (res.changes === 0) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n }\n\n async cancel(jobId: string): Promise<void> {\n const row = this.db.prepare(\"SELECT * FROM jobs WHERE id=?\").get(jobId) as\n | JobRow\n | undefined;\n if (!row) {\n throw new ZeroGError(\n \"JOBS_JOB_NOT_FOUND\",\n `job ${jobId} not found`,\n \"verify the id\"\n );\n }\n this.db\n .prepare(\n \"UPDATE jobs SET state='cancelled', finished_at=? WHERE id=? AND state IN ('queued','running')\"\n )\n .run(Date.now(), jobId);\n }\n\n async status(jobId: string): Promise<JobRecord | null> {\n const row = this.db.prepare(\"SELECT * FROM jobs WHERE id=?\").get(jobId) as\n | JobRow\n | undefined;\n return row ? this.rowToRecord(row) : null;\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n\n private rowToRecord(r: JobRow): JobRecord {\n return {\n id: r.id,\n name: r.name,\n state: r.state,\n input: JSON.parse(r.input),\n result: r.result ? JSON.parse(r.result) : undefined,\n error: r.error ?? undefined,\n metadata: {\n attempts: r.attempts,\n createdAt: r.created_at,\n startedAt: r.started_at ?? undefined,\n finishedAt: r.finished_at ?? undefined,\n lastError: r.last_error ?? undefined,\n },\n };\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ import { b as JobHandlerContext, a as JobDefinition, R as RunnerConfig, d as JobRecord } from './types-CTdOcaj1.js';
3
+ export { C as ClaimOpts, J as JobBackend, c as JobMetadata, e as JobState, W as WebhookConfig } from './types-CTdOcaj1.js';
4
+ import '@foundryprotocol/0gkit-core';
5
+
6
+ interface DefineArgs<I, O> {
7
+ name: string;
8
+ input: z.ZodType<I>;
9
+ output: z.ZodType<O>;
10
+ handler: (ctx: JobHandlerContext<I>) => Promise<O>;
11
+ maxAttempts?: number;
12
+ backoffMs?: (attempt: number) => number;
13
+ }
14
+ declare function define<I, O>(args: DefineArgs<I, O>): JobDefinition<I, O>;
15
+
16
+ declare function signWebhookBody(body: string, secret: string): string;
17
+ declare function verifyWebhook(args: {
18
+ body: string;
19
+ signature: string;
20
+ secret: string;
21
+ }): boolean;
22
+
23
+ interface StartOpts {
24
+ concurrency?: number;
25
+ }
26
+ interface StopOpts {
27
+ drain?: boolean;
28
+ timeoutMs?: number;
29
+ }
30
+ interface WaitOpts {
31
+ timeoutMs?: number;
32
+ pollMs?: number;
33
+ }
34
+ declare class JobRunner {
35
+ private config;
36
+ private definitions;
37
+ private abortController;
38
+ private workers;
39
+ private running;
40
+ constructor(config: RunnerConfig);
41
+ register<I, O>(def: JobDefinition<I, O>): this;
42
+ enqueue<I, O>(def: JobDefinition<I, O>, input: I): Promise<string>;
43
+ start(opts?: StartOpts): Promise<void>;
44
+ stop(opts?: StopOpts): Promise<void>;
45
+ waitFor(id: string, opts?: WaitOpts): Promise<JobRecord>;
46
+ private claimSafe;
47
+ private workerLoop;
48
+ private runOne;
49
+ private sleep;
50
+ private fireWebhook;
51
+ /** Look up a registered definition by name (test/inspection helper). */
52
+ hasDefinition(name: string): boolean;
53
+ }
54
+
55
+ declare const jobs: {
56
+ define: typeof define;
57
+ signWebhookBody: typeof signWebhookBody;
58
+ verifyWebhook: typeof verifyWebhook;
59
+ };
60
+
61
+ export { JobDefinition, JobHandlerContext, JobRecord, JobRunner, RunnerConfig, jobs };
package/dist/index.js ADDED
@@ -0,0 +1,215 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+ import { ZeroGError } from '@foundryprotocol/0gkit-core';
3
+
4
+ // src/define.ts
5
+ function defaultBackoff(attempt) {
6
+ const base = 500;
7
+ const cap = 6e4;
8
+ const upper = Math.min(base * Math.pow(2, attempt), cap);
9
+ const lower = upper / 2;
10
+ return Math.floor(lower + Math.random() * (upper - lower));
11
+ }
12
+ function define(args) {
13
+ return {
14
+ name: args.name,
15
+ inputSchema: args.input,
16
+ outputSchema: args.output,
17
+ handler: args.handler,
18
+ maxAttempts: args.maxAttempts ?? 3,
19
+ backoffMs: args.backoffMs ?? defaultBackoff
20
+ };
21
+ }
22
+ function signWebhookBody(body, secret) {
23
+ return createHmac("sha256", secret).update(body, "utf8").digest("hex");
24
+ }
25
+ function verifyWebhook(args) {
26
+ try {
27
+ const provided = args.signature.startsWith("sha256=") ? args.signature.slice(7) : args.signature;
28
+ const expected = signWebhookBody(args.body, args.secret);
29
+ if (provided.length !== expected.length) return false;
30
+ return timingSafeEqual(Buffer.from(provided, "hex"), Buffer.from(expected, "hex"));
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ var JobRunner = class {
36
+ constructor(config) {
37
+ this.config = config;
38
+ }
39
+ config;
40
+ definitions = /* @__PURE__ */ new Map();
41
+ abortController = new AbortController();
42
+ workers = [];
43
+ running = false;
44
+ register(def) {
45
+ this.definitions.set(def.name, def);
46
+ return this;
47
+ }
48
+ async enqueue(def, input) {
49
+ def.inputSchema.parse(input);
50
+ return this.config.backend.enqueue(def.name, input);
51
+ }
52
+ async start(opts = {}) {
53
+ if (this.running) return;
54
+ this.running = true;
55
+ this.abortController = new AbortController();
56
+ const concurrency = Math.max(1, opts.concurrency ?? 1);
57
+ for (let i = 0; i < concurrency; i++) {
58
+ this.workers.push(this.workerLoop(`w${i}`));
59
+ }
60
+ }
61
+ async stop(opts = {}) {
62
+ if (!this.running) return;
63
+ this.running = false;
64
+ const drain = opts.drain ?? true;
65
+ if (!drain) {
66
+ this.abortController.abort();
67
+ }
68
+ const timeoutMs = opts.timeoutMs ?? 5e3;
69
+ await Promise.race([
70
+ Promise.all(this.workers),
71
+ new Promise((res) => setTimeout(res, timeoutMs))
72
+ ]);
73
+ this.workers = [];
74
+ }
75
+ async waitFor(id, opts = {}) {
76
+ const deadline = Date.now() + (opts.timeoutMs ?? 3e4);
77
+ const poll = opts.pollMs ?? 25;
78
+ while (Date.now() < deadline) {
79
+ const rec = await this.config.backend.status(id);
80
+ if (rec && (rec.state === "done" || rec.state === "failed" || rec.state === "cancelled")) {
81
+ return rec;
82
+ }
83
+ await new Promise((r) => setTimeout(r, poll));
84
+ }
85
+ throw new ZeroGError(
86
+ "JOBS_BACKEND_UNREACHABLE",
87
+ `waitFor timed out for job ${id}`,
88
+ "increase timeoutMs or check the backend is reachable and a worker is running."
89
+ );
90
+ }
91
+ async claimSafe(workerId) {
92
+ try {
93
+ return await this.config.backend.claim({ workerId });
94
+ } catch (err) {
95
+ if (err instanceof ZeroGError) return null;
96
+ throw err;
97
+ }
98
+ }
99
+ async workerLoop(workerId) {
100
+ const pollMs = this.config.pollIntervalMs ?? 50;
101
+ while (this.running) {
102
+ if (this.abortController.signal.aborted) return;
103
+ const rec = await this.claimSafe(workerId);
104
+ if (!rec) {
105
+ await this.sleep(pollMs);
106
+ continue;
107
+ }
108
+ await this.runOne(rec);
109
+ }
110
+ }
111
+ async runOne(rec) {
112
+ const def = this.definitions.get(rec.name);
113
+ if (!def) {
114
+ await this.config.backend.fail(
115
+ rec.id,
116
+ `no definition registered for "${rec.name}"`,
117
+ false
118
+ );
119
+ await this.fireWebhook(
120
+ rec.id,
121
+ rec.name,
122
+ "running",
123
+ "failed",
124
+ void 0,
125
+ "no def"
126
+ );
127
+ return;
128
+ }
129
+ try {
130
+ const result = await def.handler({
131
+ input: rec.input,
132
+ jobId: rec.id,
133
+ signer: this.config.signer,
134
+ signal: this.abortController.signal,
135
+ attempt: rec.metadata.attempts,
136
+ metadata: rec.metadata
137
+ });
138
+ def.outputSchema.parse(result);
139
+ await this.config.backend.complete(rec.id, result);
140
+ await this.fireWebhook(rec.id, rec.name, "running", "done", result, void 0);
141
+ } catch (err) {
142
+ const msg = err instanceof Error ? err.message : String(err);
143
+ const aborted = this.abortController.signal.aborted;
144
+ const willRetry = rec.metadata.attempts < def.maxAttempts && !aborted;
145
+ if (willRetry) {
146
+ await this.sleep(def.backoffMs(rec.metadata.attempts));
147
+ if (this.abortController.signal.aborted) {
148
+ await this.config.backend.fail(rec.id, msg, false);
149
+ await this.fireWebhook(rec.id, rec.name, "running", "failed", void 0, msg);
150
+ return;
151
+ }
152
+ }
153
+ await this.config.backend.fail(rec.id, msg, willRetry);
154
+ await this.fireWebhook(
155
+ rec.id,
156
+ rec.name,
157
+ "running",
158
+ willRetry ? "queued" : "failed",
159
+ void 0,
160
+ msg
161
+ );
162
+ }
163
+ }
164
+ sleep(ms) {
165
+ return new Promise((res) => setTimeout(res, ms));
166
+ }
167
+ async fireWebhook(jobId, jobName, previousState, newState, result, error) {
168
+ if (!this.config.webhook) return;
169
+ const body = JSON.stringify({
170
+ jobId,
171
+ jobName,
172
+ previousState,
173
+ newState,
174
+ result,
175
+ error,
176
+ ts: Date.now()
177
+ });
178
+ const signature = signWebhookBody(body, this.config.webhook.secret);
179
+ const retries = this.config.webhook.retries ?? 2;
180
+ for (let attempt = 0; attempt <= retries; attempt++) {
181
+ try {
182
+ const res = await fetch(this.config.webhook.url, {
183
+ method: "POST",
184
+ headers: {
185
+ "content-type": "application/json",
186
+ "x-0gkit-signature": `sha256=${signature}`,
187
+ "x-0gkit-job-id": jobId,
188
+ "x-0gkit-event": "state-change"
189
+ },
190
+ body
191
+ });
192
+ if (res.ok) return;
193
+ } catch {
194
+ }
195
+ if (attempt < retries) {
196
+ await this.sleep(250 * (attempt + 1));
197
+ }
198
+ }
199
+ }
200
+ /** Look up a registered definition by name (test/inspection helper). */
201
+ hasDefinition(name) {
202
+ return this.definitions.has(name);
203
+ }
204
+ };
205
+
206
+ // src/index.ts
207
+ var jobs = {
208
+ define,
209
+ signWebhookBody,
210
+ verifyWebhook
211
+ };
212
+
213
+ export { JobRunner, jobs };
214
+ //# sourceMappingURL=index.js.map
215
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/define.ts","../src/webhook.ts","../src/runner.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { z } from \"zod\";\nimport type { JobDefinition, JobHandlerContext } from \"./types.js\";\n\ninterface DefineArgs<I, O> {\n name: string;\n input: z.ZodType<I>;\n output: z.ZodType<O>;\n handler: (ctx: JobHandlerContext<I>) => Promise<O>;\n maxAttempts?: number;\n backoffMs?: (attempt: number) => number;\n}\n\nfunction defaultBackoff(attempt: number): number {\n const base = 500;\n const cap = 60_000;\n const upper = Math.min(base * Math.pow(2, attempt), cap);\n const lower = upper / 2;\n return Math.floor(lower + Math.random() * (upper - lower));\n}\n\nexport function define<I, O>(args: DefineArgs<I, O>): JobDefinition<I, O> {\n return {\n name: args.name,\n inputSchema: args.input,\n outputSchema: args.output,\n handler: args.handler,\n maxAttempts: args.maxAttempts ?? 3,\n backoffMs: args.backoffMs ?? defaultBackoff,\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nexport function signWebhookBody(body: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(body, \"utf8\").digest(\"hex\");\n}\n\nexport function verifyWebhook(args: {\n body: string;\n signature: string;\n secret: string;\n}): boolean {\n try {\n const provided = args.signature.startsWith(\"sha256=\")\n ? args.signature.slice(7)\n : args.signature;\n const expected = signWebhookBody(args.body, args.secret);\n if (provided.length !== expected.length) return false;\n return timingSafeEqual(Buffer.from(provided, \"hex\"), Buffer.from(expected, \"hex\"));\n } catch {\n return false;\n }\n}\n","import { ZeroGError } from \"@foundryprotocol/0gkit-core\";\nimport type { JobBackend, JobDefinition, JobRecord, RunnerConfig } from \"./types.js\";\nimport { signWebhookBody } from \"./webhook.js\";\n\ninterface StartOpts {\n concurrency?: number;\n}\n\ninterface StopOpts {\n drain?: boolean;\n timeoutMs?: number;\n}\n\ninterface WaitOpts {\n timeoutMs?: number;\n pollMs?: number;\n}\n\nexport class JobRunner {\n private definitions = new Map<string, JobDefinition<unknown, unknown>>();\n private abortController = new AbortController();\n private workers: Promise<void>[] = [];\n private running = false;\n\n constructor(private config: RunnerConfig) {}\n\n register<I, O>(def: JobDefinition<I, O>): this {\n this.definitions.set(def.name, def as unknown as JobDefinition<unknown, unknown>);\n return this;\n }\n\n async enqueue<I, O>(def: JobDefinition<I, O>, input: I): Promise<string> {\n def.inputSchema.parse(input);\n return this.config.backend.enqueue(def.name, input);\n }\n\n async start(opts: StartOpts = {}): Promise<void> {\n if (this.running) return;\n this.running = true;\n this.abortController = new AbortController();\n const concurrency = Math.max(1, opts.concurrency ?? 1);\n for (let i = 0; i < concurrency; i++) {\n this.workers.push(this.workerLoop(`w${i}`));\n }\n }\n\n async stop(opts: StopOpts = {}): Promise<void> {\n if (!this.running) return;\n this.running = false;\n const drain = opts.drain ?? true;\n if (!drain) {\n this.abortController.abort();\n }\n const timeoutMs = opts.timeoutMs ?? 5000;\n await Promise.race([\n Promise.all(this.workers),\n new Promise<void>((res) => setTimeout(res, timeoutMs)),\n ]);\n this.workers = [];\n }\n\n async waitFor(id: string, opts: WaitOpts = {}): Promise<JobRecord> {\n const deadline = Date.now() + (opts.timeoutMs ?? 30_000);\n const poll = opts.pollMs ?? 25;\n while (Date.now() < deadline) {\n const rec = await this.config.backend.status(id);\n if (\n rec &&\n (rec.state === \"done\" || rec.state === \"failed\" || rec.state === \"cancelled\")\n ) {\n return rec;\n }\n await new Promise((r) => setTimeout(r, poll));\n }\n throw new ZeroGError(\n \"JOBS_BACKEND_UNREACHABLE\",\n `waitFor timed out for job ${id}`,\n \"increase timeoutMs or check the backend is reachable and a worker is running.\"\n );\n }\n\n private async claimSafe(workerId: string): Promise<JobRecord | null> {\n try {\n return await this.config.backend.claim({ workerId });\n } catch (err) {\n if (err instanceof ZeroGError) return null;\n throw err;\n }\n }\n\n private async workerLoop(workerId: string): Promise<void> {\n const pollMs = this.config.pollIntervalMs ?? 50;\n while (this.running) {\n if (this.abortController.signal.aborted) return;\n const rec = await this.claimSafe(workerId);\n if (!rec) {\n await this.sleep(pollMs);\n continue;\n }\n await this.runOne(rec);\n }\n }\n\n private async runOne(rec: JobRecord): Promise<void> {\n const def = this.definitions.get(rec.name);\n if (!def) {\n await this.config.backend.fail(\n rec.id,\n `no definition registered for \"${rec.name}\"`,\n false\n );\n await this.fireWebhook(\n rec.id,\n rec.name,\n \"running\",\n \"failed\",\n undefined,\n \"no def\"\n );\n return;\n }\n try {\n const result = await def.handler({\n input: rec.input,\n jobId: rec.id,\n signer: this.config.signer,\n signal: this.abortController.signal,\n attempt: rec.metadata.attempts,\n metadata: rec.metadata,\n });\n def.outputSchema.parse(result);\n await this.config.backend.complete(rec.id, result);\n await this.fireWebhook(rec.id, rec.name, \"running\", \"done\", result, undefined);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n const aborted = this.abortController.signal.aborted;\n const willRetry = rec.metadata.attempts < def.maxAttempts && !aborted;\n if (willRetry) {\n await this.sleep(def.backoffMs(rec.metadata.attempts));\n if (this.abortController.signal.aborted) {\n await this.config.backend.fail(rec.id, msg, false);\n await this.fireWebhook(rec.id, rec.name, \"running\", \"failed\", undefined, msg);\n return;\n }\n }\n await this.config.backend.fail(rec.id, msg, willRetry);\n await this.fireWebhook(\n rec.id,\n rec.name,\n \"running\",\n willRetry ? \"queued\" : \"failed\",\n undefined,\n msg\n );\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((res) => setTimeout(res, ms));\n }\n\n private async fireWebhook(\n jobId: string,\n jobName: string,\n previousState: JobRecord[\"state\"],\n newState: JobRecord[\"state\"],\n result: unknown,\n error: string | undefined\n ): Promise<void> {\n if (!this.config.webhook) return;\n const body = JSON.stringify({\n jobId,\n jobName,\n previousState,\n newState,\n result,\n error,\n ts: Date.now(),\n });\n const signature = signWebhookBody(body, this.config.webhook.secret);\n const retries = this.config.webhook.retries ?? 2;\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const res = await fetch(this.config.webhook.url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-0gkit-signature\": `sha256=${signature}`,\n \"x-0gkit-job-id\": jobId,\n \"x-0gkit-event\": \"state-change\",\n },\n body,\n });\n if (res.ok) return;\n } catch {\n // swallow & retry\n }\n if (attempt < retries) {\n await this.sleep(250 * (attempt + 1));\n }\n }\n }\n\n /** Look up a registered definition by name (test/inspection helper). */\n hasDefinition(name: string): boolean {\n return this.definitions.has(name);\n }\n}\n\nexport type { JobBackend, JobDefinition };\n","import { define } from \"./define.js\";\nimport { signWebhookBody, verifyWebhook } from \"./webhook.js\";\n\nexport { JobRunner } from \"./runner.js\";\nexport type {\n JobState,\n JobRecord,\n JobMetadata,\n JobDefinition,\n JobHandlerContext,\n JobBackend,\n WebhookConfig,\n RunnerConfig,\n ClaimOpts,\n} from \"./types.js\";\n\nexport const jobs = {\n define,\n signWebhookBody,\n verifyWebhook,\n};\n"]}
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ import { Signer } from '@foundryprotocol/0gkit-core';
3
+
4
+ type JobState = "queued" | "running" | "done" | "failed" | "cancelled";
5
+ interface JobMetadata {
6
+ attempts: number;
7
+ createdAt: number;
8
+ startedAt?: number;
9
+ finishedAt?: number;
10
+ lastError?: string;
11
+ }
12
+ interface JobRecord<I = unknown, O = unknown> {
13
+ id: string;
14
+ name: string;
15
+ state: JobState;
16
+ input: I;
17
+ result?: O;
18
+ error?: string;
19
+ metadata: JobMetadata;
20
+ }
21
+ interface JobHandlerContext<I> {
22
+ input: I;
23
+ jobId: string;
24
+ signer: Signer;
25
+ signal: AbortSignal;
26
+ attempt: number;
27
+ metadata: JobMetadata;
28
+ }
29
+ interface JobDefinition<I, O> {
30
+ readonly name: string;
31
+ readonly inputSchema: z.ZodType<I>;
32
+ readonly outputSchema: z.ZodType<O>;
33
+ readonly handler: (ctx: JobHandlerContext<I>) => Promise<O>;
34
+ readonly maxAttempts: number;
35
+ readonly backoffMs: (attempt: number) => number;
36
+ }
37
+ interface ClaimOpts {
38
+ workerId: string;
39
+ }
40
+ interface JobBackend {
41
+ enqueue<I>(name: string, input: I): Promise<string>;
42
+ claim(opts: ClaimOpts): Promise<JobRecord | null>;
43
+ complete<O>(id: string, result: O): Promise<void>;
44
+ fail(id: string, error: string, retry: boolean): Promise<void>;
45
+ cancel(id: string): Promise<void>;
46
+ status(id: string): Promise<JobRecord | null>;
47
+ close(): Promise<void>;
48
+ }
49
+ interface WebhookConfig {
50
+ url: string;
51
+ secret: string;
52
+ retries?: number;
53
+ }
54
+ interface RunnerConfig {
55
+ backend: JobBackend;
56
+ signer: Signer;
57
+ webhook?: WebhookConfig;
58
+ pollIntervalMs?: number;
59
+ }
60
+
61
+ export type { ClaimOpts as C, JobBackend as J, RunnerConfig as R, WebhookConfig as W, JobDefinition as a, JobHandlerContext as b, JobMetadata as c, JobRecord as d, JobState as e };
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@foundryprotocol/0gkit-jobs",
3
+ "version": "0.5.0",
4
+ "description": "Durable async job runner for 0G compute / storage / DA workflows. Memory / SQLite / Redis backends, zod-typed job definitions, HMAC-signed webhooks, graceful shutdown.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/rajkaria/0gkit/tree/main/packages/0gkit-jobs",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/rajkaria/0gkit.git",
10
+ "directory": "packages/0gkit-jobs"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/rajkaria/0gkit/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./backends/memory": {
25
+ "types": "./dist/backends/memory.d.ts",
26
+ "import": "./dist/backends/memory.js"
27
+ },
28
+ "./backends/sqlite": {
29
+ "types": "./dist/backends/sqlite.d.ts",
30
+ "import": "./dist/backends/sqlite.js"
31
+ },
32
+ "./backends/redis": {
33
+ "types": "./dist/backends/redis.d.ts",
34
+ "import": "./dist/backends/redis.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "dependencies": {
43
+ "better-sqlite3": "^11.5.0",
44
+ "zod": "^3.23.0",
45
+ "@foundryprotocol/0gkit-core": "0.4.0"
46
+ },
47
+ "peerDependencies": {
48
+ "ioredis": "^5.4.1"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "ioredis": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "devDependencies": {
56
+ "@types/better-sqlite3": "^7.6.12",
57
+ "@types/node": "^22.10.2",
58
+ "@vitest/coverage-v8": "^2.1.8",
59
+ "dependency-cruiser": "^16.0.0",
60
+ "ioredis": "^5.4.1",
61
+ "rimraf": "^6.0.1",
62
+ "tsup": "^8.3.5",
63
+ "typescript": "^5.6.3",
64
+ "vitest": "^2.1.8",
65
+ "@foundryprotocol/0gkit-testing": "0.4.0"
66
+ },
67
+ "keywords": [
68
+ "0g",
69
+ "0g-network",
70
+ "jobs",
71
+ "queue",
72
+ "worker",
73
+ "durable",
74
+ "webhook",
75
+ "toolkit"
76
+ ],
77
+ "publishConfig": {
78
+ "access": "public"
79
+ },
80
+ "scripts": {
81
+ "build": "tsup",
82
+ "dev": "tsup --watch",
83
+ "typecheck": "tsc --noEmit",
84
+ "test": "vitest run",
85
+ "coverage": "vitest run --coverage",
86
+ "lint": "depcruise src --config ../../.dependency-cruiser.cjs",
87
+ "clean": "rimraf dist"
88
+ }
89
+ }