effect-cf 0.20.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
  4. package/dist/HyperdrivePg.d.mts +13 -5
  5. package/dist/HyperdrivePg.mjs +12 -7
  6. package/dist/index.d.mts +708 -549
  7. package/dist/index.mjs +924 -587
  8. package/package.json +10 -5
  9. package/src/AiGateway.ts +82 -47
  10. package/src/AnalyticsEngine.ts +74 -58
  11. package/src/Binding.ts +66 -13
  12. package/src/BrowserRendering.ts +30 -7
  13. package/src/Cache.ts +32 -8
  14. package/src/CloudflareOtlp.ts +32 -9
  15. package/src/ContainerNamespace.ts +87 -55
  16. package/src/D1.ts +13 -3
  17. package/src/DurableObject.ts +27 -164
  18. package/src/DurableObjectAlarm.ts +30 -9
  19. package/src/DurableObjectDefinition.ts +58 -54
  20. package/src/DurableObjectNamespace.ts +99 -96
  21. package/src/DurableObjectRpcWebSocket.ts +14 -6
  22. package/src/DurableObjectSqlite.ts +6 -3
  23. package/src/DurableObjectState.ts +6 -9
  24. package/src/DurableObjectStorage.ts +7 -1
  25. package/src/DurableObjectWebSocket.ts +51 -41
  26. package/src/Email.ts +40 -13
  27. package/src/Environment.ts +26 -6
  28. package/src/Hyperdrive.ts +2 -2
  29. package/src/HyperdrivePg.ts +25 -13
  30. package/src/Images.ts +77 -82
  31. package/src/Kv.ts +37 -27
  32. package/src/Queue.ts +6 -1
  33. package/src/QueueBinding.ts +18 -21
  34. package/src/QueueDefinition.ts +15 -14
  35. package/src/R2.ts +107 -29
  36. package/src/Rpc.ts +3 -1
  37. package/src/RpcDefinition.ts +177 -133
  38. package/src/ServiceBinding.ts +112 -108
  39. package/src/Vectorize.ts +59 -14
  40. package/src/Worker.ts +172 -192
  41. package/src/WorkerDefinition.ts +5 -10
  42. package/src/WorkersAi.ts +27 -17
  43. package/src/Workflow.ts +54 -49
  44. package/src/WorkflowBinding.ts +19 -11
  45. package/src/WorkflowDefinition.ts +24 -27
  46. package/src/index.ts +4 -3
  47. package/src/internal/Entrypoint.ts +3 -1
  48. package/src/internal/ErrorMessage.ts +24 -0
  49. package/src/internal/RpcInvocation.ts +11 -8
  50. package/src/internal/Runtime.ts +52 -0
  51. package/src/internal/WorkerContext.ts +14 -2
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
package/src/Workflow.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Effect wrapper for Cloudflare Workflows entrypoints and bindings; distinct
3
+ * from Effect's runtime-agnostic `effect/unstable/workflow` cluster module.
4
+ */
1
5
  import {
2
6
  WorkflowEntrypoint as CloudflareWorkflowEntrypoint,
3
7
  type WorkflowEvent as CloudflareWorkflowEvent,
@@ -8,13 +12,15 @@ import {
8
12
  type WorkflowStepEvent,
9
13
  type WorkflowTimeoutDuration,
10
14
  } from "cloudflare:workers";
11
- import { ConfigProvider, Context, Effect, Layer, ManagedRuntime, type Scope } from "effect";
15
+ import { Context, Data, Effect, Layer, type ManagedRuntime, type Scope } from "effect";
12
16
 
13
- import { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "./Environment";
17
+ import { WorkerEnvironment, type WorkerEnv } from "./Environment";
14
18
  import { ExecutionContext, WorkerContext } from "./Worker";
15
19
  import * as WorkflowDefinition from "./WorkflowDefinition";
16
20
  import * as Entrypoint from "./internal/Entrypoint";
17
- import { fromExecutionContext, type RunWaitUntilEffect } from "./internal/WorkerContext";
21
+ import * as ErrorMessage from "./internal/ErrorMessage";
22
+ import * as Runtime from "./internal/Runtime";
23
+ import { fromExecutionContext } from "./internal/WorkerContext";
18
24
 
19
25
  export interface WorkflowEventService<Payload = unknown> {
20
26
  readonly raw: CloudflareWorkflowEvent<unknown>;
@@ -30,22 +36,41 @@ export class WorkflowEvent extends Context.Service<WorkflowEvent, WorkflowEventS
30
36
 
31
37
  type RunWorkflowStepEffect = <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>;
32
38
 
39
+ /** Failure raised when a Cloudflare Workflow step operation rejects. */
40
+ export class WorkflowStepError extends Data.TaggedError("WorkflowStepError")<{
41
+ readonly step?: string;
42
+ readonly operation: string;
43
+ readonly cause: unknown;
44
+ }> {
45
+ override get message(): string {
46
+ const step = this.step === undefined ? "" : ` in step "${this.step}"`;
47
+
48
+ return `Workflow ${this.operation} failed${step}: ${ErrorMessage.causeMessage(this.cause)}`;
49
+ }
50
+ }
51
+
33
52
  export interface WorkflowStepService {
34
53
  readonly raw: CloudflareWorkflowStep;
35
54
  do<A, E, R>(
36
55
  name: string,
37
56
  effect: Effect.Effect<A, E, R>,
38
57
  config?: WorkflowStepConfig,
39
- ): Effect.Effect<A, unknown, Exclude<R, WorkflowStepContext>>;
40
- readonly sleep: (name: string, duration: WorkflowSleepDuration) => Effect.Effect<void, unknown>;
41
- readonly sleepUntil: (name: string, timestamp: Date | number) => Effect.Effect<void, unknown>;
58
+ ): Effect.Effect<A, WorkflowStepError, Exclude<R, WorkflowStepContext>>;
59
+ readonly sleep: (
60
+ name: string,
61
+ duration: WorkflowSleepDuration,
62
+ ) => Effect.Effect<void, WorkflowStepError>;
63
+ readonly sleepUntil: (
64
+ name: string,
65
+ timestamp: Date | number,
66
+ ) => Effect.Effect<void, WorkflowStepError>;
42
67
  readonly waitForEvent: <Payload>(
43
68
  name: string,
44
69
  options: {
45
70
  readonly type: string;
46
71
  readonly timeout?: WorkflowTimeoutDuration | number;
47
72
  },
48
- ) => Effect.Effect<WorkflowStepEvent<Payload>, unknown>;
73
+ ) => Effect.Effect<WorkflowStepEvent<Payload>, WorkflowStepError>;
49
74
  }
50
75
 
51
76
  export class WorkflowStep extends Context.Service<WorkflowStep, WorkflowStepService>()(
@@ -104,19 +129,19 @@ const fromWorkflowStep = (
104
129
 
105
130
  return config === undefined ? rawStep.do(name, run) : rawStep.do(name, config, run);
106
131
  },
107
- catch: (cause) => cause,
132
+ catch: (cause) => new WorkflowStepError({ step: name, operation: "do", cause }),
108
133
  }),
