alchemy 2.0.0-beta.36 → 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 +166 -43
- 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/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/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 +4 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.js +16 -0
- 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 +22 -2
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +174 -43
- 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 +1 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +1 -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 +24 -5
- package/lib/Test/Vitest.js.map +1 -1
- package/lib/Util/LocalhostDns.d.ts +14 -10
- package/lib/Util/LocalhostDns.d.ts.map +1 -1
- package/lib/Util/LocalhostDns.js +27 -36
- package/lib/Util/LocalhostDns.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 +6 -7
- package/src/AWS/Lambda/Function.ts +30 -2
- package/src/Apply.ts +8 -1
- 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/Local/SidecarHandlers.ts +9 -1
- package/src/Cloudflare/Providers.ts +5 -0
- package/src/Cloudflare/R2/R2Bucket.ts +31 -0
- 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 -20
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
- package/src/Cloudflare/Workers/Rpc.ts +227 -18
- package/src/Cloudflare/Workers/Worker.ts +277 -63
- 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 +1 -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 +33 -4
- package/src/Util/LocalhostDns.ts +30 -36
- package/src/Util/effect.ts +25 -0
- package/src/Variable.ts +44 -0
- package/src/index.ts +3 -0
|
@@ -2,6 +2,7 @@ import type * as cf from "@cloudflare/workers-types";
|
|
|
2
2
|
import cloudflareVite from "@distilled.cloud/cloudflare-vite-plugin";
|
|
3
3
|
import * as workers from "@distilled.cloud/cloudflare/workers";
|
|
4
4
|
import * as zones from "@distilled.cloud/cloudflare/zones";
|
|
5
|
+
import * as Cause from "effect/Cause";
|
|
5
6
|
import * as Context from "effect/Context";
|
|
6
7
|
import * as Data from "effect/Data";
|
|
7
8
|
import * as Effect from "effect/Effect";
|
|
@@ -12,6 +13,7 @@ import * as Path from "effect/Path";
|
|
|
12
13
|
import * as Redacted from "effect/Redacted";
|
|
13
14
|
import * as Schedule from "effect/Schedule";
|
|
14
15
|
import * as Scope from "effect/Scope";
|
|
16
|
+
import * as Stream from "effect/Stream";
|
|
15
17
|
import { createRequire } from "node:module";
|
|
16
18
|
import path from "node:path";
|
|
17
19
|
import { pathToFileURL } from "node:url";
|
|
@@ -33,11 +35,16 @@ import {
|
|
|
33
35
|
type PlatformProps,
|
|
34
36
|
type Rpc,
|
|
35
37
|
} from "../../Platform.ts";
|
|
38
|
+
import { isYieldableEffectLike } from "../../Util/effect.ts";
|
|
36
39
|
import * as Provider from "../../Provider.ts";
|
|
37
40
|
import { Resource, type ResourceBinding } from "../../Resource.ts";
|
|
38
41
|
import * as Serverless from "../../Serverless/index.ts";
|
|
39
42
|
import { Stack } from "../../Stack.ts";
|
|
40
43
|
import type { AiGateway } from "../AiGateway/AiGateway.ts";
|
|
44
|
+
import {
|
|
45
|
+
isAnalyticsEngineDataset,
|
|
46
|
+
type AnalyticsEngineDataset,
|
|
47
|
+
} from "../AnalyticsEngine/AnalyticsEngineDataset.ts";
|
|
41
48
|
import {
|
|
42
49
|
isArtifacts as isArtifactsBinding,
|
|
43
50
|
type Artifacts as ArtifactsBinding,
|
|
@@ -76,7 +83,14 @@ import {
|
|
|
76
83
|
import { workersHttpHandler } from "./HttpServer.ts";
|
|
77
84
|
import { LocalWorkerProvider } from "./LocalWorkerProvider.ts";
|
|
78
85
|
import { Request } from "./Request.ts";
|
|
79
|
-
import {
|
|
86
|
+
import {
|
|
87
|
+
encodeRpcError,
|
|
88
|
+
ErrorTag,
|
|
89
|
+
makeRpcStub,
|
|
90
|
+
toRpcStream,
|
|
91
|
+
type RpcErrorEnvelope,
|
|
92
|
+
type RpcStreamEnvelope,
|
|
93
|
+
} from "./Rpc.ts";
|
|
80
94
|
import { WorkerBundle } from "./WorkerBundle.ts";
|
|
81
95
|
import { createWorkerName } from "./WorkerName.ts";
|
|
82
96
|
|
|
@@ -187,9 +201,11 @@ export type WorkerBindingResource =
|
|
|
187
201
|
| KVNamespace
|
|
188
202
|
| CloudflareQueue
|
|
189
203
|
| AiGateway
|
|
204
|
+
| AnalyticsEngineDataset
|
|
190
205
|
| ArtifactsBinding
|
|
191
206
|
| ImagesBinding
|
|
192
207
|
| Hyperdrive
|
|
208
|
+
| Worker
|
|
193
209
|
| DurableObjectNamespaceLike<any>;
|
|
194
210
|
|
|
195
211
|
export type WorkerBindings = {
|
|
@@ -280,6 +296,12 @@ export interface WorkerProps<
|
|
|
280
296
|
>;
|
|
281
297
|
exports?: string[];
|
|
282
298
|
bindings?: Bindings;
|
|
299
|
+
/**
|
|
300
|
+
* Cron expressions that trigger the Worker's scheduled handler.
|
|
301
|
+
*
|
|
302
|
+
* Pass an empty array to remove all Cron Triggers.
|
|
303
|
+
*/
|
|
304
|
+
crons?: string[];
|
|
283
305
|
/**
|
|
284
306
|
* One or more custom hostnames (e.g. `"app.example.com"`) to bind to this
|
|
285
307
|
* Worker. The Cloudflare Zone is inferred from the hostname — the zone must
|
|
@@ -318,6 +340,7 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
|
|
|
318
340
|
durableObjectNamespaces: Record<string, string>;
|
|
319
341
|
accountId: string;
|
|
320
342
|
domains: { hostname: string; id: string; zoneId: string }[];
|
|
343
|
+
crons: string[];
|
|
321
344
|
hash?: {
|
|
322
345
|
assets: string | undefined;
|
|
323
346
|
bundle: string | undefined;
|
|
@@ -327,6 +350,7 @@ export type Worker<Bindings extends WorkerBindings = any> = Resource<
|
|
|
327
350
|
{
|
|
328
351
|
bindings?: WorkerBinding[];
|
|
329
352
|
containers?: { className: string }[];
|
|
353
|
+
crons?: string[];
|
|
330
354
|
hyperdrives?: Record<string, Required<HyperdriveDevOrigin>>;
|
|
331
355
|
},
|
|
332
356
|
Providers
|
|
@@ -725,8 +749,11 @@ export const Worker: Platform<
|
|
|
725
749
|
const bindingEff = props.bindings?.[bindingName] as
|
|
726
750
|
| WorkerBindingResource
|
|
727
751
|
| Effect.Effect<WorkerBindingResource>;
|
|
728
|
-
|
|
729
|
-
|
|
752
|
+
// Bindings can be passed as a plain resource value, an Effect that
|
|
753
|
+
// yields a resource, or an effect-class (e.g. a `Cloudflare.Worker`
|
|
754
|
+
// class). Resolve the yieldable forms before deriving binding metadata.
|
|
755
|
+
const binding = isYieldableEffectLike(bindingEff)
|
|
756
|
+
? ((yield* bindingEff as Effect.Effect<unknown>) as WorkerBindingResource)
|
|
730
757
|
: bindingEff;
|
|
731
758
|
|
|
732
759
|
const bindingMeta: InputProps<WorkerBinding> | undefined = isAssets(
|
|
@@ -747,56 +774,68 @@ export const Worker: Platform<
|
|
|
747
774
|
type: "images",
|
|
748
775
|
name: bindingName,
|
|
749
776
|
}
|
|
750
|
-
:
|
|
777
|
+
: isAnalyticsEngineDataset(binding)
|
|
751
778
|
? {
|
|
752
|
-
type: "
|
|
779
|
+
type: "analytics_engine",
|
|
753
780
|
name: bindingName,
|
|
754
|
-
|
|
781
|
+
dataset: binding.dataset,
|
|
755
782
|
}
|
|
756
|
-
: binding
|
|
783
|
+
: isDurableObjectNamespaceLike(binding)
|
|
757
784
|
? {
|
|
758
|
-
type: "
|
|
759
|
-
id: binding.databaseId,
|
|
785
|
+
type: "durable_object_namespace",
|
|
760
786
|
name: bindingName,
|
|
787
|
+
className: binding.className ?? binding.name,
|
|
761
788
|
}
|
|
762
|
-
: binding.Type === "Cloudflare.
|
|
789
|
+
: binding.Type === "Cloudflare.D1Database"
|
|
763
790
|
? {
|
|
764
|
-
type: "
|
|
791
|
+
type: "d1",
|
|
792
|
+
id: binding.databaseId,
|
|
765
793
|
name: bindingName,
|
|
766
|
-
bucketName: binding.bucketName,
|
|
767
|
-
jurisdiction: binding.jurisdiction.pipe(
|
|
768
|
-
Output.map((jurisdiction) =>
|
|
769
|
-
jurisdiction === "default"
|
|
770
|
-
? undefined
|
|
771
|
-
: jurisdiction,
|
|
772
|
-
),
|
|
773
|
-
),
|
|
774
794
|
}
|
|
775
|
-
: binding.Type === "Cloudflare.
|
|
795
|
+
: binding.Type === "Cloudflare.R2Bucket"
|
|
776
796
|
? {
|
|
777
|
-
type: "
|
|
797
|
+
type: "r2_bucket",
|
|
778
798
|
name: bindingName,
|
|
779
|
-
|
|
799
|
+
bucketName: binding.bucketName,
|
|
800
|
+
jurisdiction: binding.jurisdiction.pipe(
|
|
801
|
+
Output.map((jurisdiction) =>
|
|
802
|
+
jurisdiction === "default"
|
|
803
|
+
? undefined
|
|
804
|
+
: jurisdiction,
|
|
805
|
+
),
|
|
806
|
+
),
|
|
780
807
|
}
|
|
781
|
-
: binding.Type === "Cloudflare.
|
|
808
|
+
: binding.Type === "Cloudflare.KVNamespace"
|
|
782
809
|
? {
|
|
783
|
-
type: "
|
|
810
|
+
type: "kv_namespace",
|
|
784
811
|
name: bindingName,
|
|
785
|
-
|
|
812
|
+
namespaceId: binding.namespaceId,
|
|
786
813
|
}
|
|
787
|
-
: binding.Type === "Cloudflare.
|
|
814
|
+
: binding.Type === "Cloudflare.Queue"
|
|
788
815
|
? {
|
|
789
|
-
type: "
|
|
816
|
+
type: "queue",
|
|
790
817
|
name: bindingName,
|
|
818
|
+
queueName: binding.queueName,
|
|
791
819
|
}
|
|
792
|
-
: binding.Type === "Cloudflare.
|
|
820
|
+
: binding.Type === "Cloudflare.AiGateway"
|
|
793
821
|
? {
|
|
794
|
-
type: "
|
|
822
|
+
type: "ai",
|
|
795
823
|
name: bindingName,
|
|
796
|
-
id: binding.hyperdriveId,
|
|
797
824
|
}
|
|
798
|
-
:
|
|
799
|
-
|
|
825
|
+
: binding.Type === "Cloudflare.Hyperdrive"
|
|
826
|
+
? {
|
|
827
|
+
type: "hyperdrive",
|
|
828
|
+
name: bindingName,
|
|
829
|
+
id: binding.hyperdriveId,
|
|
830
|
+
}
|
|
831
|
+
: isWorker(binding)
|
|
832
|
+
? {
|
|
833
|
+
type: "service",
|
|
834
|
+
name: bindingName,
|
|
835
|
+
service: binding.workerName,
|
|
836
|
+
}
|
|
837
|
+
: // TODO(sam): handle others
|
|
838
|
+
undefined;
|
|
800
839
|
|
|
801
840
|
if (bindingMeta) {
|
|
802
841
|
yield* resource.bind`${bindingName}`({
|
|
@@ -812,6 +851,7 @@ export const Worker: Platform<
|
|
|
812
851
|
const listeners: Effect.Effect<Serverless.FunctionListener>[] = [];
|
|
813
852
|
const exports: Record<string, any> = {};
|
|
814
853
|
const env: Record<string, any> = {};
|
|
854
|
+
let userShape: Record<string, unknown> | undefined;
|
|
815
855
|
|
|
816
856
|
const ctx = {
|
|
817
857
|
Type: WorkerTypeId,
|
|
@@ -878,7 +918,14 @@ export const Worker: Platform<
|
|
|
878
918
|
}),
|
|
879
919
|
serve: <Req = never>(
|
|
880
920
|
handler: HttpEffect<Req> | Effect.Effect<HttpEffect<Req>>,
|
|
881
|
-
|
|
921
|
+
options?: { shape?: Record<string, unknown> },
|
|
922
|
+
) => {
|
|
923
|
+
// Capture the user's full default-export shape so `exports` can
|
|
924
|
+
// expose any non-handler methods on it as RPC methods on the
|
|
925
|
+
// deployed `WorkerEntrypoint` subclass — see `__rpc__` below.
|
|
926
|
+
if (options?.shape) userShape = options.shape;
|
|
927
|
+
return ctx.listen(workersHttpHandler(handler));
|
|
928
|
+
},
|
|
882
929
|
listen: ((
|
|
883
930
|
handler:
|
|
884
931
|
| Serverless.FunctionListener
|
|
@@ -898,6 +945,35 @@ export const Worker: Platform<
|
|
|
898
945
|
concurrency: "unbounded",
|
|
899
946
|
});
|
|
900
947
|
const services = yield* Effect.context();
|
|
948
|
+
|
|
949
|
+
// Provide the per-request runtime layer (services + `WorkerExecutionContext`
|
|
950
|
+
// + a fresh `ExecutionContext`/`Scope`) to a user effect, then run it.
|
|
951
|
+
// This is the bridge between Cloudflare's request-time callback and
|
|
952
|
+
// the user's Effect-native handler. Used by both the standard
|
|
953
|
+
// `handle()` dispatch (fetch/scheduled/…) and the per-RPC dispatchers
|
|
954
|
+
// below — keeping them on a single layer-provision path.
|
|
955
|
+
const runUserEffect = <A, E>(
|
|
956
|
+
eff: Effect.Effect<A, E>,
|
|
957
|
+
context: cf.ExecutionContext,
|
|
958
|
+
): Promise<Exit.Exit<A, E>> => {
|
|
959
|
+
const scope = Scope.makeUnsafe();
|
|
960
|
+
return eff
|
|
961
|
+
.pipe(
|
|
962
|
+
Effect.provideContext(services),
|
|
963
|
+
Scope.provide(scope),
|
|
964
|
+
Effect.provide(Layer.succeed(WorkerExecutionContext, context)),
|
|
965
|
+
Effect.provide(
|
|
966
|
+
Layer.succeed(ExecutionContext, { scope, cache: {} }),
|
|
967
|
+
),
|
|
968
|
+
Effect.runPromiseExit,
|
|
969
|
+
)
|
|
970
|
+
.finally(() =>
|
|
971
|
+
context.waitUntil(
|
|
972
|
+
Effect.runPromise(Scope.close(scope, Exit.void)),
|
|
973
|
+
),
|
|
974
|
+
);
|
|
975
|
+
};
|
|
976
|
+
|
|
901
977
|
const handle =
|
|
902
978
|
(type: WorkerEvent["type"]) =>
|
|
903
979
|
(request: any, env: unknown, context: cf.ExecutionContext) => {
|
|
@@ -911,36 +987,76 @@ export const Worker: Platform<
|
|
|
911
987
|
for (const handler of handlers) {
|
|
912
988
|
const eff = handler(event);
|
|
913
989
|
if (Effect.isEffect(eff)) {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
),
|
|
922
|
-
Effect.provide(
|
|
923
|
-
Layer.succeed(ExecutionContext, {
|
|
924
|
-
scope,
|
|
925
|
-
cache: {},
|
|
926
|
-
}),
|
|
927
|
-
),
|
|
928
|
-
Effect.runPromise,
|
|
929
|
-
)
|
|
930
|
-
.finally(() =>
|
|
931
|
-
context.waitUntil(
|
|
932
|
-
Effect.runPromise(Scope.close(scope, Exit.void)),
|
|
933
|
-
),
|
|
934
|
-
);
|
|
990
|
+
return runUserEffect(
|
|
991
|
+
eff as Effect.Effect<unknown, unknown>,
|
|
992
|
+
context,
|
|
993
|
+
).then((exit) => {
|
|
994
|
+
if (exit._tag === "Success") return exit.value;
|
|
995
|
+
throw Cause.squash(exit.cause);
|
|
996
|
+
});
|
|
935
997
|
}
|
|
936
998
|
}
|
|
937
999
|
return Promise.reject(new Error("No event handler found"));
|
|
938
1000
|
};
|
|
1001
|
+
|
|
1002
|
+
// RPC method dispatchers — one per non-handler method on the user's
|
|
1003
|
+
// shape. Each dispatcher is invoked by the WorkerEntrypoint bridge
|
|
1004
|
+
// as `dispatcher(args, ctx)`: `args` are the user-facing call args,
|
|
1005
|
+
// `ctx` is the `this.ctx` that Cloudflare hands the bridge per RPC
|
|
1006
|
+
// request. The dispatcher runs the user effect with the same runtime
|
|
1007
|
+
// layer the fetch path uses, then envelope-encodes the result so
|
|
1008
|
+
// `Effect.fail` round-trips as `RpcErrorEnvelope` and `Stream` as
|
|
1009
|
+
// `RpcStreamEnvelope` (consumers wrap the binding with
|
|
1010
|
+
// `toPromiseApi`/`bindWorker` to decode).
|
|
1011
|
+
const exportedHandlerSet = new Set<string>(ExportedHandlerMethods);
|
|
1012
|
+
const rpcDispatchers: Record<
|
|
1013
|
+
string,
|
|
1014
|
+
(args: unknown[], ctx: cf.ExecutionContext) => Promise<unknown>
|
|
1015
|
+
> = {};
|
|
1016
|
+
if (userShape) {
|
|
1017
|
+
for (const [name, value] of Object.entries(userShape)) {
|
|
1018
|
+
if (exportedHandlerSet.has(name)) continue;
|
|
1019
|
+
if (typeof value !== "function") continue;
|
|
1020
|
+
rpcDispatchers[name] = async (
|
|
1021
|
+
args: unknown[],
|
|
1022
|
+
context: cf.ExecutionContext,
|
|
1023
|
+
) => {
|
|
1024
|
+
const result = (value as (...a: unknown[]) => unknown)(...args);
|
|
1025
|
+
if (!Effect.isEffect(result)) return result;
|
|
1026
|
+
const exit = await runUserEffect(
|
|
1027
|
+
result as Effect.Effect<unknown, unknown>,
|
|
1028
|
+
context,
|
|
1029
|
+
);
|
|
1030
|
+
if (exit._tag === "Success") {
|
|
1031
|
+
if (Stream.isStream(exit.value)) {
|
|
1032
|
+
return await Effect.runPromise(
|
|
1033
|
+
toRpcStream(exit.value) as Effect.Effect<RpcStreamEnvelope>,
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
return exit.value;
|
|
1037
|
+
}
|
|
1038
|
+
const failReason = exit.cause.reasons.find(Cause.isFailReason);
|
|
1039
|
+
if (failReason) {
|
|
1040
|
+
return {
|
|
1041
|
+
_tag: ErrorTag,
|
|
1042
|
+
error: encodeRpcError(failReason.error),
|
|
1043
|
+
} satisfies RpcErrorEnvelope;
|
|
1044
|
+
}
|
|
1045
|
+
const dieReason = exit.cause.reasons.find(Cause.isDieReason);
|
|
1046
|
+
throw (
|
|
1047
|
+
dieReason?.defect ??
|
|
1048
|
+
new Error("RPC method failed with an unexpected cause")
|
|
1049
|
+
);
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
939
1054
|
return {
|
|
940
1055
|
...exports,
|
|
941
1056
|
default: Object.fromEntries(
|
|
942
1057
|
ExportedHandlerMethods.map((method) => [method, handle(method)]),
|
|
943
1058
|
),
|
|
1059
|
+
__rpc__: rpcDispatchers,
|
|
944
1060
|
};
|
|
945
1061
|
}),
|
|
946
1062
|
};
|
|
@@ -953,7 +1069,10 @@ export const bindWorker = Effect.fnUntraced(function* <Shape, Req = never>(
|
|
|
953
1069
|
| (Worker & Rpc<Shape>)
|
|
954
1070
|
| Effect.Effect<Worker & Rpc<Shape>, never, Req>,
|
|
955
1071
|
) {
|
|
956
|
-
|
|
1072
|
+
// Worker classes and regular Effects are both yieldable here.
|
|
1073
|
+
const worker = isYieldableEffectLike(workerEff)
|
|
1074
|
+
? yield* workerEff as Effect.Effect<Worker & Rpc<Shape>, never, Req>
|
|
1075
|
+
: workerEff;
|
|
957
1076
|
const self = yield* Worker;
|
|
958
1077
|
yield* self.bind`${worker}`({
|
|
959
1078
|
bindings: [
|
|
@@ -965,13 +1084,14 @@ export const bindWorker = Effect.fnUntraced(function* <Shape, Req = never>(
|
|
|
965
1084
|
],
|
|
966
1085
|
});
|
|
967
1086
|
|
|
968
|
-
|
|
969
|
-
|
|
1087
|
+
// `bindWorker` runs at *init* phase (both at plantime and at runtime
|
|
1088
|
+
// cold-start). `WorkerEnvironment` only exists at exec phase on the
|
|
1089
|
+
// deployed worker, so we hand `makeRpcStub` an `Effect<stub>` that
|
|
1090
|
+
// resolves the binding lazily on each method call.
|
|
1091
|
+
const stubEff = WorkerEnvironment.asEffect().pipe(
|
|
1092
|
+
Effect.map((env) => (env as Record<string, unknown>)[worker.LogicalId]),
|
|
970
1093
|
);
|
|
971
|
-
|
|
972
|
-
const fetcher = workerBinding.pipe(Effect.map(fromCloudflareFetcher));
|
|
973
|
-
// TODO(sam): update makeRpcStub to support lazily evaluating the Effect<Fetcher>
|
|
974
|
-
return makeRpcStub<Shape>(fetcher);
|
|
1094
|
+
return makeRpcStub<Shape>(stubEff);
|
|
975
1095
|
});
|
|
976
1096
|
|
|
977
1097
|
export class BindWorkerPolicy extends Binding.Policy<
|
|
@@ -1039,6 +1159,12 @@ function getDurableObjectBindings(
|
|
|
1039
1159
|
);
|
|
1040
1160
|
}
|
|
1041
1161
|
|
|
1162
|
+
export function getCronBindings(
|
|
1163
|
+
bindings: ReadonlyArray<ResourceBinding<Worker["Binding"]>>,
|
|
1164
|
+
) {
|
|
1165
|
+
return Array.from(new Set(bindings.flatMap((b) => b.data.crons ?? [])));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1042
1168
|
function getDurableObjectTagMap(tags: ReadonlyArray<string>) {
|
|
1043
1169
|
return Object.fromEntries(
|
|
1044
1170
|
tags.flatMap((tag) => {
|
|
@@ -1089,9 +1215,11 @@ export const LiveWorkerProvider = () =>
|
|
|
1089
1215
|
const createScriptSubdomain = yield* workers.createScriptSubdomain;
|
|
1090
1216
|
const deleteScript = yield* workers.deleteScript;
|
|
1091
1217
|
const getScriptSubdomain = yield* workers.getScriptSubdomain;
|
|
1218
|
+
const getScriptSchedule = yield* workers.getScriptSchedule;
|
|
1092
1219
|
const getScriptSettings = yield* workers.getScriptScriptAndVersionSetting;
|
|
1093
1220
|
const getSubdomain = yield* workers.getSubdomain;
|
|
1094
1221
|
const putScript = yield* workers.putScript;
|
|
1222
|
+
const putScriptSchedule = yield* workers.putScriptSchedule;
|
|
1095
1223
|
const putDomain = yield* workers.putDomain;
|
|
1096
1224
|
const listDomains = yield* workers.listDomains;
|
|
1097
1225
|
const deleteDomain = yield* workers.deleteDomain;
|
|
@@ -1139,6 +1267,60 @@ export const LiveWorkerProvider = () =>
|
|
|
1139
1267
|
),
|
|
1140
1268
|
);
|
|
1141
1269
|
|
|
1270
|
+
const normalizeCrons = (crons: string[] | undefined): string[] =>
|
|
1271
|
+
Array.from(new Set(crons ?? []));
|
|
1272
|
+
|
|
1273
|
+
const getWorkerCrons = (scriptName: string) =>
|
|
1274
|
+
getScriptSchedule({
|
|
1275
|
+
accountId,
|
|
1276
|
+
scriptName,
|
|
1277
|
+
}).pipe(
|
|
1278
|
+
Effect.map((response) =>
|
|
1279
|
+
normalizeCrons(response.schedules.map((schedule) => schedule.cron)),
|
|
1280
|
+
),
|
|
1281
|
+
Effect.catchTag("WorkerNotFound", () => Effect.succeed([])),
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
const reconcileCrons = (
|
|
1285
|
+
scriptName: string,
|
|
1286
|
+
desired: string[],
|
|
1287
|
+
previous: string[],
|
|
1288
|
+
session: ScopedPlanStatusSession,
|
|
1289
|
+
) =>
|
|
1290
|
+
Effect.gen(function* () {
|
|
1291
|
+
const live = yield* getWorkerCrons(scriptName);
|
|
1292
|
+
const desiredSorted = [...desired].sort();
|
|
1293
|
+
const liveSorted = [...live].sort();
|
|
1294
|
+
const changed =
|
|
1295
|
+
desiredSorted.length !== liveSorted.length ||
|
|
1296
|
+
desiredSorted.some((cron, index) => cron !== liveSorted[index]);
|
|
1297
|
+
|
|
1298
|
+
if (!changed) return live;
|
|
1299
|
+
|
|
1300
|
+
if (desired.length > 0 || previous.length > 0 || live.length > 0) {
|
|
1301
|
+
yield* session.note(
|
|
1302
|
+
`Reconciling Cron Triggers (${desired.length}) ...`,
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const result = yield* putScriptSchedule({
|
|
1307
|
+
accountId,
|
|
1308
|
+
scriptName,
|
|
1309
|
+
body: desired.map((cron) => ({ cron })),
|
|
1310
|
+
}).pipe(
|
|
1311
|
+
Effect.retry({
|
|
1312
|
+
while: (error: { _tag?: string }) =>
|
|
1313
|
+
error?._tag === "WorkerNotFound",
|
|
1314
|
+
schedule: Schedule.exponential(200).pipe(
|
|
1315
|
+
Schedule.both(Schedule.recurs(15)),
|
|
1316
|
+
),
|
|
1317
|
+
}),
|
|
1318
|
+
);
|
|
1319
|
+
return normalizeCrons(
|
|
1320
|
+
result.schedules.map((schedule) => schedule.cron),
|
|
1321
|
+
);
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1142
1324
|
/**
|
|
1143
1325
|
* Infer the Cloudflare Zone ID for a given hostname by listing the
|
|
1144
1326
|
* account's zones and matching the hostname against each zone's name —
|
|
@@ -1958,6 +2140,12 @@ export const LiveWorkerProvider = () =>
|
|
|
1958
2140
|
desiredDomains,
|
|
1959
2141
|
previousDomains,
|
|
1960
2142
|
);
|
|
2143
|
+
const crons = yield* reconcileCrons(
|
|
2144
|
+
name,
|
|
2145
|
+
normalizeCrons([...getCronBindings(bindings), ...(news.crons ?? [])]),
|
|
2146
|
+
output?.crons ?? [],
|
|
2147
|
+
session,
|
|
2148
|
+
);
|
|
1961
2149
|
return {
|
|
1962
2150
|
workerId: worker.id ?? name,
|
|
1963
2151
|
workerName: name,
|
|
@@ -1970,6 +2158,7 @@ export const LiveWorkerProvider = () =>
|
|
|
1970
2158
|
durableObjectNamespaces,
|
|
1971
2159
|
accountId,
|
|
1972
2160
|
domains,
|
|
2161
|
+
crons,
|
|
1973
2162
|
hash,
|
|
1974
2163
|
} satisfies Worker["Attributes"];
|
|
1975
2164
|
});
|
|
@@ -2025,7 +2214,13 @@ export const LiveWorkerProvider = () =>
|
|
|
2025
2214
|
|
|
2026
2215
|
return Worker.Provider.of({
|
|
2027
2216
|
stables: ["workerId", "workerName"],
|
|
2028
|
-
diff: Effect.fnUntraced(function* ({
|
|
2217
|
+
diff: Effect.fnUntraced(function* ({
|
|
2218
|
+
id,
|
|
2219
|
+
news,
|
|
2220
|
+
olds,
|
|
2221
|
+
output,
|
|
2222
|
+
newBindings,
|
|
2223
|
+
}) {
|
|
2029
2224
|
if (!isResolved(news)) return undefined;
|
|
2030
2225
|
if ((output?.accountId ?? accountId) !== accountId) {
|
|
2031
2226
|
return { action: "replace" };
|
|
@@ -2047,7 +2242,23 @@ export const LiveWorkerProvider = () =>
|
|
|
2047
2242
|
const domainsChanged =
|
|
2048
2243
|
newDomains.length !== oldDomains.length ||
|
|
2049
2244
|
newDomains.some((d, i) => d !== oldDomains[i]);
|
|
2050
|
-
|
|
2245
|
+
const newCrons = normalizeCrons([
|
|
2246
|
+
...(Array.isArray(newBindings)
|
|
2247
|
+
? getCronBindings(
|
|
2248
|
+
newBindings as ResourceBinding<Worker["Binding"]>[],
|
|
2249
|
+
)
|
|
2250
|
+
: []),
|
|
2251
|
+
...(news.crons ?? []),
|
|
2252
|
+
]).sort();
|
|
2253
|
+
const oldCrons = [...(output?.crons ?? [])].sort();
|
|
2254
|
+
const cronsChanged =
|
|
2255
|
+
newCrons.length !== oldCrons.length ||
|
|
2256
|
+
newCrons.some((cron, index) => cron !== oldCrons[index]);
|
|
2257
|
+
if (
|
|
2258
|
+
domainsChanged ||
|
|
2259
|
+
cronsChanged ||
|
|
2260
|
+
(yield* hasChanged(id, news, output))
|
|
2261
|
+
) {
|
|
2051
2262
|
return {
|
|
2052
2263
|
action: "update",
|
|
2053
2264
|
stables:
|
|
@@ -2173,6 +2384,7 @@ export const LiveWorkerProvider = () =>
|
|
|
2173
2384
|
durableObjectNamespaces,
|
|
2174
2385
|
accountId,
|
|
2175
2386
|
domains: [],
|
|
2387
|
+
crons: [],
|
|
2176
2388
|
} satisfies Worker["Attributes"];
|
|
2177
2389
|
}),
|
|
2178
2390
|
read: Effect.fnUntraced(
|
|
@@ -2204,6 +2416,7 @@ export const LiveWorkerProvider = () =>
|
|
|
2204
2416
|
service: workerName,
|
|
2205
2417
|
}).pipe(Effect.map((r) => r.result ?? [])),
|
|
2206
2418
|
]);
|
|
2419
|
+
const crons = yield* getWorkerCrons(workerName);
|
|
2207
2420
|
yield* Effect.logInfo(
|
|
2208
2421
|
`Cloudflare Worker read: found ${workerName}`,
|
|
2209
2422
|
);
|
|
@@ -2224,6 +2437,7 @@ export const LiveWorkerProvider = () =>
|
|
|
2224
2437
|
? [{ id: d.id, hostname: d.hostname, zoneId: d.zoneId }]
|
|
2225
2438
|
: [],
|
|
2226
2439
|
),
|
|
2440
|
+
crons,
|
|
2227
2441
|
} satisfies Worker["Attributes"];
|
|
2228
2442
|
|
|
2229
2443
|
// Centralized ownership decision: the engine routes `read`'s
|
|
@@ -158,11 +158,11 @@ import * as Logger from "effect/Logger";
|
|
|
158
158
|
import * as Context from "effect/Context";
|
|
159
159
|
import * as Stream from "effect/Stream";
|
|
160
160
|
|
|
161
|
-
import { env, DurableObject${hasWfClasses ? ", WorkflowEntrypoint" : ""} } from "cloudflare:workers";
|
|
161
|
+
import { env, DurableObject, WorkerEntrypoint${hasWfClasses ? ", WorkflowEntrypoint" : ""} } from "cloudflare:workers";
|
|
162
162
|
import { MinimumLogLevel } from "effect/References";
|
|
163
163
|
import { NodeServices } from "@effect/platform-node";
|
|
164
164
|
import { Stack } from "alchemy/Stack";
|
|
165
|
-
import { WorkerEnvironment, makeDurableObjectBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy/Cloudflare";
|
|
165
|
+
import { WorkerEnvironment, makeDurableObjectBridge, makeWorkerBridge${hasWfClasses ? ", makeWorkflowBridge" : ""}, ExportedHandlerMethods } from "alchemy/Cloudflare";
|
|
166
166
|
|
|
167
167
|
import entry from "${importPath}";
|
|
168
168
|
|
|
@@ -223,10 +223,20 @@ let exportsPromise;
|
|
|
223
223
|
// we cache it synchronously (??=) to guarnatee only one initialization ever happens
|
|
224
224
|
const getExports = () => (exportsPromise ??= Effect.runPromise(exportsEffect))
|
|
225
225
|
const getExport = (name) => getExports().then(exports => exports[name]?.make)
|
|
226
|
-
const
|
|
226
|
+
const getDefault = () => getExports().then(exports => exports.default)
|
|
227
|
+
const getRpc = () => getExports().then(exports => exports.__rpc__ ?? {})
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
// Bridge the user's default-export shape onto a real \`WorkerEntrypoint\`
|
|
230
|
+
// subclass so Cloudflare service bindings can dispatch both the standard
|
|
231
|
+
// handler methods (fetch, scheduled, …) and any user-defined RPC methods.
|
|
232
|
+
// RPC method results are wire-encoded by \`runtimeContext.exports\`;
|
|
233
|
+
// consumers unwrap them with \`Cloudflare.toPromiseApi(env.X)\` (Promise
|
|
234
|
+
// API) or \`bindWorker(WorkerClass)\` (Effect API).
|
|
235
|
+
export default makeWorkerBridge(
|
|
236
|
+
WorkerEntrypoint,
|
|
237
|
+
ExportedHandlerMethods,
|
|
238
|
+
getDefault,
|
|
239
|
+
getRpc,
|
|
230
240
|
);
|
|
231
241
|
|
|
232
242
|
// export class proxy stubs for Durable Objects and Workflows
|