lopata 0.7.0 → 0.8.2
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/dist/dashboard/{chunk-pqnphvm2.css → chunk-csyd2tq2.css} +27 -0
- package/dist/dashboard/{chunk-5nxa3jfc.js → chunk-yxzrcvyh.js} +364 -3
- package/dist/dashboard/index.html +1 -1
- package/package.json +5 -3
- package/src/api/handlers/generations.ts +19 -5
- package/src/api/types.ts +14 -0
- package/src/bindings/cache.ts +14 -8
- package/src/bindings/durable-object.ts +80 -21
- package/src/bindings/kv.ts +12 -8
- package/src/bindings/queue.ts +22 -12
- package/src/bindings/workflow.ts +332 -25
- package/src/env.ts +3 -2
- package/src/file-watcher.ts +59 -32
- package/src/generation-manager.ts +6 -1
- package/src/generation.ts +15 -3
- package/src/plugin.ts +2 -90
- package/src/setup-globals.ts +23 -21
- package/src/testing/clock.ts +26 -0
- package/src/testing/durable-object.ts +325 -0
- package/src/testing/env-builder.ts +126 -0
- package/src/testing/fetch-mock.ts +145 -0
- package/src/testing/index.ts +288 -0
- package/src/testing/setup.ts +68 -0
- package/src/testing/types.ts +68 -0
- package/src/testing/workflow.ts +323 -0
- package/src/tracing/store.ts +6 -0
- package/src/tracing/types.ts +1 -0
- package/src/virtual-modules.ts +99 -0
- package/src/vite-plugin/config-plugin.ts +2 -0
- package/src/vite-plugin/dev-server-plugin.ts +159 -56
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite'
|
|
2
|
+
import type { SqliteWorkflowBinding, SqliteWorkflowInstance } from '../bindings/workflow'
|
|
3
|
+
import {
|
|
4
|
+
clearInstanceMocks,
|
|
5
|
+
getWaitingEventTypes,
|
|
6
|
+
isInstanceSleeping,
|
|
7
|
+
onEventWaitRegistered,
|
|
8
|
+
onSleepRegistered,
|
|
9
|
+
onStatusChange,
|
|
10
|
+
onStepComplete,
|
|
11
|
+
registerEventMock,
|
|
12
|
+
registerEventTimeoutMock,
|
|
13
|
+
registerSleepDisable,
|
|
14
|
+
registerStepMock,
|
|
15
|
+
} from '../bindings/workflow'
|
|
16
|
+
|
|
17
|
+
const TERMINAL_STATUSES = new Set(['complete', 'errored', 'terminated'])
|
|
18
|
+
const DEFAULT_TIMEOUT = 5000
|
|
19
|
+
|
|
20
|
+
function timeoutError(what: string, ms: number): Error {
|
|
21
|
+
return new Error(`${what} timed out after ${ms}ms`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TestWorkflowInstance {
|
|
25
|
+
private binding: SqliteWorkflowBinding
|
|
26
|
+
private instance: SqliteWorkflowInstance
|
|
27
|
+
private db: Database
|
|
28
|
+
private unsubs: (() => void)[] = []
|
|
29
|
+
private started = true
|
|
30
|
+
|
|
31
|
+
constructor(binding: SqliteWorkflowBinding, instance: SqliteWorkflowInstance, db: Database, prepared = false) {
|
|
32
|
+
this.binding = binding
|
|
33
|
+
this.instance = instance
|
|
34
|
+
this.db = db
|
|
35
|
+
this.started = !prepared
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get id(): string {
|
|
39
|
+
return this.instance.id
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Wait until the instance reaches one of the given statuses. */
|
|
43
|
+
async waitForStatus(...statuses: string[]): Promise<{ status: string; output?: unknown; error?: { name: string; message: string } }> {
|
|
44
|
+
const timeout = DEFAULT_TIMEOUT
|
|
45
|
+
const targets = new Set(statuses)
|
|
46
|
+
|
|
47
|
+
// Check current status first
|
|
48
|
+
const current = await this.instance.status()
|
|
49
|
+
if (targets.has(current.status)) return current
|
|
50
|
+
|
|
51
|
+
return new Promise<{ status: string; output?: unknown; error?: { name: string; message: string } }>((resolve, reject) => {
|
|
52
|
+
const timer = setTimeout(() => {
|
|
53
|
+
unsub()
|
|
54
|
+
reject(timeoutError(`waitForStatus(${statuses.join(', ')})`, timeout))
|
|
55
|
+
}, timeout)
|
|
56
|
+
|
|
57
|
+
const unsub = onStatusChange(this.instance.id, (status) => {
|
|
58
|
+
if (targets.has(status)) {
|
|
59
|
+
clearTimeout(timer)
|
|
60
|
+
unsub()
|
|
61
|
+
this.instance.status().then(resolve, reject)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
this.unsubs.push(unsub)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Wait until a specific step completes. Returns its output. */
|
|
69
|
+
async waitForStep(name: string): Promise<unknown> {
|
|
70
|
+
const timeout = DEFAULT_TIMEOUT
|
|
71
|
+
|
|
72
|
+
// Check if step already cached in DB
|
|
73
|
+
const cached = this.db
|
|
74
|
+
.query('SELECT output FROM workflow_steps WHERE instance_id = ? AND step_name = ?')
|
|
75
|
+
.get(this.instance.id, name) as { output: string | null } | null
|
|
76
|
+
if (cached) return JSON.parse(cached.output!)
|
|
77
|
+
|
|
78
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
79
|
+
const timer = setTimeout(() => {
|
|
80
|
+
unsub()
|
|
81
|
+
reject(timeoutError(`waitForStep("${name}")`, timeout))
|
|
82
|
+
}, timeout)
|
|
83
|
+
|
|
84
|
+
const unsub = onStepComplete(this.instance.id, name, (output) => {
|
|
85
|
+
clearTimeout(timer)
|
|
86
|
+
unsub()
|
|
87
|
+
resolve(output)
|
|
88
|
+
})
|
|
89
|
+
this.unsubs.push(unsub)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Wait until the instance is sleeping, then skip the sleep. */
|
|
94
|
+
async skipSleep(): Promise<void> {
|
|
95
|
+
const timeout = DEFAULT_TIMEOUT
|
|
96
|
+
|
|
97
|
+
// Already sleeping — skip immediately
|
|
98
|
+
if (isInstanceSleeping(this.instance.id)) {
|
|
99
|
+
await this.instance.skipSleep()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return new Promise<void>((resolve, reject) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
unsub()
|
|
106
|
+
reject(timeoutError('skipSleep()', timeout))
|
|
107
|
+
}, timeout)
|
|
108
|
+
|
|
109
|
+
const unsub = onSleepRegistered(this.instance.id, () => {
|
|
110
|
+
clearTimeout(timer)
|
|
111
|
+
unsub()
|
|
112
|
+
this.instance.skipSleep().then(resolve, reject)
|
|
113
|
+
})
|
|
114
|
+
this.unsubs.push(unsub)
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Wait until the instance is waiting for an event of the given type. */
|
|
119
|
+
async waitForEvent(type: string): Promise<void> {
|
|
120
|
+
const timeout = DEFAULT_TIMEOUT
|
|
121
|
+
|
|
122
|
+
// Check if already waiting
|
|
123
|
+
if (getWaitingEventTypes(this.instance.id).includes(type)) return
|
|
124
|
+
|
|
125
|
+
return new Promise<void>((resolve, reject) => {
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
unsub()
|
|
128
|
+
reject(timeoutError(`waitForEvent("${type}")`, timeout))
|
|
129
|
+
}, timeout)
|
|
130
|
+
|
|
131
|
+
const unsub = onEventWaitRegistered(this.instance.id, type, () => {
|
|
132
|
+
clearTimeout(timer)
|
|
133
|
+
unsub()
|
|
134
|
+
resolve()
|
|
135
|
+
})
|
|
136
|
+
this.unsubs.push(unsub)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Send an event to the workflow instance. */
|
|
141
|
+
async sendEvent(event: { type: string; payload?: unknown }): Promise<void> {
|
|
142
|
+
await this.instance.sendEvent(event)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Get all completed steps as a Map<name, output>. */
|
|
146
|
+
async steps(): Promise<Map<string, unknown>> {
|
|
147
|
+
const rows = this.db
|
|
148
|
+
.query('SELECT step_name, output FROM workflow_steps WHERE instance_id = ? ORDER BY completed_at ASC')
|
|
149
|
+
.all(this.instance.id) as { step_name: string; output: string | null }[]
|
|
150
|
+
const result = new Map<string, unknown>()
|
|
151
|
+
for (const row of rows) {
|
|
152
|
+
result.set(row.step_name, row.output !== null ? JSON.parse(row.output) : undefined)
|
|
153
|
+
}
|
|
154
|
+
return result
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Get the output of a single step. */
|
|
158
|
+
async stepResult(name: string): Promise<unknown> {
|
|
159
|
+
const row = this.db
|
|
160
|
+
.query('SELECT output FROM workflow_steps WHERE instance_id = ? AND step_name = ?')
|
|
161
|
+
.get(this.instance.id, name) as { output: string | null } | null
|
|
162
|
+
if (!row) throw new Error(`Step "${name}" not found in workflow instance ${this.instance.id}`)
|
|
163
|
+
return row.output !== null ? JSON.parse(row.output) : undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Pause the workflow. */
|
|
167
|
+
async pause(): Promise<void> {
|
|
168
|
+
await this.instance.pause()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Resume the workflow. */
|
|
172
|
+
async resume(): Promise<void> {
|
|
173
|
+
await this.instance.resume()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Terminate the workflow. */
|
|
177
|
+
async terminate(): Promise<void> {
|
|
178
|
+
await this.instance.terminate()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Get the current status. */
|
|
182
|
+
async status(): Promise<{ status: string; output?: unknown; error?: { name: string; message: string } }> {
|
|
183
|
+
return this.instance.status()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Mock a step to return the given result without running the callback. */
|
|
187
|
+
mockStep(name: string, result: unknown): this {
|
|
188
|
+
registerStepMock(this.instance.id, name, { type: 'result', value: result })
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Mock a step to throw the given error. */
|
|
193
|
+
mockStepError(name: string, error: Error, opts?: { times?: number }): this {
|
|
194
|
+
registerStepMock(this.instance.id, name, { type: 'error', value: error, times: opts?.times })
|
|
195
|
+
return this
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Mock a step to time out. */
|
|
199
|
+
mockStepTimeout(name: string): this {
|
|
200
|
+
registerStepMock(this.instance.id, name, { type: 'timeout' })
|
|
201
|
+
return this
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Disable all sleeps for this instance — they resolve immediately. */
|
|
205
|
+
disableSleeps(): this {
|
|
206
|
+
registerSleepDisable(this.instance.id)
|
|
207
|
+
return this
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Pre-deliver an event so waitForEvent() resolves immediately. */
|
|
211
|
+
mockEvent(event: { type: string; payload?: unknown }): this {
|
|
212
|
+
registerEventMock(this.instance.id, event.type, event.payload)
|
|
213
|
+
return this
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Mock an event wait to time out immediately. */
|
|
217
|
+
mockEventTimeout(eventType: string): this {
|
|
218
|
+
registerEventTimeoutMock(this.instance.id, eventType)
|
|
219
|
+
return this
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Start a prepared instance (created via TestWorkflowBinding.prepare()). */
|
|
223
|
+
async start(): Promise<void> {
|
|
224
|
+
if (this.started) throw new Error('Instance already started')
|
|
225
|
+
this.started = true
|
|
226
|
+
this.binding._executeInstance(this.instance.id)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** @internal Add an unsubscribe function to be cleaned up on dispose. */
|
|
230
|
+
_addUnsub(unsub: () => void): void {
|
|
231
|
+
this.unsubs.push(unsub)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Clean up all listeners and mocks. */
|
|
235
|
+
dispose(): void {
|
|
236
|
+
for (const unsub of this.unsubs) unsub()
|
|
237
|
+
this.unsubs = []
|
|
238
|
+
clearInstanceMocks(this.instance.id)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface TestWorkflowRun {
|
|
243
|
+
instance: TestWorkflowInstance
|
|
244
|
+
result: Promise<{ status: string; output?: unknown; error?: { name: string; message: string } }>
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export class TestWorkflowBinding {
|
|
248
|
+
private binding: SqliteWorkflowBinding
|
|
249
|
+
private db: Database
|
|
250
|
+
private instances: TestWorkflowInstance[] = []
|
|
251
|
+
|
|
252
|
+
constructor(binding: SqliteWorkflowBinding, db: Database) {
|
|
253
|
+
this.binding = binding
|
|
254
|
+
this.db = db
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Create a workflow instance with manual step-by-step control. */
|
|
258
|
+
async create(opts?: { id?: string; params?: unknown }): Promise<TestWorkflowInstance> {
|
|
259
|
+
const instance = await this.binding.create(opts)
|
|
260
|
+
const testInstance = new TestWorkflowInstance(this.binding, instance, this.db)
|
|
261
|
+
this.instances.push(testInstance)
|
|
262
|
+
return testInstance
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Create a workflow instance without starting it. Register mocks, then call instance.start(). */
|
|
266
|
+
async prepare(opts?: { id?: string; params?: unknown }): Promise<TestWorkflowInstance> {
|
|
267
|
+
const instance = await this.binding._createPrepared(opts)
|
|
268
|
+
const testInstance = new TestWorkflowInstance(this.binding, instance, this.db, true)
|
|
269
|
+
this.instances.push(testInstance)
|
|
270
|
+
return testInstance
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Get an existing workflow instance by ID. */
|
|
274
|
+
async get(id: string): Promise<TestWorkflowInstance> {
|
|
275
|
+
const instance = await this.binding.get(id)
|
|
276
|
+
const testInstance = new TestWorkflowInstance(this.binding, instance, this.db)
|
|
277
|
+
this.instances.push(testInstance)
|
|
278
|
+
return testInstance
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Run a workflow with auto-sleep-skip. Returns a result promise that resolves on completion. */
|
|
282
|
+
async run(opts?: { id?: string; params?: unknown; mocks?: (instance: TestWorkflowInstance) => void }): Promise<TestWorkflowRun> {
|
|
283
|
+
let rawInstance: SqliteWorkflowInstance
|
|
284
|
+
let testInstance: TestWorkflowInstance
|
|
285
|
+
|
|
286
|
+
if (opts?.mocks) {
|
|
287
|
+
// Use prepare+start to allow mocks to be registered before execution
|
|
288
|
+
rawInstance = await this.binding._createPrepared(opts)
|
|
289
|
+
testInstance = new TestWorkflowInstance(this.binding, rawInstance, this.db, true)
|
|
290
|
+
this.instances.push(testInstance)
|
|
291
|
+
testInstance.disableSleeps()
|
|
292
|
+
opts.mocks(testInstance)
|
|
293
|
+
testInstance.start()
|
|
294
|
+
} else {
|
|
295
|
+
rawInstance = await this.binding.create(opts)
|
|
296
|
+
testInstance = new TestWorkflowInstance(this.binding, rawInstance, this.db)
|
|
297
|
+
this.instances.push(testInstance)
|
|
298
|
+
|
|
299
|
+
// Auto-skip sleeps
|
|
300
|
+
const autoSkip = () => {
|
|
301
|
+
const unsub = onSleepRegistered(rawInstance.id, () => {
|
|
302
|
+
rawInstance.skipSleep().then(() => {
|
|
303
|
+
// Re-register for next sleep
|
|
304
|
+
autoSkip()
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
testInstance._addUnsub(unsub)
|
|
308
|
+
}
|
|
309
|
+
autoSkip()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Result promise
|
|
313
|
+
const result = testInstance.waitForStatus('complete', 'errored', 'terminated')
|
|
314
|
+
|
|
315
|
+
return { instance: testInstance, result }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Clean up all tracked instances. */
|
|
319
|
+
dispose(): void {
|
|
320
|
+
for (const inst of this.instances) inst.dispose()
|
|
321
|
+
this.instances = []
|
|
322
|
+
}
|
|
323
|
+
}
|
package/src/tracing/store.ts
CHANGED
|
@@ -143,6 +143,7 @@ export class TraceStore {
|
|
|
143
143
|
s.status_message,
|
|
144
144
|
s.start_time,
|
|
145
145
|
s.duration_ms,
|
|
146
|
+
json_extract(s.attributes, '$."lopata.generation_id"') as generation_id,
|
|
146
147
|
COUNT(c.span_id) as span_count,
|
|
147
148
|
SUM(CASE WHEN c.status = 'error' THEN 1 ELSE 0 END) as error_count
|
|
148
149
|
FROM spans s
|
|
@@ -164,6 +165,7 @@ export class TraceStore {
|
|
|
164
165
|
durationMs: r.duration_ms as number | null,
|
|
165
166
|
spanCount: r.span_count as number,
|
|
166
167
|
errorCount: r.error_count as number,
|
|
168
|
+
generationId: (r.generation_id as number) ?? null,
|
|
167
169
|
}))
|
|
168
170
|
|
|
169
171
|
const last = items[items.length - 1]
|
|
@@ -219,6 +221,7 @@ export class TraceStore {
|
|
|
219
221
|
s.status_message,
|
|
220
222
|
s.start_time,
|
|
221
223
|
s.duration_ms,
|
|
224
|
+
json_extract(s.attributes, '$."lopata.generation_id"') as generation_id,
|
|
222
225
|
COUNT(c.span_id) as span_count,
|
|
223
226
|
SUM(CASE WHEN c.status = 'error' THEN 1 ELSE 0 END) as error_count
|
|
224
227
|
FROM spans s
|
|
@@ -239,6 +242,7 @@ export class TraceStore {
|
|
|
239
242
|
durationMs: r.duration_ms as number | null,
|
|
240
243
|
spanCount: r.span_count as number,
|
|
241
244
|
errorCount: r.error_count as number,
|
|
245
|
+
generationId: (r.generation_id as number) ?? null,
|
|
242
246
|
}))
|
|
243
247
|
}
|
|
244
248
|
|
|
@@ -253,6 +257,7 @@ export class TraceStore {
|
|
|
253
257
|
s.status_message,
|
|
254
258
|
s.start_time,
|
|
255
259
|
s.duration_ms,
|
|
260
|
+
json_extract(s.attributes, '$."lopata.generation_id"') as generation_id,
|
|
256
261
|
(SELECT COUNT(*) FROM spans WHERE trace_id = s.trace_id) as span_count,
|
|
257
262
|
(SELECT COUNT(*) FROM spans WHERE trace_id = s.trace_id AND status = 'error') as error_count
|
|
258
263
|
FROM spans s
|
|
@@ -274,6 +279,7 @@ export class TraceStore {
|
|
|
274
279
|
durationMs: r.duration_ms as number | null,
|
|
275
280
|
spanCount: r.span_count as number,
|
|
276
281
|
errorCount: r.error_count as number,
|
|
282
|
+
generationId: (r.generation_id as number) ?? null,
|
|
277
283
|
})),
|
|
278
284
|
cursor: null,
|
|
279
285
|
}
|
package/src/tracing/types.ts
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { BrowserBinding } from './bindings/browser'
|
|
2
|
+
import { ContainerBase, getContainer, getRandom } from './bindings/container'
|
|
3
|
+
import { DurableObjectBase, WebSocketRequestResponsePair } from './bindings/durable-object'
|
|
4
|
+
import { EmailMessage } from './bindings/email'
|
|
5
|
+
import type { ImageTransformOptions, OutputOptions } from './bindings/images'
|
|
6
|
+
import { WebSocketPair } from './bindings/websocket-pair'
|
|
7
|
+
import { NonRetryableError, WorkflowEntrypointBase } from './bindings/workflow'
|
|
8
|
+
import { globalEnv } from './env'
|
|
9
|
+
import { getActiveExecutionContext } from './execution-context'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registers virtual modules for `cloudflare:workers`, `cloudflare:workflows`,
|
|
13
|
+
* `cloudflare:email`, `@cloudflare/containers`, and `@cloudflare/puppeteer`.
|
|
14
|
+
*
|
|
15
|
+
* Shared between `src/plugin.ts` (dev server) and `src/testing/setup.ts` (test preload).
|
|
16
|
+
*/
|
|
17
|
+
export function registerVirtualModules(build: { module: (name: string, fn: () => any) => void }) {
|
|
18
|
+
build.module('cloudflare:workers', () => {
|
|
19
|
+
return {
|
|
20
|
+
exports: {
|
|
21
|
+
DurableObject: DurableObjectBase,
|
|
22
|
+
WorkflowEntrypoint: WorkflowEntrypointBase,
|
|
23
|
+
WorkerEntrypoint: class WorkerEntrypoint {
|
|
24
|
+
protected ctx: unknown
|
|
25
|
+
protected env: unknown
|
|
26
|
+
constructor(ctx: unknown, env: unknown) {
|
|
27
|
+
this.ctx = ctx
|
|
28
|
+
this.env = env
|
|
29
|
+
;(this as any)[Symbol.for('lopata.RpcTarget')] = true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
WebSocketRequestResponsePair,
|
|
33
|
+
WebSocketPair,
|
|
34
|
+
RpcTarget: class RpcTarget {
|
|
35
|
+
constructor() {
|
|
36
|
+
;(this as any)[Symbol.for('lopata.RpcTarget')] = true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
env: globalEnv,
|
|
40
|
+
waitUntil(promise: Promise<unknown>): void {
|
|
41
|
+
const ctx = getActiveExecutionContext()
|
|
42
|
+
if (ctx) {
|
|
43
|
+
ctx.waitUntil(promise)
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
loader: 'object',
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
build.module('@cloudflare/containers', () => {
|
|
52
|
+
return {
|
|
53
|
+
exports: {
|
|
54
|
+
Container: ContainerBase,
|
|
55
|
+
getContainer,
|
|
56
|
+
getRandom,
|
|
57
|
+
switchPort(request: Request, port: number): Request {
|
|
58
|
+
const headers = new Headers(request.headers)
|
|
59
|
+
headers.set('cf-container-target-port', port.toString())
|
|
60
|
+
return new Request(request, { headers })
|
|
61
|
+
},
|
|
62
|
+
loadBalance: getRandom,
|
|
63
|
+
},
|
|
64
|
+
loader: 'object',
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
build.module('cloudflare:email', () => {
|
|
69
|
+
return {
|
|
70
|
+
exports: {
|
|
71
|
+
EmailMessage,
|
|
72
|
+
},
|
|
73
|
+
loader: 'object',
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
build.module('cloudflare:workflows', () => {
|
|
78
|
+
return {
|
|
79
|
+
exports: {
|
|
80
|
+
NonRetryableError,
|
|
81
|
+
},
|
|
82
|
+
loader: 'object',
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
build.module('@cloudflare/puppeteer', () => {
|
|
87
|
+
return {
|
|
88
|
+
exports: {
|
|
89
|
+
default: {
|
|
90
|
+
launch: (endpoint: BrowserBinding, opts?: { keep_alive?: number }) => endpoint.launch(opts),
|
|
91
|
+
connect: (endpoint: BrowserBinding, sessionId: string) => endpoint.connect(sessionId),
|
|
92
|
+
sessions: (endpoint: BrowserBinding) => endpoint.sessions(),
|
|
93
|
+
},
|
|
94
|
+
ActiveSession: {} as any, // type-only re-export placeholder
|
|
95
|
+
},
|
|
96
|
+
loader: 'object',
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|