109
134
  ),
110
- ) as Effect.Effect<A, unknown, Exclude<R, WorkflowStepContext>>,
135
+ ) as Effect.Effect<A, WorkflowStepError, Exclude<R, WorkflowStepContext>>,
111
136
  sleep: (name, duration) =>
112
137
  Effect.tryPromise({
113
138
  try: () => step.sleep(name, duration),
114
- catch: (cause) => cause,
139
+ catch: (cause) => new WorkflowStepError({ step: name, operation: "sleep", cause }),
115
140
  }),
116
141
  sleepUntil: (name, timestamp) =>
117
142
  Effect.tryPromise({
118
143
  try: () => step.sleepUntil(name, timestamp),
119
- catch: (cause) => cause,
144
+ catch: (cause) => new WorkflowStepError({ step: name, operation: "sleepUntil", cause }),
120
145
  }),
121
146
  waitForEvent: <Payload>(
122
147
  name: string,
@@ -127,7 +152,7 @@ const fromWorkflowStep = (
127
152
  ) =>
128
153
  Effect.tryPromise({
129
154
  try: () => step.waitForEvent(name, options) as Promise<WorkflowStepEvent<Payload>>,
130
- catch: (cause) => cause,
155
+ catch: (cause) => new WorkflowStepError({ step: name, operation: "waitForEvent", cause }),
131
156
  }),
132
157
  });
