@workflow/world-postgres 4.0.1-beta.2 → 4.0.1-beta.3
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/boss.d.ts +15 -0
- package/dist/boss.d.ts.map +1 -0
- package/dist/boss.js +18 -0
- package/dist/boss.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/drizzle/index.d.ts +6 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +7 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/drizzle/schema.d.ts +582 -0
- package/dist/drizzle/schema.d.ts.map +1 -0
- package/dist/drizzle/schema.js +88 -0
- package/dist/drizzle/schema.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/queue.d.ts +17 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +103 -0
- package/dist/queue.js.map +1 -0
- package/dist/storage.d.ts +7 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +369 -0
- package/dist/storage.js.map +1 -0
- package/dist/streamer.d.ts +5 -0
- package/dist/streamer.d.ts.map +1 -0
- package/dist/streamer.js +157 -0
- package/dist/streamer.js.map +1 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +20 -0
- package/dist/util.js.map +1 -0
- package/dist/zod.d.ts +3 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +10 -0
- package/dist/zod.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { StepStatusSchema, WorkflowRunStatusSchema, } from '@workflow/world';
|
|
2
|
+
import { boolean, customType, index, integer, jsonb, pgEnum, pgTable, primaryKey, text, timestamp, varchar, } from 'drizzle-orm/pg-core';
|
|
3
|
+
function mustBeMoreThanOne(t) {
|
|
4
|
+
return t;
|
|
5
|
+
}
|
|
6
|
+
export const workflowRunStatus = pgEnum('status', mustBeMoreThanOne(WorkflowRunStatusSchema.options));
|
|
7
|
+
export const stepStatus = pgEnum('step_status', mustBeMoreThanOne(StepStatusSchema.options));
|
|
8
|
+
export const runs = pgTable('workflow_runs', {
|
|
9
|
+
runId: varchar('id').primaryKey(),
|
|
10
|
+
output: jsonb('output').$type(),
|
|
11
|
+
deploymentId: varchar('deployment_id').notNull(),
|
|
12
|
+
status: workflowRunStatus('status').notNull(),
|
|
13
|
+
workflowName: varchar('name').notNull(),
|
|
14
|
+
executionContext: jsonb('execution_context').$type(),
|
|
15
|
+
input: jsonb('input').$type().notNull(),
|
|
16
|
+
error: text('error'),
|
|
17
|
+
errorCode: varchar('error_code'),
|
|
18
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
19
|
+
updatedAt: timestamp('updated_at')
|
|
20
|
+
.defaultNow()
|
|
21
|
+
.$onUpdateFn(() => new Date())
|
|
22
|
+
.notNull(),
|
|
23
|
+
completedAt: timestamp('completed_at'),
|
|
24
|
+
startedAt: timestamp('started_at'),
|
|
25
|
+
}, (tb) => ({
|
|
26
|
+
workflowNameIdx: index().on(tb.workflowName),
|
|
27
|
+
statusIdx: index().on(tb.status),
|
|
28
|
+
}));
|
|
29
|
+
export const events = pgTable('workflow_events', {
|
|
30
|
+
eventId: varchar('id').primaryKey(),
|
|
31
|
+
eventType: varchar('type').$type().notNull(),
|
|
32
|
+
correlationId: varchar('correlation_id'),
|
|
33
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
34
|
+
runId: varchar('run_id').notNull(),
|
|
35
|
+
eventData: jsonb('payload'),
|
|
36
|
+
}, (tb) => ({
|
|
37
|
+
runFk: index().on(tb.runId),
|
|
38
|
+
correlationIdFk: index().on(tb.correlationId),
|
|
39
|
+
}));
|
|
40
|
+
export const steps = pgTable('workflow_steps', {
|
|
41
|
+
runId: varchar('run_id').notNull(),
|
|
42
|
+
stepId: varchar('step_id').primaryKey(),
|
|
43
|
+
stepName: varchar('step_name').notNull(),
|
|
44
|
+
status: stepStatus('status').notNull(),
|
|
45
|
+
input: jsonb('input').$type().notNull(),
|
|
46
|
+
output: jsonb('output').$type(),
|
|
47
|
+
error: text('error'),
|
|
48
|
+
errorCode: varchar('error_code'),
|
|
49
|
+
attempt: integer('attempt').notNull(),
|
|
50
|
+
startedAt: timestamp('started_at'),
|
|
51
|
+
completedAt: timestamp('completed_at'),
|
|
52
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
53
|
+
updatedAt: timestamp('updated_at')
|
|
54
|
+
.defaultNow()
|
|
55
|
+
.$onUpdateFn(() => new Date())
|
|
56
|
+
.notNull(),
|
|
57
|
+
}, (tb) => ({
|
|
58
|
+
runFk: index().on(tb.runId),
|
|
59
|
+
statusIdx: index().on(tb.status),
|
|
60
|
+
}));
|
|
61
|
+
export const hooks = pgTable('workflow_hooks', {
|
|
62
|
+
runId: varchar('run_id').notNull(),
|
|
63
|
+
hookId: varchar('hook_id').primaryKey(),
|
|
64
|
+
token: varchar('token').notNull(),
|
|
65
|
+
ownerId: varchar('owner_id').notNull(),
|
|
66
|
+
projectId: varchar('project_id').notNull(),
|
|
67
|
+
environment: varchar('environment').notNull(),
|
|
68
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
69
|
+
metadata: jsonb('metadata').$type(),
|
|
70
|
+
}, (tb) => ({
|
|
71
|
+
runFk: index().on(tb.runId),
|
|
72
|
+
tokenIdx: index().on(tb.token),
|
|
73
|
+
}));
|
|
74
|
+
const bytea = customType({
|
|
75
|
+
dataType() {
|
|
76
|
+
return 'bytea';
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
export const streams = pgTable('workflow_stream_chunks', {
|
|
80
|
+
chunkId: varchar('id').$type().notNull(),
|
|
81
|
+
streamId: varchar('stream_id').notNull(),
|
|
82
|
+
chunkData: bytea('data').notNull(),
|
|
83
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
84
|
+
eof: boolean('eof').notNull(),
|
|
85
|
+
}, (tb) => ({
|
|
86
|
+
primaryKey: primaryKey({ columns: [tb.streamId, tb.chunkId] }),
|
|
87
|
+
}));
|
|
88
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/drizzle/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAoBvD,wBAAgB,WAAW,CACzB,MAAM,GAAE,mBAQP,GACA,KAAK,GAAG;IAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAkBpC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import PgBoss from 'pg-boss';
|
|
2
|
+
import createPostgres from 'postgres';
|
|
3
|
+
import { createClient } from './drizzle/index.js';
|
|
4
|
+
import { createQueue } from './queue.js';
|
|
5
|
+
import { createEventsStorage, createHooksStorage, createRunsStorage, createStepsStorage, } from './storage.js';
|
|
6
|
+
import { createStreamer } from './streamer.js';
|
|
7
|
+
function createStorage(drizzle) {
|
|
8
|
+
return {
|
|
9
|
+
runs: createRunsStorage(drizzle),
|
|
10
|
+
events: createEventsStorage(drizzle),
|
|
11
|
+
hooks: createHooksStorage(drizzle),
|
|
12
|
+
steps: createStepsStorage(drizzle),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function createWorld(config = {
|
|
16
|
+
connectionString: process.env.WORKFLOW_POSTGRES_URL ||
|
|
17
|
+
'postgres://world:world@localhost:5432/world',
|
|
18
|
+
jobPrefix: process.env.WORKFLOW_POSTGRES_JOB_PREFIX,
|
|
19
|
+
queueConcurrency: parseInt(process.env.WORKFLOW_POSTGRES_WORKER_CONCURRENCY || '10', 10) ||
|
|
20
|
+
10,
|
|
21
|
+
}) {
|
|
22
|
+
const boss = new PgBoss({
|
|
23
|
+
connectionString: config.connectionString,
|
|
24
|
+
});
|
|
25
|
+
const postgres = createPostgres(config.connectionString);
|
|
26
|
+
const drizzle = createClient(postgres);
|
|
27
|
+
const queue = createQueue(boss, config);
|
|
28
|
+
const storage = createStorage(drizzle);
|
|
29
|
+
const streamer = createStreamer(postgres, drizzle);
|
|
30
|
+
return {
|
|
31
|
+
...storage,
|
|
32
|
+
...streamer,
|
|
33
|
+
...queue,
|
|
34
|
+
async start() {
|
|
35
|
+
await queue.start();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,cAAc,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAgB,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,SAAS,aAAa,CAAC,OAAgB;IACrC,OAAO;QACL,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;QAChC,MAAM,EAAE,mBAAmB,CAAC,OAAO,CAAC;QACpC,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC;QAClC,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC;KACnC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,SAA8B;IAC5B,gBAAgB,EACd,OAAO,CAAC,GAAG,CAAC,qBAAqB;QACjC,6CAA6C;IAC/C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,4BAA4B;IACnD,gBAAgB,EACd,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oCAAoC,IAAI,IAAI,EAAE,EAAE,CAAC;QACtE,EAAE;CACL;IAED,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;KAC1C,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAEnD,OAAO;QACL,GAAG,OAAO;QACV,GAAG,QAAQ;QACX,GAAG,KAAK;QACR,KAAK,CAAC,KAAK;YACT,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Queue } from '@workflow/world';
|
|
2
|
+
import type PgBoss from 'pg-boss';
|
|
3
|
+
import type { PostgresWorldConfig } from './config.js';
|
|
4
|
+
/**
|
|
5
|
+
* The Postgres queue works by creating two job types in pg-boss:
|
|
6
|
+
* - `workflow` for workflow jobs
|
|
7
|
+
* - `step` for step jobs
|
|
8
|
+
*
|
|
9
|
+
* When a message is queued, it is sent to pg-boss with the appropriate job type.
|
|
10
|
+
* When a job is processed, it is deserialized and then re-queued into the _embedded world_, showing that
|
|
11
|
+
* we can reuse the embedded world, mix and match worlds to build
|
|
12
|
+
* hybrid architectures, and even migrate between worlds.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createQueue(boss: PgBoss, config: PostgresWorldConfig): Queue & {
|
|
15
|
+
start(): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,KAAK,EAIX,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAGlC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,mBAAmB,GAC1B,KAAK,GAAG;IAAE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAgGpC"}
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as Stream from 'node:stream';
|
|
2
|
+
import { JsonTransport } from '@vercel/queue';
|
|
3
|
+
import { MessageId, QueuePayloadSchema, } from '@workflow/world';
|
|
4
|
+
import { createEmbeddedWorld } from '@workflow/world-local';
|
|
5
|
+
import { monotonicFactory } from 'ulid';
|
|
6
|
+
import { MessageData } from './boss.js';
|
|
7
|
+
/**
|
|
8
|
+
* The Postgres queue works by creating two job types in pg-boss:
|
|
9
|
+
* - `workflow` for workflow jobs
|
|
10
|
+
* - `step` for step jobs
|
|
11
|
+
*
|
|
12
|
+
* When a message is queued, it is sent to pg-boss with the appropriate job type.
|
|
13
|
+
* When a job is processed, it is deserialized and then re-queued into the _embedded world_, showing that
|
|
14
|
+
* we can reuse the embedded world, mix and match worlds to build
|
|
15
|
+
* hybrid architectures, and even migrate between worlds.
|
|
16
|
+
*/
|
|
17
|
+
export function createQueue(boss, config) {
|
|
18
|
+
const port = process.env.PORT ? Number(process.env.PORT) : undefined;
|
|
19
|
+
const embeddedWorld = createEmbeddedWorld({ dataDir: undefined, port });
|
|
20
|
+
const transport = new JsonTransport();
|
|
21
|
+
const generateMessageId = monotonicFactory();
|
|
22
|
+
const prefix = config.jobPrefix || 'workflow_';
|
|
23
|
+
const Queues = {
|
|
24
|
+
__wkf_workflow_: `${prefix}flows`,
|
|
25
|
+
__wkf_step_: `${prefix}steps`,
|
|
26
|
+
};
|
|
27
|
+
const createQueueHandler = embeddedWorld.createQueueHandler;
|
|
28
|
+
const getDeploymentId = async () => {
|
|
29
|
+
return 'postgres';
|
|
30
|
+
};
|
|
31
|
+
const createdQueues = new Map();
|
|
32
|
+
function createQueue(name) {
|
|
33
|
+
let createdQueue = createdQueues.get(name);
|
|
34
|
+
if (!createdQueue) {
|
|
35
|
+
createdQueue = boss.createQueue(name);
|
|
36
|
+
createdQueues.set(name, createdQueue);
|
|
37
|
+
}
|
|
38
|
+
return createdQueue;
|
|
39
|
+
}
|
|
40
|
+
const queue = async (queue, message, opts) => {
|
|
41
|
+
await boss.start();
|
|
42
|
+
const [prefix, queueId] = parseQueueName(queue);
|
|
43
|
+
const jobName = Queues[prefix];
|
|
44
|
+
await createQueue(jobName);
|
|
45
|
+
const body = transport.serialize(message);
|
|
46
|
+
const messageId = MessageId.parse(`msg_${generateMessageId()}`);
|
|
47
|
+
await boss.send({
|
|
48
|
+
name: jobName,
|
|
49
|
+
options: {
|
|
50
|
+
singletonKey: opts?.idempotencyKey ?? messageId,
|
|
51
|
+
retryLimit: 3,
|
|
52
|
+
},
|
|
53
|
+
data: MessageData.encode({
|
|
54
|
+
id: queueId,
|
|
55
|
+
data: body,
|
|
56
|
+
attempt: 1,
|
|
57
|
+
messageId,
|
|
58
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
return { messageId };
|
|
62
|
+
};
|
|
63
|
+
async function setupListener(queue, jobName) {
|
|
64
|
+
await createQueue(jobName);
|
|
65
|
+
await Promise.all(Array.from({ length: config.queueConcurrency || 10 }, async () => {
|
|
66
|
+
await boss.work(jobName, work);
|
|
67
|
+
}));
|
|
68
|
+
async function work([job]) {
|
|
69
|
+
const messageData = MessageData.parse(job.data);
|
|
70
|
+
const bodyStream = Stream.Readable.toWeb(Stream.Readable.from([messageData.data]));
|
|
71
|
+
const body = await transport.deserialize(bodyStream);
|
|
72
|
+
const message = QueuePayloadSchema.parse(body);
|
|
73
|
+
const queueName = `${queue}${messageData.id}`;
|
|
74
|
+
await embeddedWorld.queue(queueName, message, {
|
|
75
|
+
idempotencyKey: messageData.idempotencyKey,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function setupListeners() {
|
|
80
|
+
for (const [prefix, jobName] of Object.entries(Queues)) {
|
|
81
|
+
await setupListener(prefix, jobName);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
createQueueHandler,
|
|
86
|
+
getDeploymentId,
|
|
87
|
+
queue,
|
|
88
|
+
async start() {
|
|
89
|
+
boss = await boss.start();
|
|
90
|
+
await setupListeners();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const parseQueueName = (name) => {
|
|
95
|
+
const prefixes = ['__wkf_step_', '__wkf_workflow_'];
|
|
96
|
+
for (const prefix of prefixes) {
|
|
97
|
+
if (name.startsWith(prefix)) {
|
|
98
|
+
return [prefix, name.slice(prefix.length)];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Invalid queue name: ${name}`);
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Storage } from '@workflow/world';
|
|
2
|
+
import { type Drizzle } from './drizzle/index.js';
|
|
3
|
+
export declare function createRunsStorage(drizzle: Drizzle): Storage['runs'];
|
|
4
|
+
export declare function createEventsStorage(drizzle: Drizzle): Storage['events'];
|
|
5
|
+
export declare function createHooksStorage(drizzle: Drizzle): Storage['hooks'];
|
|
6
|
+
export declare function createStepsStorage(drizzle: Drizzle): Storage['steps'];
|
|
7
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAKV,OAAO,EACR,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,oBAAoB,CAAC;AAI1D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAgJnE;AAMD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAmFvE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkFrE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA2GrE"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { WorkflowAPIError } from '@workflow/errors';
|
|
2
|
+
import { and, desc, eq, gt, lt, sql } from 'drizzle-orm';
|
|
3
|
+
import { monotonicFactory } from 'ulid';
|
|
4
|
+
import { Schema } from './drizzle/index.js';
|
|
5
|
+
import { compact } from './util.js';
|
|
6
|
+
export function createRunsStorage(drizzle) {
|
|
7
|
+
const ulid = monotonicFactory();
|
|
8
|
+
const { runs } = Schema;
|
|
9
|
+
const get = drizzle
|
|
10
|
+
.select()
|
|
11
|
+
.from(runs)
|
|
12
|
+
.where(eq(runs.runId, sql.placeholder('id')))
|
|
13
|
+
.limit(1)
|
|
14
|
+
.prepare('workflow_runs_get');
|
|
15
|
+
return {
|
|
16
|
+
async get(id) {
|
|
17
|
+
const [value] = await get.execute({ id });
|
|
18
|
+
if (!value) {
|
|
19
|
+
throw new WorkflowAPIError(`Run not found: ${id}`, { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
return compact(value);
|
|
22
|
+
},
|
|
23
|
+
async cancel(id) {
|
|
24
|
+
// TODO: we might want to guard this for only specific statuses
|
|
25
|
+
const [value] = await drizzle
|
|
26
|
+
.update(Schema.runs)
|
|
27
|
+
.set({ status: 'cancelled', completedAt: sql `now()` })
|
|
28
|
+
.where(eq(runs.runId, id))
|
|
29
|
+
.returning();
|
|
30
|
+
if (!value) {
|
|
31
|
+
throw new WorkflowAPIError(`Run not found: ${id}`, { status: 404 });
|
|
32
|
+
}
|
|
33
|
+
return compact(value);
|
|
34
|
+
},
|
|
35
|
+
async pause(id) {
|
|
36
|
+
// TODO: we might want to guard this for only specific statuses
|
|
37
|
+
const [value] = await drizzle
|
|
38
|
+
.update(Schema.runs)
|
|
39
|
+
.set({ status: 'paused' })
|
|
40
|
+
.where(eq(runs.runId, id))
|
|
41
|
+
.returning();
|
|
42
|
+
if (!value) {
|
|
43
|
+
throw new WorkflowAPIError(`Run not found: ${id}`, { status: 404 });
|
|
44
|
+
}
|
|
45
|
+
return compact(value);
|
|
46
|
+
},
|
|
47
|
+
async resume(id) {
|
|
48
|
+
const [value] = await drizzle
|
|
49
|
+
.update(Schema.runs)
|
|
50
|
+
.set({ status: 'running' })
|
|
51
|
+
.where(and(eq(runs.runId, id), eq(runs.status, 'paused')))
|
|
52
|
+
.returning();
|
|
53
|
+
if (!value) {
|
|
54
|
+
throw new WorkflowAPIError(`Paused run not found: ${id}`, {
|
|
55
|
+
status: 404,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return compact(value);
|
|
59
|
+
},
|
|
60
|
+
async list(params) {
|
|
61
|
+
const limit = params?.pagination?.limit ?? 20;
|
|
62
|
+
const fromCursor = params?.pagination?.cursor;
|
|
63
|
+
const all = await drizzle
|
|
64
|
+
.select()
|
|
65
|
+
.from(runs)
|
|
66
|
+
.where(and(map(fromCursor, (c) => lt(runs.runId, c)), map(params?.workflowName, (wf) => eq(runs.workflowName, wf)), map(params?.status, (wf) => eq(runs.status, wf))))
|
|
67
|
+
.orderBy(desc(runs.runId))
|
|
68
|
+
.limit(limit + 1);
|
|
69
|
+
const values = all.slice(0, limit);
|
|
70
|
+
const hasMore = all.length > limit;
|
|
71
|
+
return {
|
|
72
|
+
data: values.map(compact),
|
|
73
|
+
hasMore,
|
|
74
|
+
cursor: values.at(-1)?.runId ?? null,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
async create(data) {
|
|
78
|
+
const runId = `wrun_${ulid()}`;
|
|
79
|
+
const [value] = await drizzle
|
|
80
|
+
.insert(runs)
|
|
81
|
+
.values({
|
|
82
|
+
runId,
|
|
83
|
+
input: data.input,
|
|
84
|
+
executionContext: data.executionContext,
|
|
85
|
+
deploymentId: data.deploymentId,
|
|
86
|
+
status: 'pending',
|
|
87
|
+
workflowName: data.workflowName,
|
|
88
|
+
})
|
|
89
|
+
.onConflictDoNothing()
|
|
90
|
+
.returning();
|
|
91
|
+
if (!value) {
|
|
92
|
+
throw new WorkflowAPIError(`Run ${runId} already exists`, {
|
|
93
|
+
status: 409,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return compact(value);
|
|
97
|
+
},
|
|
98
|
+
async update(id, data) {
|
|
99
|
+
// Fetch current run to check if startedAt is already set
|
|
100
|
+
const [currentRun] = await drizzle
|
|
101
|
+
.select()
|
|
102
|
+
.from(runs)
|
|
103
|
+
.where(eq(runs.runId, id))
|
|
104
|
+
.limit(1);
|
|
105
|
+
if (!currentRun) {
|
|
106
|
+
throw new WorkflowAPIError(`Run not found: ${id}`, { status: 404 });
|
|
107
|
+
}
|
|
108
|
+
const updates = {
|
|
109
|
+
...data,
|
|
110
|
+
output: data.output,
|
|
111
|
+
};
|
|
112
|
+
// Only set startedAt the first time transitioning to 'running'
|
|
113
|
+
if (data.status === 'running' && !currentRun.startedAt) {
|
|
114
|
+
updates.startedAt = new Date();
|
|
115
|
+
}
|
|
116
|
+
if (data.status === 'completed' ||
|
|
117
|
+
data.status === 'failed' ||
|
|
118
|
+
data.status === 'cancelled') {
|
|
119
|
+
updates.completedAt = new Date();
|
|
120
|
+
}
|
|
121
|
+
const [value] = await drizzle
|
|
122
|
+
.update(runs)
|
|
123
|
+
.set(updates)
|
|
124
|
+
.where(eq(runs.runId, id))
|
|
125
|
+
.returning();
|
|
126
|
+
if (!value) {
|
|
127
|
+
throw new WorkflowAPIError(`Run not found: ${id}`, { status: 404 });
|
|
128
|
+
}
|
|
129
|
+
return compact(value);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function map(obj, fn) {
|
|
134
|
+
return obj ? fn(obj) : undefined;
|
|
135
|
+
}
|
|
136
|
+
export function createEventsStorage(drizzle) {
|
|
137
|
+
const ulid = monotonicFactory();
|
|
138
|
+
const { events } = Schema;
|
|
139
|
+
return {
|
|
140
|
+
async create(runId, data) {
|
|
141
|
+
const eventId = `wevt_${ulid()}`;
|
|
142
|
+
const [value] = await drizzle
|
|
143
|
+
.insert(events)
|
|
144
|
+
.values({
|
|
145
|
+
runId,
|
|
146
|
+
eventId,
|
|
147
|
+
correlationId: data.correlationId,
|
|
148
|
+
eventType: data.eventType,
|
|
149
|
+
eventData: 'eventData' in data ? data.eventData : undefined,
|
|
150
|
+
})
|
|
151
|
+
.returning({ createdAt: events.createdAt });
|
|
152
|
+
if (!value) {
|
|
153
|
+
throw new WorkflowAPIError(`Event ${eventId} could not be created`, {
|
|
154
|
+
status: 409,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return { ...data, ...value, runId, eventId };
|
|
158
|
+
},
|
|
159
|
+
async list(params) {
|
|
160
|
+
const limit = params?.pagination?.limit ?? 100;
|
|
161
|
+
const sortOrder = params.pagination?.sortOrder || 'asc';
|
|
162
|
+
const order = sortOrder === 'desc'
|
|
163
|
+
? { by: desc(events.eventId), compare: lt }
|
|
164
|
+
: { by: events.eventId, compare: gt };
|
|
165
|
+
const all = await drizzle
|
|
166
|
+
.select()
|
|
167
|
+
.from(events)
|
|
168
|
+
.where(and(eq(events.runId, params.runId), map(params.pagination?.cursor, (c) => order.compare(events.eventId, c))))
|
|
169
|
+
.orderBy(order.by)
|
|
170
|
+
.limit(limit + 1);
|
|
171
|
+
const values = all.slice(0, limit);
|
|
172
|
+
return {
|
|
173
|
+
data: values.map(compact),
|
|
174
|
+
cursor: values.at(-1)?.eventId ?? null,
|
|
175
|
+
hasMore: all.length > limit,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
async listByCorrelationId(params) {
|
|
179
|
+
const limit = params?.pagination?.limit ?? 100;
|
|
180
|
+
const sortOrder = params.pagination?.sortOrder || 'asc';
|
|
181
|
+
const order = sortOrder === 'desc'
|
|
182
|
+
? { by: desc(events.eventId), compare: lt }
|
|
183
|
+
: { by: events.eventId, compare: gt };
|
|
184
|
+
const all = await drizzle
|
|
185
|
+
.select()
|
|
186
|
+
.from(events)
|
|
187
|
+
.where(and(eq(events.correlationId, params.correlationId), map(params.pagination?.cursor, (c) => order.compare(events.eventId, c))))
|
|
188
|
+
.orderBy(order.by)
|
|
189
|
+
.limit(limit + 1);
|
|
190
|
+
const values = all.slice(0, limit);
|
|
191
|
+
return {
|
|
192
|
+
data: values.map(compact),
|
|
193
|
+
cursor: values.at(-1)?.eventId ?? null,
|
|
194
|
+
hasMore: all.length > limit,
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
export function createHooksStorage(drizzle) {
|
|
200
|
+
const { hooks } = Schema;
|
|
201
|
+
const getByToken = drizzle
|
|
202
|
+
.select()
|
|
203
|
+
.from(hooks)
|
|
204
|
+
.where(eq(hooks.token, sql.placeholder('token')))
|
|
205
|
+
.limit(1)
|
|
206
|
+
.prepare('workflow_hooks_get_by_token');
|
|
207
|
+
return {
|
|
208
|
+
async get(hookId) {
|
|
209
|
+
const [value] = await drizzle
|
|
210
|
+
.select()
|
|
211
|
+
.from(hooks)
|
|
212
|
+
.where(eq(hooks.hookId, hookId))
|
|
213
|
+
.limit(1);
|
|
214
|
+
return compact(value);
|
|
215
|
+
},
|
|
216
|
+
async create(runId, data) {
|
|
217
|
+
const [value] = await drizzle
|
|
218
|
+
.insert(hooks)
|
|
219
|
+
.values({
|
|
220
|
+
runId,
|
|
221
|
+
hookId: data.hookId,
|
|
222
|
+
token: data.token,
|
|
223
|
+
ownerId: '', // TODO: get from context
|
|
224
|
+
projectId: '', // TODO: get from context
|
|
225
|
+
environment: '', // TODO: get from context
|
|
226
|
+
})
|
|
227
|
+
.onConflictDoNothing()
|
|
228
|
+
.returning();
|
|
229
|
+
if (!value) {
|
|
230
|
+
throw new WorkflowAPIError(`Hook ${data.hookId} already exists`, {
|
|
231
|
+
status: 409,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return compact(value);
|
|
235
|
+
},
|
|
236
|
+
async getByToken(token) {
|
|
237
|
+
const [value] = await getByToken.execute({ token });
|
|
238
|
+
if (!value) {
|
|
239
|
+
throw new WorkflowAPIError(`Hook not found for token: ${token}`, {
|
|
240
|
+
status: 404,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return compact(value);
|
|
244
|
+
},
|
|
245
|
+
async list(params) {
|
|
246
|
+
const limit = params?.pagination?.limit ?? 100;
|
|
247
|
+
const fromCursor = params?.pagination?.cursor;
|
|
248
|
+
const all = await drizzle
|
|
249
|
+
.select()
|
|
250
|
+
.from(hooks)
|
|
251
|
+
.where(and(map(params.runId, (id) => eq(hooks.runId, id)), map(fromCursor, (c) => lt(hooks.hookId, c))))
|
|
252
|
+
.orderBy(desc(hooks.hookId))
|
|
253
|
+
.limit(limit + 1);
|
|
254
|
+
const values = all.slice(0, limit);
|
|
255
|
+
const hasMore = all.length > limit;
|
|
256
|
+
return {
|
|
257
|
+
data: values.map(compact),
|
|
258
|
+
cursor: values.at(-1)?.hookId ?? null,
|
|
259
|
+
hasMore,
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
async dispose(hookId) {
|
|
263
|
+
const [value] = await drizzle
|
|
264
|
+
.delete(hooks)
|
|
265
|
+
.where(eq(hooks.hookId, hookId))
|
|
266
|
+
.returning();
|
|
267
|
+
if (!value) {
|
|
268
|
+
throw new WorkflowAPIError(`Hook not found: ${hookId}`, {
|
|
269
|
+
status: 404,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return compact(value);
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
export function createStepsStorage(drizzle) {
|
|
277
|
+
const { steps } = Schema;
|
|
278
|
+
const get = drizzle
|
|
279
|
+
.select()
|
|
280
|
+
.from(steps)
|
|
281
|
+
.where(and(eq(steps.stepId, sql.placeholder('stepId')), eq(steps.runId, sql.placeholder('runId'))))
|
|
282
|
+
.limit(1)
|
|
283
|
+
.prepare('workflow_steps_get');
|
|
284
|
+
return {
|
|
285
|
+
async create(runId, data) {
|
|
286
|
+
const [value] = await drizzle
|
|
287
|
+
.insert(steps)
|
|
288
|
+
.values({
|
|
289
|
+
runId,
|
|
290
|
+
stepId: data.stepId,
|
|
291
|
+
stepName: data.stepName,
|
|
292
|
+
input: data.input,
|
|
293
|
+
status: 'pending',
|
|
294
|
+
attempt: 1,
|
|
295
|
+
})
|
|
296
|
+
.onConflictDoNothing()
|
|
297
|
+
.returning();
|
|
298
|
+
if (!value) {
|
|
299
|
+
throw new WorkflowAPIError(`Step ${data.stepId} already exists`, {
|
|
300
|
+
status: 409,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return compact(value);
|
|
304
|
+
},
|
|
305
|
+
async get(runId, stepId) {
|
|
306
|
+
const [value] = await get.execute({ stepId, runId });
|
|
307
|
+
if (!value) {
|
|
308
|
+
throw new WorkflowAPIError(`Step not found: ${stepId}`, {
|
|
309
|
+
status: 404,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return compact(value);
|
|
313
|
+
},
|
|
314
|
+
async update(runId, stepId, data) {
|
|
315
|
+
// Fetch current step to check if startedAt is already set
|
|
316
|
+
const [currentStep] = await drizzle
|
|
317
|
+
.select()
|
|
318
|
+
.from(steps)
|
|
319
|
+
.where(and(eq(steps.stepId, stepId), eq(steps.runId, runId)))
|
|
320
|
+
.limit(1);
|
|
321
|
+
if (!currentStep) {
|
|
322
|
+
throw new WorkflowAPIError(`Step not found: ${stepId}`, {
|
|
323
|
+
status: 404,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const updates = {
|
|
327
|
+
...data,
|
|
328
|
+
output: data.output,
|
|
329
|
+
};
|
|
330
|
+
const now = new Date();
|
|
331
|
+
// Only set startedAt the first time the step transitions to 'running'
|
|
332
|
+
if (data.status === 'running' && !currentStep.startedAt) {
|
|
333
|
+
updates.startedAt = now;
|
|
334
|
+
}
|
|
335
|
+
if (data.status === 'completed' || data.status === 'failed') {
|
|
336
|
+
updates.completedAt = now;
|
|
337
|
+
}
|
|
338
|
+
const [value] = await drizzle
|
|
339
|
+
.update(steps)
|
|
340
|
+
.set(updates)
|
|
341
|
+
.where(and(eq(steps.stepId, stepId), eq(steps.runId, runId)))
|
|
342
|
+
.returning();
|
|
343
|
+
if (!value) {
|
|
344
|
+
throw new WorkflowAPIError(`Step not found: ${stepId}`, {
|
|
345
|
+
status: 404,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return compact(value);
|
|
349
|
+
},
|
|
350
|
+
async list(params) {
|
|
351
|
+
const limit = params?.pagination?.limit ?? 20;
|
|
352
|
+
const fromCursor = params?.pagination?.cursor;
|
|
353
|
+
const all = await drizzle
|
|
354
|
+
.select()
|
|
355
|
+
.from(steps)
|
|
356
|
+
.where(and(eq(steps.runId, params.runId), map(fromCursor, (c) => lt(steps.stepId, c))))
|
|
357
|
+
.orderBy(desc(steps.stepId))
|
|
358
|
+
.limit(limit + 1);
|
|
359
|
+
const values = all.slice(0, limit);
|
|
360
|
+
const hasMore = all.length > limit;
|
|
361
|
+
return {
|
|
362
|
+
data: values.map(compact),
|
|
363
|
+
hasMore,
|
|
364
|
+
cursor: values.at(-1)?.stepId ?? null,
|
|
365
|
+
};
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
//# sourceMappingURL=storage.js.map
|