@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/src/index.d.ts +12 -0
  3. package/dist/src/index.js +9 -0
  4. package/dist/src/mongodb-agent-run-service.d.ts +21 -0
  5. package/dist/src/mongodb-agent-run-service.js +127 -0
  6. package/dist/src/mongodb-ai-storage-service.d.ts +41 -0
  7. package/dist/src/mongodb-ai-storage-service.js +408 -0
  8. package/dist/src/mongodb-channel-store.d.ts +19 -0
  9. package/dist/src/mongodb-channel-store.js +52 -0
  10. package/dist/src/mongodb-deployment-service.d.ts +17 -0
  11. package/dist/src/mongodb-deployment-service.js +75 -0
  12. package/dist/src/mongodb-eventhub-store.d.ts +13 -0
  13. package/dist/src/mongodb-eventhub-store.js +37 -0
  14. package/dist/src/mongodb-secret-service.d.ts +30 -0
  15. package/dist/src/mongodb-secret-service.js +110 -0
  16. package/dist/src/mongodb-workflow-run-service.d.ts +36 -0
  17. package/dist/src/mongodb-workflow-run-service.js +146 -0
  18. package/dist/src/mongodb-workflow-service.d.ts +55 -0
  19. package/dist/src/mongodb-workflow-service.js +351 -0
  20. package/dist/src/pikku-mongodb.d.ts +12 -0
  21. package/dist/src/pikku-mongodb.js +37 -0
  22. package/dist/tsconfig.tsbuildinfo +1 -0
  23. package/package.json +29 -0
  24. package/run-tests.sh +53 -0
  25. package/src/index.ts +14 -0
  26. package/src/mongodb-agent-run-service.ts +206 -0
  27. package/src/mongodb-ai-storage-service.ts +576 -0
  28. package/src/mongodb-channel-store.ts +96 -0
  29. package/src/mongodb-deployment-service.ts +114 -0
  30. package/src/mongodb-eventhub-store.ts +52 -0
  31. package/src/mongodb-secret-service.ts +178 -0
  32. package/src/mongodb-services.test.ts +149 -0
  33. package/src/mongodb-workflow-run-service.ts +246 -0
  34. package/src/mongodb-workflow-service.ts +583 -0
  35. package/src/pikku-mongodb.ts +41 -0
  36. package/tsconfig.json +17 -0