133
158
 
@@ -167,28 +192,18 @@ export const make = <ROut, LayerError, Payload = unknown, Result = unknown>(
167
192
  constructor(ctx: globalThis.ExecutionContext, env: WorkerEnv) {
168
193
  super(ctx, env);
169
194
 
170
- let runWaitUntilEffect: RunWaitUntilEffect = () =>
171
- Promise.reject(new Error("WorkerContext runtime is not initialized"));
172
-
173
- const services = Layer.mergeAll(
174
- Layer.succeed(ExecutionContext, ctx),
175
- ConfigProvider.layer(Effect.succeed(WorkerConfig.providerFromEnv(env))),
176
- Layer.succeed(
177
- WorkerContext,
178
- fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect)),
179
- ),
180
- Layer.succeed(WorkerEnvironment, env),
181
- ) as Layer.Layer<ExecutionContext | WorkerContext | WorkerEnvironment, never, never>;
182
-
183
- const runtimeLayer = Entrypoint.provideEntrypointServices<
195
+ this.runtime = Runtime.makeEntrypointRuntime<
184
196
  ROut,
185
197
  LayerError,
186
- ExecutionContext | WorkerContext | WorkerEnvironment
187
- >(layer, services);
188
-
189
- this.runtime = ManagedRuntime.make(runtimeLayer);
190
- runWaitUntilEffect = <A, E>(effect: Effect.Effect<A, E, never>) =>
191
- this.runtime.runPromiseExit(effect as Effect.Effect<A, E, RuntimeContext<ROut>>);
198
+ ExecutionContext | WorkerContext
199
+ >(
200
+ layer,
201
+ env,
202
+ Layer.mergeAll(
203
+ Layer.succeed(ExecutionContext, ctx),
204
+ Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
205
+ ),
206
+ );
192
207
  }
193
208
 
194
209
  run(
@@ -199,21 +214,11 @@ export const make = <ROut, LayerError, Payload = unknown, Result = unknown>(
199
214
  Layer.succeed(WorkflowEvent, fromWorkflowEvent(event)),
200
215
  Layer.succeed(
201
216
  WorkflowStep,
202
- fromWorkflowStep(
203
- step,
204
- (effect) =>
205
- this.runtime.runPromise(
206
- effect as Effect.Effect<unknown, unknown, RuntimeContext<ROut>>,
207
- ) as never,
208
- ),
217
+ fromWorkflowStep(step, (effect) => this.runtime.runPromise(effect)),
209
218
  ),
210
219
  );
211
220
 
212
- return this.runtime.runPromise(
213
- Effect.scoped(
214
- options.run(event.payload).pipe(Effect.provide(workflowServices)),
215
- ) as Effect.Effect<Result, unknown, RuntimeContext<ROut>>,
216
- );
221
+ return Runtime.runEventPromise(this.runtime, options.run(event.payload), workflowServices);
217
222
  }
218
223
  }
219
224
 
