@pikku/mongodb 0.12.20 → 0.12.22
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 +52 -0
- package/dist/src/mongodb-secret-service.js +1 -1
- package/package.json +10 -6
- package/run-tests.sh +21 -6
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/index.ts +0 -14
- package/src/mongodb-agent-run-service.ts +0 -234
- package/src/mongodb-agent-storage-service.ts +0 -632
- package/src/mongodb-channel-store.ts +0 -109
- package/src/mongodb-deployment-service.ts +0 -136
- package/src/mongodb-eventhub-store.ts +0 -52
- package/src/mongodb-secret-service.ts +0 -232
- package/src/mongodb-services.test.ts +0 -235
- package/src/mongodb-session-store.ts +0 -50
- package/src/mongodb-workflow-run-service.ts +0 -235
- package/src/mongodb-workflow-service.test.ts +0 -238
- package/src/mongodb-workflow-service.ts +0 -687
- package/src/pikku-mongodb.ts +0 -41
- package/tsconfig.json +0 -17
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { describe, test, before, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { MongoClient, type Db } from 'mongodb'
|
|
4
|
-
import { MongoMemoryServer } from 'mongodb-memory-server'
|
|
5
|
-
import { defineServiceTests } from '@pikku/core/testing'
|
|
6
|
-
|
|
7
|
-
import { MongoDBChannelStore } from './mongodb-channel-store.js'
|
|
8
|
-
import { MongoDBEventHubStore } from './mongodb-eventhub-store.js'
|
|
9
|
-
import { MongoDBWorkflowService } from './mongodb-workflow-service.js'
|
|
10
|
-
import { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js'
|
|
11
|
-
import { MongoDBDeploymentService } from './mongodb-deployment-service.js'
|
|
12
|
-
import { MongoDBAgentStorageService } from './mongodb-agent-storage-service.js'
|
|
13
|
-
import { MongoDBAgentRunService } from './mongodb-agent-run-service.js'
|
|
14
|
-
import { MongoDBSecretService } from './mongodb-secret-service.js'
|
|
15
|
-
import { MongoDBSessionStore } from './mongodb-session-store.js'
|
|
16
|
-
|
|
17
|
-
function registerTests(name: string, getDb: () => Db) {
|
|
18
|
-
defineServiceTests({
|
|
19
|
-
name,
|
|
20
|
-
services: {
|
|
21
|
-
channelStore: async () => {
|
|
22
|
-
const s = new MongoDBChannelStore(getDb())
|
|
23
|
-
await s.init()
|
|
24
|
-
return s
|
|
25
|
-
},
|
|
26
|
-
eventHubStore: async () => {
|
|
27
|
-
const s = new MongoDBEventHubStore(getDb())
|
|
28
|
-
await s.init()
|
|
29
|
-
return s
|
|
30
|
-
},
|
|
31
|
-
workflowService: async () => {
|
|
32
|
-
const s = new MongoDBWorkflowService(getDb())
|
|
33
|
-
await s.init()
|
|
34
|
-
return s
|
|
35
|
-
},
|
|
36
|
-
workflowRunService: async () => new MongoDBWorkflowRunService(getDb()),
|
|
37
|
-
deploymentService: async () => {
|
|
38
|
-
const s = new MongoDBDeploymentService(
|
|
39
|
-
{ heartbeatInterval: 60000, heartbeatTtl: 120000 },
|
|
40
|
-
getDb()
|
|
41
|
-
)
|
|
42
|
-
await s.init()
|
|
43
|
-
return s
|
|
44
|
-
},
|
|
45
|
-
agentStorageService: async () => {
|
|
46
|
-
const s = new MongoDBAgentStorageService(getDb())
|
|
47
|
-
await s.init()
|
|
48
|
-
return s
|
|
49
|
-
},
|
|
50
|
-
agentRunService: async () => new MongoDBAgentRunService(getDb()),
|
|
51
|
-
secretService: async (config) => {
|
|
52
|
-
const s = new MongoDBSecretService(getDb(), config)
|
|
53
|
-
await s.init()
|
|
54
|
-
return s
|
|
55
|
-
},
|
|
56
|
-
sessionStore: async () => {
|
|
57
|
-
const s = new MongoDBSessionStore(getDb())
|
|
58
|
-
await s.init()
|
|
59
|
-
return s
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
describe(`MongoDBSecretService audit [${name}]`, () => {
|
|
65
|
-
const kek = 'test-key-encryption-key-32chars!'
|
|
66
|
-
|
|
67
|
-
test('audit logs writes, reads, and deletes', async () => {
|
|
68
|
-
const service = new MongoDBSecretService(getDb(), {
|
|
69
|
-
key: kek,
|
|
70
|
-
audit: true,
|
|
71
|
-
auditReads: true,
|
|
72
|
-
})
|
|
73
|
-
await service.init()
|
|
74
|
-
await service.setSecret('audit-test', 'value')
|
|
75
|
-
await service.getSecret('audit-test')
|
|
76
|
-
await service.deleteSecret('audit-test')
|
|
77
|
-
|
|
78
|
-
const logs = await getDb()
|
|
79
|
-
.collection('secrets_audit')
|
|
80
|
-
.find({ secretKey: 'audit-test' })
|
|
81
|
-
.sort({ performedAt: 1 })
|
|
82
|
-
.toArray()
|
|
83
|
-
|
|
84
|
-
assert.equal(logs.length, 3)
|
|
85
|
-
assert.equal(logs[0]!.action, 'write')
|
|
86
|
-
assert.equal(logs[1]!.action, 'read')
|
|
87
|
-
assert.equal(logs[2]!.action, 'delete')
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('audit skips reads when auditReads is false', async () => {
|
|
91
|
-
const service = new MongoDBSecretService(getDb(), {
|
|
92
|
-
key: kek,
|
|
93
|
-
audit: true,
|
|
94
|
-
auditReads: false,
|
|
95
|
-
})
|
|
96
|
-
await service.init()
|
|
97
|
-
await service.setSecret('no-read-audit', 'value')
|
|
98
|
-
await service.getSecret('no-read-audit')
|
|
99
|
-
|
|
100
|
-
const logs = await getDb()
|
|
101
|
-
.collection('secrets_audit')
|
|
102
|
-
.find({ secretKey: 'no-read-audit' })
|
|
103
|
-
.toArray()
|
|
104
|
-
|
|
105
|
-
assert.equal(logs.length, 1)
|
|
106
|
-
assert.equal(logs[0]!.action, 'write')
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
describe(`MongoDBSecretService decryption failures [${name}]`, () => {
|
|
111
|
-
const kek = 'test-key-encryption-key-32chars!'
|
|
112
|
-
const otherKek = 'a-totally-different-kek-32chars!'
|
|
113
|
-
|
|
114
|
-
test('getSecrets throws naming a secret it cannot decrypt', async () => {
|
|
115
|
-
const writer = new MongoDBSecretService(getDb(), { key: kek })
|
|
116
|
-
await writer.init()
|
|
117
|
-
await writer.setSecret('undecryptable', { token: 'abc' })
|
|
118
|
-
|
|
119
|
-
const reader = new MongoDBSecretService(getDb(), { key: otherKek })
|
|
120
|
-
await reader.init()
|
|
121
|
-
|
|
122
|
-
await assert.rejects(
|
|
123
|
-
() => reader.getSecrets(['undecryptable']),
|
|
124
|
-
(error: Error) => {
|
|
125
|
-
assert.equal(
|
|
126
|
-
error.message,
|
|
127
|
-
'Failed to decrypt secret "undecryptable" (key_version 1): ' +
|
|
128
|
-
'the configured KEK does not match the key it was wrapped under'
|
|
129
|
-
)
|
|
130
|
-
assert.ok(error.cause, 'the underlying crypto failure is preserved')
|
|
131
|
-
return true
|
|
132
|
-
}
|
|
133
|
-
)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
test('getSecrets throws when no KEK is available for a stored key_version', async () => {
|
|
137
|
-
const writer = new MongoDBSecretService(getDb(), {
|
|
138
|
-
key: kek,
|
|
139
|
-
keyVersion: 7,
|
|
140
|
-
})
|
|
141
|
-
await writer.init()
|
|
142
|
-
await writer.setSecret('old-version', 'value')
|
|
143
|
-
|
|
144
|
-
const reader = new MongoDBSecretService(getDb(), {
|
|
145
|
-
key: kek,
|
|
146
|
-
keyVersion: 8,
|
|
147
|
-
})
|
|
148
|
-
await reader.init()
|
|
149
|
-
|
|
150
|
-
await assert.rejects(
|
|
151
|
-
() => reader.getSecrets(['old-version']),
|
|
152
|
-
(error: Error) => {
|
|
153
|
-
assert.equal(
|
|
154
|
-
error.message,
|
|
155
|
-
'Failed to decrypt secret "old-version" (key_version 7): ' +
|
|
156
|
-
'the configured KEK does not match the key it was wrapped under'
|
|
157
|
-
)
|
|
158
|
-
return true
|
|
159
|
-
}
|
|
160
|
-
)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
test('getSecrets returns every secret when they all decrypt', async () => {
|
|
164
|
-
const service = new MongoDBSecretService(getDb(), { key: kek })
|
|
165
|
-
await service.init()
|
|
166
|
-
await service.setSecret('batch-a', { v: 1 })
|
|
167
|
-
await service.setSecret('batch-b', 'two')
|
|
168
|
-
|
|
169
|
-
const out = await service.getSecrets<{
|
|
170
|
-
'batch-a': { v: number }
|
|
171
|
-
'batch-b': string
|
|
172
|
-
}>(['batch-a', 'batch-b'])
|
|
173
|
-
|
|
174
|
-
assert.deepEqual(out['batch-a']!.reveal(), { v: 1 })
|
|
175
|
-
assert.equal(out['batch-b']!.reveal(), 'two')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
test('getSecrets omits keys that were never stored', async () => {
|
|
179
|
-
const service = new MongoDBSecretService(getDb(), { key: kek })
|
|
180
|
-
await service.init()
|
|
181
|
-
await service.setSecret('present', 'here')
|
|
182
|
-
|
|
183
|
-
const out = await service.getSecrets(['present', 'never-stored'])
|
|
184
|
-
|
|
185
|
-
assert.deepEqual(Object.keys(out), ['present'])
|
|
186
|
-
assert.equal(out.present!.reveal(), 'here')
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
describe('MongoDB Services - In-Memory', () => {
|
|
192
|
-
let mongod: MongoMemoryServer
|
|
193
|
-
let client: MongoClient
|
|
194
|
-
let db: Db
|
|
195
|
-
|
|
196
|
-
before(async () => {
|
|
197
|
-
mongod = await MongoMemoryServer.create()
|
|
198
|
-
const uri = mongod.getUri()
|
|
199
|
-
client = new MongoClient(uri)
|
|
200
|
-
await client.connect()
|
|
201
|
-
db = client.db('pikku_test')
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
after(async () => {
|
|
205
|
-
await client.close()
|
|
206
|
-
await mongod.stop()
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
registerTests('MongoMemory', () => db)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
describe(
|
|
213
|
-
'MongoDB Services - Real',
|
|
214
|
-
{
|
|
215
|
-
skip: !process.env.MONGODB_URL ? 'MONGODB_URL not set' : undefined,
|
|
216
|
-
},
|
|
217
|
-
() => {
|
|
218
|
-
let client: MongoClient
|
|
219
|
-
let db: Db
|
|
220
|
-
|
|
221
|
-
before(async () => {
|
|
222
|
-
client = new MongoClient(process.env.MONGODB_URL!)
|
|
223
|
-
await client.connect()
|
|
224
|
-
db = client.db('pikku_test')
|
|
225
|
-
await db.dropDatabase()
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
after(async () => {
|
|
229
|
-
if (db) await db.dropDatabase()
|
|
230
|
-
if (client) await client.close()
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
registerTests('Real MongoDB', () => db)
|
|
234
|
-
}
|
|
235
|
-
)
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { SessionStore } from '@pikku/core/services'
|
|
2
|
-
import type { CoreUserSession } from '@pikku/core/types'
|
|
3
|
-
import type { Db, Collection } from 'mongodb'
|
|
4
|
-
|
|
5
|
-
interface SessionDoc {
|
|
6
|
-
_id: string
|
|
7
|
-
session: any
|
|
8
|
-
createdAt: Date
|
|
9
|
-
updatedAt: Date
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class MongoDBSessionStore implements SessionStore {
|
|
13
|
-
private initialized = false
|
|
14
|
-
private sessions!: Collection<SessionDoc>
|
|
15
|
-
|
|
16
|
-
constructor(private db: Db) {}
|
|
17
|
-
|
|
18
|
-
public async init(): Promise<void> {
|
|
19
|
-
if (this.initialized) return
|
|
20
|
-
|
|
21
|
-
this.sessions = this.db.collection<SessionDoc>('pikkuUserSessions')
|
|
22
|
-
this.initialized = true
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async get(pikkuUserId: string): Promise<CoreUserSession | undefined> {
|
|
26
|
-
const doc = await this.sessions.findOne({ _id: pikkuUserId })
|
|
27
|
-
if (!doc) {
|
|
28
|
-
return undefined
|
|
29
|
-
}
|
|
30
|
-
return doc.session as CoreUserSession
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async set(pikkuUserId: string, session: CoreUserSession): Promise<void> {
|
|
34
|
-
const now = new Date()
|
|
35
|
-
await this.sessions.updateOne(
|
|
36
|
-
{ _id: pikkuUserId },
|
|
37
|
-
{
|
|
38
|
-
$set: { session, updatedAt: now },
|
|
39
|
-
$setOnInsert: { createdAt: now },
|
|
40
|
-
},
|
|
41
|
-
{ upsert: true }
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async clear(pikkuUserId: string): Promise<void> {
|
|
46
|
-
await this.sessions.deleteOne({ _id: pikkuUserId })
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
public async close(): Promise<void> {}
|
|
50
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
WorkflowPlannedStep,
|
|
3
|
-
WorkflowRun,
|
|
4
|
-
StepState,
|
|
5
|
-
WorkflowStatus,
|
|
6
|
-
WorkflowRunService,
|
|
7
|
-
} from '@pikku/core/workflow'
|
|
8
|
-
import type { Db, Collection } from 'mongodb'
|
|
9
|
-
|
|
10
|
-
interface WorkflowRunDoc {
|
|
11
|
-
_id: string
|
|
12
|
-
workflow: string
|
|
13
|
-
status: string
|
|
14
|
-
input: any
|
|
15
|
-
output: any | null
|
|
16
|
-
error: any | null
|
|
17
|
-
state: Record<string, unknown>
|
|
18
|
-
inline: boolean
|
|
19
|
-
graphHash: string | null
|
|
20
|
-
deterministic?: boolean
|
|
21
|
-
plannedSteps?: WorkflowPlannedStep[]
|
|
22
|
-
wire: any | null
|
|
23
|
-
createdAt: Date
|
|
24
|
-
updatedAt: Date
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface WorkflowStepDoc {
|
|
28
|
-
_id: string
|
|
29
|
-
workflowRunId: string
|
|
30
|
-
stepName: string
|
|
31
|
-
rpcName: string | null
|
|
32
|
-
data: any | null
|
|
33
|
-
status: string
|
|
34
|
-
result: any | null
|
|
35
|
-
error: any | null
|
|
36
|
-
branchTaken: string | null
|
|
37
|
-
retries: number | null
|
|
38
|
-
retryDelay: string | null
|
|
39
|
-
createdAt: Date
|
|
40
|
-
updatedAt: Date
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface WorkflowStepHistoryDoc {
|
|
44
|
-
_id: string
|
|
45
|
-
workflowStepId: string
|
|
46
|
-
status: string
|
|
47
|
-
result: any | null
|
|
48
|
-
error: any | null
|
|
49
|
-
createdAt: Date
|
|
50
|
-
runningAt: Date | null
|
|
51
|
-
scheduledAt: Date | null
|
|
52
|
-
succeededAt: Date | null
|
|
53
|
-
failedAt: Date | null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface WorkflowVersionDoc {
|
|
57
|
-
workflowName: string
|
|
58
|
-
graphHash: string
|
|
59
|
-
graph: any
|
|
60
|
-
source: string
|
|
61
|
-
createdAt: Date
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class MongoDBWorkflowRunService implements WorkflowRunService {
|
|
65
|
-
private runs: Collection<WorkflowRunDoc>
|
|
66
|
-
private steps: Collection<WorkflowStepDoc>
|
|
67
|
-
private stepHistory: Collection<WorkflowStepHistoryDoc>
|
|
68
|
-
private versions: Collection<WorkflowVersionDoc>
|
|
69
|
-
|
|
70
|
-
constructor(db: Db) {
|
|
71
|
-
this.runs = db.collection<WorkflowRunDoc>('workflow_runs')
|
|
72
|
-
this.steps = db.collection<WorkflowStepDoc>('workflow_step')
|
|
73
|
-
this.stepHistory = db.collection<WorkflowStepHistoryDoc>(
|
|
74
|
-
'workflow_step_history'
|
|
75
|
-
)
|
|
76
|
-
this.versions = db.collection<WorkflowVersionDoc>('workflow_versions')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async listRuns(options?: {
|
|
80
|
-
workflowName?: string
|
|
81
|
-
status?: string
|
|
82
|
-
limit?: number
|
|
83
|
-
offset?: number
|
|
84
|
-
}): Promise<WorkflowRun[]> {
|
|
85
|
-
const { workflowName, status, limit = 50, offset = 0 } = options ?? {}
|
|
86
|
-
|
|
87
|
-
const filter: Record<string, any> = {}
|
|
88
|
-
if (workflowName) filter.workflow = workflowName
|
|
89
|
-
if (status) filter.status = status
|
|
90
|
-
|
|
91
|
-
const result = await this.runs
|
|
92
|
-
.find(filter)
|
|
93
|
-
.sort({ createdAt: -1 })
|
|
94
|
-
.skip(offset)
|
|
95
|
-
.limit(limit)
|
|
96
|
-
.toArray()
|
|
97
|
-
|
|
98
|
-
return result.map((row) => this.mapRunRow(row))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async getRun(id: string): Promise<WorkflowRun | null> {
|
|
102
|
-
const row = await this.runs.findOne({ _id: id })
|
|
103
|
-
if (!row) return null
|
|
104
|
-
return this.mapRunRow(row)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async getRunSteps(
|
|
108
|
-
runId: string
|
|
109
|
-
): Promise<
|
|
110
|
-
Array<StepState & { stepName: string; rpcName?: string; data?: any }>
|
|
111
|
-
> {
|
|
112
|
-
const result = await this.steps
|
|
113
|
-
.find({ workflowRunId: runId })
|
|
114
|
-
.sort({ createdAt: 1 })
|
|
115
|
-
.toArray()
|
|
116
|
-
|
|
117
|
-
const stepIds = result.map((r) => r._id)
|
|
118
|
-
const historyCounts = await this.stepHistory
|
|
119
|
-
.aggregate<{
|
|
120
|
-
_id: string
|
|
121
|
-
count: number
|
|
122
|
-
}>([
|
|
123
|
-
{ $match: { workflowStepId: { $in: stepIds } } },
|
|
124
|
-
{ $group: { _id: '$workflowStepId', count: { $sum: 1 } } },
|
|
125
|
-
])
|
|
126
|
-
.toArray()
|
|
127
|
-
|
|
128
|
-
const countMap = new Map(historyCounts.map((h) => [h._id, h.count]))
|
|
129
|
-
|
|
130
|
-
return result.map((row) => ({
|
|
131
|
-
stepId: row._id,
|
|
132
|
-
stepName: row.stepName,
|
|
133
|
-
rpcName: row.rpcName ?? undefined,
|
|
134
|
-
data: row.data ?? undefined,
|
|
135
|
-
status: row.status as StepState['status'],
|
|
136
|
-
result: row.result ?? undefined,
|
|
137
|
-
error: row.error ?? undefined,
|
|
138
|
-
attemptCount: countMap.get(row._id) ?? 1,
|
|
139
|
-
retries: row.retries != null ? Number(row.retries) : undefined,
|
|
140
|
-
retryDelay: row.retryDelay ?? undefined,
|
|
141
|
-
createdAt: new Date(row.createdAt),
|
|
142
|
-
updatedAt: new Date(row.updatedAt),
|
|
143
|
-
}))
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async getRunHistory(
|
|
147
|
-
runId: string
|
|
148
|
-
): Promise<Array<StepState & { stepName: string }>> {
|
|
149
|
-
const steps = await this.steps.find({ workflowRunId: runId }).toArray()
|
|
150
|
-
|
|
151
|
-
const stepMap = new Map(steps.map((s) => [s._id, s]))
|
|
152
|
-
const stepIds = steps.map((s) => s._id)
|
|
153
|
-
|
|
154
|
-
const history = await this.stepHistory
|
|
155
|
-
.find({ workflowStepId: { $in: stepIds } })
|
|
156
|
-
.sort({ createdAt: 1 })
|
|
157
|
-
.toArray()
|
|
158
|
-
|
|
159
|
-
let attemptCounters: Record<string, number> = {}
|
|
160
|
-
return history.map((row) => {
|
|
161
|
-
const stepId = row.workflowStepId
|
|
162
|
-
const step = stepMap.get(stepId)!
|
|
163
|
-
attemptCounters[stepId] = (attemptCounters[stepId] ?? 0) + 1
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
stepId,
|
|
167
|
-
stepName: step.stepName,
|
|
168
|
-
status: row.status as StepState['status'],
|
|
169
|
-
result: row.result ?? undefined,
|
|
170
|
-
error: row.error ?? undefined,
|
|
171
|
-
attemptCount: attemptCounters[stepId]!,
|
|
172
|
-
retries: step.retries != null ? Number(step.retries) : undefined,
|
|
173
|
-
retryDelay: step.retryDelay ?? undefined,
|
|
174
|
-
createdAt: new Date(row.createdAt),
|
|
175
|
-
updatedAt: new Date(row.createdAt),
|
|
176
|
-
runningAt: row.runningAt ? new Date(row.runningAt) : undefined,
|
|
177
|
-
scheduledAt: row.scheduledAt ? new Date(row.scheduledAt) : undefined,
|
|
178
|
-
succeededAt: row.succeededAt ? new Date(row.succeededAt) : undefined,
|
|
179
|
-
failedAt: row.failedAt ? new Date(row.failedAt) : undefined,
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async getDistinctWorkflowNames(): Promise<string[]> {
|
|
185
|
-
const result = await this.runs.distinct('workflow')
|
|
186
|
-
return result.sort()
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async getWorkflowVersion(
|
|
190
|
-
name: string,
|
|
191
|
-
graphHash: string
|
|
192
|
-
): Promise<{ graph: any; source: string } | null> {
|
|
193
|
-
const row = await this.versions.findOne({
|
|
194
|
-
workflowName: name,
|
|
195
|
-
graphHash,
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
if (!row) return null
|
|
199
|
-
return {
|
|
200
|
-
graph: row.graph,
|
|
201
|
-
source: row.source,
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async deleteRun(id: string): Promise<boolean> {
|
|
206
|
-
const steps = await this.steps.find({ workflowRunId: id }).toArray()
|
|
207
|
-
const stepIds = steps.map((s) => s._id)
|
|
208
|
-
|
|
209
|
-
if (stepIds.length > 0) {
|
|
210
|
-
await this.stepHistory.deleteMany({ workflowStepId: { $in: stepIds } })
|
|
211
|
-
await this.steps.deleteMany({ workflowRunId: id })
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const result = await this.runs.deleteOne({ _id: id })
|
|
215
|
-
return result.deletedCount > 0
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
private mapRunRow(row: WorkflowRunDoc): WorkflowRun {
|
|
219
|
-
return {
|
|
220
|
-
id: row._id,
|
|
221
|
-
workflow: row.workflow,
|
|
222
|
-
status: row.status as WorkflowStatus,
|
|
223
|
-
input: row.input,
|
|
224
|
-
output: row.output ?? undefined,
|
|
225
|
-
error: row.error ?? undefined,
|
|
226
|
-
inline: row.inline,
|
|
227
|
-
graphHash: row.graphHash ?? undefined,
|
|
228
|
-
deterministic: row.deterministic ?? undefined,
|
|
229
|
-
plannedSteps: row.plannedSteps ?? undefined,
|
|
230
|
-
wire: row.wire ?? { type: 'unknown' },
|
|
231
|
-
createdAt: new Date(row.createdAt),
|
|
232
|
-
updatedAt: new Date(row.updatedAt),
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|