pot-api 0.1.0 → 0.2.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/dist/index.js +10 -10
- package/dist/queue.d.ts +19 -0
- package/dist/queue.js +125 -0
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { verify } from 'pot-sdk';
|
|
4
4
|
import { resolveKeys, buildApiKeyRecord, validateConfig } from './config.js';
|
|
5
|
-
import { createJob, getJob, updateJob } from './
|
|
5
|
+
import { createJob, getJob, updateJob } from './queue.js';
|
|
6
6
|
const app = express();
|
|
7
7
|
app.use(express.json({ limit: '1mb' }));
|
|
8
8
|
const PORT = parseInt(process.env.PORT ?? '3141', 10);
|
|
9
9
|
// ─── Health ───────────────────────────────────────────────
|
|
10
10
|
app.get('/', (_req, res) => {
|
|
11
|
-
res.json({ name: 'pot-api', version: '0.
|
|
11
|
+
res.json({ name: 'pot-api', version: '0.2.0', status: 'ok', queue: process.env.REDIS_URL ? 'redis' : 'memory' });
|
|
12
12
|
});
|
|
13
13
|
// ─── Sync verify ──────────────────────────────────────────
|
|
14
14
|
app.post('/verify', async (req, res) => {
|
|
@@ -36,7 +36,7 @@ app.post('/verify', async (req, res) => {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
// ─── Async verify ─────────────────────────────────────────
|
|
39
|
-
app.post('/verify/async', (req, res) => {
|
|
39
|
+
app.post('/verify/async', async (req, res) => {
|
|
40
40
|
const { output, question, tier = 'basic', callbackUrl, apiKeys } = req.body;
|
|
41
41
|
if (!output || !question) {
|
|
42
42
|
res.status(400).json({ error: 'output and question are required' });
|
|
@@ -48,14 +48,14 @@ app.post('/verify/async', (req, res) => {
|
|
|
48
48
|
res.status(400).json({ error: configError });
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
const job = createJob({ output, question, tier, callbackUrl });
|
|
51
|
+
const job = await createJob({ output, question, tier, callbackUrl });
|
|
52
52
|
res.status(202).json({ jobId: job.id, status: 'pending', pollUrl: `/jobs/${job.id}` });
|
|
53
|
-
// Run in background
|
|
53
|
+
// Run in background (no await — fire and forget)
|
|
54
54
|
runJob(job.id, output, question, tier, buildApiKeyRecord(keys), callbackUrl);
|
|
55
55
|
});
|
|
56
56
|
// ─── Poll job status ───────────────────────────────────────
|
|
57
|
-
app.get('/jobs/:id', (req, res) => {
|
|
58
|
-
const job = getJob(req.params.id);
|
|
57
|
+
app.get('/jobs/:id', async (req, res) => {
|
|
58
|
+
const job = await getJob(req.params.id);
|
|
59
59
|
if (!job) {
|
|
60
60
|
res.status(404).json({ error: 'Job not found' });
|
|
61
61
|
return;
|
|
@@ -64,17 +64,17 @@ app.get('/jobs/:id', (req, res) => {
|
|
|
64
64
|
});
|
|
65
65
|
// ─── Background runner + webhook push ────────────────────
|
|
66
66
|
async function runJob(jobId, output, question, tier, apiKeys, callbackUrl) {
|
|
67
|
-
updateJob(jobId, { status: 'running' });
|
|
67
|
+
await updateJob(jobId, { status: 'running' });
|
|
68
68
|
try {
|
|
69
69
|
const result = await verify(output, { tier, apiKeys, question });
|
|
70
|
-
updateJob(jobId, { status: 'done', result });
|
|
70
|
+
await updateJob(jobId, { status: 'done', result });
|
|
71
71
|
if (callbackUrl) {
|
|
72
72
|
await pushWebhook(callbackUrl, { jobId, status: 'done', result });
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
catch (err) {
|
|
76
76
|
const error = err instanceof Error ? err.message : String(err);
|
|
77
|
-
updateJob(jobId, { status: 'error', error });
|
|
77
|
+
await updateJob(jobId, { status: 'error', error });
|
|
78
78
|
if (callbackUrl) {
|
|
79
79
|
await pushWebhook(callbackUrl, { jobId, status: 'error', error });
|
|
80
80
|
}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue abstraction for pot-api.
|
|
3
|
+
*
|
|
4
|
+
* Auto-selects backend based on REDIS_URL env var:
|
|
5
|
+
* - REDIS_URL set → BullMQ (persistent, survives restarts)
|
|
6
|
+
* - REDIS_URL unset → In-Memory Map (default, zero dependencies)
|
|
7
|
+
*
|
|
8
|
+
* No breaking changes to the API surface.
|
|
9
|
+
*/
|
|
10
|
+
import type { Job } from './jobs.js';
|
|
11
|
+
export interface QueueBackend {
|
|
12
|
+
createJob(input: Job['input']): Promise<Job> | Job;
|
|
13
|
+
getJob(id: string): Promise<Job | undefined> | Job | undefined;
|
|
14
|
+
updateJob(id: string, patch: Partial<Job>): Promise<void> | void;
|
|
15
|
+
}
|
|
16
|
+
export declare function getQueue(): QueueBackend;
|
|
17
|
+
export declare function createJob(input: Job['input']): Promise<Job>;
|
|
18
|
+
export declare function getJob(id: string): Promise<Job | undefined>;
|
|
19
|
+
export declare function updateJob(id: string, patch: Partial<Job>): Promise<void>;
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue abstraction for pot-api.
|
|
3
|
+
*
|
|
4
|
+
* Auto-selects backend based on REDIS_URL env var:
|
|
5
|
+
* - REDIS_URL set → BullMQ (persistent, survives restarts)
|
|
6
|
+
* - REDIS_URL unset → In-Memory Map (default, zero dependencies)
|
|
7
|
+
*
|
|
8
|
+
* No breaking changes to the API surface.
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from 'crypto';
|
|
11
|
+
// ─── In-Memory Backend (default) ─────────────────────────────────────────────
|
|
12
|
+
class InMemoryBackend {
|
|
13
|
+
store = new Map();
|
|
14
|
+
constructor() {
|
|
15
|
+
// Auto-cleanup jobs older than 1h
|
|
16
|
+
setInterval(() => {
|
|
17
|
+
const cutoff = Date.now() - 60 * 60 * 1000;
|
|
18
|
+
for (const [id, job] of this.store.entries()) {
|
|
19
|
+
if (new Date(job.createdAt).getTime() < cutoff) {
|
|
20
|
+
this.store.delete(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, 10 * 60 * 1000).unref();
|
|
24
|
+
}
|
|
25
|
+
createJob(input) {
|
|
26
|
+
const job = {
|
|
27
|
+
id: randomUUID(),
|
|
28
|
+
status: 'pending',
|
|
29
|
+
createdAt: new Date().toISOString(),
|
|
30
|
+
updatedAt: new Date().toISOString(),
|
|
31
|
+
input,
|
|
32
|
+
};
|
|
33
|
+
this.store.set(job.id, job);
|
|
34
|
+
return job;
|
|
35
|
+
}
|
|
36
|
+
getJob(id) {
|
|
37
|
+
return this.store.get(id);
|
|
38
|
+
}
|
|
39
|
+
updateJob(id, patch) {
|
|
40
|
+
const job = this.store.get(id);
|
|
41
|
+
if (job) {
|
|
42
|
+
Object.assign(job, patch, { updatedAt: new Date().toISOString() });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ─── BullMQ Backend (optional, requires REDIS_URL) ───────────────────────────
|
|
47
|
+
class BullMQBackend {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
client;
|
|
50
|
+
prefix = 'pot-api:jobs';
|
|
51
|
+
ready;
|
|
52
|
+
constructor(redisUrl) {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
54
|
+
this.ready = (new Function('m', 'return import(m)'))('ioredis').then(({ default: Redis }) => {
|
|
55
|
+
this.client = new Redis(redisUrl, {
|
|
56
|
+
maxRetriesPerRequest: 3,
|
|
57
|
+
lazyConnect: false,
|
|
58
|
+
});
|
|
59
|
+
this.client.on('error', (err) => {
|
|
60
|
+
console.error('[pot-api] Redis error:', err.message);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
key(id) {
|
|
65
|
+
return `${this.prefix}:${id}`;
|
|
66
|
+
}
|
|
67
|
+
async createJob(input) {
|
|
68
|
+
await this.ready;
|
|
69
|
+
const job = {
|
|
70
|
+
id: randomUUID(),
|
|
71
|
+
status: 'pending',
|
|
72
|
+
createdAt: new Date().toISOString(),
|
|
73
|
+
updatedAt: new Date().toISOString(),
|
|
74
|
+
input,
|
|
75
|
+
};
|
|
76
|
+
// Store as JSON string, TTL = 24h
|
|
77
|
+
await this.client.set(this.key(job.id), JSON.stringify(job), 'EX', 86400);
|
|
78
|
+
return job;
|
|
79
|
+
}
|
|
80
|
+
async getJob(id) {
|
|
81
|
+
await this.ready;
|
|
82
|
+
const raw = await this.client.get(this.key(id));
|
|
83
|
+
if (!raw)
|
|
84
|
+
return undefined;
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(raw);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async updateJob(id, patch) {
|
|
93
|
+
await this.ready;
|
|
94
|
+
const job = await this.getJob(id);
|
|
95
|
+
if (!job)
|
|
96
|
+
return;
|
|
97
|
+
const updated = { ...job, ...patch, updatedAt: new Date().toISOString() };
|
|
98
|
+
await this.client.set(this.key(job.id), JSON.stringify(updated), 'EX', 86400);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
102
|
+
let _backend = null;
|
|
103
|
+
export function getQueue() {
|
|
104
|
+
if (_backend)
|
|
105
|
+
return _backend;
|
|
106
|
+
const redisUrl = process.env.REDIS_URL;
|
|
107
|
+
if (redisUrl) {
|
|
108
|
+
_backend = new BullMQBackend(redisUrl);
|
|
109
|
+
console.log('[pot-api] Using Redis backend:', redisUrl.replace(/:\/\/.*@/, '://***@'));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
_backend = new InMemoryBackend();
|
|
113
|
+
}
|
|
114
|
+
return _backend;
|
|
115
|
+
}
|
|
116
|
+
// ─── Convenience wrappers (same API as jobs.ts) ───────────────────────────────
|
|
117
|
+
export async function createJob(input) {
|
|
118
|
+
return getQueue().createJob(input);
|
|
119
|
+
}
|
|
120
|
+
export async function getJob(id) {
|
|
121
|
+
return getQueue().getJob(id);
|
|
122
|
+
}
|
|
123
|
+
export async function updateJob(id, patch) {
|
|
124
|
+
return getQueue().updateJob(id, patch);
|
|
125
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pot-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "REST API server for pot-sdk — sync and async AI output verification with webhook callbacks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"pot-api": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
|
-
"files": [
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
12
15
|
"scripts": {
|
|
13
16
|
"build": "tsc",
|
|
14
17
|
"prepare": "npm run build && chmod +x dist/index.js",
|
|
@@ -17,11 +20,12 @@
|
|
|
17
20
|
},
|
|
18
21
|
"dependencies": {
|
|
19
22
|
"express": "^4.18.0",
|
|
20
|
-
"pot-sdk": "^0.1.0"
|
|
23
|
+
"pot-sdk": "^0.1.0",
|
|
24
|
+
"ioredis": "^5.3.2"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
27
|
"@types/express": "^4.17.0",
|
|
24
28
|
"@types/node": "^25.3.0",
|
|
25
29
|
"typescript": "^5.3.0"
|
|
26
30
|
}
|
|
27
|
-
}
|
|
31
|
+
}
|