@@ -224,21 +229,21 @@ export const step = <A, E, R>(
224
229
  name: string,
225
230
  effect: Effect.Effect<A, E, R>,
226
231
  config?: WorkflowStepConfig,
227
- ): Effect.Effect<A, unknown, WorkflowStep | Exclude<R, WorkflowStepContext>> =>
232
+ ): Effect.Effect<A, WorkflowStepError, WorkflowStep | Exclude<R, WorkflowStepContext>> =>
228
233
  Effect.flatMap(WorkflowStep, (workflowStep) =>
229
234
  workflowStep.do(name, effect, config),
230
- ) as Effect.Effect<A, unknown, WorkflowStep | Exclude<R, WorkflowStepContext>>;
235
+ ) as Effect.Effect<A, WorkflowStepError, WorkflowStep | Exclude<R, WorkflowStepContext>>;
231
236
 
232
237
  export const sleep = (
233
238
  name: string,
234
239
  duration: WorkflowSleepDuration,
235
- ): Effect.Effect<void, unknown, WorkflowStep> =>
240
+ ): Effect.Effect<void, WorkflowStepError, WorkflowStep> =>
236
241
  Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.sleep(name, duration));
237
242
 
238
243
  export const sleepUntil = (
239
244
  name: string,
240
245
  timestamp: Date | number,
241
- ): Effect.Effect<void, unknown, WorkflowStep> =>
246
+ ): Effect.Effect<void, WorkflowStepError, WorkflowStep> =>
242
247
  Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.sleepUntil(name, timestamp));
243
248
 
244
249
  export const waitForEvent = <Payload>(
@@ -247,7 +252,7 @@ export const waitForEvent = <Payload>(
247
252
  readonly type: string;
248
253
  readonly timeout?: WorkflowTimeoutDuration | number;
249
254
  },
250
- ): Effect.Effect<WorkflowStepEvent<Payload>, unknown, WorkflowStep> =>
255
+ ): Effect.Effect<WorkflowStepEvent<Payload>, WorkflowStepError, WorkflowStep> =>
251
256
  Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.waitForEvent<Payload>(name, options));
252
257
 
253
258
  export type Definition<
@@ -8,6 +8,7 @@ import { type Context, Data, Effect, Option, Schema as S } from "effect";
8
8
 
9
9
  import * as Binding from "./Binding";
10
10
  import type * as RpcDefinition from "./RpcDefinition";
11
+ import * as ErrorMessage from "./internal/ErrorMessage";
11
12
 
12
13
  const expectedWorkflow = "Workflow binding with create(), createBatch(), and get()";
13
14
 
@@ -92,20 +93,28 @@ export interface WorkflowBindingClient<
92
93
  readonly get: (
93
94
  instanceId: string,
94
95
  ) => Effect.Effect<WorkflowInstance<S.Schema.Type<Result>>, WorkflowOperationError>;
95
- readonly unsafeRaw: Effect.Effect<CloudflareWorkflow<S.Codec.Encoded<Payload>>>;
96
+ readonly rawUnsafe: Effect.Effect<CloudflareWorkflow<S.Codec.Encoded<Payload>>>;
96
97
  }
97
98
 
