ai-workflows 2.1.3 → 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.
Files changed (188) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +8 -1
  3. package/README.md +2 -0
  4. package/dist/barrier.d.ts +6 -0
  5. package/dist/barrier.d.ts.map +1 -1
  6. package/dist/barrier.js +45 -7
  7. package/dist/barrier.js.map +1 -1
  8. package/dist/cascade-context.d.ts.map +1 -1
  9. package/dist/cascade-context.js +25 -25
  10. package/dist/cascade-context.js.map +1 -1
  11. package/dist/cascade-executor.d.ts.map +1 -1
  12. package/dist/cascade-executor.js +1 -1
  13. package/dist/cascade-executor.js.map +1 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +23 -7
  16. package/dist/context.js.map +1 -1
  17. package/dist/cron-parser.d.ts +65 -0
  18. package/dist/cron-parser.d.ts.map +1 -0
  19. package/dist/cron-parser.js +294 -0
  20. package/dist/cron-parser.js.map +1 -0
  21. package/dist/cron-scheduler.d.ts +117 -0
  22. package/dist/cron-scheduler.d.ts.map +1 -0
  23. package/dist/cron-scheduler.js +176 -0
  24. package/dist/cron-scheduler.js.map +1 -0
  25. package/dist/database-context.d.ts +184 -0
  26. package/dist/database-context.d.ts.map +1 -0
  27. package/dist/database-context.js +428 -0
  28. package/dist/database-context.js.map +1 -0
  29. package/dist/digital-objects-adapter.d.ts +159 -0
  30. package/dist/digital-objects-adapter.d.ts.map +1 -0
  31. package/dist/digital-objects-adapter.js +229 -0
  32. package/dist/digital-objects-adapter.js.map +1 -0
  33. package/dist/durable-execution-cloudflare.d.ts +427 -0
  34. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  35. package/dist/durable-execution-cloudflare.js +510 -0
  36. package/dist/durable-execution-cloudflare.js.map +1 -0
  37. package/dist/durable-execution.d.ts +482 -0
  38. package/dist/durable-execution.d.ts.map +1 -0
  39. package/dist/durable-execution.js +594 -0
  40. package/dist/durable-execution.js.map +1 -0
  41. package/dist/durable-workflow.d.ts +176 -0
  42. package/dist/durable-workflow.d.ts.map +1 -0
  43. package/dist/durable-workflow.js +552 -0
  44. package/dist/durable-workflow.js.map +1 -0
  45. package/dist/graph/topological-sort.d.ts.map +1 -1
  46. package/dist/graph/topological-sort.js +5 -5
  47. package/dist/graph/topological-sort.js.map +1 -1
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +15 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/logger.d.ts +101 -0
  53. package/dist/logger.d.ts.map +1 -0
  54. package/dist/logger.js +115 -0
  55. package/dist/logger.js.map +1 -0
  56. package/dist/on.d.ts.map +1 -1
  57. package/dist/on.js +3 -3
  58. package/dist/on.js.map +1 -1
  59. package/dist/runtime.d.ts +169 -0
  60. package/dist/runtime.d.ts.map +1 -0
  61. package/dist/runtime.js +275 -0
  62. package/dist/runtime.js.map +1 -0
  63. package/dist/send.d.ts.map +1 -1
  64. package/dist/send.js +4 -3
  65. package/dist/send.js.map +1 -1
  66. package/dist/telemetry.d.ts +150 -0
  67. package/dist/telemetry.d.ts.map +1 -0
  68. package/dist/telemetry.js +388 -0
  69. package/dist/telemetry.js.map +1 -0
  70. package/dist/timer-registry.d.ts +25 -0
  71. package/dist/timer-registry.d.ts.map +1 -1
  72. package/dist/timer-registry.js +42 -8
  73. package/dist/timer-registry.js.map +1 -1
  74. package/dist/types.d.ts +17 -6
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +1 -1
  77. package/dist/types.js.map +1 -1
  78. package/dist/worker/durable-step.d.ts +481 -0
  79. package/dist/worker/durable-step.d.ts.map +1 -0
  80. package/dist/worker/durable-step.js +606 -0
  81. package/dist/worker/durable-step.js.map +1 -0
  82. package/dist/worker/index.d.ts +106 -0
  83. package/dist/worker/index.d.ts.map +1 -0
  84. package/dist/worker/index.js +124 -0
  85. package/dist/worker/index.js.map +1 -0
  86. package/dist/worker/state-adapter.d.ts +230 -0
  87. package/dist/worker/state-adapter.d.ts.map +1 -0
  88. package/dist/worker/state-adapter.js +409 -0
  89. package/dist/worker/state-adapter.js.map +1 -0
  90. package/dist/worker/topological-executor.d.ts +282 -0
  91. package/dist/worker/topological-executor.d.ts.map +1 -0
  92. package/dist/worker/topological-executor.js +396 -0
  93. package/dist/worker/topological-executor.js.map +1 -0
  94. package/dist/worker/workflow-builder.d.ts +286 -0
  95. package/dist/worker/workflow-builder.d.ts.map +1 -0
  96. package/dist/worker/workflow-builder.js +565 -0
  97. package/dist/worker/workflow-builder.js.map +1 -0
  98. package/dist/worker.d.ts +800 -0
  99. package/dist/worker.d.ts.map +1 -0
  100. package/dist/worker.js +2428 -0
  101. package/dist/worker.js.map +1 -0
  102. package/dist/workflow-builder.d.ts +287 -0
  103. package/dist/workflow-builder.d.ts.map +1 -0
  104. package/dist/workflow-builder.js +762 -0
  105. package/dist/workflow-builder.js.map +1 -0
  106. package/dist/workflow.d.ts +14 -30
  107. package/dist/workflow.d.ts.map +1 -1
  108. package/dist/workflow.js +132 -292
  109. package/dist/workflow.js.map +1 -1
  110. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  111. package/examples/02-content-moderation-cascade.ts +454 -0
  112. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  113. package/examples/04-database-persistence.ts +518 -0
  114. package/examples/README.md +173 -0
  115. package/package.json +30 -13
  116. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  117. package/src/__tests__/durable-workflow.test.ts +297 -0
  118. package/src/barrier.ts +48 -7
  119. package/src/cascade-context.ts +36 -29
  120. package/src/cascade-executor.ts +3 -2
  121. package/src/context.ts +41 -12
  122. package/src/cron-parser.ts +347 -0
  123. package/src/cron-scheduler.ts +239 -0
  124. package/src/database-context.ts +658 -0
  125. package/src/digital-objects-adapter.ts +351 -0
  126. package/src/durable-execution-cloudflare.ts +855 -0
  127. package/src/durable-execution.ts +1042 -0
  128. package/src/durable-workflow.ts +717 -0
  129. package/src/graph/topological-sort.ts +6 -8
  130. package/src/index.ts +69 -0
  131. package/src/logger.ts +148 -0
  132. package/src/on.ts +8 -9
  133. package/src/runtime.ts +436 -0
  134. package/src/send.ts +4 -5
  135. package/src/telemetry.ts +577 -0
  136. package/src/timer-registry.ts +44 -10
  137. package/src/types.ts +32 -17
  138. package/src/worker/durable-step.ts +976 -0
  139. package/src/worker/index.ts +216 -0
  140. package/src/worker/state-adapter.ts +589 -0
  141. package/src/worker/topological-executor.ts +625 -0
  142. package/src/worker/workflow-builder.ts +871 -0
  143. package/src/worker.ts +2906 -0
  144. package/src/workflow-builder.ts +1068 -0
  145. package/src/workflow.ts +188 -351
  146. package/test/barrier-join.test.ts +32 -24
  147. package/test/cascade-executor.test.ts +9 -16
  148. package/test/cron-parser.test.ts +314 -0
  149. package/test/cron-scheduler.test.ts +291 -0
  150. package/test/database-context.test.ts +770 -0
  151. package/test/db-provider-adapter.test.ts +862 -0
  152. package/test/durable-execution-cloudflare.test.ts +606 -0
  153. package/test/durable-execution-in-process.test.ts +286 -0
  154. package/test/durable-execution.test.ts +247 -0
  155. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  156. package/test/integration.test.ts +442 -0
  157. package/test/rpc-surface.test.ts +946 -0
  158. package/test/runtime.test.ts +262 -0
  159. package/test/schedule-timer-cleanup.test.ts +30 -21
  160. package/test/send-race-conditions.test.ts +30 -40
  161. package/test/worker/durable-cascade.test.ts +1117 -0
  162. package/test/worker/durable-step.test.ts +723 -0
  163. package/test/worker/topological-executor.test.ts +1240 -0
  164. package/test/worker/workflow-builder.test.ts +1067 -0
  165. package/test/worker.test.ts +608 -0
  166. package/test/workflow-builder.test.ts +1670 -0
  167. package/test/workflow-cron.test.ts +256 -0
  168. package/test/workflow-state-adapter.test.ts +923 -0
  169. package/test/workflow.test.ts +25 -22
  170. package/tsconfig.json +3 -1
  171. package/vitest.config.ts +38 -1
  172. package/vitest.workers.config.ts +44 -0
  173. package/wrangler.jsonc +22 -0
  174. package/.turbo/turbo-test.log +0 -169
  175. package/LICENSE +0 -21
  176. package/src/context.js +0 -83
  177. package/src/every.js +0 -267
  178. package/src/index.js +0 -71
  179. package/src/on.js +0 -79
  180. package/src/send.js +0 -111
  181. package/src/types.js +0 -4
  182. package/src/workflow.js +0 -455
  183. package/test/context.test.js +0 -116
  184. package/test/every.test.js +0 -282
  185. package/test/on.test.js +0 -80
  186. package/test/send.test.js +0 -89
  187. package/test/workflow.test.js +0 -224
  188. 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
+ })