@gencow/core 0.1.24 → 0.1.26
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/crud.d.ts +2 -2
- package/dist/crud.js +225 -208
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/reactive.js +10 -3
- package/dist/retry.js +1 -1
- package/dist/rls-db.d.ts +2 -2
- package/dist/rls-db.js +1 -5
- package/dist/scheduler.d.ts +2 -0
- package/dist/scheduler.js +16 -6
- package/dist/server.d.ts +0 -1
- package/dist/server.js +0 -1
- package/dist/storage.js +29 -22
- package/dist/v.d.ts +2 -2
- package/dist/workflow.js +4 -11
- package/dist/workflows-api.js +5 -12
- package/package.json +45 -42
- package/src/__tests__/auth.test.ts +90 -86
- package/src/__tests__/crons.test.ts +69 -67
- package/src/__tests__/crud-codegen-integration.test.ts +164 -170
- package/src/__tests__/crud-owner-rls.test.ts +308 -301
- package/src/__tests__/crud.test.ts +694 -711
- package/src/__tests__/dist-exports.test.ts +120 -120
- package/src/__tests__/fixtures/basic/auth.ts +16 -16
- package/src/__tests__/fixtures/basic/drizzle.config.ts +1 -4
- package/src/__tests__/fixtures/basic/index.ts +1 -1
- package/src/__tests__/fixtures/basic/schema.ts +1 -1
- package/src/__tests__/fixtures/basic/tasks.ts +4 -4
- package/src/__tests__/fixtures/common/auth-schema.ts +38 -34
- package/src/__tests__/helpers/basic-rls-fixture.ts +80 -78
- package/src/__tests__/helpers/pglite-migrations.ts +2 -5
- package/src/__tests__/helpers/pglite-rls-session.ts +13 -16
- package/src/__tests__/helpers/seed-like-fill.ts +47 -41
- package/src/__tests__/helpers/test-gencow-ctx-rls.ts +4 -7
- package/src/__tests__/httpaction.test.ts +91 -91
- package/src/__tests__/image-optimization.test.ts +570 -574
- package/src/__tests__/load.test.ts +321 -308
- package/src/__tests__/network-sim.test.ts +238 -215
- package/src/__tests__/reactive.test.ts +380 -358
- package/src/__tests__/retry.test.ts +99 -84
- package/src/__tests__/rls-crud-basic.test.ts +172 -245
- package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +81 -81
- package/src/__tests__/rls-custom-mutation-handlers.test.ts +47 -94
- package/src/__tests__/rls-custom-query-handlers.test.ts +92 -92
- package/src/__tests__/rls-db-leased-connection.test.ts +2 -6
- package/src/__tests__/rls-session-and-policies.test.ts +181 -199
- package/src/__tests__/scheduler-durable-v2.test.ts +199 -181
- package/src/__tests__/scheduler-durable.test.ts +117 -117
- package/src/__tests__/scheduler-exec.test.ts +258 -246
- package/src/__tests__/scheduler.test.ts +129 -111
- package/src/__tests__/storage.test.ts +282 -269
- package/src/__tests__/tsconfig.json +6 -6
- package/src/__tests__/validator.test.ts +236 -232
- package/src/__tests__/workflow.test.ts +309 -286
- package/src/__tests__/ws-integration.test.ts +223 -218
- package/src/__tests__/ws-scale.test.ts +168 -159
- package/src/auth-config.ts +18 -18
- package/src/auth.ts +106 -106
- package/src/crons.ts +77 -77
- package/src/crud.ts +523 -479
- package/src/index.ts +69 -5
- package/src/reactive.ts +357 -331
- package/src/retry.ts +51 -54
- package/src/rls-db.ts +195 -205
- package/src/rls.ts +33 -36
- package/src/scheduler.ts +237 -211
- package/src/server.ts +0 -1
- package/src/storage.ts +632 -593
- package/src/v.ts +119 -114
- package/src/workflow-types.ts +67 -70
- package/src/workflow.ts +99 -116
- package/src/workflows-api.ts +231 -241
- package/dist/db.d.ts +0 -13
- package/dist/db.js +0 -16
- package/src/db.ts +0 -18
package/src/workflows-api.ts
CHANGED
|
@@ -1,203 +1,194 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
2
|
import { mutation, query } from "./reactive.js";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
createWorkflowRealtimeToken,
|
|
5
|
+
deserializeWorkflowValue,
|
|
6
|
+
getWorkflowResumeActionName,
|
|
7
|
+
getWorkflowRealtimeKey,
|
|
8
|
+
serializeWorkflowValue,
|
|
9
9
|
} from "./workflow.js";
|
|
10
10
|
import { GencowValidationError, v } from "./v.js";
|
|
11
11
|
import type {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
WorkflowDerivedStatus,
|
|
13
|
+
WorkflowSnapshot,
|
|
14
|
+
WorkflowSignalResult,
|
|
15
|
+
WorkflowStatus,
|
|
16
|
+
WorkflowStepSnapshot,
|
|
17
|
+
WorkflowSummary,
|
|
18
18
|
} from "./workflow-types.js";
|
|
19
19
|
import { deriveWorkflowStatus } from "./workflow-types.js";
|
|
20
20
|
|
|
21
21
|
declare global {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// eslint-disable-next-line no-var
|
|
23
|
+
var __gencow_workflowsApiRegistered: boolean | undefined;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
type WorkflowRow = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
args: unknown;
|
|
30
|
+
status: WorkflowStatus;
|
|
31
|
+
current_step: string | null;
|
|
32
|
+
result: unknown;
|
|
33
|
+
error: string | null;
|
|
34
|
+
retry_count: number;
|
|
35
|
+
max_retries: number;
|
|
36
|
+
max_duration_ms: number;
|
|
37
|
+
started_at: string | Date;
|
|
38
|
+
updated_at: string | Date;
|
|
39
|
+
completed_at: string | Date | null;
|
|
40
|
+
realtime_token: string | null;
|
|
41
|
+
user_id: string | null;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
type WorkflowStepRow = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
step_name: string;
|
|
46
|
+
status: WorkflowStatus;
|
|
47
|
+
output: unknown;
|
|
48
|
+
error: string | null;
|
|
49
|
+
started_at: string | Date | null;
|
|
50
|
+
updated_at: string | Date;
|
|
51
|
+
completed_at: string | Date | null;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
type WorkflowSignalTargetRow = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
status: WorkflowStatus;
|
|
58
|
+
current_step: string | null;
|
|
59
|
+
user_id: string | null;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
const WORKFLOW_STATUSES = new Set<WorkflowStatus>([
|
|
63
|
-
|
|
64
|
-
"running",
|
|
65
|
-
"completed",
|
|
66
|
-
"failed",
|
|
67
|
-
]);
|
|
68
|
-
const WORKFLOW_DERIVED_PENDING_STATUSES = new Set<WorkflowDerivedStatus>([
|
|
69
|
-
"queued",
|
|
70
|
-
"waiting",
|
|
71
|
-
"sleeping",
|
|
72
|
-
]);
|
|
62
|
+
const WORKFLOW_STATUSES = new Set<WorkflowStatus>(["pending", "running", "completed", "failed"]);
|
|
63
|
+
const WORKFLOW_DERIVED_PENDING_STATUSES = new Set<WorkflowDerivedStatus>(["queued", "waiting", "sleeping"]);
|
|
73
64
|
|
|
74
65
|
type WorkflowDbLike = {
|
|
75
|
-
|
|
66
|
+
execute: (query: unknown) => Promise<unknown>;
|
|
76
67
|
};
|
|
77
68
|
|
|
78
69
|
function rowsFromResult<T>(result: unknown): T[] {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
if (Array.isArray(result)) return result as T[];
|
|
71
|
+
if (result && typeof result === "object" && Array.isArray((result as { rows?: unknown[] }).rows)) {
|
|
72
|
+
return (result as { rows: T[] }).rows;
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
84
75
|
}
|
|
85
76
|
|
|
86
77
|
function parseJsonField(value: unknown): unknown {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
if (typeof value !== "string") return value;
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(value);
|
|
81
|
+
} catch {
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
93
84
|
}
|
|
94
85
|
|
|
95
86
|
function toIsoString(value: string | Date): string {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
if (value instanceof Date) return value.toISOString();
|
|
88
|
+
const parsed = new Date(value);
|
|
89
|
+
return Number.isFinite(parsed.getTime()) ? parsed.toISOString() : String(value);
|
|
99
90
|
}
|
|
100
91
|
|
|
101
92
|
function toOptionalIsoString(value: string | Date | null): string | null {
|
|
102
|
-
|
|
93
|
+
return value ? toIsoString(value) : null;
|
|
103
94
|
}
|
|
104
95
|
|
|
105
96
|
function mapWorkflowSummary(row: WorkflowRow): WorkflowSummary {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
97
|
+
return {
|
|
98
|
+
id: row.id,
|
|
99
|
+
name: row.name,
|
|
100
|
+
status: row.status,
|
|
101
|
+
derivedStatus: deriveWorkflowStatus(row.status, row.current_step),
|
|
102
|
+
currentStep: row.current_step,
|
|
103
|
+
error: row.error,
|
|
104
|
+
retryCount: row.retry_count,
|
|
105
|
+
maxRetries: row.max_retries,
|
|
106
|
+
maxDurationMs: Number(row.max_duration_ms),
|
|
107
|
+
startedAt: toIsoString(row.started_at),
|
|
108
|
+
updatedAt: toIsoString(row.updated_at),
|
|
109
|
+
completedAt: toOptionalIsoString(row.completed_at),
|
|
110
|
+
};
|
|
120
111
|
}
|
|
121
112
|
|
|
122
113
|
function mapWorkflowStep(row: WorkflowStepRow): WorkflowStepSnapshot {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
114
|
+
return {
|
|
115
|
+
name: row.step_name,
|
|
116
|
+
status: row.status,
|
|
117
|
+
output: deserializeWorkflowValue(parseJsonField(row.output)),
|
|
118
|
+
error: row.error,
|
|
119
|
+
startedAt: toOptionalIsoString(row.started_at),
|
|
120
|
+
updatedAt: toIsoString(row.updated_at),
|
|
121
|
+
completedAt: toOptionalIsoString(row.completed_at),
|
|
122
|
+
};
|
|
132
123
|
}
|
|
133
124
|
|
|
134
125
|
function normalizeListLimit(limit: number | undefined): number {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
126
|
+
if (limit == null) return 20;
|
|
127
|
+
if (!Number.isFinite(limit)) {
|
|
128
|
+
throw new GencowValidationError(`Argument "limit": expected a finite number, got ${limit}`);
|
|
129
|
+
}
|
|
130
|
+
return Math.max(1, Math.min(100, Math.floor(limit)));
|
|
140
131
|
}
|
|
141
132
|
|
|
142
133
|
function normalizeStatus(status: string | undefined): WorkflowStatus | undefined {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
return status as WorkflowStatus;
|
|
134
|
+
if (status == null) return undefined;
|
|
135
|
+
if (!WORKFLOW_STATUSES.has(status as WorkflowStatus)) {
|
|
136
|
+
throw new GencowValidationError(`Argument "status": expected one of pending, running, completed, failed`);
|
|
137
|
+
}
|
|
138
|
+
return status as WorkflowStatus;
|
|
150
139
|
}
|
|
151
140
|
|
|
152
141
|
function normalizeDerivedStatus(status: string | undefined): WorkflowDerivedStatus | undefined {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
142
|
+
if (status == null) return undefined;
|
|
143
|
+
if (WORKFLOW_DERIVED_PENDING_STATUSES.has(status as WorkflowDerivedStatus)) {
|
|
144
|
+
return status as WorkflowDerivedStatus;
|
|
145
|
+
}
|
|
146
|
+
return normalizeStatus(status) as WorkflowDerivedStatus;
|
|
158
147
|
}
|
|
159
148
|
|
|
160
149
|
function toWorkflowStatusFilter(status: WorkflowDerivedStatus | undefined): WorkflowStatus | undefined {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
150
|
+
if (status == null) return undefined;
|
|
151
|
+
if (WORKFLOW_DERIVED_PENDING_STATUSES.has(status)) {
|
|
152
|
+
return "pending";
|
|
153
|
+
}
|
|
154
|
+
return status as WorkflowStatus;
|
|
166
155
|
}
|
|
167
156
|
|
|
168
157
|
async function ensureWorkflowRealtimeToken(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
db: WorkflowDbLike,
|
|
159
|
+
workflowId: string,
|
|
160
|
+
currentToken: string | null,
|
|
172
161
|
): Promise<string | null> {
|
|
173
|
-
|
|
162
|
+
if (currentToken && currentToken.trim() !== "") return currentToken;
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
164
|
+
const nextToken = createWorkflowRealtimeToken();
|
|
165
|
+
const updateResult = await db.execute(sql`
|
|
177
166
|
UPDATE _gencow_workflows
|
|
178
167
|
SET realtime_token = ${nextToken}
|
|
179
168
|
WHERE id = ${workflowId}
|
|
180
169
|
AND (realtime_token IS NULL OR realtime_token = '')
|
|
181
170
|
RETURNING realtime_token
|
|
182
171
|
`);
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
const updatedToken =
|
|
173
|
+
rowsFromResult<{ realtime_token: string | null }>(updateResult)[0]?.realtime_token ?? null;
|
|
174
|
+
if (updatedToken && updatedToken.trim() !== "") return updatedToken;
|
|
185
175
|
|
|
186
|
-
|
|
176
|
+
const rereadResult = await db.execute(sql`
|
|
187
177
|
SELECT realtime_token
|
|
188
178
|
FROM _gencow_workflows
|
|
189
179
|
WHERE id = ${workflowId}
|
|
190
180
|
LIMIT 1
|
|
191
181
|
`);
|
|
192
|
-
|
|
193
|
-
|
|
182
|
+
const rereadToken =
|
|
183
|
+
rowsFromResult<{ realtime_token: string | null }>(rereadResult)[0]?.realtime_token ?? null;
|
|
184
|
+
return rereadToken && rereadToken.trim() !== "" ? rereadToken : null;
|
|
194
185
|
}
|
|
195
186
|
|
|
196
187
|
async function loadWorkflowSignalTarget(
|
|
197
|
-
|
|
198
|
-
|
|
188
|
+
db: WorkflowDbLike,
|
|
189
|
+
workflowId: string,
|
|
199
190
|
): Promise<WorkflowSignalTargetRow | null> {
|
|
200
|
-
|
|
191
|
+
const result = await db.execute(sql`
|
|
201
192
|
SELECT
|
|
202
193
|
id,
|
|
203
194
|
name,
|
|
@@ -208,18 +199,18 @@ async function loadWorkflowSignalTarget(
|
|
|
208
199
|
WHERE id = ${workflowId}
|
|
209
200
|
LIMIT 1
|
|
210
201
|
`);
|
|
211
|
-
|
|
202
|
+
return rowsFromResult<WorkflowSignalTargetRow>(result)[0] ?? null;
|
|
212
203
|
}
|
|
213
204
|
|
|
214
205
|
export async function loadWorkflowSnapshot(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
206
|
+
db: WorkflowDbLike,
|
|
207
|
+
workflowId: string,
|
|
208
|
+
options?: {
|
|
209
|
+
viewerUserId?: string | null;
|
|
210
|
+
requireViewerMatch?: boolean;
|
|
211
|
+
},
|
|
221
212
|
): Promise<WorkflowSnapshot | null> {
|
|
222
|
-
|
|
213
|
+
const workflowResult = await db.execute(sql`
|
|
223
214
|
SELECT
|
|
224
215
|
id,
|
|
225
216
|
name,
|
|
@@ -240,18 +231,18 @@ export async function loadWorkflowSnapshot(
|
|
|
240
231
|
WHERE id = ${workflowId}
|
|
241
232
|
LIMIT 1
|
|
242
233
|
`);
|
|
243
|
-
|
|
244
|
-
|
|
234
|
+
const row = rowsFromResult<WorkflowRow>(workflowResult)[0] ?? null;
|
|
235
|
+
if (!row) return null;
|
|
245
236
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
237
|
+
const viewerUserId = options?.viewerUserId ?? null;
|
|
238
|
+
if (options?.requireViewerMatch && row.user_id && row.user_id !== viewerUserId) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
250
241
|
|
|
251
|
-
|
|
252
|
-
|
|
242
|
+
const realtimeToken = await ensureWorkflowRealtimeToken(db, workflowId, row.realtime_token);
|
|
243
|
+
if (!realtimeToken) return null;
|
|
253
244
|
|
|
254
|
-
|
|
245
|
+
const stepsResult = await db.execute(sql`
|
|
255
246
|
SELECT
|
|
256
247
|
step_name,
|
|
257
248
|
status,
|
|
@@ -265,66 +256,66 @@ export async function loadWorkflowSnapshot(
|
|
|
265
256
|
ORDER BY COALESCE(started_at, updated_at) ASC, step_name ASC
|
|
266
257
|
`);
|
|
267
258
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
259
|
+
return {
|
|
260
|
+
...mapWorkflowSummary(row),
|
|
261
|
+
args: deserializeWorkflowValue(parseJsonField(row.args)),
|
|
262
|
+
result: deserializeWorkflowValue(parseJsonField(row.result)),
|
|
263
|
+
steps: rowsFromResult<WorkflowStepRow>(stepsResult).map(mapWorkflowStep),
|
|
264
|
+
realtimeKey: getWorkflowRealtimeKey(row.id, realtimeToken),
|
|
265
|
+
};
|
|
275
266
|
}
|
|
276
267
|
|
|
277
268
|
export function registerWorkflowsApi(): void {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
269
|
+
if (globalThis.__gencow_workflowsApiRegistered) return;
|
|
270
|
+
globalThis.__gencow_workflowsApiRegistered = true;
|
|
271
|
+
|
|
272
|
+
query("workflows.get", {
|
|
273
|
+
args: { id: v.string() },
|
|
274
|
+
public: true,
|
|
275
|
+
handler: async (ctx, args): Promise<WorkflowSnapshot | null> => {
|
|
276
|
+
return loadWorkflowSnapshot(ctx.unsafeDb, args.id, {
|
|
277
|
+
viewerUserId: ctx.auth.getUserIdentity()?.id ?? null,
|
|
278
|
+
requireViewerMatch: true,
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
mutation("workflows.signal", {
|
|
284
|
+
args: {
|
|
285
|
+
id: v.string(),
|
|
286
|
+
event: v.string(),
|
|
287
|
+
payload: v.optional(v.any()),
|
|
288
|
+
},
|
|
289
|
+
public: true,
|
|
290
|
+
handler: async (ctx, args): Promise<WorkflowSignalResult> => {
|
|
291
|
+
const normalizedEvent = args.event.trim();
|
|
292
|
+
if (!normalizedEvent) {
|
|
293
|
+
throw new GencowValidationError(`Argument "event": expected a non-empty string`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const workflow = await loadWorkflowSignalTarget(ctx.unsafeDb, args.id);
|
|
297
|
+
const viewerUserId = ctx.auth.getUserIdentity()?.id ?? null;
|
|
298
|
+
|
|
299
|
+
if (!workflow || (workflow.user_id && workflow.user_id !== viewerUserId)) {
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
workflowId: args.id,
|
|
303
|
+
event: normalizedEvent,
|
|
304
|
+
scheduledJobId: null,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (workflow.status === "completed" || workflow.status === "failed") {
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
workflowId: workflow.id,
|
|
312
|
+
event: normalizedEvent,
|
|
313
|
+
scheduledJobId: null,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const persistedPayload = serializeWorkflowValue(args.payload);
|
|
318
|
+
await ctx.unsafeDb.execute(sql`
|
|
328
319
|
INSERT INTO _gencow_workflow_events (
|
|
329
320
|
id,
|
|
330
321
|
workflow_id,
|
|
@@ -339,41 +330,40 @@ export function registerWorkflowsApi(): void {
|
|
|
339
330
|
)
|
|
340
331
|
`);
|
|
341
332
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
? await ctx.unsafeDb.execute(sql`
|
|
333
|
+
let scheduledJobId: string | null = null;
|
|
334
|
+
if (workflow.status === "pending" && workflow.current_step?.startsWith("wait:")) {
|
|
335
|
+
try {
|
|
336
|
+
scheduledJobId = ctx.scheduler.runAfter(0, getWorkflowResumeActionName(workflow.name), {
|
|
337
|
+
workflowId: workflow.id,
|
|
338
|
+
});
|
|
339
|
+
} catch {
|
|
340
|
+
scheduledJobId = null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
ok: true,
|
|
346
|
+
workflowId: workflow.id,
|
|
347
|
+
event: normalizedEvent,
|
|
348
|
+
scheduledJobId,
|
|
349
|
+
};
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
query("workflows.list", {
|
|
354
|
+
args: {
|
|
355
|
+
limit: v.optional(v.number()),
|
|
356
|
+
status: v.optional(v.string()),
|
|
357
|
+
},
|
|
358
|
+
handler: async (ctx, args): Promise<WorkflowSummary[]> => {
|
|
359
|
+
const userId = ctx.auth.requireAuth().id;
|
|
360
|
+
const limit = normalizeListLimit(args.limit);
|
|
361
|
+
const requestedStatus = normalizeDerivedStatus(args.status);
|
|
362
|
+
const status = toWorkflowStatusFilter(requestedStatus);
|
|
363
|
+
|
|
364
|
+
const result =
|
|
365
|
+
status == null
|
|
366
|
+
? await ctx.unsafeDb.execute(sql`
|
|
377
367
|
SELECT
|
|
378
368
|
id,
|
|
379
369
|
name,
|
|
@@ -394,7 +384,7 @@ export function registerWorkflowsApi(): void {
|
|
|
394
384
|
ORDER BY started_at DESC
|
|
395
385
|
LIMIT ${limit}
|
|
396
386
|
`)
|
|
397
|
-
|
|
387
|
+
: await ctx.unsafeDb.execute(sql`
|
|
398
388
|
SELECT
|
|
399
389
|
id,
|
|
400
390
|
name,
|
|
@@ -417,9 +407,9 @@ export function registerWorkflowsApi(): void {
|
|
|
417
407
|
LIMIT ${limit}
|
|
418
408
|
`);
|
|
419
409
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
410
|
+
return rowsFromResult<WorkflowRow>(result)
|
|
411
|
+
.map(mapWorkflowSummary)
|
|
412
|
+
.filter((row) => requestedStatus == null || row.derivedStatus === requestedStatus);
|
|
413
|
+
},
|
|
414
|
+
});
|
|
425
415
|
}
|
package/dist/db.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated — 레거시 싱글톤 DB 인스턴스.
|
|
3
|
-
* 새 코드에서는 ctx.db를 사용하세요.
|
|
4
|
-
* 서버의 createDatabase() (database.ts)가 실제 DB 연결을 관리합니다.
|
|
5
|
-
*/
|
|
6
|
-
import { PGlite } from "@electric-sql/pglite";
|
|
7
|
-
/** @deprecated Use ctx.db instead */
|
|
8
|
-
export declare function createDb(dataDir?: string): Promise<{
|
|
9
|
-
db: import("drizzle-orm/pglite").PgliteDatabase<Record<string, never>> & {
|
|
10
|
-
$client: PGlite;
|
|
11
|
-
};
|
|
12
|
-
client: PGlite;
|
|
13
|
-
}>;
|
package/dist/db.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated — 레거시 싱글톤 DB 인스턴스.
|
|
3
|
-
* 새 코드에서는 ctx.db를 사용하세요.
|
|
4
|
-
* 서버의 createDatabase() (database.ts)가 실제 DB 연결을 관리합니다.
|
|
5
|
-
*/
|
|
6
|
-
import { PGlite } from "@electric-sql/pglite";
|
|
7
|
-
import { drizzle } from "drizzle-orm/pglite";
|
|
8
|
-
let pgliteInstance = null;
|
|
9
|
-
/** @deprecated Use ctx.db instead */
|
|
10
|
-
export async function createDb(dataDir = "./data") {
|
|
11
|
-
if (!pgliteInstance) {
|
|
12
|
-
pgliteInstance = new PGlite(dataDir);
|
|
13
|
-
}
|
|
14
|
-
const db = drizzle(pgliteInstance);
|
|
15
|
-
return { db, client: pgliteInstance };
|
|
16
|
-
}
|
package/src/db.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated — 레거시 싱글톤 DB 인스턴스.
|
|
3
|
-
* 새 코드에서는 ctx.db를 사용하세요.
|
|
4
|
-
* 서버의 createDatabase() (database.ts)가 실제 DB 연결을 관리합니다.
|
|
5
|
-
*/
|
|
6
|
-
import { PGlite } from "@electric-sql/pglite";
|
|
7
|
-
import { drizzle } from "drizzle-orm/pglite";
|
|
8
|
-
|
|
9
|
-
let pgliteInstance: PGlite | null = null;
|
|
10
|
-
|
|
11
|
-
/** @deprecated Use ctx.db instead */
|
|
12
|
-
export async function createDb(dataDir: string = "./data") {
|
|
13
|
-
if (!pgliteInstance) {
|
|
14
|
-
pgliteInstance = new PGlite(dataDir);
|
|
15
|
-
}
|
|
16
|
-
const db = drizzle(pgliteInstance);
|
|
17
|
-
return { db, client: pgliteInstance };
|
|
18
|
-
}
|