@@ -0,0 +1,576 @@
1
+ import type {
2
+ AIStorageService,
3
+ AIRunStateService,
4
+ CreateRunInput,
5
+ } from '@pikku/core/services'
6
+ import type { AIThread, AIMessage, AgentRunState } from '@pikku/core/ai-agent'
7
+ import type { Db, Collection } from 'mongodb'
8
+
9
+ interface AIThreadDoc {
10
+ _id: string
11
+ resourceId: string
12
+ title: string | null
13
+ metadata: any | null
14
+ createdAt: Date
15
+ updatedAt: Date
16
+ }
17
+
18
+ interface AIMessageDoc {
19
+ _id: string
20
+ threadId: string
21
+ role: string
22
+ content: any | null
23
+ createdAt: Date
24
+ }
25
+
26
+ interface AIToolCallDoc {
27
+ _id: string
28
+ threadId: string
29
+ messageId: string
30
+ runId: string | null
31
+ toolName: string
32
+ args: any
33
+ result: string | null
34
+ approvalStatus: string | null
35
+ approvalType: string | null
36
+ agentRunId: string | null
37
+ displayToolName: string | null
38
+ displayArgs: any | null
39
+ createdAt: Date
40
+ }
41
+
42
+ interface AIWorkingMemoryDoc {
43
+ id: string
44
+ scope: string
45
+ data: any
46
+ updatedAt: Date
47
+ }
48
+
49
+ interface AIRunDoc {
50
+ _id: string
51
+ agentName: string
52
+ threadId: string
53
+ resourceId: string
54
+ status: string
55
+ errorMessage: string | null
56
+ suspendReason: string | null
57
+ missingRpcs: any | null
58
+ usageInputTokens: number
59
+ usageOutputTokens: number
60
+ usageModel: string
61
+ createdAt: Date
62
+ updatedAt: Date
63
+ }
64
+
65
+ export class MongoDBAIStorageService
66
+ implements AIStorageService, AIRunStateService
67
+ {
68
+ private initialized = false
69
+ private threads!: Collection<AIThreadDoc>
70
+ private aiMessages!: Collection<AIMessageDoc>
71
+ private toolCalls!: Collection<AIToolCallDoc>
72
+ private workingMemory!: Collection<AIWorkingMemoryDoc>
73
+ private aiRuns!: Collection<AIRunDoc>
74
+
75
+ constructor(private db: Db) {}
76
+
77
+ public async init(): Promise<void> {
78
+ if (this.initialized) return
79
+
80
+ this.threads = this.db.collection<AIThreadDoc>('ai_threads')
81
+ this.aiMessages = this.db.collection<AIMessageDoc>('ai_message')
82
+ this.toolCalls = this.db.collection<AIToolCallDoc>('ai_tool_call')
83
+ this.workingMemory =
84
+ this.db.collection<AIWorkingMemoryDoc>('ai_working_memory')
85
+ this.aiRuns = this.db.collection<AIRunDoc>('ai_run')
86
+
87
+ await this.threads.createIndex({ resourceId: 1 })
88
+
89
+ await this.aiMessages.createIndex({ threadId: 1, createdAt: 1 })
90
+
91
+ await this.toolCalls.createIndex({ threadId: 1 })
92
+ await this.toolCalls.createIndex({ messageId: 1 })
93
+
94
+ await this.workingMemory.createIndex({ id: 1, scope: 1 }, { unique: true })
95
+
96
+ await this.aiRuns.createIndex({ threadId: 1, createdAt: 1 })
97
+
98
+ this.initialized = true
99
+ }
100
+
101
+ async createThread(
102
+ resourceId: string,
103
+ options?: {
104
+ threadId?: string
105
+ title?: string
106
+ metadata?: Record<string, unknown>
107
+ }
108
+ ): Promise<AIThread> {
109
+ const id = options?.threadId ?? crypto.randomUUID()
110
+ const now = new Date()
111
+
112
+ await this.threads.insertOne({
113
+ _id: id,
114
+ resourceId,
115
+ title: options?.title ?? null,
116
+ metadata: options?.metadata ?? null,
117
+ createdAt: now,
118
+ updatedAt: now,
119
+ })
120
+
121
+ return {
122
+ id,
123
+ resourceId,
124
+ title: options?.title,
125
+ metadata: options?.metadata,
126
+ createdAt: now,
127
+ updatedAt: now,
128
+ }
129
+ }
130
+
131
+ async getThread(threadId: string): Promise<AIThread> {
132
+ const row = await this.threads.findOne({ _id: threadId })
133
+
134
+ if (!row) {
135
+ throw new Error(`Thread not found: ${threadId}`)
136
+ }
137
+
138
+ return {
139
+ id: row._id,
140
+ resourceId: row.resourceId,
141
+ title: row.title ?? undefined,
142
+ metadata: row.metadata ?? undefined,
143
+ createdAt: new Date(row.createdAt),
144
+ updatedAt: new Date(row.updatedAt),
145
+ }
146
+ }
147
+
148
+ async getThreads(resourceId: string): Promise<AIThread[]> {
149
+ const result = await this.threads
150
+ .find({ resourceId })
151
+ .sort({ updatedAt: -1 })
152
+ .toArray()
153
+
154
+ return result.map((row) => ({
155
+ id: row._id,
156
+ resourceId: row.resourceId,
157
+ title: row.title ?? undefined,
158
+ metadata: row.metadata ?? undefined,
159
+ createdAt: new Date(row.createdAt),
160
+ updatedAt: new Date(row.updatedAt),
161
+ }))
162
+ }
163
+
164
+ async deleteThread(threadId: string): Promise<void> {
165
+ await this.toolCalls.deleteMany({ threadId })
166
+ await this.aiMessages.deleteMany({ threadId })
167
+ await this.aiRuns.deleteMany({ threadId })
168
+ await this.threads.deleteOne({ _id: threadId })
169
+ }
170
+
171
+ async getMessages(
172
+ threadId: string,
173
+ options?: { lastN?: number; cursor?: string }
174
+ ): Promise<AIMessage[]> {
175
+ let filter: Record<string, any> = { threadId }
176
+
177
+ if (options?.cursor) {
178
+ const cursorRow = await this.aiMessages.findOne({ _id: options.cursor })
179
+ if (cursorRow) {
180
+ filter.createdAt = { $lt: cursorRow.createdAt }
181
+ }
182
+ }
183
+
184
+ let msgResult
185
+ if (options?.cursor || options?.lastN) {
186
+ const innerResult = await this.aiMessages
187
+ .find(filter)
188
+ .sort({ createdAt: -1 })
189
+ .limit(options?.lastN ?? 50)
190
+ .toArray()
191
+ innerResult.reverse()
192
+ msgResult = innerResult
193
+ } else {
194
+ msgResult = await this.aiMessages
195
+ .find(filter)
196
+ .sort({ createdAt: 1 })
197
+ .toArray()
198
+ }
199
+
200
+ const tcResult = await this.toolCalls
201
+ .find({ threadId })
202
+ .sort({ createdAt: 1 })
203
+ .toArray()
204
+
205
+ const tcByMessage = new Map<string, (typeof tcResult)[number][]>()
206
+ for (const tc of tcResult) {
207
+ const msgId = tc.messageId
208
+ if (!tcByMessage.has(msgId)) tcByMessage.set(msgId, [])
209
+ tcByMessage.get(msgId)!.push(tc)
210
+ }
211
+
212
+ const messages: AIMessage[] = []
213
+ for (const row of msgResult) {
214
+ let parsedContent: AIMessage['content'] = row.content ?? undefined
215
+ if (typeof row.content === 'string') {
216
+ try {
217
+ const parsed = JSON.parse(row.content)
218
+ if (Array.isArray(parsed)) {
219
+ parsedContent = parsed
220
+ } else if (typeof parsed === 'string') {
221
+ parsedContent = parsed
222
+ }
223
+ } catch {
224
+ // Not JSON, use raw string
225
+ }
226
+ }
227
+
228
+ const msg: AIMessage = {
229
+ id: row._id,
230
+ role: row.role as AIMessage['role'],
231
+ content: parsedContent,
232
+ createdAt: new Date(row.createdAt),
233
+ }
234
+
235
+ const tcs = tcByMessage.get(msg.id)
236
+ if (tcs?.length) {
237
+ msg.toolCalls = tcs.map((tc) => ({
238
+ id: tc._id,
239
+ name: tc.toolName,
240
+ args: tc.args as Record<string, unknown>,
241
+ }))
242
+
243
+ const completed = tcs.filter((tc) => tc.result != null)
244
+ if (completed.length) {
245
+ messages.push(msg)
246
+ messages.push({
247
+ id: `tool-results-${msg.id}`,
248
+ role: 'tool',
249
+ toolResults: completed.map((tc) => ({
250
+ id: tc._id,
251
+ name: tc.toolName,
252
+ result: tc.result!,
253
+ })),
254
+ createdAt: msg.createdAt,
255
+ })
256
+ continue
257
+ }
258
+ }
259
+
260
+ messages.push(msg)
261
+ }
262
+
263
+ return messages
264
+ }
265
+
266
+ async saveMessages(threadId: string, messages: AIMessage[]): Promise<void> {
267
+ if (messages.length === 0) return
268
+
269
+ const toolMessages = messages.filter((m) => m.role === 'tool')
270
+ const nonToolMessages = messages.filter((m) => m.role !== 'tool')
271
+
272
+ if (nonToolMessages.length > 0) {
273
+ await this.aiMessages.insertMany(
274
+ nonToolMessages.map((msg) => ({
275
+ _id: msg.id,
276
+ threadId,
277
+ role: msg.role,
278
+ content: msg.content != null ? JSON.stringify(msg.content) : null,
279
+ createdAt: msg.createdAt ?? new Date(),
280
+ }))
281
+ )
282
+ }
283
+
284
+ const toolCallDocs = nonToolMessages.flatMap(
285
+ (msg) => msg.toolCalls?.map((tc) => ({ ...tc, messageId: msg.id })) ?? []
286
+ )
287
+ if (toolCallDocs.length > 0) {
288
+ await this.toolCalls.insertMany(
289
+ toolCallDocs.map((tc) => ({
290
+ _id: tc.id,
291
+ threadId,
292
+ messageId: tc.messageId,
293
+ runId: null,
294
+ toolName: tc.name,
295
+ args: tc.args,
296
+ result: null,
297
+ approvalStatus: null,
298
+ approvalType: null,
299
+ agentRunId: null,
300
+ displayToolName: null,
301
+ displayArgs: null,
302
+ createdAt: new Date(),
303
+ }))
304
+ )
305
+ }
306
+
307
+ for (const toolMsg of toolMessages) {
308
+ if (!toolMsg.toolResults) continue
309
+ for (const tr of toolMsg.toolResults) {
310
+ await this.toolCalls.updateOne(
311
+ { _id: tr.id },
312
+ { $set: { result: tr.result } }
313
+ )
314
+ }
315
+ }
316
+
317
+ await this.threads.updateOne(
318
+ { _id: threadId },
319
+ { $set: { updatedAt: new Date() } }
320
+ )
321
+ }
322
+
323
+ async getWorkingMemory(
324
+ id: string,
325
+ scope: 'resource' | 'thread'
326
+ ): Promise<Record<string, unknown> | null> {
327
+ const row = await this.workingMemory.findOne({ id, scope })
328
+ if (!row) return null
329
+ return row.data
330
+ }
331
+
332
+ async saveWorkingMemory(
333
+ id: string,
334
+ scope: 'resource' | 'thread',
335
+ data: Record<string, unknown>
336
+ ): Promise<void> {
337
+ await this.workingMemory.updateOne(
338
+ { id, scope },
339
+ {
340
+ $set: {
341
+ data,
342
+ updatedAt: new Date(),
343
+ },
344
+ $setOnInsert: {
345
+ id,
346
+ scope,
347
+ },
348
+ },
349
+ { upsert: true }
350
+ )
351
+ }
352
+
353
+ async createRun(run: CreateRunInput): Promise<string> {
354
+ const runId = crypto.randomUUID()
355
+
356
+ await this.aiRuns.insertOne({
357
+ _id: runId,
358
+ agentName: run.agentName,
359
+ threadId: run.threadId,
360
+ resourceId: run.resourceId,
361
+ status: run.status,
362
+ errorMessage: run.errorMessage ?? null,
363
+ suspendReason: run.suspendReason ?? null,
364
+ missingRpcs: run.missingRpcs ?? null,
365
+ usageInputTokens: run.usage.inputTokens,
366
+ usageOutputTokens: run.usage.outputTokens,
367
+ usageModel: run.usage.model,
368
+ createdAt: run.createdAt,
369
+ updatedAt: run.updatedAt,
370
+ })
371
+
372
+ if (run.pendingApprovals?.length) {
373
+ await this.setApprovals(runId, run.pendingApprovals)
374
+ }
375
+
376
+ return runId
377
+ }
378
+
379
+ async updateRun(
380
+ runId: string,
381
+ updates: Partial<AgentRunState>
382
+ ): Promise<void> {
383
+ const setValues: Record<string, any> = { updatedAt: new Date() }
384
+
385
+ if (updates.status !== undefined) {
386
+ setValues.status = updates.status
387
+ }
388
+ if (updates.errorMessage !== undefined) {
389
+ setValues.errorMessage = updates.errorMessage
390
+ }
391
+ if (updates.suspendReason !== undefined) {
392
+ setValues.suspendReason = updates.suspendReason
393
+ }
394
+ if (updates.missingRpcs !== undefined) {
395
+ setValues.missingRpcs = updates.missingRpcs
396
+ }
397
+ if (updates.usage !== undefined) {
398
+ setValues.usageInputTokens = updates.usage.inputTokens
399
+ setValues.usageOutputTokens = updates.usage.outputTokens
400
+ setValues.usageModel = updates.usage.model
401
+ }
402
+
403
+ await this.aiRuns.updateOne({ _id: runId }, { $set: setValues })
404
+
405
+ if (updates.pendingApprovals !== undefined) {
406
+ await this.toolCalls.updateMany(
407
+ { runId, approvalStatus: { $ne: null } },
408
+ {
409
+ $set: {
410
+ approvalStatus: null,
411
+ runId: null,
412
+ approvalType: null,
413
+ agentRunId: null,
414
+ displayToolName: null,
415
+ displayArgs: null,
416
+ },
417
+ }
418
+ )
419
+
420
+ if (updates.pendingApprovals.length) {
421
+ await this.setApprovals(runId, updates.pendingApprovals)
422
+ }
423
+ }
424
+ }
425
+
426
+ async getRun(runId: string): Promise<AgentRunState | null> {
427
+ const row = await this.aiRuns.findOne({ _id: runId })
428
+ if (!row) return null
429
+
430
+ const approvals = await this.toolCalls
431
+ .find({ runId, approvalStatus: 'pending' })
432
+ .toArray()
433
+
434
+ return this.mapRunRow(row, approvals)
435
+ }
436
+
437
+ async getRunsByThread(threadId: string): Promise<AgentRunState[]> {
438
+ const result = await this.aiRuns
439
+ .find({ threadId })
440
+ .sort({ createdAt: -1 })
441
+ .toArray()
442
+
443
+ const runs: AgentRunState[] = []
444
+ for (const row of result) {
445
+ const approvals = await this.toolCalls
446
+ .find({ runId: row._id, approvalStatus: 'pending' })
447
+ .toArray()
448
+ runs.push(this.mapRunRow(row, approvals))
449
+ }
450
+ return runs
451
+ }
452
+
453
+ private async setApprovals(
454
+ runId: string,
455
+ approvals: NonNullable<AgentRunState['pendingApprovals']>
456
+ ): Promise<void> {
457
+ for (const a of approvals) {
458
+ if (a.type === 'agent-call') {
459
+ await this.toolCalls.updateOne(
460
+ { _id: a.toolCallId },
461
+ {
462
+ $set: {
463
+ approvalStatus: 'pending',
464
+ runId,
465
+ approvalType: 'agent-call',
466
+ agentRunId: a.agentRunId,
467
+ displayToolName: a.displayToolName,
468
+ displayArgs: a.displayArgs,
469
+ },
470
+ }
471
+ )
472
+ } else {
473
+ await this.toolCalls.updateOne(
474
+ { _id: a.toolCallId },
475
+ {
476
+ $set: {
477
+ approvalStatus: 'pending',
478
+ runId,
479
+ approvalType: 'tool-call',
480
+ },
481
+ }
482
+ )
483
+ }
484
+ }
485
+ }
486
+
487
+ private mapRunRow(row: any, approvalRows?: any[]): AgentRunState {
488
+ const pendingApprovals = approvalRows?.length
489
+ ? approvalRows.map((a: any) => {
490
+ if (a.approvalType === 'agent-call') {
491
+ return {
492
+ type: 'agent-call' as const,
493
+ toolCallId: a._id as string,
494
+ agentName: a.toolName as string,
495
+ agentRunId: a.agentRunId as string,
496
+ displayToolName: a.displayToolName as string,
497
+ displayArgs: a.displayArgs as unknown,
498
+ }
499
+ }
500
+ return {
501
+ type: 'tool-call' as const,
502
+ toolCallId: a._id as string,
503
+ toolName: a.toolName as string,
504
+ args: a.args as unknown,
505
+ }
506
+ })
507
+ : undefined
508
+
509
+ return {
510
+ runId: row._id as string,
511
+ agentName: row.agentName as string,
512
+ threadId: row.threadId as string,
513
+ resourceId: row.resourceId as string,
514
+ status: row.status as AgentRunState['status'],
515
+ errorMessage: row.errorMessage ?? undefined,
516
+ suspendReason: row.suspendReason as AgentRunState['suspendReason'],
517
+ missingRpcs: row.missingRpcs ?? undefined,
518
+ pendingApprovals,
519
+ usage: {
520
+ inputTokens: Number(row.usageInputTokens),
521
+ outputTokens: Number(row.usageOutputTokens),
522
+ model: row.usageModel as string,
523
+ },
524
+ createdAt: new Date(row.createdAt as string),
525
+ updatedAt: new Date(row.updatedAt as string),
526
+ }
527
+ }
528
+
529
+ async findRunByToolCallId(toolCallId: string): Promise<{
530
+ run: AgentRunState
531
+ approval: NonNullable<AgentRunState['pendingApprovals']>[number]
532
+ } | null> {
533
+ const tc = await this.toolCalls.findOne({
534
+ _id: toolCallId,
535
+ approvalStatus: 'pending',
536
+ })
537
+
538
+ if (!tc || !tc.runId) return null
539
+
540
+ const run = await this.getRun(tc.runId)
541
+ if (!run) return null
542
+
543
+ let approval: NonNullable<AgentRunState['pendingApprovals']>[number]
544
+ if (tc.approvalType === 'agent-call') {
545
+ approval = {
546
+ type: 'agent-call',
547
+ toolCallId: tc._id,
548
+ agentName: tc.toolName,
549
+ agentRunId: tc.agentRunId!,
550
+ displayToolName: tc.displayToolName!,
551
+ displayArgs: tc.displayArgs as unknown,
552
+ }
553
+ } else {
554
+ approval = {
555
+ type: 'tool-call',
556
+ toolCallId: tc._id,
557
+ toolName: tc.toolName,
558
+ args: tc.args as unknown,
559
+ }
560
+ }
561
+
562
+ return { run, approval }
563
+ }
564
+
565
+ async resolveApproval(
566
+ toolCallId: string,
567
+ status: 'approved' | 'denied'
568
+ ): Promise<void> {
569
+ await this.toolCalls.updateOne(
570
+ { _id: toolCallId },
571
+ { $set: { approvalStatus: status } }
572
+ )
573
+ }
574
+
575
+ public async close(): Promise<void> {}
576
+ }
@@ -0,0 +1,96 @@
1
+ import type { CoreUserSession } from '@pikku/core'
2
+ import type { Channel } from '@pikku/core/channel'
3
+ import { ChannelStore } from '@pikku/core/channel'
4
+ import type { Db, Collection } from 'mongodb'
5
+
6
+ interface ChannelDoc {
7
+ _id: string
8
+ channelName: string
9
+ createdAt: Date
10
+ openingData: any
11
+ userSession: any | null
12
+ lastWire: Date
13
+ }
14
+
15
+ interface ChannelSubscriptionDoc {
16
+ channelId: string
17
+ topic: string
18
+ }
19
+
20
+ export class MongoDBChannelStore extends ChannelStore {
21
+ private initialized = false
22
+ private channels!: Collection<ChannelDoc>
23
+ private subscriptions!: Collection<ChannelSubscriptionDoc>
24
+
25
+ constructor(private db: Db) {
26
+ super()
27
+ }
28
+
29
+ public async init(): Promise<void> {
30
+ if (this.initialized) return
31
+
32
+ this.channels = this.db.collection<ChannelDoc>('channels')
33
+ this.subscriptions = this.db.collection<ChannelSubscriptionDoc>(
34
+ 'channel_subscriptions'
35
+ )
36
+
37
+ await this.subscriptions.createIndex(
38
+ { channelId: 1, topic: 1 },
39
+ { unique: true }
40
+ )
41
+ await this.subscriptions.createIndex({ topic: 1 })
42
+
43
+ this.initialized = true
44
+ }
45
+
46
+ public async addChannel({
47
+ channelId,
48
+ channelName,
49
+ openingData,
50
+ }: Channel): Promise<void> {
51
+ await this.channels.insertOne({
52
+ _id: channelId,
53
+ channelName,
54
+ openingData: openingData || {},
55
+ userSession: null,
56
+ createdAt: new Date(),
57
+ lastWire: new Date(),
58
+ })
59
+ }
60
+
61
+ public async removeChannels(channelIds: string[]): Promise<void> {
62
+ if (channelIds.length === 0) return
63
+
64
+ await this.channels.deleteMany({ _id: { $in: channelIds } })
65
+ await this.subscriptions.deleteMany({ channelId: { $in: channelIds } })
66
+ }
67
+
68
+ public async setUserSession(
69
+ channelId: string,
70
+ session: CoreUserSession | null
71
+ ): Promise<void> {
72
+ await this.channels.updateOne(
73
+ { _id: channelId },
74
+ { $set: { userSession: session } }
75
+ )
76
+ }
77
+
78
+ public async getChannelAndSession(
79
+ channelId: string
80
+ ): Promise<Channel & { session: CoreUserSession }> {
81
+ const row = await this.channels.findOne({ _id: channelId })
82
+
83
+ if (!row) {
84
+ throw new Error(`Channel not found: ${channelId}`)
85
+ }
86
+
87
+ return {
88
+ channelId: row._id,
89
+ channelName: row.channelName,
90
+ openingData: row.openingData ?? {},
91
+ session: (row.userSession ?? {}) as CoreUserSession,
92
+ }
93
+ }
94
+
95
+ public async close(): Promise<void> {}
96
+ }