ai-workflows 2.1.1 → 2.3.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +17 -1
- package/README.md +305 -184
- package/dist/barrier.d.ts +159 -0
- package/dist/barrier.d.ts.map +1 -0
- package/dist/barrier.js +377 -0
- package/dist/barrier.js.map +1 -0
- package/dist/cascade-context.d.ts +149 -0
- package/dist/cascade-context.d.ts.map +1 -0
- package/dist/cascade-context.js +324 -0
- package/dist/cascade-context.js.map +1 -0
- package/dist/cascade-executor.d.ts +196 -0
- package/dist/cascade-executor.d.ts.map +1 -0
- package/dist/cascade-executor.js +384 -0
- package/dist/cascade-executor.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -8
- package/dist/context.js.map +1 -1
- package/dist/cron-parser.d.ts +65 -0
- package/dist/cron-parser.d.ts.map +1 -0
- package/dist/cron-parser.js +294 -0
- package/dist/cron-parser.js.map +1 -0
- package/dist/cron-scheduler.d.ts +117 -0
- package/dist/cron-scheduler.d.ts.map +1 -0
- package/dist/cron-scheduler.js +176 -0
- package/dist/cron-scheduler.js.map +1 -0
- package/dist/database-context.d.ts +184 -0
- package/dist/database-context.d.ts.map +1 -0
- package/dist/database-context.js +428 -0
- package/dist/database-context.js.map +1 -0
- package/dist/dependency-graph.d.ts +157 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +382 -0
- package/dist/dependency-graph.js.map +1 -0
- package/dist/digital-objects-adapter.d.ts +159 -0
- package/dist/digital-objects-adapter.d.ts.map +1 -0
- package/dist/digital-objects-adapter.js +229 -0
- package/dist/digital-objects-adapter.js.map +1 -0
- package/dist/durable-execution-cloudflare.d.ts +427 -0
- package/dist/durable-execution-cloudflare.d.ts.map +1 -0
- package/dist/durable-execution-cloudflare.js +510 -0
- package/dist/durable-execution-cloudflare.js.map +1 -0
- package/dist/durable-execution.d.ts +482 -0
- package/dist/durable-execution.d.ts.map +1 -0
- package/dist/durable-execution.js +594 -0
- package/dist/durable-execution.js.map +1 -0
- package/dist/durable-workflow.d.ts +176 -0
- package/dist/durable-workflow.d.ts.map +1 -0
- package/dist/durable-workflow.js +552 -0
- package/dist/durable-workflow.js.map +1 -0
- package/dist/every.d.ts +31 -2
- package/dist/every.d.ts.map +1 -1
- package/dist/every.js +63 -32
- package/dist/every.js.map +1 -1
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +8 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/topological-sort.d.ts +121 -0
- package/dist/graph/topological-sort.d.ts.map +1 -0
- package/dist/graph/topological-sort.js +292 -0
- package/dist/graph/topological-sort.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +101 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/on.d.ts +35 -10
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +53 -19
- package/dist/on.js.map +1 -1
- package/dist/runtime.d.ts +169 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +275 -0
- package/dist/runtime.js.map +1 -0
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +4 -3
- package/dist/send.js.map +1 -1
- package/dist/telemetry.d.ts +150 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +388 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/timer-registry.d.ts +77 -0
- package/dist/timer-registry.d.ts.map +1 -0
- package/dist/timer-registry.js +154 -0
- package/dist/timer-registry.js.map +1 -0
- package/dist/types.d.ts +105 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -1
- package/dist/types.js.map +1 -1
- package/dist/worker/durable-step.d.ts +481 -0
- package/dist/worker/durable-step.d.ts.map +1 -0
- package/dist/worker/durable-step.js +606 -0
- package/dist/worker/durable-step.js.map +1 -0
- package/dist/worker/index.d.ts +106 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +124 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/state-adapter.d.ts +230 -0
- package/dist/worker/state-adapter.d.ts.map +1 -0
- package/dist/worker/state-adapter.js +409 -0
- package/dist/worker/state-adapter.js.map +1 -0
- package/dist/worker/topological-executor.d.ts +282 -0
- package/dist/worker/topological-executor.d.ts.map +1 -0
- package/dist/worker/topological-executor.js +396 -0
- package/dist/worker/topological-executor.js.map +1 -0
- package/dist/worker/workflow-builder.d.ts +286 -0
- package/dist/worker/workflow-builder.d.ts.map +1 -0
- package/dist/worker/workflow-builder.js +565 -0
- package/dist/worker/workflow-builder.js.map +1 -0
- package/dist/worker.d.ts +800 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +2428 -0
- package/dist/worker.js.map +1 -0
- package/dist/workflow-builder.d.ts +287 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +762 -0
- package/dist/workflow-builder.js.map +1 -0
- package/dist/workflow.d.ts +14 -30
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +136 -292
- package/dist/workflow.js.map +1 -1
- package/examples/01-ecommerce-order-pipeline.ts +358 -0
- package/examples/02-content-moderation-cascade.ts +454 -0
- package/examples/03-scheduled-reporting-dependencies.ts +479 -0
- package/examples/04-database-persistence.ts +518 -0
- package/examples/README.md +173 -0
- package/package.json +21 -4
- package/src/__tests__/digital-objects-adapter.test.ts +274 -0
- package/src/__tests__/durable-workflow.test.ts +297 -0
- package/src/barrier.ts +507 -0
- package/src/cascade-context.ts +495 -0
- package/src/cascade-executor.ts +588 -0
- package/src/context.ts +51 -17
- package/src/cron-parser.ts +347 -0
- package/src/cron-scheduler.ts +239 -0
- package/src/database-context.ts +658 -0
- package/src/dependency-graph.ts +518 -0
- package/src/digital-objects-adapter.ts +351 -0
- package/src/durable-execution-cloudflare.ts +855 -0
- package/src/durable-execution.ts +1042 -0
- package/src/durable-workflow.ts +717 -0
- package/src/every.ts +104 -35
- package/src/graph/index.ts +19 -0
- package/src/graph/topological-sort.ts +412 -0
- package/src/index.ts +147 -0
- package/src/logger.ts +148 -0
- package/src/on.ts +81 -26
- package/src/runtime.ts +436 -0
- package/src/send.ts +4 -5
- package/src/telemetry.ts +577 -0
- package/src/timer-registry.ts +179 -0
- package/src/types.ts +146 -10
- package/src/worker/durable-step.ts +976 -0
- package/src/worker/index.ts +216 -0
- package/src/worker/state-adapter.ts +589 -0
- package/src/worker/topological-executor.ts +625 -0
- package/src/worker/workflow-builder.ts +871 -0
- package/src/worker.ts +2906 -0
- package/src/workflow-builder.ts +1068 -0
- package/src/workflow.ts +199 -355
- package/test/barrier-join.test.ts +442 -0
- package/test/barrier-unhandled-rejections.test.ts +359 -0
- package/test/cascade-context.test.ts +390 -0
- package/test/cascade-executor.test.ts +852 -0
- package/test/cron-parser.test.ts +314 -0
- package/test/cron-scheduler.test.ts +291 -0
- package/test/database-context.test.ts +770 -0
- package/test/db-provider-adapter.test.ts +862 -0
- package/test/dependency-graph.test.ts +512 -0
- package/test/durable-execution-cloudflare.test.ts +606 -0
- package/test/durable-execution-in-process.test.ts +286 -0
- package/test/durable-execution.test.ts +247 -0
- package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
- package/test/graph/topological-sort.test.ts +586 -0
- package/test/integration.test.ts +442 -0
- package/test/rpc-surface.test.ts +946 -0
- package/test/runtime.test.ts +262 -0
- package/test/schedule-timer-cleanup.test.ts +353 -0
- package/test/send-race-conditions.test.ts +400 -0
- package/test/type-safety-every.test.ts +303 -0
- package/test/worker/durable-cascade.test.ts +1117 -0
- package/test/worker/durable-step.test.ts +723 -0
- package/test/worker/topological-executor.test.ts +1240 -0
- package/test/worker/workflow-builder.test.ts +1067 -0
- package/test/worker.test.ts +608 -0
- package/test/workflow-builder.test.ts +1670 -0
- package/test/workflow-cron.test.ts +256 -0
- package/test/workflow-state-adapter.test.ts +923 -0
- package/test/workflow.test.ts +25 -22
- package/tsconfig.json +3 -1
- package/vitest.config.ts +38 -1
- package/vitest.workers.config.ts +44 -0
- package/wrangler.jsonc +22 -0
- package/.turbo/turbo-test.log +0 -7
- package/src/context.js +0 -83
- package/src/every.js +0 -267
- package/src/index.js +0 -71
- package/src/on.js +0 -79
- package/src/send.js +0 -111
- package/src/types.js +0 -4
- package/src/workflow.js +0 -455
- package/test/context.test.js +0 -116
- package/test/every.test.js +0 -282
- package/test/on.test.js +0 -80
- package/test/send.test.js +0 -89
- package/test/workflow.test.js +0 -224
- package/vitest.config.js +0 -7
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DBProvider Integration Adapter Tests (RED Phase)
|
|
3
|
+
*
|
|
4
|
+
* Tests for integrating ai-database's DBProvider with ai-workflows.
|
|
5
|
+
* These tests verify the adapter that bridges ai-database's DBProvider
|
|
6
|
+
* interface with ai-workflows' DatabaseContext.
|
|
7
|
+
*
|
|
8
|
+
* ## Test Categories
|
|
9
|
+
* 1. Adapter creation and configuration
|
|
10
|
+
* 2. CRUD operations via DBProvider
|
|
11
|
+
* 3. Event operations (emit, subscribe, replay)
|
|
12
|
+
* 4. Action operations (create, complete, list)
|
|
13
|
+
* 5. Artifact operations (store, retrieve)
|
|
14
|
+
* 6. Transaction support
|
|
15
|
+
* 7. Workflow integration
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
21
|
+
// These imports will fail until the adapter is implemented
|
|
22
|
+
// import { createDBProviderAdapter, DBProviderAdapter } from '../src/db-provider-adapter.js'
|
|
23
|
+
// import type { DBProvider, DBProviderExtended } from 'ai-database'
|
|
24
|
+
import type { DatabaseContext, ActionData, ArtifactData } from '../src/types.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Mock DBProvider interface matching ai-database
|
|
28
|
+
*/
|
|
29
|
+
interface MockDBProvider {
|
|
30
|
+
get(type: string, id: string): Promise<Record<string, unknown> | null>
|
|
31
|
+
list(
|
|
32
|
+
type: string,
|
|
33
|
+
options?: { limit?: number; offset?: number; where?: Record<string, unknown> }
|
|
34
|
+
): Promise<Record<string, unknown>[]>
|
|
35
|
+
search(
|
|
36
|
+
type: string,
|
|
37
|
+
query: string,
|
|
38
|
+
options?: Record<string, unknown>
|
|
39
|
+
): Promise<Record<string, unknown>[]>
|
|
40
|
+
create(
|
|
41
|
+
type: string,
|
|
42
|
+
id: string | undefined,
|
|
43
|
+
data: Record<string, unknown>
|
|
44
|
+
): Promise<Record<string, unknown>>
|
|
45
|
+
update(type: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>
|
|
46
|
+
delete(type: string, id: string): Promise<boolean>
|
|
47
|
+
related(type: string, id: string, relation: string): Promise<Record<string, unknown>[]>
|
|
48
|
+
relate(
|
|
49
|
+
fromType: string,
|
|
50
|
+
fromId: string,
|
|
51
|
+
relation: string,
|
|
52
|
+
toType: string,
|
|
53
|
+
toId: string,
|
|
54
|
+
metadata?: Record<string, unknown>
|
|
55
|
+
): Promise<void>
|
|
56
|
+
unrelate(
|
|
57
|
+
fromType: string,
|
|
58
|
+
fromId: string,
|
|
59
|
+
relation: string,
|
|
60
|
+
toType: string,
|
|
61
|
+
toId: string
|
|
62
|
+
): Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extended Mock DBProvider with events, actions, artifacts
|
|
67
|
+
*/
|
|
68
|
+
interface MockDBProviderExtended extends MockDBProvider {
|
|
69
|
+
// Events API
|
|
70
|
+
on(pattern: string, handler: (event: unknown) => void): () => void
|
|
71
|
+
emit(type: string, data: unknown): Promise<{ id: string }>
|
|
72
|
+
listEvents(options?: Record<string, unknown>): Promise<unknown[]>
|
|
73
|
+
replayEvents(options: { handler: (event: unknown) => void }): Promise<void>
|
|
74
|
+
|
|
75
|
+
// Actions API
|
|
76
|
+
createAction(options: {
|
|
77
|
+
type: string
|
|
78
|
+
actor: string
|
|
79
|
+
object: string
|
|
80
|
+
data?: unknown
|
|
81
|
+
}): Promise<{ id: string; status: string }>
|
|
82
|
+
getAction(id: string): Promise<{ id: string; status: string } | null>
|
|
83
|
+
updateAction(
|
|
84
|
+
id: string,
|
|
85
|
+
updates: Record<string, unknown>
|
|
86
|
+
): Promise<{ id: string; status: string }>
|
|
87
|
+
listActions(options?: Record<string, unknown>): Promise<unknown[]>
|
|
88
|
+
|
|
89
|
+
// Artifacts API
|
|
90
|
+
getArtifact(url: string, type: string): Promise<{ content: unknown } | null>
|
|
91
|
+
setArtifact(
|
|
92
|
+
url: string,
|
|
93
|
+
type: string,
|
|
94
|
+
data: { content: unknown; sourceHash: string }
|
|
95
|
+
): Promise<void>
|
|
96
|
+
deleteArtifact(url: string, type?: string): Promise<void>
|
|
97
|
+
listArtifacts(url: string): Promise<unknown[]>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a mock DBProvider for testing
|
|
102
|
+
*/
|
|
103
|
+
function createMockDBProvider(): MockDBProvider {
|
|
104
|
+
const stores = new Map<string, Map<string, Record<string, unknown>>>()
|
|
105
|
+
|
|
106
|
+
const getStore = (type: string) => {
|
|
107
|
+
if (!stores.has(type)) {
|
|
108
|
+
stores.set(type, new Map())
|
|
109
|
+
}
|
|
110
|
+
return stores.get(type)!
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
async get(type, id) {
|
|
115
|
+
return getStore(type).get(id) ?? null
|
|
116
|
+
},
|
|
117
|
+
async list(type, options) {
|
|
118
|
+
let results = Array.from(getStore(type).values())
|
|
119
|
+
if (options?.where) {
|
|
120
|
+
results = results.filter((r) => {
|
|
121
|
+
for (const [k, v] of Object.entries(options.where!)) {
|
|
122
|
+
if (r[k] !== v) return false
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
if (options?.offset) results = results.slice(options.offset)
|
|
128
|
+
if (options?.limit) results = results.slice(0, options.limit)
|
|
129
|
+
return results
|
|
130
|
+
},
|
|
131
|
+
async search(type, query) {
|
|
132
|
+
return Array.from(getStore(type).values()).filter((r) =>
|
|
133
|
+
JSON.stringify(r).toLowerCase().includes(query.toLowerCase())
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
async create(type, id, data) {
|
|
137
|
+
const entityId = id ?? `id-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
138
|
+
const record = { ...data, $id: entityId, $type: type }
|
|
139
|
+
getStore(type).set(entityId, record)
|
|
140
|
+
return record
|
|
141
|
+
},
|
|
142
|
+
async update(type, id, data) {
|
|
143
|
+
const existing = getStore(type).get(id)
|
|
144
|
+
const updated = { ...(existing ?? {}), ...data, $id: id, $type: type }
|
|
145
|
+
getStore(type).set(id, updated)
|
|
146
|
+
return updated
|
|
147
|
+
},
|
|
148
|
+
async delete(type, id) {
|
|
149
|
+
return getStore(type).delete(id)
|
|
150
|
+
},
|
|
151
|
+
async related(type, id, relation) {
|
|
152
|
+
// Simplified: return empty array
|
|
153
|
+
return []
|
|
154
|
+
},
|
|
155
|
+
async relate() {
|
|
156
|
+
// No-op for mock
|
|
157
|
+
},
|
|
158
|
+
async unrelate() {
|
|
159
|
+
// No-op for mock
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a mock extended DBProvider with events, actions, artifacts
|
|
166
|
+
*/
|
|
167
|
+
function createMockDBProviderExtended(): MockDBProviderExtended {
|
|
168
|
+
const baseProvider = createMockDBProvider()
|
|
169
|
+
const eventHandlers = new Map<string, Set<(event: unknown) => void>>()
|
|
170
|
+
const events: Array<{ id: string; type: string; data: unknown }> = []
|
|
171
|
+
const actions = new Map<
|
|
172
|
+
string,
|
|
173
|
+
{ id: string; status: string; actor: string; object: string; data?: unknown }
|
|
174
|
+
>()
|
|
175
|
+
const artifacts = new Map<string, { content: unknown; sourceHash: string }>()
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
...baseProvider,
|
|
179
|
+
|
|
180
|
+
on(pattern, handler) {
|
|
181
|
+
if (!eventHandlers.has(pattern)) {
|
|
182
|
+
eventHandlers.set(pattern, new Set())
|
|
183
|
+
}
|
|
184
|
+
eventHandlers.get(pattern)!.add(handler)
|
|
185
|
+
return () => eventHandlers.get(pattern)?.delete(handler)
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
async emit(type, data) {
|
|
189
|
+
const id = `event-${Date.now()}`
|
|
190
|
+
events.push({ id, type, data })
|
|
191
|
+
eventHandlers.get(type)?.forEach((h) => h({ id, type, data }))
|
|
192
|
+
return { id }
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
async listEvents() {
|
|
196
|
+
return events
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
async replayEvents(options) {
|
|
200
|
+
for (const event of events) {
|
|
201
|
+
await options.handler(event)
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
async createAction(options) {
|
|
206
|
+
const id = `action-${Date.now()}`
|
|
207
|
+
const action = {
|
|
208
|
+
id,
|
|
209
|
+
status: 'pending',
|
|
210
|
+
actor: options.actor,
|
|
211
|
+
object: options.object,
|
|
212
|
+
data: options.data,
|
|
213
|
+
}
|
|
214
|
+
actions.set(id, action)
|
|
215
|
+
return action
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
async getAction(id) {
|
|
219
|
+
return actions.get(id) ?? null
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
async updateAction(id, updates) {
|
|
223
|
+
const existing = actions.get(id)
|
|
224
|
+
if (!existing) throw new Error('Action not found')
|
|
225
|
+
const updated = { ...existing, ...updates }
|
|
226
|
+
actions.set(id, updated)
|
|
227
|
+
return updated
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
async listActions() {
|
|
231
|
+
return Array.from(actions.values())
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
async getArtifact(url, type) {
|
|
235
|
+
return artifacts.get(`${url}:${type}`) ?? null
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async setArtifact(url, type, data) {
|
|
239
|
+
artifacts.set(`${url}:${type}`, data)
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
async deleteArtifact(url, type) {
|
|
243
|
+
if (type) {
|
|
244
|
+
artifacts.delete(`${url}:${type}`)
|
|
245
|
+
} else {
|
|
246
|
+
// Delete all artifacts for URL
|
|
247
|
+
for (const key of artifacts.keys()) {
|
|
248
|
+
if (key.startsWith(`${url}:`)) {
|
|
249
|
+
artifacts.delete(key)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
async listArtifacts(url) {
|
|
256
|
+
const results: Array<{ content: unknown; sourceHash: string; type: string }> = []
|
|
257
|
+
for (const [key, value] of artifacts.entries()) {
|
|
258
|
+
if (key.startsWith(`${url}:`)) {
|
|
259
|
+
const type = key.split(':')[1]
|
|
260
|
+
results.push({ ...value, type })
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return results
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
describe('DBProviderAdapter', () => {
|
|
269
|
+
describe('createDBProviderAdapter', () => {
|
|
270
|
+
it('should create an adapter from a DBProvider', () => {
|
|
271
|
+
// RED: This test will fail until the adapter is implemented
|
|
272
|
+
const mockProvider = createMockDBProvider()
|
|
273
|
+
|
|
274
|
+
// The adapter should be creatable from a DBProvider
|
|
275
|
+
// This will fail as createDBProviderAdapter doesn't exist yet
|
|
276
|
+
expect(() => {
|
|
277
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
278
|
+
const adapter = createDBProviderAdapter(mockProvider)
|
|
279
|
+
expect(adapter).toBeDefined()
|
|
280
|
+
}).not.toThrow()
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('should require a valid DBProvider', () => {
|
|
284
|
+
// RED: Should throw if no provider is passed
|
|
285
|
+
expect(() => {
|
|
286
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
287
|
+
createDBProviderAdapter(null)
|
|
288
|
+
}).toThrow('DBProvider is required')
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('should accept options for configuration', () => {
|
|
292
|
+
const mockProvider = createMockDBProvider()
|
|
293
|
+
|
|
294
|
+
// RED: Should accept configuration options
|
|
295
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
296
|
+
const adapter = createDBProviderAdapter(mockProvider, {
|
|
297
|
+
workflowId: 'test-workflow',
|
|
298
|
+
source: 'test-source',
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
expect(adapter).toBeDefined()
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe('DatabaseContext interface', () => {
|
|
306
|
+
it('should implement DatabaseContext interface', () => {
|
|
307
|
+
const mockProvider = createMockDBProvider()
|
|
308
|
+
|
|
309
|
+
// RED: Adapter should implement DatabaseContext
|
|
310
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
311
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
312
|
+
|
|
313
|
+
expect(typeof adapter.recordEvent).toBe('function')
|
|
314
|
+
expect(typeof adapter.createAction).toBe('function')
|
|
315
|
+
expect(typeof adapter.completeAction).toBe('function')
|
|
316
|
+
expect(typeof adapter.storeArtifact).toBe('function')
|
|
317
|
+
expect(typeof adapter.getArtifact).toBe('function')
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
describe('recordEvent()', () => {
|
|
322
|
+
it('should record events to the DBProvider', async () => {
|
|
323
|
+
const mockProvider = createMockDBProvider()
|
|
324
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
325
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
326
|
+
|
|
327
|
+
// RED: Should store event in DBProvider
|
|
328
|
+
await adapter.recordEvent('Customer.created', { id: '123', name: 'John' })
|
|
329
|
+
|
|
330
|
+
// Verify event was stored
|
|
331
|
+
const events = await mockProvider.list('WorkflowEvent')
|
|
332
|
+
expect(events).toHaveLength(1)
|
|
333
|
+
expect(events[0]).toMatchObject({
|
|
334
|
+
eventType: 'Customer.created',
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('should store event data as JSON', async () => {
|
|
339
|
+
const mockProvider = createMockDBProvider()
|
|
340
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
341
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
342
|
+
|
|
343
|
+
const eventData = { orderId: 'order-1', total: 99.99 }
|
|
344
|
+
await adapter.recordEvent('Order.completed', eventData)
|
|
345
|
+
|
|
346
|
+
const events = await mockProvider.list('WorkflowEvent')
|
|
347
|
+
const storedData = JSON.parse(events[0]?.data as string)
|
|
348
|
+
expect(storedData).toEqual(eventData)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('should include timestamp on events', async () => {
|
|
352
|
+
const mockProvider = createMockDBProvider()
|
|
353
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
354
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
355
|
+
|
|
356
|
+
const before = Date.now()
|
|
357
|
+
await adapter.recordEvent('Test.event', {})
|
|
358
|
+
const after = Date.now()
|
|
359
|
+
|
|
360
|
+
const events = await mockProvider.list('WorkflowEvent')
|
|
361
|
+
const timestamp = events[0]?.timestamp as number
|
|
362
|
+
expect(timestamp).toBeGreaterThanOrEqual(before)
|
|
363
|
+
expect(timestamp).toBeLessThanOrEqual(after)
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('should include workflow ID when configured', async () => {
|
|
367
|
+
const mockProvider = createMockDBProvider()
|
|
368
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
369
|
+
const adapter = createDBProviderAdapter(mockProvider, {
|
|
370
|
+
workflowId: 'wf-123',
|
|
371
|
+
}) as DatabaseContext
|
|
372
|
+
|
|
373
|
+
await adapter.recordEvent('Test.event', {})
|
|
374
|
+
|
|
375
|
+
const events = await mockProvider.list('WorkflowEvent')
|
|
376
|
+
expect(events[0]?.workflowId).toBe('wf-123')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('should emit to extended provider events API if available', async () => {
|
|
380
|
+
const mockProvider = createMockDBProviderExtended()
|
|
381
|
+
const emitSpy = vi.spyOn(mockProvider, 'emit')
|
|
382
|
+
|
|
383
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
384
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
385
|
+
|
|
386
|
+
await adapter.recordEvent('Customer.created', { id: '1' })
|
|
387
|
+
|
|
388
|
+
// Should emit event through provider's events API
|
|
389
|
+
expect(emitSpy).toHaveBeenCalled()
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
describe('createAction()', () => {
|
|
394
|
+
it('should create an action in the DBProvider', async () => {
|
|
395
|
+
const mockProvider = createMockDBProvider()
|
|
396
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
397
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
398
|
+
|
|
399
|
+
await adapter.createAction({
|
|
400
|
+
actor: 'user:john',
|
|
401
|
+
object: 'Order/order-123',
|
|
402
|
+
action: 'approve',
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
const actions = await mockProvider.list('WorkflowAction')
|
|
406
|
+
expect(actions).toHaveLength(1)
|
|
407
|
+
expect(actions[0]).toMatchObject({
|
|
408
|
+
actor: 'user:john',
|
|
409
|
+
object: 'Order/order-123',
|
|
410
|
+
action: 'approve',
|
|
411
|
+
status: 'pending',
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should respect initial status', async () => {
|
|
416
|
+
const mockProvider = createMockDBProvider()
|
|
417
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
418
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
419
|
+
|
|
420
|
+
await adapter.createAction({
|
|
421
|
+
actor: 'system',
|
|
422
|
+
object: 'Task/task-1',
|
|
423
|
+
action: 'process',
|
|
424
|
+
status: 'active',
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const actions = await mockProvider.list('WorkflowAction')
|
|
428
|
+
expect(actions[0]?.status).toBe('active')
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it('should store action metadata', async () => {
|
|
432
|
+
const mockProvider = createMockDBProvider()
|
|
433
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
434
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
435
|
+
|
|
436
|
+
await adapter.createAction({
|
|
437
|
+
actor: 'system',
|
|
438
|
+
object: 'Report/report-1',
|
|
439
|
+
action: 'generate',
|
|
440
|
+
metadata: { format: 'pdf', pages: 10 },
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
const actions = await mockProvider.list('WorkflowAction')
|
|
444
|
+
const metadata = JSON.parse(actions[0]?.metadata as string)
|
|
445
|
+
expect(metadata).toEqual({ format: 'pdf', pages: 10 })
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it('should use extended provider actions API if available', async () => {
|
|
449
|
+
const mockProvider = createMockDBProviderExtended()
|
|
450
|
+
const createActionSpy = vi.spyOn(mockProvider, 'createAction')
|
|
451
|
+
|
|
452
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
453
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
454
|
+
|
|
455
|
+
await adapter.createAction({
|
|
456
|
+
actor: 'user:alice',
|
|
457
|
+
object: 'Document/doc-1',
|
|
458
|
+
action: 'review',
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
// Should use provider's native createAction if available
|
|
462
|
+
expect(createActionSpy).toHaveBeenCalled()
|
|
463
|
+
})
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
describe('completeAction()', () => {
|
|
467
|
+
it('should mark action as completed', async () => {
|
|
468
|
+
const mockProvider = createMockDBProvider()
|
|
469
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
470
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
471
|
+
|
|
472
|
+
// Create action first
|
|
473
|
+
await adapter.createAction({
|
|
474
|
+
actor: 'user:john',
|
|
475
|
+
object: 'Order/order-123',
|
|
476
|
+
action: 'approve',
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const actions = await mockProvider.list('WorkflowAction')
|
|
480
|
+
const actionId = actions[0]?.$id as string
|
|
481
|
+
|
|
482
|
+
// Complete it
|
|
483
|
+
await adapter.completeAction(actionId, { approved: true })
|
|
484
|
+
|
|
485
|
+
const updated = await mockProvider.get('WorkflowAction', actionId)
|
|
486
|
+
expect(updated?.status).toBe('completed')
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('should store action result', async () => {
|
|
490
|
+
const mockProvider = createMockDBProvider()
|
|
491
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
492
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
493
|
+
|
|
494
|
+
await adapter.createAction({
|
|
495
|
+
actor: 'system',
|
|
496
|
+
object: 'Task/task-1',
|
|
497
|
+
action: 'process',
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const actions = await mockProvider.list('WorkflowAction')
|
|
501
|
+
const actionId = actions[0]?.$id as string
|
|
502
|
+
|
|
503
|
+
await adapter.completeAction(actionId, { output: 'processed', items: 42 })
|
|
504
|
+
|
|
505
|
+
const updated = await mockProvider.get('WorkflowAction', actionId)
|
|
506
|
+
const result = JSON.parse(updated?.result as string)
|
|
507
|
+
expect(result).toEqual({ output: 'processed', items: 42 })
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
it('should throw for non-existent action', async () => {
|
|
511
|
+
const mockProvider = createMockDBProvider()
|
|
512
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
513
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
514
|
+
|
|
515
|
+
await expect(adapter.completeAction('non-existent', {})).rejects.toThrow('Action not found')
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
it('should record completedAt timestamp', async () => {
|
|
519
|
+
const mockProvider = createMockDBProvider()
|
|
520
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
521
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
522
|
+
|
|
523
|
+
await adapter.createAction({
|
|
524
|
+
actor: 'user:bob',
|
|
525
|
+
object: 'Request/req-1',
|
|
526
|
+
action: 'approve',
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
const actions = await mockProvider.list('WorkflowAction')
|
|
530
|
+
const actionId = actions[0]?.$id as string
|
|
531
|
+
|
|
532
|
+
const before = Date.now()
|
|
533
|
+
await adapter.completeAction(actionId, { approved: true })
|
|
534
|
+
const after = Date.now()
|
|
535
|
+
|
|
536
|
+
const updated = await mockProvider.get('WorkflowAction', actionId)
|
|
537
|
+
const completedAt = updated?.completedAt as number
|
|
538
|
+
expect(completedAt).toBeGreaterThanOrEqual(before)
|
|
539
|
+
expect(completedAt).toBeLessThanOrEqual(after)
|
|
540
|
+
})
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
describe('storeArtifact()', () => {
|
|
544
|
+
it('should store an artifact', async () => {
|
|
545
|
+
const mockProvider = createMockDBProvider()
|
|
546
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
547
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
548
|
+
|
|
549
|
+
await adapter.storeArtifact({
|
|
550
|
+
key: 'compiled/workflow-1/code.esm',
|
|
551
|
+
type: 'esm',
|
|
552
|
+
sourceHash: 'abc123',
|
|
553
|
+
content: 'export function handler() {}',
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
const artifacts = await mockProvider.list('WorkflowArtifact')
|
|
557
|
+
expect(artifacts).toHaveLength(1)
|
|
558
|
+
expect(artifacts[0]).toMatchObject({
|
|
559
|
+
key: 'compiled/workflow-1/code.esm',
|
|
560
|
+
artifactType: 'esm',
|
|
561
|
+
sourceHash: 'abc123',
|
|
562
|
+
})
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('should store complex artifact content', async () => {
|
|
566
|
+
const mockProvider = createMockDBProvider()
|
|
567
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
568
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
569
|
+
|
|
570
|
+
const ast = {
|
|
571
|
+
type: 'Program',
|
|
572
|
+
body: [{ type: 'ExportDeclaration' }],
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
await adapter.storeArtifact({
|
|
576
|
+
key: 'parsed/module.ast',
|
|
577
|
+
type: 'ast',
|
|
578
|
+
sourceHash: 'def456',
|
|
579
|
+
content: ast,
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
const stored = await adapter.getArtifact('parsed/module.ast')
|
|
583
|
+
expect(stored).toEqual(ast)
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
it('should store artifact metadata', async () => {
|
|
587
|
+
const mockProvider = createMockDBProvider()
|
|
588
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
589
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
590
|
+
|
|
591
|
+
await adapter.storeArtifact({
|
|
592
|
+
key: 'bundle/app.js',
|
|
593
|
+
type: 'bundle',
|
|
594
|
+
sourceHash: 'ghi789',
|
|
595
|
+
content: 'bundled code',
|
|
596
|
+
metadata: { size: 1024, modules: 5 },
|
|
597
|
+
})
|
|
598
|
+
|
|
599
|
+
const artifacts = await mockProvider.list('WorkflowArtifact')
|
|
600
|
+
const metadata = JSON.parse(artifacts[0]?.metadata as string)
|
|
601
|
+
expect(metadata).toEqual({ size: 1024, modules: 5 })
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
it('should use extended provider artifacts API if available', async () => {
|
|
605
|
+
const mockProvider = createMockDBProviderExtended()
|
|
606
|
+
const setArtifactSpy = vi.spyOn(mockProvider, 'setArtifact')
|
|
607
|
+
|
|
608
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
609
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
610
|
+
|
|
611
|
+
await adapter.storeArtifact({
|
|
612
|
+
key: 'test/artifact',
|
|
613
|
+
type: 'bundle',
|
|
614
|
+
sourceHash: 'hash123',
|
|
615
|
+
content: 'test content',
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
// Should use provider's native setArtifact if available
|
|
619
|
+
expect(setArtifactSpy).toHaveBeenCalled()
|
|
620
|
+
})
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
describe('getArtifact()', () => {
|
|
624
|
+
it('should retrieve an artifact', async () => {
|
|
625
|
+
const mockProvider = createMockDBProvider()
|
|
626
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
627
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
628
|
+
|
|
629
|
+
await adapter.storeArtifact({
|
|
630
|
+
key: 'test-key',
|
|
631
|
+
type: 'esm',
|
|
632
|
+
sourceHash: 'hash123',
|
|
633
|
+
content: 'export default {}',
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
const content = await adapter.getArtifact('test-key')
|
|
637
|
+
expect(content).toBe('export default {}')
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('should return null for non-existent artifact', async () => {
|
|
641
|
+
const mockProvider = createMockDBProvider()
|
|
642
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
643
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
644
|
+
|
|
645
|
+
const content = await adapter.getArtifact('non-existent')
|
|
646
|
+
expect(content).toBeNull()
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
it('should deserialize complex content', async () => {
|
|
650
|
+
const mockProvider = createMockDBProvider()
|
|
651
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
652
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
653
|
+
|
|
654
|
+
const original = { nested: { data: [1, 2, 3] } }
|
|
655
|
+
await adapter.storeArtifact({
|
|
656
|
+
key: 'complex-key',
|
|
657
|
+
type: 'ast',
|
|
658
|
+
sourceHash: 'hash456',
|
|
659
|
+
content: original,
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
const content = await adapter.getArtifact('complex-key')
|
|
663
|
+
expect(content).toEqual(original)
|
|
664
|
+
})
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
describe('Extended features with DBProviderExtended', () => {
|
|
668
|
+
describe('Event subscription', () => {
|
|
669
|
+
it('should forward event subscriptions to provider', async () => {
|
|
670
|
+
const mockProvider = createMockDBProviderExtended()
|
|
671
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
672
|
+
const adapter = createDBProviderAdapter(mockProvider)
|
|
673
|
+
|
|
674
|
+
const handler = vi.fn()
|
|
675
|
+
// RED: Adapter should expose subscribe method for extended providers
|
|
676
|
+
adapter.subscribe('Customer.created', handler)
|
|
677
|
+
|
|
678
|
+
await mockProvider.emit('Customer.created', { id: '1' })
|
|
679
|
+
|
|
680
|
+
expect(handler).toHaveBeenCalled()
|
|
681
|
+
})
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
describe('Event replay', () => {
|
|
685
|
+
it('should support event replay through provider', async () => {
|
|
686
|
+
const mockProvider = createMockDBProviderExtended()
|
|
687
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
688
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
689
|
+
|
|
690
|
+
// Record some events
|
|
691
|
+
await adapter.recordEvent('Step1.completed', { result: 'a' })
|
|
692
|
+
await adapter.recordEvent('Step2.completed', { result: 'b' })
|
|
693
|
+
|
|
694
|
+
const replayed: Array<{ event: string; data: unknown }> = []
|
|
695
|
+
|
|
696
|
+
// RED: Adapter should expose replay method
|
|
697
|
+
// @ts-expect-error - replay not implemented yet
|
|
698
|
+
await adapter.replay(async (event: string, data: unknown) => {
|
|
699
|
+
replayed.push({ event, data })
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
expect(replayed).toHaveLength(2)
|
|
703
|
+
})
|
|
704
|
+
})
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
describe('Transaction support', () => {
|
|
708
|
+
it('should pass through transaction support from provider', async () => {
|
|
709
|
+
const mockProvider = createMockDBProvider() as MockDBProvider & {
|
|
710
|
+
beginTransaction: () => Promise<{
|
|
711
|
+
commit: () => Promise<void>
|
|
712
|
+
rollback: () => Promise<void>
|
|
713
|
+
}>
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Add transaction support to mock
|
|
717
|
+
mockProvider.beginTransaction = vi.fn().mockResolvedValue({
|
|
718
|
+
commit: vi.fn().mockResolvedValue(undefined),
|
|
719
|
+
rollback: vi.fn().mockResolvedValue(undefined),
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
723
|
+
const adapter = createDBProviderAdapter(mockProvider)
|
|
724
|
+
|
|
725
|
+
// RED: Adapter should expose transaction support
|
|
726
|
+
// @ts-expect-error - beginTransaction not exposed yet
|
|
727
|
+
const txn = await adapter.beginTransaction()
|
|
728
|
+
expect(txn).toBeDefined()
|
|
729
|
+
expect(typeof txn.commit).toBe('function')
|
|
730
|
+
expect(typeof txn.rollback).toBe('function')
|
|
731
|
+
})
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
describe('Workflow integration', () => {
|
|
735
|
+
it('should be usable as WorkflowOptions.db', async () => {
|
|
736
|
+
const mockProvider = createMockDBProvider()
|
|
737
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
738
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
739
|
+
|
|
740
|
+
// RED: Adapter should be assignable to WorkflowOptions.db
|
|
741
|
+
// This verifies the type compatibility
|
|
742
|
+
const workflowOptions = {
|
|
743
|
+
db: adapter,
|
|
744
|
+
context: { userId: '123' },
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
expect(workflowOptions.db).toBeDefined()
|
|
748
|
+
expect(typeof workflowOptions.db.recordEvent).toBe('function')
|
|
749
|
+
expect(typeof workflowOptions.db.createAction).toBe('function')
|
|
750
|
+
expect(typeof workflowOptions.db.completeAction).toBe('function')
|
|
751
|
+
expect(typeof workflowOptions.db.storeArtifact).toBe('function')
|
|
752
|
+
expect(typeof workflowOptions.db.getArtifact).toBe('function')
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
it('should maintain workflow context across operations', async () => {
|
|
756
|
+
const mockProvider = createMockDBProvider()
|
|
757
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
758
|
+
const adapter = createDBProviderAdapter(mockProvider, {
|
|
759
|
+
workflowId: 'wf-integration-test',
|
|
760
|
+
source: 'integration-test',
|
|
761
|
+
}) as DatabaseContext
|
|
762
|
+
|
|
763
|
+
// Simulate workflow execution
|
|
764
|
+
await adapter.recordEvent('Workflow.started', { input: { orderId: 'order-1' } })
|
|
765
|
+
await adapter.createAction({
|
|
766
|
+
actor: 'system',
|
|
767
|
+
object: 'Order/order-1',
|
|
768
|
+
action: 'validate',
|
|
769
|
+
})
|
|
770
|
+
await adapter.storeArtifact({
|
|
771
|
+
key: 'wf-integration-test/compiled',
|
|
772
|
+
type: 'esm',
|
|
773
|
+
sourceHash: 'hash123',
|
|
774
|
+
content: 'compiled workflow',
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
// Verify all operations used the workflow context
|
|
778
|
+
const events = await mockProvider.list('WorkflowEvent')
|
|
779
|
+
expect(events.every((e) => e.workflowId === 'wf-integration-test')).toBe(true)
|
|
780
|
+
expect(events.every((e) => e.source === 'integration-test')).toBe(true)
|
|
781
|
+
})
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
describe('Error handling', () => {
|
|
785
|
+
it('should propagate provider errors', async () => {
|
|
786
|
+
const mockProvider = createMockDBProvider()
|
|
787
|
+
mockProvider.create = vi.fn().mockRejectedValue(new Error('Provider error'))
|
|
788
|
+
|
|
789
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
790
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
791
|
+
|
|
792
|
+
await expect(adapter.recordEvent('Test.event', {})).rejects.toThrow('Provider error')
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
it('should handle null/undefined data gracefully', async () => {
|
|
796
|
+
const mockProvider = createMockDBProvider()
|
|
797
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
798
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
799
|
+
|
|
800
|
+
// Should not throw for null/undefined data
|
|
801
|
+
await expect(adapter.recordEvent('Test.event', null)).resolves.not.toThrow()
|
|
802
|
+
await expect(adapter.recordEvent('Test.event', undefined)).resolves.not.toThrow()
|
|
803
|
+
})
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
describe('Type safety', () => {
|
|
807
|
+
it('should preserve type information through the adapter', async () => {
|
|
808
|
+
const mockProvider = createMockDBProvider()
|
|
809
|
+
// @ts-expect-error - createDBProviderAdapter not implemented yet
|
|
810
|
+
const adapter = createDBProviderAdapter(mockProvider) as DatabaseContext
|
|
811
|
+
|
|
812
|
+
// Store typed artifact
|
|
813
|
+
interface MyArtifact {
|
|
814
|
+
version: number
|
|
815
|
+
data: string[]
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const artifact: MyArtifact = { version: 1, data: ['a', 'b', 'c'] }
|
|
819
|
+
|
|
820
|
+
await adapter.storeArtifact({
|
|
821
|
+
key: 'typed-artifact',
|
|
822
|
+
type: 'bundle',
|
|
823
|
+
sourceHash: 'hash789',
|
|
824
|
+
content: artifact,
|
|
825
|
+
})
|
|
826
|
+
|
|
827
|
+
// RED: getArtifact should preserve type
|
|
828
|
+
const retrieved = (await adapter.getArtifact('typed-artifact')) as MyArtifact
|
|
829
|
+
expect(retrieved.version).toBe(1)
|
|
830
|
+
expect(retrieved.data).toEqual(['a', 'b', 'c'])
|
|
831
|
+
})
|
|
832
|
+
})
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
describe('DBProviderAdapter class', () => {
|
|
836
|
+
it('should be exportable as a class', () => {
|
|
837
|
+
// RED: The adapter should also be available as a class
|
|
838
|
+
// @ts-expect-error - DBProviderAdapter not implemented yet
|
|
839
|
+
expect(DBProviderAdapter).toBeDefined()
|
|
840
|
+
// @ts-expect-error - DBProviderAdapter not implemented yet
|
|
841
|
+
expect(typeof DBProviderAdapter).toBe('function')
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
it('should be instantiable with new', () => {
|
|
845
|
+
const mockProvider = createMockDBProvider()
|
|
846
|
+
|
|
847
|
+
// RED: Should be instantiable
|
|
848
|
+
// @ts-expect-error - DBProviderAdapter not implemented yet
|
|
849
|
+
const adapter = new DBProviderAdapter(mockProvider)
|
|
850
|
+
expect(adapter).toBeDefined()
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
it('should expose the underlying provider', () => {
|
|
854
|
+
const mockProvider = createMockDBProvider()
|
|
855
|
+
|
|
856
|
+
// @ts-expect-error - DBProviderAdapter not implemented yet
|
|
857
|
+
const adapter = new DBProviderAdapter(mockProvider)
|
|
858
|
+
|
|
859
|
+
// RED: Should expose the provider for advanced use cases
|
|
860
|
+
expect(adapter.provider).toBe(mockProvider)
|
|
861
|
+
})
|
|
862
|
+
})
|