alchemy 2.0.0-beta.35 → 2.0.0-beta.37
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/bin/exec.js +176 -26
- package/bin/exec.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +22 -2
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +6 -1
- package/lib/Apply.js.map +1 -1
- package/lib/Cli/main.d.ts.map +1 -1
- package/lib/Cli/main.js +2 -0
- package/lib/Cli/main.js.map +1 -1
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts +54 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js +17 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts +37 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js +45 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/index.d.ts +3 -0
- package/lib/Cloudflare/AnalyticsEngine/index.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/index.js +3 -0
- package/lib/Cloudflare/AnalyticsEngine/index.js.map +1 -0
- package/lib/Cloudflare/D1/D1Connection.d.ts +47 -3
- package/lib/Cloudflare/D1/D1Connection.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1Connection.js +62 -5
- package/lib/Cloudflare/D1/D1Connection.js.map +1 -1
- package/lib/Cloudflare/Images/Images.d.ts +103 -0
- package/lib/Cloudflare/Images/Images.d.ts.map +1 -0
- package/lib/Cloudflare/Images/Images.js +82 -0
- package/lib/Cloudflare/Images/Images.js.map +1 -0
- package/lib/Cloudflare/Images/ImagesBinding.d.ts +73 -0
- package/lib/Cloudflare/Images/ImagesBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Images/ImagesBinding.js +83 -0
- package/lib/Cloudflare/Images/ImagesBinding.js.map +1 -0
- package/lib/Cloudflare/Images/index.d.ts +3 -0
- package/lib/Cloudflare/Images/index.d.ts.map +1 -0
- package/lib/Cloudflare/Images/index.js +3 -0
- package/lib/Cloudflare/Images/index.js.map +1 -0
- package/lib/Cloudflare/Local/SidecarHandlers.js +9 -1
- package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +6 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.d.ts +135 -0
- package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.js +146 -7
- package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +6 -1
- package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Api.js +23 -1
- package/lib/Cloudflare/StateStore/Api.js.map +1 -1
- package/lib/Cloudflare/StateStore/Store.d.ts +16 -1
- package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Store.js +22 -5
- package/lib/Cloudflare/StateStore/Store.js.map +1 -1
- package/lib/Cloudflare/Workers/CronEventSource.d.ts +35 -0
- package/lib/Cloudflare/Workers/CronEventSource.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/CronEventSource.js +55 -0
- package/lib/Cloudflare/Workers/CronEventSource.js.map +1 -0
- package/lib/Cloudflare/Workers/InferEnv.d.ts +37 -2
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.d.ts +76 -3
- package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.js +197 -13
- package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +26 -6
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +183 -41
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +15 -5
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.d.ts +50 -47
- package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.js +41 -35
- package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
- package/lib/Cloudflare/Workers/index.d.ts +1 -0
- package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/index.js +1 -0
- package/lib/Cloudflare/Workers/index.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +2 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +2 -0
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Deploy.d.ts +4 -1
- package/lib/Deploy.d.ts.map +1 -1
- package/lib/Deploy.js +2 -2
- package/lib/Deploy.js.map +1 -1
- package/lib/Destroy.d.ts +4 -1
- package/lib/Destroy.d.ts.map +1 -1
- package/lib/Destroy.js +2 -2
- package/lib/Destroy.js.map +1 -1
- package/lib/Output.d.ts +45 -5
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js +100 -4
- package/lib/Output.js.map +1 -1
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +93 -12
- package/lib/Plan.js.map +1 -1
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +16 -3
- package/lib/Platform.js.map +1 -1
- package/lib/Ref.d.ts +4 -1
- package/lib/Ref.d.ts.map +1 -1
- package/lib/Ref.js +1 -1
- package/lib/Ref.js.map +1 -1
- package/lib/Resource.d.ts +8 -0
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js +10 -0
- package/lib/Resource.js.map +1 -1
- package/lib/RuntimeContext.d.ts +3 -1
- package/lib/RuntimeContext.d.ts.map +1 -1
- package/lib/RuntimeContext.js.map +1 -1
- package/lib/Secret.d.ts +50 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +23 -0
- package/lib/Secret.js.map +1 -0
- package/lib/Serverless/Function.d.ts +9 -1
- package/lib/Serverless/Function.d.ts.map +1 -1
- package/lib/Stack.d.ts +14 -5
- package/lib/Stack.d.ts.map +1 -1
- package/lib/Stack.js +31 -19
- package/lib/Stack.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +15 -1
- package/lib/State/HttpStateApi.d.ts.map +1 -1
- package/lib/State/HttpStateApi.js +11 -0
- package/lib/State/HttpStateApi.js.map +1 -1
- package/lib/State/HttpStateStore.d.ts.map +1 -1
- package/lib/State/HttpStateStore.js +11 -0
- package/lib/State/HttpStateStore.js.map +1 -1
- package/lib/State/InMemoryState.d.ts +2 -2
- package/lib/State/InMemoryState.d.ts.map +1 -1
- package/lib/State/InMemoryState.js +12 -6
- package/lib/State/InMemoryState.js.map +1 -1
- package/lib/State/LocalState.d.ts.map +1 -1
- package/lib/State/LocalState.js +11 -2
- package/lib/State/LocalState.js.map +1 -1
- package/lib/State/State.d.ts +21 -0
- package/lib/State/State.d.ts.map +1 -1
- package/lib/Test/Bun.d.ts.map +1 -1
- package/lib/Test/Bun.js +30 -7
- package/lib/Test/Bun.js.map +1 -1
- package/lib/Test/Core.d.ts +17 -2
- package/lib/Test/Core.d.ts.map +1 -1
- package/lib/Test/Core.js +29 -11
- package/lib/Test/Core.js.map +1 -1
- package/lib/Test/Vitest.d.ts.map +1 -1
- package/lib/Test/Vitest.js +26 -5
- package/lib/Test/Vitest.js.map +1 -1
- package/lib/Util/LocalhostDns.d.ts +23 -0
- package/lib/Util/LocalhostDns.d.ts.map +1 -0
- package/lib/Util/LocalhostDns.js +46 -0
- package/lib/Util/LocalhostDns.js.map +1 -0
- package/lib/Util/PlatformServices.d.ts.map +1 -1
- package/lib/Util/PlatformServices.js +2 -0
- package/lib/Util/PlatformServices.js.map +1 -1
- package/lib/Util/effect.d.ts +7 -0
- package/lib/Util/effect.d.ts.map +1 -1
- package/lib/Util/effect.js +6 -0
- package/lib/Util/effect.js.map +1 -1
- package/lib/Variable.d.ts +29 -0
- package/lib/Variable.d.ts.map +1 -0
- package/lib/Variable.js +9 -0
- package/lib/Variable.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/AWS/Lambda/Function.ts +30 -2
- package/src/Apply.ts +8 -1
- package/src/Cli/main.ts +3 -0
- package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.ts +82 -0
- package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.ts +106 -0
- package/src/Cloudflare/AnalyticsEngine/index.ts +2 -0
- package/src/Cloudflare/D1/D1Connection.ts +95 -12
- package/src/Cloudflare/Images/Images.ts +121 -0
- package/src/Cloudflare/Images/ImagesBinding.ts +192 -0
- package/src/Cloudflare/Images/index.ts +2 -0
- package/src/Cloudflare/Local/SidecarHandlers.ts +9 -1
- package/src/Cloudflare/Providers.ts +8 -0
- package/src/Cloudflare/R2/R2Bucket.ts +271 -7
- package/src/Cloudflare/StateStore/Api.ts +34 -1
- package/src/Cloudflare/StateStore/Store.ts +40 -10
- package/src/Cloudflare/Workers/CronEventSource.ts +89 -0
- package/src/Cloudflare/Workers/InferEnv.ts +94 -16
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
- package/src/Cloudflare/Workers/Rpc.ts +227 -18
- package/src/Cloudflare/Workers/Worker.ts +295 -62
- package/src/Cloudflare/Workers/WorkerBundle.ts +15 -5
- package/src/Cloudflare/Workers/Workflow.ts +74 -70
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Cloudflare/index.ts +2 -0
- package/src/Deploy.ts +5 -1
- package/src/Destroy.ts +5 -1
- package/src/Output.ts +121 -12
- package/src/Plan.ts +101 -12
- package/src/Platform.ts +19 -4
- package/src/Ref.ts +10 -5
- package/src/Resource.ts +22 -0
- package/src/RuntimeContext.ts +1 -0
- package/src/Secret.ts +87 -0
- package/src/Serverless/Function.ts +10 -1
- package/src/Stack.ts +52 -29
- package/src/State/HttpStateApi.ts +21 -0
- package/src/State/HttpStateStore.ts +21 -0
- package/src/State/InMemoryState.ts +29 -8
- package/src/State/LocalState.ts +28 -4
- package/src/State/State.ts +21 -0
- package/src/Test/Bun.ts +43 -7
- package/src/Test/Core.ts +29 -8
- package/src/Test/Vitest.ts +36 -4
- package/src/Util/LocalhostDns.ts +52 -0
- package/src/Util/PlatformServices.ts +2 -0
- package/src/Util/effect.ts +25 -0
- package/src/Variable.ts +44 -0
- package/src/index.ts +3 -0
|
@@ -29,7 +29,7 @@ export const STATE_STORE_SCRIPT_NAME = "alchemy-state-store" as const;
|
|
|
29
29
|
* compare against this constant; a mismatch (or 404) triggers a
|
|
30
30
|
* forced redeploy via the bootstrap flow.
|
|
31
31
|
*/
|
|
32
|
-
export const STATE_STORE_VERSION =
|
|
32
|
+
export const STATE_STORE_VERSION = 4 as const;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Hard-coded OTLP/HTTP endpoints. Point at the public ingest relay
|
|
@@ -245,6 +245,39 @@ export default Worker(
|
|
|
245
245
|
}),
|
|
246
246
|
),
|
|
247
247
|
)
|
|
248
|
+
.handle("getStackOutput", ({ params }) =>
|
|
249
|
+
store
|
|
250
|
+
.getByName(params.stack)
|
|
251
|
+
.getOutput({ stage: params.stage })
|
|
252
|
+
.pipe(
|
|
253
|
+
Effect.withSpan("state_store.getStackOutput", {
|
|
254
|
+
attributes: {
|
|
255
|
+
"alchemy.state_store.op": "getStackOutput",
|
|
256
|
+
"alchemy.state_store.stack": params.stack,
|
|
257
|
+
"alchemy.state_store.stage": params.stage,
|
|
258
|
+
},
|
|
259
|
+
}),
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
.handle("setStackOutput", ({ params, payload }) =>
|
|
263
|
+
store
|
|
264
|
+
.getByName(params.stack)
|
|
265
|
+
.setOutput({ stage: params.stage, value: payload as any })
|
|
266
|
+
.pipe(
|
|
267
|
+
Effect.tap(() =>
|
|
268
|
+
store
|
|
269
|
+
.getByName(Store.ROOT_DO_NAME)
|
|
270
|
+
.registerStack({ stack: params.stack }),
|
|
271
|
+
),
|
|
272
|
+
Effect.withSpan("state_store.setStackOutput", {
|
|
273
|
+
attributes: {
|
|
274
|
+
"alchemy.state_store.op": "setStackOutput",
|
|
275
|
+
"alchemy.state_store.stack": params.stack,
|
|
276
|
+
"alchemy.state_store.stage": params.stage,
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
),
|
|
280
|
+
)
|
|
248
281
|
.handle("deleteStack", ({ params, query }) =>
|
|
249
282
|
store
|
|
250
283
|
.getByName(params.stack)
|
|
@@ -36,7 +36,7 @@ export default class Store extends DurableObjectNamespace<Store>()(
|
|
|
36
36
|
),
|
|
37
37
|
).pipe(Effect.orDie);
|
|
38
38
|
|
|
39
|
-
const encryptValue = (value:
|
|
39
|
+
const encryptValue = (value: unknown) =>
|
|
40
40
|
Effect.tryPromise(async () => {
|
|
41
41
|
const plaintext = new TextEncoder().encode(
|
|
42
42
|
JSON.stringify(encodeState(value)),
|
|
@@ -188,15 +188,39 @@ export default class Store extends DurableObjectNamespace<Store>()(
|
|
|
188
188
|
deleteStack: ({ stage }: { stage?: string } = {}) =>
|
|
189
189
|
stage === undefined
|
|
190
190
|
? storage.deleteAll()
|
|
191
|
-
: storage
|
|
192
|
-
.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
: storage.list<string>({ prefix: stagePrefix(stage) }).pipe(
|
|
192
|
+
Effect.flatMap((entries) => {
|
|
193
|
+
const keys = [...entries.keys(), stackOutputKey(stage)];
|
|
194
|
+
return storage.delete(keys).pipe(Effect.asVoid);
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* (Stack DO only) Read the persisted stack output for `stage`.
|
|
200
|
+
* Returns `undefined` when the stage has not been deployed.
|
|
201
|
+
*/
|
|
202
|
+
getOutput: ({ stage }: { stage: string }) =>
|
|
203
|
+
storage
|
|
204
|
+
.get<string>(stackOutputKey(stage))
|
|
205
|
+
.pipe(
|
|
206
|
+
Effect.flatMap((entry) =>
|
|
207
|
+
entry == null ? Effect.succeed(undefined) : decryptEntry(entry),
|
|
208
|
+
),
|
|
209
|
+
),
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* (Stack DO only) Persist the resolved stack output for
|
|
213
|
+
* `stage`. Returns the stored value unchanged.
|
|
214
|
+
*/
|
|
215
|
+
setOutput: ({ stage, value }: { stage: string; value: any }) =>
|
|
216
|
+
encryptValue(value).pipe(
|
|
217
|
+
Effect.flatMap((encrypted) =>
|
|
218
|
+
storage
|
|
219
|
+
.put<string>(stackOutputKey(stage), encrypted)
|
|
220
|
+
.pipe(Effect.asVoid),
|
|
221
|
+
),
|
|
222
|
+
Effect.map(() => value),
|
|
223
|
+
),
|
|
200
224
|
|
|
201
225
|
/**
|
|
202
226
|
* (Stack DO only) Return every resource in a stage whose
|
|
@@ -236,6 +260,9 @@ const SEP = "\x00";
|
|
|
236
260
|
/** Key prefix for resource entries in a stack DO. */
|
|
237
261
|
const RESOURCE_PREFIX = `r${SEP}`;
|
|
238
262
|
|
|
263
|
+
/** Key prefix for stack-output entries in a stack DO. */
|
|
264
|
+
const STACK_OUTPUT_PREFIX = `o${SEP}`;
|
|
265
|
+
|
|
239
266
|
/** Key prefix for stack-index entries in the root DO. */
|
|
240
267
|
const STACK_INDEX_PREFIX = "s:";
|
|
241
268
|
|
|
@@ -249,6 +276,9 @@ const resourceKey = (stage: string, fqn: string) =>
|
|
|
249
276
|
/** Prefix matching every resource key inside a specific stage. */
|
|
250
277
|
const stagePrefix = (stage: string) => `${RESOURCE_PREFIX}${stage}${SEP}`;
|
|
251
278
|
|
|
279
|
+
/** Build the stack-output key inside a *stack DO*. */
|
|
280
|
+
const stackOutputKey = (stage: string) => `${STACK_OUTPUT_PREFIX}${stage}`;
|
|
281
|
+
|
|
252
282
|
/**
|
|
253
283
|
* Parse a resource key back into its (stage, fqn) tuple. Returns
|
|
254
284
|
* undefined for keys that do not match the expected shape.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type * as cf from "@cloudflare/workers-types";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as Binding from "../../Binding.ts";
|
|
6
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
7
|
+
import { RuntimeContext } from "../../RuntimeContext.ts";
|
|
8
|
+
import type { FunctionContext } from "../../Serverless/Function.ts";
|
|
9
|
+
import { isWorker, isWorkerEvent } from "./Worker.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to Cloudflare Cron Triggers with an Effect handler.
|
|
13
|
+
*
|
|
14
|
+
* This wires both pieces of a scheduled Worker:
|
|
15
|
+
*
|
|
16
|
+
* - **Runtime**: registers a `scheduled` listener on the Worker.
|
|
17
|
+
* - **Deploy-time**: attaches the cron expression to the host Worker.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* yield* Cloudflare.cron("0 12 * * *").subscribe((controller) =>
|
|
22
|
+
* Effect.log(`scheduled at ${controller.scheduledTime}`),
|
|
23
|
+
* );
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const cron = (expression: string) => ({
|
|
27
|
+
subscribe: <Req = never>(
|
|
28
|
+
process: (
|
|
29
|
+
controller: cf.ScheduledController,
|
|
30
|
+
) => Effect.Effect<void, unknown, Req>,
|
|
31
|
+
) => CronEventSource.use((source) => source(expression, process)),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type CronEventSourceService = <Req = never>(
|
|
35
|
+
expression: string,
|
|
36
|
+
process: (
|
|
37
|
+
controller: cf.ScheduledController,
|
|
38
|
+
) => Effect.Effect<void, unknown, Req>,
|
|
39
|
+
) => Effect.Effect<void, never, never>;
|
|
40
|
+
|
|
41
|
+
export class CronEventSource extends Context.Service<
|
|
42
|
+
CronEventSource,
|
|
43
|
+
CronEventSourceService
|
|
44
|
+
>()("Cloudflare.Workers.CronEventSource") {}
|
|
45
|
+
|
|
46
|
+
export class CronEventSourcePolicy extends Binding.Policy<
|
|
47
|
+
CronEventSourcePolicy,
|
|
48
|
+
(expression: string) => Effect.Effect<void>
|
|
49
|
+
>()("Cloudflare.Workers.CronEventSource") {}
|
|
50
|
+
|
|
51
|
+
export const CronEventSourcePolicyLive = CronEventSourcePolicy.layer.succeed(
|
|
52
|
+
Effect.fnUntraced(function* (host: ResourceLike, expression: string) {
|
|
53
|
+
if (isWorker(host)) {
|
|
54
|
+
yield* host.bind(`Cron(${expression})`, {
|
|
55
|
+
crons: [expression],
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
return yield* Effect.die(
|
|
59
|
+
`Cloudflare.cron(...).subscribe(...) is only supported on ` +
|
|
60
|
+
`Cloudflare.Worker hosts (got '${host.Type}').`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export const CronEventSourceLive = Layer.effect(
|
|
67
|
+
CronEventSource,
|
|
68
|
+
Effect.gen(function* () {
|
|
69
|
+
const policy = yield* CronEventSourcePolicy;
|
|
70
|
+
return Effect.fn(function* <Req>(
|
|
71
|
+
expression: string,
|
|
72
|
+
process: (
|
|
73
|
+
controller: cf.ScheduledController,
|
|
74
|
+
) => Effect.Effect<void, unknown, Req>,
|
|
75
|
+
) {
|
|
76
|
+
yield* policy(expression);
|
|
77
|
+
|
|
78
|
+
const ctx = (yield* RuntimeContext) as unknown as FunctionContext;
|
|
79
|
+
yield* ctx.listen<void, Req>((event) => {
|
|
80
|
+
if (!isWorkerEvent(event) || event.type !== "scheduled") return;
|
|
81
|
+
|
|
82
|
+
const controller = event.input as cf.ScheduledController;
|
|
83
|
+
if (controller.cron !== expression) return;
|
|
84
|
+
|
|
85
|
+
return process(controller).pipe(Effect.catchCause(() => Effect.void));
|
|
86
|
+
});
|
|
87
|
+
}) as CronEventSourceService;
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
|
|
3
3
|
import type * as Effect from "effect/Effect";
|
|
4
|
+
import type * as Stream from "effect/Stream";
|
|
5
|
+
import type { Rpc } from "../../Platform.ts";
|
|
4
6
|
import type { UnwrapEffect } from "../../Util/effect.ts";
|
|
5
7
|
import type * as Cloudflare from "../index.ts";
|
|
8
|
+
import type { RpcErrorEnvelope, RpcStreamEnvelope } from "./Rpc.ts";
|
|
6
9
|
import type { Worker } from "./Worker.ts";
|
|
7
10
|
|
|
8
11
|
export type InferEnv<W> = W extends
|
|
@@ -13,20 +16,95 @@ export type InferEnv<W> = W extends
|
|
|
13
16
|
}
|
|
14
17
|
: never;
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Cloudflare service-binding wire shape for an Effect-native Worker.
|
|
21
|
+
*
|
|
22
|
+
* Effect/Stream return values are encoded as envelopes on the wire (see
|
|
23
|
+
* `RpcErrorEnvelope`, `RpcStreamEnvelope`), so the mapped types reflect what
|
|
24
|
+
* the raw binding actually resolves to. `fetch` is dropped from the user
|
|
25
|
+
* shape and re-introduced via `Service` so callers get the standard
|
|
26
|
+
* `(input, init?) => Promise<Response>` signature.
|
|
27
|
+
*
|
|
28
|
+
* Use {@link toPromiseApi} to wrap a binding into a Promise<T>-flavored view
|
|
29
|
+
* where envelopes are decoded for you.
|
|
30
|
+
*/
|
|
31
|
+
export type RpcWireShape<Shape> = {
|
|
32
|
+
[K in keyof Shape as K extends "fetch" ? never : K]: Shape[K] extends (
|
|
33
|
+
...args: infer A
|
|
34
|
+
) => Effect.Effect<infer T, any, any>
|
|
35
|
+
? (...args: A) => Promise<T | RpcErrorEnvelope>
|
|
36
|
+
: Shape[K] extends (...args: infer A) => Stream.Stream<any, any, any>
|
|
37
|
+
? (...args: A) => Promise<RpcStreamEnvelope | RpcErrorEnvelope>
|
|
38
|
+
: Shape[K] extends Effect.Effect<infer T, any, any>
|
|
39
|
+
? Promise<T | RpcErrorEnvelope>
|
|
40
|
+
: Shape[K] extends Stream.Stream<any, any, any>
|
|
41
|
+
? Promise<RpcStreamEnvelope | RpcErrorEnvelope>
|
|
42
|
+
: Shape[K] extends (...args: infer A) => infer R
|
|
43
|
+
? (...args: A) => Promise<Awaited<R>>
|
|
44
|
+
: Promise<Shape[K]>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Recover the user's RPC `Shape` from any of the forms a caller might pass
|
|
49
|
+
* to {@link toPromiseApi}:
|
|
50
|
+
*
|
|
51
|
+
* - the Worker class value's type, e.g. `typeof Backend`, which extends
|
|
52
|
+
* `Effect.Effect<Worker & Rpc<Shape>, …>`
|
|
53
|
+
* - the unwrapped `Worker & Rpc<Shape>` type
|
|
54
|
+
* - a bare `Shape` (when the caller types it explicitly)
|
|
55
|
+
*/
|
|
56
|
+
export type ExtractRpcShape<W> =
|
|
57
|
+
W extends Effect.Effect<infer R, any, any>
|
|
58
|
+
? R extends Rpc<infer Shape>
|
|
59
|
+
? Shape
|
|
60
|
+
: R
|
|
61
|
+
: W extends Rpc<infer Shape>
|
|
62
|
+
? Shape
|
|
63
|
+
: W;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Promise-flavored consumer view returned by {@link toPromiseApi}: error
|
|
67
|
+
* envelopes are thrown as exceptions and stream envelopes are unwrapped to
|
|
68
|
+
* their raw `ReadableStream<Uint8Array>` body.
|
|
69
|
+
*/
|
|
70
|
+
export type RpcPromiseShape<Shape> = {
|
|
71
|
+
[K in keyof Shape as K extends "fetch" ? never : K]: Shape[K] extends (
|
|
72
|
+
...args: infer A
|
|
73
|
+
) => Effect.Effect<infer T, any, any>
|
|
74
|
+
? (...args: A) => Promise<T>
|
|
75
|
+
: Shape[K] extends (...args: infer A) => Stream.Stream<any, any, any>
|
|
76
|
+
? (...args: A) => Promise<ReadableStream<Uint8Array>>
|
|
77
|
+
: Shape[K] extends Effect.Effect<infer T, any, any>
|
|
78
|
+
? Promise<T>
|
|
79
|
+
: Shape[K] extends Stream.Stream<any, any, any>
|
|
80
|
+
? Promise<ReadableStream<Uint8Array>>
|
|
81
|
+
: Shape[K] extends (...args: infer A) => infer R
|
|
82
|
+
? (...args: A) => Promise<Awaited<R>>
|
|
83
|
+
: Promise<Shape[K]>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type GetBindingType<T> = T extends Cloudflare.Assets
|
|
17
87
|
? Service
|
|
18
|
-
: T extends
|
|
19
|
-
?
|
|
20
|
-
: T extends Cloudflare.
|
|
21
|
-
?
|
|
22
|
-
: T extends Cloudflare.
|
|
23
|
-
?
|
|
24
|
-
: T extends Cloudflare.
|
|
25
|
-
?
|
|
26
|
-
: T extends Cloudflare.
|
|
27
|
-
?
|
|
28
|
-
: T extends Cloudflare.
|
|
29
|
-
?
|
|
30
|
-
: T extends Cloudflare.
|
|
31
|
-
?
|
|
32
|
-
:
|
|
88
|
+
: T extends Rpc<infer Shape extends object>
|
|
89
|
+
? RpcWireShape<Shape> & Service
|
|
90
|
+
: T extends Cloudflare.D1Database
|
|
91
|
+
? D1Database
|
|
92
|
+
: T extends Cloudflare.R2Bucket
|
|
93
|
+
? R2Bucket
|
|
94
|
+
: T extends Cloudflare.KVNamespace
|
|
95
|
+
? KVNamespace
|
|
96
|
+
: T extends Cloudflare.Queue
|
|
97
|
+
? Queue<unknown>
|
|
98
|
+
: T extends Cloudflare.AiGateway
|
|
99
|
+
? Ai
|
|
100
|
+
: T extends Cloudflare.AnalyticsEngineDataset
|
|
101
|
+
? AnalyticsEngineDataset
|
|
102
|
+
: T extends Cloudflare.Artifacts
|
|
103
|
+
? Artifacts
|
|
104
|
+
: T extends Cloudflare.Images
|
|
105
|
+
? ImagesBinding
|
|
106
|
+
: T extends Cloudflare.Hyperdrive
|
|
107
|
+
? Hyperdrive
|
|
108
|
+
: T extends Cloudflare.DurableObjectNamespaceLike
|
|
109
|
+
? DurableObjectNamespace<Exclude<T["Shape"], undefined>>
|
|
110
|
+
: never;
|
|
@@ -7,7 +7,12 @@ import { Stack } from "../../Stack.ts";
|
|
|
7
7
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
8
8
|
import { Sidecar } from "../Local/Sidecar.ts";
|
|
9
9
|
import { getCompatibility } from "./Compatibility.ts";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getCronBindings,
|
|
12
|
+
Worker,
|
|
13
|
+
type WorkerBinding,
|
|
14
|
+
type WorkerProps,
|
|
15
|
+
} from "./Worker.ts";
|
|
11
16
|
import { createWorkerName } from "./WorkerName.ts";
|
|
12
17
|
|
|
13
18
|
export const LocalWorkerProvider = () =>
|
|
@@ -105,6 +110,9 @@ export const LocalWorkerProvider = () =>
|
|
|
105
110
|
tags: [],
|
|
106
111
|
durableObjectNamespaces,
|
|
107
112
|
domains: [],
|
|
113
|
+
crons: Array.from(
|
|
114
|
+
new Set([...getCronBindings(bindings), ...(props.crons ?? [])]),
|
|
115
|
+
),
|
|
108
116
|
accountId,
|
|
109
117
|
} satisfies Worker["Attributes"];
|
|
110
118
|
});
|
|
@@ -8,8 +8,10 @@ import * as Sink from "effect/Sink";
|
|
|
8
8
|
import * as Stream from "effect/Stream";
|
|
9
9
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
10
10
|
import type { HttpEffect } from "../../Http.ts";
|
|
11
|
+
import { isYieldableEffect } from "../../Util/effect.ts";
|
|
11
12
|
import { fromCloudflareFetcher } from "../Fetcher.ts";
|
|
12
13
|
import { serveWebRequest } from "./HttpServer.ts";
|
|
14
|
+
import type { ExtractRpcShape, RpcPromiseShape } from "./InferEnv.ts";
|
|
13
15
|
import { fromWebSocket } from "./WebSocket.ts";
|
|
14
16
|
|
|
15
17
|
export const StreamTag = "~alchemy/rpc/stream";
|
|
@@ -188,22 +190,122 @@ export const decodeRpcResult = (
|
|
|
188
190
|
return Effect.succeed(decodeRpcValue(value));
|
|
189
191
|
};
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Wrap a Cloudflare service-binding stub (or an `Effect` that resolves
|
|
195
|
+
* to one — useful when the stub depends on a service like
|
|
196
|
+
* `WorkerEnvironment` that's only available at *exec* phase) into an
|
|
197
|
+
* Effect-typed RPC client.
|
|
198
|
+
*
|
|
199
|
+
* `Service.fetch`/`Service.connect` are passed through eagerly when the
|
|
200
|
+
* stub is already resolved; everything else is treated as an RPC method
|
|
201
|
+
* whose dispatch is deferred until call time, so the user effect runs in
|
|
202
|
+
* the right runtime layer (which is what `bindWorker` actually wants —
|
|
203
|
+
* its methods are called at exec, even though it's *defined* at init).
|
|
204
|
+
*/
|
|
205
|
+
export const makeRpcStub = <Shape>(
|
|
206
|
+
stubSource: unknown | Effect.Effect<unknown, never, never>,
|
|
207
|
+
): Shape => {
|
|
208
|
+
const isLazy = isYieldableEffect(stubSource);
|
|
209
|
+
const eagerFetcher = isLazy
|
|
210
|
+
? undefined
|
|
211
|
+
: fromCloudflareFetcher(stubSource as cf.Fetcher);
|
|
212
|
+
const proxyTarget: object = eagerFetcher ?? {};
|
|
213
|
+
|
|
214
|
+
return new Proxy(proxyTarget, {
|
|
215
|
+
get: (target: any, prop) => {
|
|
216
|
+
if (!isLazy && prop in target) return target[prop];
|
|
217
|
+
if (typeof prop !== "string" && typeof prop !== "symbol") {
|
|
218
|
+
return target[prop];
|
|
219
|
+
}
|
|
220
|
+
return (...args: any[]) =>
|
|
221
|
+
Effect.gen(function* () {
|
|
222
|
+
const stub = isLazy
|
|
223
|
+
? yield* stubSource as Effect.Effect<any>
|
|
224
|
+
: stubSource;
|
|
225
|
+
return yield* Effect.tryPromise({
|
|
226
|
+
try: () => (stub as any)[prop](...args),
|
|
227
|
+
catch: (cause) => new RpcCallError({ method: String(prop), cause }),
|
|
228
|
+
}).pipe(Effect.flatMap(decodeRpcResult));
|
|
229
|
+
});
|
|
230
|
+
},
|
|
204
231
|
}) as Shape;
|
|
205
232
|
};
|
|
206
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Wrap a raw Cloudflare service binding (the wire-typed view emitted by
|
|
236
|
+
* `InferEnv`) into a Promise-flavored API for non-Effect consumers.
|
|
237
|
+
*
|
|
238
|
+
* Effect-native worker methods serialize their results as envelopes:
|
|
239
|
+
* `Effect.fail` becomes `RpcErrorEnvelope`, `Stream` becomes
|
|
240
|
+
* `RpcStreamEnvelope`, and `Effect.succeed`/raw values pass through. This
|
|
241
|
+
* proxy auto-decodes those envelopes — error envelopes are thrown so the
|
|
242
|
+
* caller's `await` rejects, and stream envelopes are unwrapped to their raw
|
|
243
|
+
* `ReadableStream<Uint8Array>` body.
|
|
244
|
+
*
|
|
245
|
+
* `Service.fetch` and `Service.connect` pass through untouched, so the
|
|
246
|
+
* returned proxy is still a usable `Service` binding.
|
|
247
|
+
*
|
|
248
|
+
* For Effect-to-Effect cross-worker calls, prefer `bindWorker(Worker)` —
|
|
249
|
+
* it preserves typed errors and decoded streams.
|
|
250
|
+
*
|
|
251
|
+
* The generic parameter is the Worker *class* (or its Effect form), the
|
|
252
|
+
* same value you would pass to `bindWorker`. Shape is recovered from the
|
|
253
|
+
* embedded `Rpc<Shape>`.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* import * as Cloudflare from "alchemy/Cloudflare";
|
|
258
|
+
* import Backend from "./backend.ts";
|
|
259
|
+
*
|
|
260
|
+
* const backend = Cloudflare.toPromiseApi<typeof Backend>(env.BACKEND);
|
|
261
|
+
* const greeting = await backend.hello(key); // throws on Effect.fail
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
export const toPromiseApi = <W>(
|
|
265
|
+
stub: any,
|
|
266
|
+
): RpcPromiseShape<ExtractRpcShape<W>> & Service =>
|
|
267
|
+
new Proxy(stub, {
|
|
268
|
+
get: (target, prop) => {
|
|
269
|
+
// `Service` methods (fetch/connect) and any non-string keys (Symbol.dispose, etc.)
|
|
270
|
+
// pass through to the underlying Cloudflare binding unchanged.
|
|
271
|
+
if (typeof prop !== "string" || prop === "fetch" || prop === "connect") {
|
|
272
|
+
const value = (target as any)[prop];
|
|
273
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return async (...args: unknown[]) => {
|
|
277
|
+
const result = await (target as any)[prop](...args);
|
|
278
|
+
if (isRpcErrorEnvelope(result)) {
|
|
279
|
+
throw decodeRpcThrowable(result.error);
|
|
280
|
+
}
|
|
281
|
+
if (isRpcStreamEnvelope(result)) {
|
|
282
|
+
return result.body;
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
}) as any;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Reconstruct a throwable from {@link encodeRpcError}'s wire form. Plain
|
|
291
|
+
* `Error` payloads are rebuilt as `Error` instances so `.message`/`.name`/
|
|
292
|
+
* `.stack` survive. Tagged errors (anything with a `_tag`) and primitives
|
|
293
|
+
* are thrown as-is so `try { ... } catch (e) { if (e._tag === ...) }` keeps
|
|
294
|
+
* working.
|
|
295
|
+
*/
|
|
296
|
+
const decodeRpcThrowable = (error: unknown): unknown => {
|
|
297
|
+
if (error === null || typeof error !== "object") return error;
|
|
298
|
+
const obj = error as Record<string, unknown>;
|
|
299
|
+
if ("_tag" in obj) return obj;
|
|
300
|
+
if (typeof obj.message === "string" || typeof obj.name === "string") {
|
|
301
|
+
const e = new Error(typeof obj.message === "string" ? obj.message : "");
|
|
302
|
+
if (typeof obj.name === "string") e.name = obj.name;
|
|
303
|
+
if (typeof obj.stack === "string") e.stack = obj.stack;
|
|
304
|
+
return e;
|
|
305
|
+
}
|
|
306
|
+
return error;
|
|
307
|
+
};
|
|
308
|
+
|
|
207
309
|
/**
|
|
208
310
|
* Create a DurableObjectBridge class that proxies RPC method calls through
|
|
209
311
|
* the Effect runtime, encoding success/fail/stream results as RPC envelopes.
|
|
@@ -336,6 +438,109 @@ export const makeDurableObjectBridge =
|
|
|
336
438
|
}
|
|
337
439
|
};
|
|
338
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Create a `WorkerBridge` class — a `WorkerEntrypoint` subclass that
|
|
443
|
+
* Cloudflare instantiates per request and dispatches both standard
|
|
444
|
+
* handlers (`fetch`/`scheduled`/…) and RPC method calls into.
|
|
445
|
+
*
|
|
446
|
+
* The bridge is intentionally layer-ignorant: all runtime-layer plumbing
|
|
447
|
+
* (services, scope, `WorkerExecutionContext`) lives in the platform's
|
|
448
|
+
* `runtimeContext.exports` flow, which builds:
|
|
449
|
+
*
|
|
450
|
+
* - `getDefault()` → `{ fetch, scheduled, … }` — handler dispatchers
|
|
451
|
+
* that the bridge forwards `(input, env, ctx)` to.
|
|
452
|
+
* - `getRpc()` → `{ method: (args, ctx) => Promise<envelope> }` —
|
|
453
|
+
* pre-wrapped RPC dispatchers that already run the user effect with
|
|
454
|
+
* the right runtime layer and envelope-encode the result.
|
|
455
|
+
*
|
|
456
|
+
* Cloudflare's script-validate step requires standard handler methods
|
|
457
|
+
* (`fetch`/`scheduled`/etc.) to be visible on the class prototype. We
|
|
458
|
+
* declare static stubs there and override them per-instance with
|
|
459
|
+
* closure-bound forwarders that capture `ctx`/`env` from the constructor
|
|
460
|
+
* (prototype-level `this` doesn't survive the proxy + RPC dispatch
|
|
461
|
+
* round-trip in some Cloudflare runtime versions).
|
|
462
|
+
*/
|
|
463
|
+
export const makeWorkerBridge = (
|
|
464
|
+
WorkerEntrypoint: abstract new (
|
|
465
|
+
ctx: unknown,
|
|
466
|
+
env: unknown,
|
|
467
|
+
) => { fetch?(request: cf.Request): Promise<cf.Response> | cf.Response },
|
|
468
|
+
ExportedHandlerMethods: ReadonlyArray<string>,
|
|
469
|
+
getDefault: () => Promise<Record<string, any>>,
|
|
470
|
+
getRpc: () => Promise<Record<string, any>>,
|
|
471
|
+
) => {
|
|
472
|
+
class WorkerBridge extends WorkerEntrypoint {
|
|
473
|
+
constructor(ctx: unknown, env: unknown) {
|
|
474
|
+
super(ctx, env);
|
|
475
|
+
|
|
476
|
+
// Override handler methods per-instance with closure-bound forwarders.
|
|
477
|
+
// We read `this.ctx`/`this.env` at *call time* rather than capturing
|
|
478
|
+
// the constructor args because Cloudflare's runtime appears to
|
|
479
|
+
// populate those instance fields differently depending on whether
|
|
480
|
+
// the entrypoint is invoked via direct fetch or RPC — `super(ctx, env)`
|
|
481
|
+
// alone isn't enough to guarantee they survive into the call.
|
|
482
|
+
const self = this as unknown as { ctx: unknown; env: unknown };
|
|
483
|
+
// Cloudflare invokes `WorkerEntrypoint.fetch(request)` with only the
|
|
484
|
+
// primary input on the *parameter list* — `env`/`ctx` live on `this`
|
|
485
|
+
// (and the runtime sometimes passes `(request, undefined, undefined)`
|
|
486
|
+
// anyway for legacy compat). Other handlers, including `scheduled`, may
|
|
487
|
+
// receive the classic `(input, env, ctx)` arguments. The platform's
|
|
488
|
+
// underlying handler dispatch (`handle("fetch")`, `handle("scheduled")`,
|
|
489
|
+
// etc.) follows the plain-export signature `(input, env, context)`, so
|
|
490
|
+
// prefer explicit handler args when Cloudflare provides them and fall
|
|
491
|
+
// back to the WorkerEntrypoint instance fields.
|
|
492
|
+
for (const method of ExportedHandlerMethods) {
|
|
493
|
+
(this as any)[method] = async (
|
|
494
|
+
input: unknown,
|
|
495
|
+
envArg?: unknown,
|
|
496
|
+
ctxArg?: unknown,
|
|
497
|
+
) => {
|
|
498
|
+
const def = await getDefault();
|
|
499
|
+
return def?.[method]?.(input, envArg ?? self.env, ctxArg ?? self.ctx);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// RPC method dispatch — anything Cloudflare RPC tries to invoke that
|
|
504
|
+
// isn't an own property goes through the dispatcher table built by
|
|
505
|
+
// the platform's exports flow.
|
|
506
|
+
return new Proxy(this, {
|
|
507
|
+
get: (target, prop) => {
|
|
508
|
+
if (typeof prop !== "string") return (target as any)[prop];
|
|
509
|
+
if (prop in target) return (target as any)[prop];
|
|
510
|
+
return async (...args: unknown[]) => {
|
|
511
|
+
const rpc = await getRpc();
|
|
512
|
+
const dispatcher = rpc?.[prop];
|
|
513
|
+
if (typeof dispatcher !== "function") {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Method "${prop}" not found on worker. ` +
|
|
516
|
+
`Make sure it's returned from the worker's default export.`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return dispatcher(args, self.ctx);
|
|
520
|
+
};
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Stub prototype methods so Cloudflare's script-validate detects the
|
|
527
|
+
// standard handler set; per-instance overrides above are what actually
|
|
528
|
+
// run.
|
|
529
|
+
for (const method of ExportedHandlerMethods) {
|
|
530
|
+
Object.defineProperty(WorkerBridge.prototype, method, {
|
|
531
|
+
value: function () {
|
|
532
|
+
throw new Error(
|
|
533
|
+
`Bridge method '${method}' was called before instance setup`,
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
writable: true,
|
|
537
|
+
configurable: true,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return WorkerBridge;
|
|
542
|
+
};
|
|
543
|
+
|
|
339
544
|
/**
|
|
340
545
|
* Create a WorkflowBridge class that extends `WorkflowEntrypoint` and
|
|
341
546
|
* delegates the `run(event, step)` call to the Effect-native workflow body
|
|
@@ -354,26 +559,30 @@ export const makeWorkflowBridge =
|
|
|
354
559
|
getExport: (
|
|
355
560
|
name: string,
|
|
356
561
|
) => Promise<
|
|
357
|
-
(
|
|
562
|
+
(
|
|
563
|
+
env: unknown,
|
|
564
|
+
) => Effect.Effect<(input: unknown) => Effect.Effect<unknown, never, any>>
|
|
358
565
|
>,
|
|
359
566
|
) =>
|
|
360
567
|
(className: string) =>
|
|
361
568
|
class WorkflowBridge extends WorkflowEntrypoint {
|
|
362
|
-
readonly
|
|
569
|
+
readonly fn: Promise<
|
|
570
|
+
(input: unknown) => Effect.Effect<unknown, never, any>
|
|
571
|
+
>;
|
|
363
572
|
readonly env: unknown;
|
|
364
573
|
|
|
365
574
|
constructor(ctx: unknown, env: unknown) {
|
|
366
575
|
super(ctx, env);
|
|
367
576
|
this.env = env;
|
|
368
|
-
this.
|
|
577
|
+
this.fn = getExport(className).then((factory) =>
|
|
369
578
|
Effect.runPromise(factory(env)),
|
|
370
579
|
);
|
|
371
580
|
}
|
|
372
581
|
|
|
373
582
|
async run(event: any, step: any): Promise<unknown> {
|
|
374
|
-
const
|
|
583
|
+
const fn = await this.fn;
|
|
375
584
|
return Effect.runPromise(
|
|
376
|
-
|
|
585
|
+
fn(event.payload).pipe(
|
|
377
586
|
Effect.provideService(
|
|
378
587
|
WorkflowEventService,
|
|
379
588
|
wrapWorkflowEvent(event),
|