@axiom-lattice/pg-stores 1.0.2 → 1.0.4
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +126 -3
- package/dist/index.d.ts +126 -3
- package/dist/index.js +545 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +545 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +7 -0
- package/src/migrations/schedule_migrations.ts +116 -0
- package/src/stores/PostgreSQLScheduleStorage.ts +591 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL implementation of ScheduleStorage
|
|
3
|
+
*
|
|
4
|
+
* Provides persistent storage for scheduled tasks
|
|
5
|
+
* Data survives service restarts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Pool, PoolConfig } from "pg";
|
|
9
|
+
import {
|
|
10
|
+
ScheduleStorage,
|
|
11
|
+
ScheduledTaskDefinition,
|
|
12
|
+
ScheduledTaskStatus,
|
|
13
|
+
ScheduleExecutionType,
|
|
14
|
+
} from "@axiom-lattice/protocols";
|
|
15
|
+
import { MigrationManager } from "../migrations/migration";
|
|
16
|
+
import { createScheduledTasksTable } from "../migrations/schedule_migrations";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* PostgreSQL ScheduleStorage options
|
|
20
|
+
*/
|
|
21
|
+
export interface PostgreSQLScheduleStorageOptions {
|
|
22
|
+
/**
|
|
23
|
+
* PostgreSQL connection pool configuration
|
|
24
|
+
* Can be a connection string or PoolConfig object
|
|
25
|
+
*/
|
|
26
|
+
poolConfig: string | PoolConfig;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether to run migrations automatically on initialization
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
autoMigrate?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Database row type for scheduled tasks
|
|
37
|
+
*/
|
|
38
|
+
interface ScheduledTaskRow {
|
|
39
|
+
task_id: string;
|
|
40
|
+
task_type: string;
|
|
41
|
+
payload: string | Record<string, any>;
|
|
42
|
+
assistant_id: string | null;
|
|
43
|
+
thread_id: string | null;
|
|
44
|
+
execution_type: string;
|
|
45
|
+
execute_at: Date | null;
|
|
46
|
+
delay_ms: number | null;
|
|
47
|
+
cron_expression: string | null;
|
|
48
|
+
timezone: string | null;
|
|
49
|
+
next_run_at: Date | null;
|
|
50
|
+
last_run_at: Date | null;
|
|
51
|
+
status: string;
|
|
52
|
+
run_count: number;
|
|
53
|
+
max_runs: number | null;
|
|
54
|
+
retry_count: number;
|
|
55
|
+
max_retries: number;
|
|
56
|
+
last_error: string | null;
|
|
57
|
+
created_at: Date;
|
|
58
|
+
updated_at: Date;
|
|
59
|
+
expires_at: Date | null;
|
|
60
|
+
metadata: string | Record<string, any> | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* PostgreSQL implementation of ScheduleStorage
|
|
65
|
+
*/
|
|
66
|
+
export class PostgreSQLScheduleStorage implements ScheduleStorage {
|
|
67
|
+
private pool: Pool;
|
|
68
|
+
private migrationManager: MigrationManager;
|
|
69
|
+
private initialized: boolean = false;
|
|
70
|
+
|
|
71
|
+
constructor(options: PostgreSQLScheduleStorageOptions) {
|
|
72
|
+
// Create Pool from config
|
|
73
|
+
if (typeof options.poolConfig === "string") {
|
|
74
|
+
this.pool = new Pool({ connectionString: options.poolConfig });
|
|
75
|
+
} else {
|
|
76
|
+
this.pool = new Pool(options.poolConfig);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
80
|
+
this.migrationManager.register(createScheduledTasksTable);
|
|
81
|
+
|
|
82
|
+
// Auto-migrate by default
|
|
83
|
+
if (options.autoMigrate !== false) {
|
|
84
|
+
this.initialize().catch((error) => {
|
|
85
|
+
console.error("Failed to initialize PostgreSQLScheduleStorage:", error);
|
|
86
|
+
throw error;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Dispose resources and close the connection pool
|
|
93
|
+
*/
|
|
94
|
+
async dispose(): Promise<void> {
|
|
95
|
+
if (this.pool) {
|
|
96
|
+
await this.pool.end();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Initialize the store and run migrations
|
|
102
|
+
*/
|
|
103
|
+
async initialize(): Promise<void> {
|
|
104
|
+
if (this.initialized) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await this.migrationManager.migrate();
|
|
109
|
+
this.initialized = true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ensure store is initialized
|
|
114
|
+
*/
|
|
115
|
+
private async ensureInitialized(): Promise<void> {
|
|
116
|
+
if (!this.initialized) {
|
|
117
|
+
await this.initialize();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Save a new task
|
|
123
|
+
*/
|
|
124
|
+
async save(task: ScheduledTaskDefinition): Promise<void> {
|
|
125
|
+
await this.ensureInitialized();
|
|
126
|
+
|
|
127
|
+
await this.pool.query(
|
|
128
|
+
`
|
|
129
|
+
INSERT INTO lattice_scheduled_tasks (
|
|
130
|
+
task_id, task_type, payload, assistant_id, thread_id, execution_type,
|
|
131
|
+
execute_at, delay_ms, cron_expression, timezone,
|
|
132
|
+
next_run_at, last_run_at, status, run_count, max_runs,
|
|
133
|
+
retry_count, max_retries, last_error,
|
|
134
|
+
created_at, updated_at, expires_at, metadata
|
|
135
|
+
) VALUES (
|
|
136
|
+
$1, $2, $3, $4, $5, $6,
|
|
137
|
+
$7, $8, $9, $10,
|
|
138
|
+
$11, $12, $13, $14, $15,
|
|
139
|
+
$16, $17, $18,
|
|
140
|
+
$19, $20, $21, $22
|
|
141
|
+
)
|
|
142
|
+
ON CONFLICT (task_id) DO UPDATE SET
|
|
143
|
+
task_type = EXCLUDED.task_type,
|
|
144
|
+
payload = EXCLUDED.payload,
|
|
145
|
+
assistant_id = EXCLUDED.assistant_id,
|
|
146
|
+
thread_id = EXCLUDED.thread_id,
|
|
147
|
+
execution_type = EXCLUDED.execution_type,
|
|
148
|
+
execute_at = EXCLUDED.execute_at,
|
|
149
|
+
delay_ms = EXCLUDED.delay_ms,
|
|
150
|
+
cron_expression = EXCLUDED.cron_expression,
|
|
151
|
+
timezone = EXCLUDED.timezone,
|
|
152
|
+
next_run_at = EXCLUDED.next_run_at,
|
|
153
|
+
last_run_at = EXCLUDED.last_run_at,
|
|
154
|
+
status = EXCLUDED.status,
|
|
155
|
+
run_count = EXCLUDED.run_count,
|
|
156
|
+
max_runs = EXCLUDED.max_runs,
|
|
157
|
+
retry_count = EXCLUDED.retry_count,
|
|
158
|
+
max_retries = EXCLUDED.max_retries,
|
|
159
|
+
last_error = EXCLUDED.last_error,
|
|
160
|
+
updated_at = EXCLUDED.updated_at,
|
|
161
|
+
expires_at = EXCLUDED.expires_at,
|
|
162
|
+
metadata = EXCLUDED.metadata
|
|
163
|
+
`,
|
|
164
|
+
[
|
|
165
|
+
task.taskId,
|
|
166
|
+
task.taskType,
|
|
167
|
+
JSON.stringify(task.payload),
|
|
168
|
+
task.assistantId ?? null,
|
|
169
|
+
task.threadId ?? null,
|
|
170
|
+
task.executionType,
|
|
171
|
+
task.executeAt ? new Date(task.executeAt) : null,
|
|
172
|
+
task.delayMs ?? null,
|
|
173
|
+
task.cronExpression ?? null,
|
|
174
|
+
task.timezone ?? null,
|
|
175
|
+
task.nextRunAt ? new Date(task.nextRunAt) : null,
|
|
176
|
+
task.lastRunAt ? new Date(task.lastRunAt) : null,
|
|
177
|
+
task.status,
|
|
178
|
+
task.runCount,
|
|
179
|
+
task.maxRuns ?? null,
|
|
180
|
+
task.retryCount,
|
|
181
|
+
task.maxRetries,
|
|
182
|
+
task.lastError ?? null,
|
|
183
|
+
new Date(task.createdAt),
|
|
184
|
+
new Date(task.updatedAt),
|
|
185
|
+
task.expiresAt ? new Date(task.expiresAt) : null,
|
|
186
|
+
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
187
|
+
]
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get task by ID
|
|
193
|
+
*/
|
|
194
|
+
async get(taskId: string): Promise<ScheduledTaskDefinition | null> {
|
|
195
|
+
await this.ensureInitialized();
|
|
196
|
+
|
|
197
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
198
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE task_id = $1`,
|
|
199
|
+
[taskId]
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (result.rows.length === 0) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return this.mapRowToTask(result.rows[0]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Update task
|
|
211
|
+
*/
|
|
212
|
+
async update(
|
|
213
|
+
taskId: string,
|
|
214
|
+
updates: Partial<ScheduledTaskDefinition>
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
await this.ensureInitialized();
|
|
217
|
+
|
|
218
|
+
const setClauses: string[] = [];
|
|
219
|
+
const values: any[] = [];
|
|
220
|
+
let paramIndex = 1;
|
|
221
|
+
|
|
222
|
+
// Build dynamic UPDATE query
|
|
223
|
+
if (updates.taskType !== undefined) {
|
|
224
|
+
setClauses.push(`task_type = $${paramIndex++}`);
|
|
225
|
+
values.push(updates.taskType);
|
|
226
|
+
}
|
|
227
|
+
if (updates.payload !== undefined) {
|
|
228
|
+
setClauses.push(`payload = $${paramIndex++}`);
|
|
229
|
+
values.push(JSON.stringify(updates.payload));
|
|
230
|
+
}
|
|
231
|
+
if (updates.assistantId !== undefined) {
|
|
232
|
+
setClauses.push(`assistant_id = $${paramIndex++}`);
|
|
233
|
+
values.push(updates.assistantId);
|
|
234
|
+
}
|
|
235
|
+
if (updates.threadId !== undefined) {
|
|
236
|
+
setClauses.push(`thread_id = $${paramIndex++}`);
|
|
237
|
+
values.push(updates.threadId);
|
|
238
|
+
}
|
|
239
|
+
if (updates.executionType !== undefined) {
|
|
240
|
+
setClauses.push(`execution_type = $${paramIndex++}`);
|
|
241
|
+
values.push(updates.executionType);
|
|
242
|
+
}
|
|
243
|
+
if (updates.executeAt !== undefined) {
|
|
244
|
+
setClauses.push(`execute_at = $${paramIndex++}`);
|
|
245
|
+
values.push(updates.executeAt ? new Date(updates.executeAt) : null);
|
|
246
|
+
}
|
|
247
|
+
if (updates.delayMs !== undefined) {
|
|
248
|
+
setClauses.push(`delay_ms = $${paramIndex++}`);
|
|
249
|
+
values.push(updates.delayMs);
|
|
250
|
+
}
|
|
251
|
+
if (updates.cronExpression !== undefined) {
|
|
252
|
+
setClauses.push(`cron_expression = $${paramIndex++}`);
|
|
253
|
+
values.push(updates.cronExpression);
|
|
254
|
+
}
|
|
255
|
+
if (updates.timezone !== undefined) {
|
|
256
|
+
setClauses.push(`timezone = $${paramIndex++}`);
|
|
257
|
+
values.push(updates.timezone);
|
|
258
|
+
}
|
|
259
|
+
if (updates.nextRunAt !== undefined) {
|
|
260
|
+
setClauses.push(`next_run_at = $${paramIndex++}`);
|
|
261
|
+
values.push(updates.nextRunAt ? new Date(updates.nextRunAt) : null);
|
|
262
|
+
}
|
|
263
|
+
if (updates.lastRunAt !== undefined) {
|
|
264
|
+
setClauses.push(`last_run_at = $${paramIndex++}`);
|
|
265
|
+
values.push(updates.lastRunAt ? new Date(updates.lastRunAt) : null);
|
|
266
|
+
}
|
|
267
|
+
if (updates.status !== undefined) {
|
|
268
|
+
setClauses.push(`status = $${paramIndex++}`);
|
|
269
|
+
values.push(updates.status);
|
|
270
|
+
}
|
|
271
|
+
if (updates.runCount !== undefined) {
|
|
272
|
+
setClauses.push(`run_count = $${paramIndex++}`);
|
|
273
|
+
values.push(updates.runCount);
|
|
274
|
+
}
|
|
275
|
+
if (updates.maxRuns !== undefined) {
|
|
276
|
+
setClauses.push(`max_runs = $${paramIndex++}`);
|
|
277
|
+
values.push(updates.maxRuns);
|
|
278
|
+
}
|
|
279
|
+
if (updates.retryCount !== undefined) {
|
|
280
|
+
setClauses.push(`retry_count = $${paramIndex++}`);
|
|
281
|
+
values.push(updates.retryCount);
|
|
282
|
+
}
|
|
283
|
+
if (updates.maxRetries !== undefined) {
|
|
284
|
+
setClauses.push(`max_retries = $${paramIndex++}`);
|
|
285
|
+
values.push(updates.maxRetries);
|
|
286
|
+
}
|
|
287
|
+
if (updates.lastError !== undefined) {
|
|
288
|
+
setClauses.push(`last_error = $${paramIndex++}`);
|
|
289
|
+
values.push(updates.lastError);
|
|
290
|
+
}
|
|
291
|
+
if (updates.expiresAt !== undefined) {
|
|
292
|
+
setClauses.push(`expires_at = $${paramIndex++}`);
|
|
293
|
+
values.push(updates.expiresAt ? new Date(updates.expiresAt) : null);
|
|
294
|
+
}
|
|
295
|
+
if (updates.metadata !== undefined) {
|
|
296
|
+
setClauses.push(`metadata = $${paramIndex++}`);
|
|
297
|
+
values.push(updates.metadata ? JSON.stringify(updates.metadata) : null);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Always update updated_at
|
|
301
|
+
setClauses.push(`updated_at = $${paramIndex++}`);
|
|
302
|
+
values.push(new Date());
|
|
303
|
+
|
|
304
|
+
// Add taskId as the last parameter
|
|
305
|
+
values.push(taskId);
|
|
306
|
+
|
|
307
|
+
if (setClauses.length === 0) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await this.pool.query(
|
|
312
|
+
`UPDATE lattice_scheduled_tasks SET ${setClauses.join(
|
|
313
|
+
", "
|
|
314
|
+
)} WHERE task_id = $${paramIndex}`,
|
|
315
|
+
values
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Delete task
|
|
321
|
+
*/
|
|
322
|
+
async delete(taskId: string): Promise<void> {
|
|
323
|
+
await this.ensureInitialized();
|
|
324
|
+
|
|
325
|
+
await this.pool.query(
|
|
326
|
+
`DELETE FROM lattice_scheduled_tasks WHERE task_id = $1`,
|
|
327
|
+
[taskId]
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get all active tasks (pending or paused)
|
|
333
|
+
*/
|
|
334
|
+
async getActiveTasks(): Promise<ScheduledTaskDefinition[]> {
|
|
335
|
+
await this.ensureInitialized();
|
|
336
|
+
|
|
337
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
338
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE status IN ($1, $2) ORDER BY created_at ASC`,
|
|
339
|
+
[ScheduledTaskStatus.PENDING, ScheduledTaskStatus.PAUSED]
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get tasks by type
|
|
347
|
+
*/
|
|
348
|
+
async getTasksByType(taskType: string): Promise<ScheduledTaskDefinition[]> {
|
|
349
|
+
await this.ensureInitialized();
|
|
350
|
+
|
|
351
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
352
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE task_type = $1 ORDER BY created_at DESC`,
|
|
353
|
+
[taskType]
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Get tasks by status
|
|
361
|
+
*/
|
|
362
|
+
async getTasksByStatus(
|
|
363
|
+
status: ScheduledTaskStatus
|
|
364
|
+
): Promise<ScheduledTaskDefinition[]> {
|
|
365
|
+
await this.ensureInitialized();
|
|
366
|
+
|
|
367
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
368
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE status = $1 ORDER BY created_at DESC`,
|
|
369
|
+
[status]
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get tasks by execution type
|
|
377
|
+
*/
|
|
378
|
+
async getTasksByExecutionType(
|
|
379
|
+
executionType: ScheduleExecutionType
|
|
380
|
+
): Promise<ScheduledTaskDefinition[]> {
|
|
381
|
+
await this.ensureInitialized();
|
|
382
|
+
|
|
383
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
384
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE execution_type = $1 ORDER BY created_at DESC`,
|
|
385
|
+
[executionType]
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get tasks by assistant ID
|
|
393
|
+
*/
|
|
394
|
+
async getTasksByAssistantId(
|
|
395
|
+
assistantId: string
|
|
396
|
+
): Promise<ScheduledTaskDefinition[]> {
|
|
397
|
+
await this.ensureInitialized();
|
|
398
|
+
|
|
399
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
400
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE assistant_id = $1 ORDER BY created_at DESC`,
|
|
401
|
+
[assistantId]
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Get tasks by thread ID
|
|
409
|
+
*/
|
|
410
|
+
async getTasksByThreadId(
|
|
411
|
+
threadId: string
|
|
412
|
+
): Promise<ScheduledTaskDefinition[]> {
|
|
413
|
+
await this.ensureInitialized();
|
|
414
|
+
|
|
415
|
+
const result = await this.pool.query<ScheduledTaskRow>(
|
|
416
|
+
`SELECT * FROM lattice_scheduled_tasks WHERE thread_id = $1 ORDER BY created_at DESC`,
|
|
417
|
+
[threadId]
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get all tasks with optional filters
|
|
425
|
+
*/
|
|
426
|
+
async getAllTasks(filters?: {
|
|
427
|
+
status?: ScheduledTaskStatus;
|
|
428
|
+
executionType?: ScheduleExecutionType;
|
|
429
|
+
taskType?: string;
|
|
430
|
+
assistantId?: string;
|
|
431
|
+
threadId?: string;
|
|
432
|
+
limit?: number;
|
|
433
|
+
offset?: number;
|
|
434
|
+
}): Promise<ScheduledTaskDefinition[]> {
|
|
435
|
+
await this.ensureInitialized();
|
|
436
|
+
|
|
437
|
+
const whereClauses: string[] = [];
|
|
438
|
+
const values: any[] = [];
|
|
439
|
+
let paramIndex = 1;
|
|
440
|
+
|
|
441
|
+
if (filters?.status !== undefined) {
|
|
442
|
+
whereClauses.push(`status = $${paramIndex++}`);
|
|
443
|
+
values.push(filters.status);
|
|
444
|
+
}
|
|
445
|
+
if (filters?.executionType !== undefined) {
|
|
446
|
+
whereClauses.push(`execution_type = $${paramIndex++}`);
|
|
447
|
+
values.push(filters.executionType);
|
|
448
|
+
}
|
|
449
|
+
if (filters?.taskType !== undefined) {
|
|
450
|
+
whereClauses.push(`task_type = $${paramIndex++}`);
|
|
451
|
+
values.push(filters.taskType);
|
|
452
|
+
}
|
|
453
|
+
if (filters?.assistantId !== undefined) {
|
|
454
|
+
whereClauses.push(`assistant_id = $${paramIndex++}`);
|
|
455
|
+
values.push(filters.assistantId);
|
|
456
|
+
}
|
|
457
|
+
if (filters?.threadId !== undefined) {
|
|
458
|
+
whereClauses.push(`thread_id = $${paramIndex++}`);
|
|
459
|
+
values.push(filters.threadId);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let query = `SELECT * FROM lattice_scheduled_tasks`;
|
|
463
|
+
if (whereClauses.length > 0) {
|
|
464
|
+
query += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
465
|
+
}
|
|
466
|
+
query += ` ORDER BY created_at DESC`;
|
|
467
|
+
|
|
468
|
+
if (filters?.limit !== undefined) {
|
|
469
|
+
query += ` LIMIT $${paramIndex++}`;
|
|
470
|
+
values.push(filters.limit);
|
|
471
|
+
}
|
|
472
|
+
if (filters?.offset !== undefined) {
|
|
473
|
+
query += ` OFFSET $${paramIndex++}`;
|
|
474
|
+
values.push(filters.offset);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const result = await this.pool.query<ScheduledTaskRow>(query, values);
|
|
478
|
+
|
|
479
|
+
return result.rows.map((row) => this.mapRowToTask(row));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Count tasks with optional filters
|
|
484
|
+
*/
|
|
485
|
+
async countTasks(filters?: {
|
|
486
|
+
status?: ScheduledTaskStatus;
|
|
487
|
+
executionType?: ScheduleExecutionType;
|
|
488
|
+
taskType?: string;
|
|
489
|
+
assistantId?: string;
|
|
490
|
+
threadId?: string;
|
|
491
|
+
}): Promise<number> {
|
|
492
|
+
await this.ensureInitialized();
|
|
493
|
+
|
|
494
|
+
const whereClauses: string[] = [];
|
|
495
|
+
const values: any[] = [];
|
|
496
|
+
let paramIndex = 1;
|
|
497
|
+
|
|
498
|
+
if (filters?.status !== undefined) {
|
|
499
|
+
whereClauses.push(`status = $${paramIndex++}`);
|
|
500
|
+
values.push(filters.status);
|
|
501
|
+
}
|
|
502
|
+
if (filters?.executionType !== undefined) {
|
|
503
|
+
whereClauses.push(`execution_type = $${paramIndex++}`);
|
|
504
|
+
values.push(filters.executionType);
|
|
505
|
+
}
|
|
506
|
+
if (filters?.taskType !== undefined) {
|
|
507
|
+
whereClauses.push(`task_type = $${paramIndex++}`);
|
|
508
|
+
values.push(filters.taskType);
|
|
509
|
+
}
|
|
510
|
+
if (filters?.assistantId !== undefined) {
|
|
511
|
+
whereClauses.push(`assistant_id = $${paramIndex++}`);
|
|
512
|
+
values.push(filters.assistantId);
|
|
513
|
+
}
|
|
514
|
+
if (filters?.threadId !== undefined) {
|
|
515
|
+
whereClauses.push(`thread_id = $${paramIndex++}`);
|
|
516
|
+
values.push(filters.threadId);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
let query = `SELECT COUNT(*) as count FROM lattice_scheduled_tasks`;
|
|
520
|
+
if (whereClauses.length > 0) {
|
|
521
|
+
query += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const result = await this.pool.query<{ count: string }>(query, values);
|
|
525
|
+
|
|
526
|
+
return parseInt(result.rows[0].count, 10);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Delete completed/cancelled/failed tasks older than specified time
|
|
531
|
+
*/
|
|
532
|
+
async deleteOldTasks(olderThanMs: number): Promise<number> {
|
|
533
|
+
await this.ensureInitialized();
|
|
534
|
+
|
|
535
|
+
const cutoff = new Date(Date.now() - olderThanMs);
|
|
536
|
+
|
|
537
|
+
const result = await this.pool.query(
|
|
538
|
+
`
|
|
539
|
+
DELETE FROM lattice_scheduled_tasks
|
|
540
|
+
WHERE status IN ($1, $2, $3)
|
|
541
|
+
AND updated_at < $4
|
|
542
|
+
`,
|
|
543
|
+
[
|
|
544
|
+
ScheduledTaskStatus.COMPLETED,
|
|
545
|
+
ScheduledTaskStatus.CANCELLED,
|
|
546
|
+
ScheduledTaskStatus.FAILED,
|
|
547
|
+
cutoff,
|
|
548
|
+
]
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
return result.rowCount ?? 0;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Map database row to ScheduledTaskDefinition
|
|
556
|
+
*/
|
|
557
|
+
private mapRowToTask(row: ScheduledTaskRow): ScheduledTaskDefinition {
|
|
558
|
+
return {
|
|
559
|
+
taskId: row.task_id,
|
|
560
|
+
taskType: row.task_type,
|
|
561
|
+
payload:
|
|
562
|
+
typeof row.payload === "string"
|
|
563
|
+
? JSON.parse(row.payload)
|
|
564
|
+
: row.payload || {},
|
|
565
|
+
assistantId: row.assistant_id ?? undefined,
|
|
566
|
+
threadId: row.thread_id ?? undefined,
|
|
567
|
+
executionType: row.execution_type as ScheduleExecutionType,
|
|
568
|
+
executeAt: row.execute_at ? row.execute_at.getTime() : undefined,
|
|
569
|
+
delayMs: row.delay_ms ?? undefined,
|
|
570
|
+
cronExpression: row.cron_expression ?? undefined,
|
|
571
|
+
timezone: row.timezone ?? undefined,
|
|
572
|
+
nextRunAt: row.next_run_at ? row.next_run_at.getTime() : undefined,
|
|
573
|
+
lastRunAt: row.last_run_at ? row.last_run_at.getTime() : undefined,
|
|
574
|
+
status: row.status as ScheduledTaskStatus,
|
|
575
|
+
runCount: row.run_count,
|
|
576
|
+
maxRuns: row.max_runs ?? undefined,
|
|
577
|
+
retryCount: row.retry_count,
|
|
578
|
+
maxRetries: row.max_retries,
|
|
579
|
+
lastError: row.last_error ?? undefined,
|
|
580
|
+
createdAt: row.created_at.getTime(),
|
|
581
|
+
updatedAt: row.updated_at.getTime(),
|
|
582
|
+
expiresAt: row.expires_at ? row.expires_at.getTime() : undefined,
|
|
583
|
+
metadata:
|
|
584
|
+
row.metadata === null
|
|
585
|
+
? undefined
|
|
586
|
+
: typeof row.metadata === "string"
|
|
587
|
+
? JSON.parse(row.metadata)
|
|
588
|
+
: row.metadata,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|