98
99
  export class WorkflowOperationError extends Data.TaggedError("WorkflowOperationError")<{
99
100
  readonly binding: string;
100
101
  readonly operation: string;
101
102
  readonly cause: unknown;
102
- }> {}
103
+ }> {
104
+ override get message(): string {
105
+ return `Workflow ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
106
+ }
107
+ }
103
108
 
104
109
  export class WorkflowResultDecodeError extends Data.TaggedError("WorkflowResultDecodeError")<{
105
110
  readonly binding: string;
106
111
  readonly instanceId: string;
107
112
  readonly cause: unknown;
108
- }> {}
113
+ }> {
114
+ override get message(): string {
115
+ return `Workflow result decode failed for binding "${this.binding}" instance "${this.instanceId}": ${ErrorMessage.causeMessage(this.cause)}`;
116
+ }
117
+ }
109
118
 
110
119
  const workflowError = (binding: string, operation: string, cause: unknown) =>
111
120
  new WorkflowOperationError({ binding, operation, cause });
@@ -200,10 +209,9 @@ export const makeClient = <
200
209
  };
201
210
 
202
211
  return (workflow) => ({
203
- create: Effect.fnUntraced(function* (
204
- payload: PayloadValue,
205
- options?: WorkflowInstanceCreateOptions<EncodedPayload>,
206
- ) {
212
+ create: Effect.fn("WorkflowBinding.create", {
213
+ attributes: { binding: definition.binding, operation: "create" },
214
+ })(function* (payload: PayloadValue, options?: WorkflowInstanceCreateOptions<EncodedPayload>) {
207
215
  const encoded = yield* encodePayload(payload);
208
216
  const raw = yield* tryWorkflowPromise(definition.binding, "create", () =>
209
217
  workflow.create({ ...options, params: encoded }),
@@ -211,9 +219,9 @@ export const makeClient = <
211
219
 
212
220
  return wrapInstance(raw);
213
221
  }),
214
- createBatch: Effect.fnUntraced(function* (
215
- batch: WorkflowInstanceCreateBatchOptions<PayloadValue, EncodedPayload>,
216
- ) {
222
+ createBatch: Effect.fn("WorkflowBinding.createBatch", {
223
+ attributes: { binding: definition.binding, operation: "createBatch" },
224
+ })(function* (batch: WorkflowInstanceCreateBatchOptions<PayloadValue, EncodedPayload>) {
217
225
  const encodedBatch: Array<CloudflareWorkflowInstanceCreateOptions<EncodedPayload>> = [];
218
226
 
219
227
  for (const item of batch) {
@@ -235,7 +243,7 @@ export const makeClient = <
235
243
  tryWorkflowPromise(definition.binding, "get", () => workflow.get(instanceId)).pipe(
236
244
  Effect.map(wrapInstance),
237
245
  ),
238
- unsafeRaw: Effect.succeed(workflow),
246
+ rawUnsafe: Effect.succeed(workflow),
239
247
  });
240
248
  };
241
249
 
@@ -46,7 +46,11 @@ export interface TagClass<
46
46
  Id extends string,
47
47
  Payload extends RpcDefinition.ServiceFreeSchema,
48
48
  Result extends RpcDefinition.ServiceFreeSchema,
49
- > extends Context.ServiceClass<Self, Id, WorkflowBinding.WorkflowBindingClient<Payload, Result>> {
49
+ > extends Context.ServiceClass<
50
+ Self,
51
+ `effect-cf/Workflow/${Id}`,
52
+ WorkflowBinding.WorkflowBindingClient<Payload, Result>
53
+ > {
50
54
  readonly id: Id;
51
55
  readonly payload: Payload;
52
56
  readonly result: Result;
@@ -86,11 +90,7 @@ export interface TagClass<
86
90
  WorkflowBinding.WorkflowOperationError,
87
91
  Self
88
92
  >;
89
- readonly unsafeRaw: () => Effect.Effect<
90
- globalThis.Workflow<S.Codec.Encoded<Payload>>,
91
- never,
92
- Self
93
- >;
93
+ readonly rawUnsafe: Effect.Effect<globalThis.Workflow<S.Codec.Encoded<Payload>>, never, Self>;
94
94
  }
95
95
 
96
96
  const makeDefinition = <
@@ -148,7 +148,9 @@ export const Tag =
148
148
  },
149
149
  ) => {
150
150
  const workflowDefinition = makeDefinition(id, definition);
151
- const tag = Context.Service<Self, WorkflowBinding.WorkflowBindingClient<Payload, Result>>()(id);
151
+ const tag = Context.Service<Self, WorkflowBinding.WorkflowBindingClient<Payload, Result>>()(
152
+ `effect-cf/Workflow/${id}` as const,
153
+ );
152
154
 
153
155
  const layer = (binding: LayerOptions) =>
154
156
  WorkflowBinding.layer(tag, {
@@ -183,11 +185,7 @@ export const Tag =
183
185
  return yield* workflow.get(instanceId);
184
186
  });
185
187
 
186
- const unsafeRaw = Effect.fnUntraced(function* () {
187
- const workflow = yield* tag;
188
-
189
- return yield* workflow.unsafeRaw;
190
- });
188
+ const rawUnsafe = Effect.flatMap(tag, (workflow) => workflow.rawUnsafe);
191
189
 
192
190
  return Object.assign(tag, {
193
191
  id: workflowDefinition.id,
@@ -198,7 +196,7 @@ export const Tag =
198
196
  create,
199
197
  createBatch,
200
198
  get,
201
- unsafeRaw,
199
+ rawUnsafe,
202
200
  }) as TagClass<Self, Id, Payload, Result>;
203
201
  };
204
202
 
@@ -215,20 +213,19 @@ const wrapHandler = <ROut, const Self extends Definition.Any>(
215
213
  const decodePayload = S.decodeUnknownEffect(definition.payload);
216
214
  const encodeResult = S.encodeEffect(definition.result);
217
215
 
218
- return (payload) =>
219
- Effect.gen(function* () {
220
- const decodedPayload = yield* decodePayload(payload);
221
- const event = yield* WorkflowEntrypoint.WorkflowEvent;
222
- const decodedEvent = {
223
- ...event,
224
- payload: decodedPayload,
225
- } as WorkflowEntrypoint.WorkflowEventService<S.Schema.Type<Self["payload"]>>;
226
- const result = yield* handler(decodedPayload as S.Schema.Type<Self["payload"]>).pipe(
227
- Effect.provideService(WorkflowEntrypoint.WorkflowEvent, decodedEvent),
228
- );
229
-
230
- return yield* encodeResult(result as S.Schema.Type<Self["result"]>);
231
- });
216
+ return Effect.fnUntraced(function* (payload: S.Codec.Encoded<Self["payload"]>) {
217
+ const decodedPayload = yield* decodePayload(payload);
218
+ const event = yield* WorkflowEntrypoint.WorkflowEvent;
219
+ const decodedEvent = {
220
+ ...event,
221
+ payload: decodedPayload,
222
+ } as WorkflowEntrypoint.WorkflowEventService<S.Schema.Type<Self["payload"]>>;
223
+ const result = yield* handler(decodedPayload as S.Schema.Type<Self["payload"]>).pipe(
224
+ Effect.provideService(WorkflowEntrypoint.WorkflowEvent, decodedEvent),
225
+ );
226
+
227
+ return yield* encodeResult(result as S.Schema.Type<Self["result"]>);
228
+ });
232
229
  };
233
230
 
234
231
  export const implement = <ROut, const Self extends Definition.Any>(
package/src/index.ts CHANGED
@@ -13,9 +13,10 @@ export * as DurableObjectNamespace from "./DurableObjectNamespace";
13
13
  export * as DurableObjectRpcWebSocket from "./DurableObjectRpcWebSocket";
14
14
  export * as DurableObjectSqlite from "./DurableObjectSqlite";
15
15
  export * as DurableObjectState from "./DurableObjectState";
16
- export * as DurableObjectWebSocket from "./DurableObjectWebSocket";
17
16
  export * as DurableObjectStorage from "./DurableObjectStorage";
17
+ export * as DurableObjectWebSocket from "./DurableObjectWebSocket";
18
18
  export * as Email from "./Email";
19
+ export * as Environment from "./Environment";
19
20
  export * as Hyperdrive from "./Hyperdrive";
20
21
  export * as Images from "./Images";
21
22
  export * as Kv from "./Kv";
@@ -26,11 +27,11 @@ export * as R2 from "./R2";
26
27
  export * as Rpc from "./Rpc";
27
28
  export * as RpcDefinition from "./RpcDefinition";
28
29
  export * as ServiceBinding from "./ServiceBinding";
30
+ export * as Vectorize from "./Vectorize";
29
31
  export * as Worker from "./Worker";
30
32
  export * as WorkerDefinition from "./WorkerDefinition";
33
+ export * as WorkersAi from "./WorkersAi";
31
34
  export * as Workflow from "./Workflow";
32
35
  export * as WorkflowBinding from "./WorkflowBinding";
33
36
  export * as WorkflowDefinition from "./WorkflowDefinition";
34
- export * as Vectorize from "./Vectorize";
35
- export * as WorkersAi from "./WorkersAi";
36
37
  export { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "./Environment";
@@ -30,7 +30,9 @@ export const defineEntrypointRpcMethods = <Self>(
30
30
  value(this: Self, ...args: AnyArgs) {
31
31
  return run(
32
32
  this,
33
- Effect.suspend(() => method(...args)),
33
+ Effect.suspend(() => method(...args)).pipe(
34
+ Effect.mapError(RpcDefinition.encodeWireError),
35
+ ),
34
36
  );
35
37
  },
36
38
  });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Renders the most useful human-readable description of an unknown `cause`
3
+ * for use in tagged error `message` getters.
4
+ */
5
+ export const causeMessage = (cause: unknown): string => {
6
+ if (typeof cause === "object" && cause !== null) {
7
+ const message = Reflect.get(cause, "message");
8
+
9
+ if (typeof message === "string" && message.length > 0) {
10
+ return message;
11
+ }
12
+ }
13
+
14
+ try {
15
+ return String(cause);
16
+ } catch {
17
+ return "unknown cause";
18
+ }
19
+ };
20
+
21
+ /** Renders a violation list as a single `path: message; ...` line. */
22
+ export const violationsMessage = (
23
+ violations: ReadonlyArray<{ readonly path: string; readonly message: string }>,
24
+ ): string => violations.map((violation) => `${violation.path}: ${violation.message}`).join("; ");
@@ -58,17 +58,20 @@ export const lookupRpcMethod = <Api, Method extends AsyncMethodKey<Api>, Error>(
58
58
  catch: makeError,
59
59
  });
60
60
 
61
- export const invokeRpcMethod = <Api, Method extends AsyncMethodKey<Api>, Error>(
61
+ export const invokeRpcMethod = Effect.fnUntraced(function* <
62
+ Api,
63
+ Method extends AsyncMethodKey<Api>,
64
+ Error,
65
+ >(
62
66
  target: unknown,
63
67
  method: Method,
64
68
  args: AsyncMethodArgs<Api, Method>,
65
69
  makeError: (cause: unknown) => Error,
66
- ): Effect.Effect<AsyncMethodCloudflareReturn<Api, Method>, Error> =>
67
- Effect.gen(function* () {
68
- const fn = yield* lookupRpcMethod<Api, Method, Error>(target, method, makeError);
70
+ ): Effect.fn.Return<AsyncMethodCloudflareReturn<Api, Method>, Error> {
71
+ const fn = yield* lookupRpcMethod<Api, Method, Error>(target, method, makeError);
69
72
 
70
- return yield* Effect.try({
71
- try: () => fn(...args),
72
- catch: makeError,
73
- });
73
+ return yield* Effect.try({
74
+ try: () => fn(...args),
75
+ catch: makeError,
74
76
  });
77
+ });
@@ -0,0 +1,52 @@
1
+ import { ConfigProvider, Effect, Layer, ManagedRuntime, type Scope } from "effect";
2
+
3
+ import { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "../Environment";
4
+ import { provideEntrypointServices } from "./Entrypoint";
5
+
6
+ /**
7
+ * Assembles the shared `ManagedRuntime` used by the Cloudflare entrypoint
8
+ * classes (Worker, Durable Object, Workflow).
9
+ *
10
+ * The entrypoint-specific services are merged with the env-backed
11
+ * `ConfigProvider` and `WorkerEnvironment`, then provided to the user-supplied
12
+ * layer so the resulting runtime satisfies both the user services and the
13
+ * platform services.
14
+ */
15
+ export const makeEntrypointRuntime = <ROut, LayerError, Services>(
16
+ layer: Layer.Layer<ROut, LayerError, Services | WorkerEnvironment>,
17
+ env: WorkerEnv,
18
+ services: Layer.Layer<Services>,
19
+ ): ManagedRuntime.ManagedRuntime<ROut | Services | WorkerEnvironment, LayerError> => {
20
+ const entrypointServices = Layer.mergeAll(
21
+ services,
22
+ ConfigProvider.layer(WorkerConfig.providerFromEnv(env)),
23
+ Layer.succeed(WorkerEnvironment, env),
24
+ );
25
+
26
+ return ManagedRuntime.make(provideEntrypointServices(layer, entrypointServices));
27
+ };
28
+
29
+ /**
30
+ * Runs a Cloudflare event effect on an entrypoint runtime.
31
+ *
32
+ * When an event layer is given it is built inside the event's Effect scope
33
+ * (finalized when the event effect completes). The casts widen away the
34
+ * `REvent` requirement satisfied by the event layer; TypeScript cannot reduce
35
+ * `Exclude<...>` over the generic union.
36
+ */
37
+ export const runEventPromise = <A, E, R, REvent, EventLayerError, LayerError>(
38
+ runtime: ManagedRuntime.ManagedRuntime<R, LayerError>,
39
+ effect: Effect.Effect<A, E, R | REvent | Scope.Scope>,
40
+ eventLayer: Layer.Layer<REvent, EventLayerError, R> | undefined,
41
+ ): Promise<A> => {
42
+ const withEventLayer =
43
+ eventLayer === undefined
44
+ ? (effect as Effect.Effect<A, E, R | Scope.Scope>)
45
+ : (effect.pipe(Effect.provide(eventLayer, { local: true })) as Effect.Effect<
46
+ A,
47
+ E | EventLayerError,
48
+ R | Scope.Scope
49
+ >);
50
+
51
+ return runtime.runPromise(Effect.scoped(withEventLayer));
52
+ };
@@ -6,7 +6,11 @@ export type RunWaitUntilEffect = <A, E>(
6
6
  effect: Effect.Effect<A, E, never>,
7
7
  ) => Promise<Exit.Exit<A, unknown>>;
8
8
 
9
- const causeError = <E>(cause: Cause.Cause<E>) => new Error(Cause.pretty(cause));
9
+ const causeError = <E>(cause: Cause.Cause<E>) => {
10
+ const squashed = Cause.squash(cause);
11
+
12
+ return squashed instanceof Error ? squashed : new Error(Cause.pretty(cause), { cause: squashed });
13
+ };
10
14
 
11
15
  const failureHandler = <E, R>(
12
16
  cause: Cause.Cause<E>,
@@ -25,9 +29,17 @@ const failureHandler = <E, R>(
25
29
  ),
26
30
  );
27
31
 
32
+ /**
33
+ * Builds the `WorkerContext` service from a native `ExecutionContext`.
34
+ *
35
+ * Background effects capture the calling fiber's context via `Effect.context`
36
+ * before being handed to `ctx.waitUntil`, so the default module-level
37
+ * `Effect.runPromiseExit` runner is sufficient; entrypoints may still pass a
38
+ * runtime-bound runner.
39
+ */
28
40
  export const fromExecutionContext = (
29
41
  ctx: globalThis.ExecutionContext,
30
- runPromiseExit: RunWaitUntilEffect,
42
+ runPromiseExit: RunWaitUntilEffect = (effect) => Effect.runPromiseExit(effect),
31
43
  ): WorkerContextService => {
32
44
  const schedule = <A, E, R, R2 = never>(
33
45
  effect: Effect.Effect<A, E, R>,