@pikku/mongodb 0.12.1
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/CHANGELOG.md +15 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +9 -0
- package/dist/src/mongodb-agent-run-service.d.ts +21 -0
- package/dist/src/mongodb-agent-run-service.js +127 -0
- package/dist/src/mongodb-ai-storage-service.d.ts +41 -0
- package/dist/src/mongodb-ai-storage-service.js +408 -0
- package/dist/src/mongodb-channel-store.d.ts +19 -0
- package/dist/src/mongodb-channel-store.js +52 -0
- package/dist/src/mongodb-deployment-service.d.ts +17 -0
- package/dist/src/mongodb-deployment-service.js +75 -0
- package/dist/src/mongodb-eventhub-store.d.ts +13 -0
- package/dist/src/mongodb-eventhub-store.js +37 -0
- package/dist/src/mongodb-secret-service.d.ts +30 -0
- package/dist/src/mongodb-secret-service.js +110 -0
- package/dist/src/mongodb-workflow-run-service.d.ts +36 -0
- package/dist/src/mongodb-workflow-run-service.js +146 -0
- package/dist/src/mongodb-workflow-service.d.ts +55 -0
- package/dist/src/mongodb-workflow-service.js +351 -0
- package/dist/src/pikku-mongodb.d.ts +12 -0
- package/dist/src/pikku-mongodb.js +37 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +29 -0
- package/run-tests.sh +53 -0
- package/src/index.ts +14 -0
- package/src/mongodb-agent-run-service.ts +206 -0
- package/src/mongodb-ai-storage-service.ts +576 -0
- package/src/mongodb-channel-store.ts +96 -0
- package/src/mongodb-deployment-service.ts +114 -0
- package/src/mongodb-eventhub-store.ts +52 -0
- package/src/mongodb-secret-service.ts +178 -0
- package/src/mongodb-services.test.ts +149 -0
- package/src/mongodb-workflow-run-service.ts +246 -0
- package/src/mongodb-workflow-service.ts +583 -0
- package/src/pikku-mongodb.ts +41 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import type { SerializedError } from '@pikku/core'
|
|
2
|
+
import {
|
|
3
|
+
PikkuWorkflowService,
|
|
4
|
+
type WorkflowRun,
|
|
5
|
+
type WorkflowRunWire,
|
|
6
|
+
type StepState,
|
|
7
|
+
type WorkflowStatus,
|
|
8
|
+
type WorkflowVersionStatus,
|
|
9
|
+
} from '@pikku/core/workflow'
|
|
10
|
+
import type { Db, Collection } from 'mongodb'
|
|
11
|
+
import { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js'
|
|
12
|
+
|
|
13
|
+
interface WorkflowRunDoc {
|
|
14
|
+
_id: string
|
|
15
|
+
workflow: string
|
|
16
|
+
status: string
|
|
17
|
+
input: any
|
|
18
|
+
output: any | null
|
|
19
|
+
error: any | null
|
|
20
|
+
state: Record<string, unknown>
|
|
21
|
+
inline: boolean
|
|
22
|
+
graphHash: string | null
|
|
23
|
+
wire: any | null
|
|
24
|
+
createdAt: Date
|
|
25
|
+
updatedAt: Date
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface WorkflowStepDoc {
|
|
29
|
+
_id: string
|
|
30
|
+
workflowRunId: string
|
|
31
|
+
stepName: string
|
|
32
|
+
rpcName: string | null
|
|
33
|
+
data: any | null
|
|
34
|
+
status: string
|
|
35
|
+
result: any | null
|
|
36
|
+
error: any | null
|
|
37
|
+
branchTaken: string | null
|
|
38
|
+
retries: number | null
|
|
39
|
+
retryDelay: string | null
|
|
40
|
+
createdAt: Date
|
|
41
|
+
updatedAt: Date
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface WorkflowStepHistoryDoc {
|
|
45
|
+
_id: string
|
|
46
|
+
workflowStepId: string
|
|
47
|
+
status: string
|
|
48
|
+
result: any | null
|
|
49
|
+
error: any | null
|
|
50
|
+
createdAt: Date
|
|
51
|
+
runningAt: Date | null
|
|
52
|
+
scheduledAt: Date | null
|
|
53
|
+
succeededAt: Date | null
|
|
54
|
+
failedAt: Date | null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface WorkflowVersionDoc {
|
|
58
|
+
workflowName: string
|
|
59
|
+
graphHash: string
|
|
60
|
+
graph: any
|
|
61
|
+
source: string
|
|
62
|
+
status: string
|
|
63
|
+
createdAt: Date
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class MongoDBWorkflowService extends PikkuWorkflowService {
|
|
67
|
+
private initialized = false
|
|
68
|
+
private runService: MongoDBWorkflowRunService
|
|
69
|
+
private runs: Collection<WorkflowRunDoc>
|
|
70
|
+
private steps: Collection<WorkflowStepDoc>
|
|
71
|
+
private stepHistory: Collection<WorkflowStepHistoryDoc>
|
|
72
|
+
private versions: Collection<WorkflowVersionDoc>
|
|
73
|
+
|
|
74
|
+
constructor(db: Db) {
|
|
75
|
+
super()
|
|
76
|
+
this.runService = new MongoDBWorkflowRunService(db)
|
|
77
|
+
this.runs = db.collection<WorkflowRunDoc>('workflow_runs')
|
|
78
|
+
this.steps = db.collection<WorkflowStepDoc>('workflow_step')
|
|
79
|
+
this.stepHistory = db.collection<WorkflowStepHistoryDoc>(
|
|
80
|
+
'workflow_step_history'
|
|
81
|
+
)
|
|
82
|
+
this.versions = db.collection<WorkflowVersionDoc>('workflow_versions')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async init(): Promise<void> {
|
|
86
|
+
if (this.initialized) return
|
|
87
|
+
|
|
88
|
+
await this.runs.createIndex({ workflow: 1 })
|
|
89
|
+
await this.runs.createIndex({ status: 1 })
|
|
90
|
+
await this.runs.createIndex({ createdAt: -1 })
|
|
91
|
+
|
|
92
|
+
await this.steps.createIndex(
|
|
93
|
+
{ workflowRunId: 1, stepName: 1 },
|
|
94
|
+
{ unique: true }
|
|
95
|
+
)
|
|
96
|
+
await this.steps.createIndex({ workflowRunId: 1 })
|
|
97
|
+
|
|
98
|
+
await this.stepHistory.createIndex({ workflowStepId: 1 })
|
|
99
|
+
await this.stepHistory.createIndex({ createdAt: 1 })
|
|
100
|
+
|
|
101
|
+
await this.versions.createIndex(
|
|
102
|
+
{ workflowName: 1, graphHash: 1 },
|
|
103
|
+
{ unique: true }
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
this.initialized = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async createRun(
|
|
110
|
+
workflowName: string,
|
|
111
|
+
input: any,
|
|
112
|
+
inline: boolean,
|
|
113
|
+
graphHash: string,
|
|
114
|
+
wire: WorkflowRunWire
|
|
115
|
+
): Promise<string> {
|
|
116
|
+
const id = crypto.randomUUID()
|
|
117
|
+
const now = new Date()
|
|
118
|
+
|
|
119
|
+
await this.runs.insertOne({
|
|
120
|
+
_id: id,
|
|
121
|
+
workflow: workflowName,
|
|
122
|
+
status: 'running',
|
|
123
|
+
input,
|
|
124
|
+
output: null,
|
|
125
|
+
error: null,
|
|
126
|
+
state: {},
|
|
127
|
+
inline,
|
|
128
|
+
graphHash,
|
|
129
|
+
wire,
|
|
130
|
+
createdAt: now,
|
|
131
|
+
updatedAt: now,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
return id
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getRun(id: string): Promise<WorkflowRun | null> {
|
|
138
|
+
return this.runService.getRun(id)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async updateRunStatus(
|
|
142
|
+
id: string,
|
|
143
|
+
status: WorkflowStatus,
|
|
144
|
+
output?: any,
|
|
145
|
+
error?: SerializedError
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
await this.runs.updateOne(
|
|
148
|
+
{ _id: id },
|
|
149
|
+
{
|
|
150
|
+
$set: {
|
|
151
|
+
status,
|
|
152
|
+
output: output ?? null,
|
|
153
|
+
error: error ?? null,
|
|
154
|
+
updatedAt: new Date(),
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async insertStepState(
|
|
161
|
+
runId: string,
|
|
162
|
+
stepName: string,
|
|
163
|
+
rpcName: string | null,
|
|
164
|
+
data: any,
|
|
165
|
+
stepOptions?: { retries?: number; retryDelay?: string | number }
|
|
166
|
+
): Promise<StepState> {
|
|
167
|
+
const stepId = crypto.randomUUID()
|
|
168
|
+
const now = new Date()
|
|
169
|
+
|
|
170
|
+
await this.steps.insertOne({
|
|
171
|
+
_id: stepId,
|
|
172
|
+
workflowRunId: runId,
|
|
173
|
+
stepName,
|
|
174
|
+
rpcName,
|
|
175
|
+
data: data ?? null,
|
|
176
|
+
status: 'pending',
|
|
177
|
+
result: null,
|
|
178
|
+
error: null,
|
|
179
|
+
branchTaken: null,
|
|
180
|
+
retries: stepOptions?.retries ?? null,
|
|
181
|
+
retryDelay: stepOptions?.retryDelay?.toString() ?? null,
|
|
182
|
+
createdAt: now,
|
|
183
|
+
updatedAt: now,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
await this.insertHistoryRecord(stepId, 'pending')
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
stepId,
|
|
190
|
+
status: 'pending',
|
|
191
|
+
result: undefined,
|
|
192
|
+
error: undefined,
|
|
193
|
+
attemptCount: 1,
|
|
194
|
+
retries: stepOptions?.retries,
|
|
195
|
+
retryDelay: stepOptions?.retryDelay?.toString(),
|
|
196
|
+
createdAt: now,
|
|
197
|
+
updatedAt: now,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async getStepState(runId: string, stepName: string): Promise<StepState> {
|
|
202
|
+
const row = await this.steps.findOne({
|
|
203
|
+
workflowRunId: runId,
|
|
204
|
+
stepName,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
if (!row) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Step not found: runId=${runId}, stepName=${stepName}. Use insertStepState to create it.`
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const attemptCount = await this.stepHistory.countDocuments({
|
|
214
|
+
workflowStepId: row._id,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
stepId: row._id,
|
|
219
|
+
status: row.status as StepState['status'],
|
|
220
|
+
result: row.result ?? undefined,
|
|
221
|
+
error: row.error ?? undefined,
|
|
222
|
+
attemptCount,
|
|
223
|
+
retries: row.retries != null ? Number(row.retries) : undefined,
|
|
224
|
+
retryDelay: row.retryDelay ?? undefined,
|
|
225
|
+
createdAt: new Date(row.createdAt),
|
|
226
|
+
updatedAt: new Date(row.updatedAt),
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async getRunHistory(
|
|
231
|
+
runId: string
|
|
232
|
+
): Promise<Array<StepState & { stepName: string }>> {
|
|
233
|
+
return this.runService.getRunHistory(runId)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async setStepRunning(stepId: string): Promise<void> {
|
|
237
|
+
await this.steps.updateOne(
|
|
238
|
+
{ _id: stepId },
|
|
239
|
+
{ $set: { status: 'running', updatedAt: new Date() } }
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const latestHistory = await this.stepHistory
|
|
243
|
+
.find({ workflowStepId: stepId })
|
|
244
|
+
.sort({ createdAt: -1 })
|
|
245
|
+
.limit(1)
|
|
246
|
+
.toArray()
|
|
247
|
+
|
|
248
|
+
if (latestHistory.length > 0) {
|
|
249
|
+
await this.stepHistory.updateOne(
|
|
250
|
+
{ _id: latestHistory[0]!._id },
|
|
251
|
+
{ $set: { status: 'running' } }
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async setStepScheduled(stepId: string): Promise<void> {
|
|
257
|
+
await this.steps.updateOne(
|
|
258
|
+
{ _id: stepId },
|
|
259
|
+
{ $set: { status: 'scheduled', updatedAt: new Date() } }
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private async insertHistoryRecord(
|
|
264
|
+
stepId: string,
|
|
265
|
+
status: string,
|
|
266
|
+
result?: any,
|
|
267
|
+
error?: SerializedError
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
const now = new Date()
|
|
270
|
+
const doc: any = {
|
|
271
|
+
_id: crypto.randomUUID(),
|
|
272
|
+
workflowStepId: stepId,
|
|
273
|
+
status,
|
|
274
|
+
result: result ?? null,
|
|
275
|
+
error: error ?? null,
|
|
276
|
+
createdAt: now,
|
|
277
|
+
runningAt: null,
|
|
278
|
+
scheduledAt: null,
|
|
279
|
+
succeededAt: null,
|
|
280
|
+
failedAt: null,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const timestampField = this.getTimestampFieldForStatus(status)
|
|
284
|
+
if (timestampField !== 'createdAt') {
|
|
285
|
+
doc[timestampField] = now
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await this.stepHistory.insertOne(doc)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getTimestampFieldForStatus(status: string): string {
|
|
292
|
+
switch (status) {
|
|
293
|
+
case 'running':
|
|
294
|
+
return 'runningAt'
|
|
295
|
+
case 'scheduled':
|
|
296
|
+
return 'scheduledAt'
|
|
297
|
+
case 'succeeded':
|
|
298
|
+
return 'succeededAt'
|
|
299
|
+
case 'failed':
|
|
300
|
+
return 'failedAt'
|
|
301
|
+
default:
|
|
302
|
+
return 'createdAt'
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async setStepResult(stepId: string, result: any): Promise<void> {
|
|
307
|
+
await this.steps.updateOne(
|
|
308
|
+
{ _id: stepId },
|
|
309
|
+
{
|
|
310
|
+
$set: {
|
|
311
|
+
status: 'succeeded',
|
|
312
|
+
result,
|
|
313
|
+
error: null,
|
|
314
|
+
updatedAt: new Date(),
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
const latestHistory = await this.stepHistory
|
|
320
|
+
.find({ workflowStepId: stepId })
|
|
321
|
+
.sort({ createdAt: -1 })
|
|
322
|
+
.limit(1)
|
|
323
|
+
.toArray()
|
|
324
|
+
|
|
325
|
+
if (latestHistory.length > 0) {
|
|
326
|
+
await this.stepHistory.updateOne(
|
|
327
|
+
{ _id: latestHistory[0]!._id },
|
|
328
|
+
{ $set: { status: 'succeeded', result } }
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async setStepError(stepId: string, error: Error): Promise<void> {
|
|
334
|
+
const serializedError: SerializedError = {
|
|
335
|
+
message: error.message,
|
|
336
|
+
stack: error.stack,
|
|
337
|
+
code: (error as any).code,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await this.steps.updateOne(
|
|
341
|
+
{ _id: stepId },
|
|
342
|
+
{
|
|
343
|
+
$set: {
|
|
344
|
+
status: 'failed',
|
|
345
|
+
error: serializedError,
|
|
346
|
+
result: null,
|
|
347
|
+
updatedAt: new Date(),
|
|
348
|
+
},
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const latestHistory = await this.stepHistory
|
|
353
|
+
.find({ workflowStepId: stepId })
|
|
354
|
+
.sort({ createdAt: -1 })
|
|
355
|
+
.limit(1)
|
|
356
|
+
.toArray()
|
|
357
|
+
|
|
358
|
+
if (latestHistory.length > 0) {
|
|
359
|
+
await this.stepHistory.updateOne(
|
|
360
|
+
{ _id: latestHistory[0]!._id },
|
|
361
|
+
{ $set: { status: 'failed', error: serializedError } }
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async createRetryAttempt(
|
|
367
|
+
stepId: string,
|
|
368
|
+
status: 'pending' | 'running'
|
|
369
|
+
): Promise<StepState> {
|
|
370
|
+
await this.steps.updateOne(
|
|
371
|
+
{ _id: stepId },
|
|
372
|
+
{
|
|
373
|
+
$set: {
|
|
374
|
+
status,
|
|
375
|
+
result: null,
|
|
376
|
+
error: null,
|
|
377
|
+
updatedAt: new Date(),
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
await this.insertHistoryRecord(stepId, status)
|
|
383
|
+
|
|
384
|
+
const row = await this.steps.findOne({ _id: stepId })
|
|
385
|
+
if (!row) throw new Error(`Step not found: ${stepId}`)
|
|
386
|
+
|
|
387
|
+
const attemptCount = await this.stepHistory.countDocuments({
|
|
388
|
+
workflowStepId: stepId,
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
stepId: row._id,
|
|
393
|
+
status: row.status as StepState['status'],
|
|
394
|
+
result: row.result ?? undefined,
|
|
395
|
+
error: row.error ?? undefined,
|
|
396
|
+
attemptCount,
|
|
397
|
+
retries: row.retries != null ? Number(row.retries) : undefined,
|
|
398
|
+
retryDelay: row.retryDelay ?? undefined,
|
|
399
|
+
createdAt: new Date(row.createdAt),
|
|
400
|
+
updatedAt: new Date(row.updatedAt),
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T> {
|
|
405
|
+
return fn()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async withStepLock<T>(
|
|
409
|
+
_runId: string,
|
|
410
|
+
_stepName: string,
|
|
411
|
+
fn: () => Promise<T>
|
|
412
|
+
): Promise<T> {
|
|
413
|
+
return fn()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async getCompletedGraphState(runId: string): Promise<{
|
|
417
|
+
completedNodeIds: string[]
|
|
418
|
+
failedNodeIds: string[]
|
|
419
|
+
branchKeys: Record<string, string>
|
|
420
|
+
}> {
|
|
421
|
+
const results = await this.steps
|
|
422
|
+
.find({
|
|
423
|
+
workflowRunId: runId,
|
|
424
|
+
status: { $in: ['succeeded', 'failed'] },
|
|
425
|
+
})
|
|
426
|
+
.toArray()
|
|
427
|
+
|
|
428
|
+
const completedNodeIds: string[] = []
|
|
429
|
+
const failedNodeIds: string[] = []
|
|
430
|
+
const branchKeys: Record<string, string> = {}
|
|
431
|
+
|
|
432
|
+
for (const row of results) {
|
|
433
|
+
const nodeId = row.stepName
|
|
434
|
+
|
|
435
|
+
if (row.status === 'succeeded') {
|
|
436
|
+
completedNodeIds.push(nodeId)
|
|
437
|
+
if (row.branchTaken) {
|
|
438
|
+
branchKeys[nodeId] = row.branchTaken
|
|
439
|
+
}
|
|
440
|
+
} else if (row.status === 'failed') {
|
|
441
|
+
const maxAttempts = (row.retries ?? 0) + 1
|
|
442
|
+
const attemptCount = await this.stepHistory.countDocuments({
|
|
443
|
+
workflowStepId: row._id,
|
|
444
|
+
})
|
|
445
|
+
if (attemptCount >= maxAttempts) {
|
|
446
|
+
failedNodeIds.push(nodeId)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return { completedNodeIds, failedNodeIds, branchKeys }
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async getNodesWithoutSteps(
|
|
455
|
+
runId: string,
|
|
456
|
+
nodeIds: string[]
|
|
457
|
+
): Promise<string[]> {
|
|
458
|
+
if (nodeIds.length === 0) return []
|
|
459
|
+
|
|
460
|
+
const result = await this.steps
|
|
461
|
+
.find({
|
|
462
|
+
workflowRunId: runId,
|
|
463
|
+
stepName: { $in: nodeIds },
|
|
464
|
+
})
|
|
465
|
+
.project({ stepName: 1 })
|
|
466
|
+
.toArray()
|
|
467
|
+
|
|
468
|
+
const existingStepNames = new Set(result.map((r) => r.stepName))
|
|
469
|
+
return nodeIds.filter((id) => !existingStepNames.has(id))
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async getNodeResults(
|
|
473
|
+
runId: string,
|
|
474
|
+
nodeIds: string[]
|
|
475
|
+
): Promise<Record<string, any>> {
|
|
476
|
+
if (nodeIds.length === 0) return {}
|
|
477
|
+
|
|
478
|
+
const result = await this.steps
|
|
479
|
+
.find({
|
|
480
|
+
workflowRunId: runId,
|
|
481
|
+
stepName: { $in: nodeIds },
|
|
482
|
+
status: 'succeeded',
|
|
483
|
+
})
|
|
484
|
+
.toArray()
|
|
485
|
+
|
|
486
|
+
const results: Record<string, any> = {}
|
|
487
|
+
for (const row of result) {
|
|
488
|
+
results[row.stepName] = row.result
|
|
489
|
+
}
|
|
490
|
+
return results
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async setBranchTaken(stepId: string, branchKey: string): Promise<void> {
|
|
494
|
+
await this.steps.updateOne(
|
|
495
|
+
{ _id: stepId },
|
|
496
|
+
{ $set: { branchTaken: branchKey, updatedAt: new Date() } }
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async updateRunState(
|
|
501
|
+
runId: string,
|
|
502
|
+
name: string,
|
|
503
|
+
value: unknown
|
|
504
|
+
): Promise<void> {
|
|
505
|
+
await this.runs.updateOne(
|
|
506
|
+
{ _id: runId },
|
|
507
|
+
{
|
|
508
|
+
$set: {
|
|
509
|
+
[`state.${name}`]: value,
|
|
510
|
+
updatedAt: new Date(),
|
|
511
|
+
},
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async getRunState(runId: string): Promise<Record<string, unknown>> {
|
|
517
|
+
const row = await this.runs.findOne(
|
|
518
|
+
{ _id: runId },
|
|
519
|
+
{ projection: { state: 1 } }
|
|
520
|
+
)
|
|
521
|
+
if (!row) return {}
|
|
522
|
+
return row.state ?? {}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async upsertWorkflowVersion(
|
|
526
|
+
name: string,
|
|
527
|
+
graphHash: string,
|
|
528
|
+
graph: any,
|
|
529
|
+
source: string,
|
|
530
|
+
status?: WorkflowVersionStatus
|
|
531
|
+
): Promise<void> {
|
|
532
|
+
await this.versions.updateOne(
|
|
533
|
+
{ workflowName: name, graphHash },
|
|
534
|
+
{
|
|
535
|
+
$setOnInsert: {
|
|
536
|
+
workflowName: name,
|
|
537
|
+
graphHash,
|
|
538
|
+
graph,
|
|
539
|
+
source,
|
|
540
|
+
status: status ?? 'active',
|
|
541
|
+
createdAt: new Date(),
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{ upsert: true }
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async updateWorkflowVersionStatus(
|
|
549
|
+
name: string,
|
|
550
|
+
graphHash: string,
|
|
551
|
+
status: WorkflowVersionStatus
|
|
552
|
+
): Promise<void> {
|
|
553
|
+
await this.versions.updateOne(
|
|
554
|
+
{ workflowName: name, graphHash },
|
|
555
|
+
{ $set: { status } }
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async getWorkflowVersion(
|
|
560
|
+
name: string,
|
|
561
|
+
graphHash: string
|
|
562
|
+
): Promise<{ graph: any; source: string } | null> {
|
|
563
|
+
return this.runService.getWorkflowVersion(name, graphHash)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async getAIGeneratedWorkflows(
|
|
567
|
+
agentName?: string
|
|
568
|
+
): Promise<Array<{ workflowName: string; graphHash: string; graph: any }>> {
|
|
569
|
+
const filter: Record<string, any> = { source: 'ai-agent', status: 'active' }
|
|
570
|
+
if (agentName) {
|
|
571
|
+
const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
572
|
+
filter.workflowName = { $regex: `^ai:${escaped}:` }
|
|
573
|
+
}
|
|
574
|
+
const docs = await this.versions.find(filter).toArray()
|
|
575
|
+
return docs.map((doc) => ({
|
|
576
|
+
workflowName: doc.workflowName,
|
|
577
|
+
graphHash: doc.graphHash,
|
|
578
|
+
graph: doc.graph,
|
|
579
|
+
}))
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async close(): Promise<void> {}
|
|
583
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Logger } from '@pikku/core/services'
|
|
2
|
+
import { MongoClient, type MongoClientOptions, type Db } from 'mongodb'
|
|
3
|
+
|
|
4
|
+
export class PikkuMongoDB {
|
|
5
|
+
public db: Db
|
|
6
|
+
private client: MongoClient
|
|
7
|
+
private ownsConnection: boolean
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
private logger: Logger,
|
|
11
|
+
clientOrUri: MongoClient | string,
|
|
12
|
+
private dbName: string,
|
|
13
|
+
options?: MongoClientOptions
|
|
14
|
+
) {
|
|
15
|
+
if (typeof clientOrUri === 'string') {
|
|
16
|
+
this.client = new MongoClient(clientOrUri, options)
|
|
17
|
+
this.ownsConnection = true
|
|
18
|
+
} else {
|
|
19
|
+
this.client = clientOrUri
|
|
20
|
+
this.ownsConnection = false
|
|
21
|
+
}
|
|
22
|
+
this.db = this.client.db(dbName)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async init() {
|
|
26
|
+
this.logger.info(`Connecting to MongoDB database: ${this.dbName}`)
|
|
27
|
+
try {
|
|
28
|
+
await this.client.db('admin').command({ ping: 1 })
|
|
29
|
+
this.logger.info('MongoDB connection successful')
|
|
30
|
+
} catch (error) {
|
|
31
|
+
this.logger.error('Error connecting to MongoDB', error)
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async close() {
|
|
37
|
+
if (this.ownsConnection) {
|
|
38
|
+
await this.client.close()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"module": "Node18",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"target": "esnext",
|
|
8
|
+
"declaration": true
|
|
9
|
+
},
|
|
10
|
+
"include": ["src/**/*.ts"],
|
|
11
|
+
"exclude": ["**/*.test.ts", "node_modules"],
|
|
12
|
+
"references": [
|
|
13
|
+
{
|
|
14
|
+
"path": "../../core/tsconfig.json"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|