effect-cf 0.34.0 → 0.36.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.
package/src/Worker.ts CHANGED
@@ -5,14 +5,20 @@ import {
5
5
  Effect,
6
6
  Layer,
7
7
  type ManagedRuntime,
8
+ References,
8
9
  type Scope,
9
10
  Tracer,
10
11
  } from "effect";
11
- import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
12
+ import {
13
+ HttpEffect,
14
+ HttpMiddleware,
15
+ HttpServerRequest,
16
+ HttpServerResponse,
17
+ } from "effect/unstable/http";
12
18
 
13
19
  import { WorkerEnvironment, type WorkerEnv } from "./Environment";
14
20
  import { fromMessage, fromMessageBatch, type QueueHandler } from "./Queue";
15
- import type * as RpcDefinition from "./RpcDefinition";
21
+ import * as RpcDefinition from "./RpcDefinition";
16
22
  import type { ReceiverOptions, RpcInvocationInfo } from "./RpcTracing";
17
23
  import * as Entrypoint from "./internal/Entrypoint";
18
24
  import * as Runtime from "./internal/Runtime";
@@ -75,20 +81,13 @@ export type ReservedMethodName =
75
81
  | "webSocketError";
76
82
 
77
83
  const reservedMethodNames = new Set<string>([
78
- "constructor",
79
- "dup",
80
- "fetch",
81
- "connect",
84
+ ...RpcDefinition.reservedMethodNames,
82
85
  "queue",
83
86
  "scheduled",
84
87
  "tail",
85
88
  "tailStream",
86
89
  "test",
87
90
  "trace",
88
- "alarm",
89
- "webSocketMessage",
90
- "webSocketClose",
91
- "webSocketError",
92
91
  ]);
93
92
 
94
93
  type WorkerBaseContext<ROut> = ExecutionContext | WorkerContext | WorkerEnvironment | ROut;
@@ -164,10 +163,12 @@ export interface WorkerOptions<
164
163
  /**
165
164
  * Layer provided around each Cloudflare event handled by this Worker.
166
165
  *
167
- * The layer is built inside the event's Effect scope and finalized when the
168
- * event effect completes. Use this for event-scoped resources such as OTLP
169
- * trace/log exporters. Worker fetch and RPC handlers schedule a best-effort
170
- * flush capped at two seconds; queue handlers do not flush automatically.
166
+ * The layer is built inside the event's Effect scope. For fetch handlers that
167
+ * return an Effect `HttpServerResponse` stream, its scope remains open until
168
+ * the response body finishes or is canceled. Native `Response` values pass
169
+ * through unchanged, so their body streams do not carry this scoped-lifetime
170
+ * guarantee. Fetch, queue, and RPC handlers schedule a best-effort telemetry
171
+ * flush capped at two seconds.
171
172
  */
172
173
  readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
173
174
  readonly fetch?: Effect.Effect<
@@ -238,9 +239,14 @@ export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
238
239
  ) => Promise<Response>;
239
240
  }
240
241
 
241
- const renderFetchSuccess = <E, R>(
242
+ const renderFetchSuccess = <E, R, REvent, EventLayerError, EventLayerRequirements>(
242
243
  effect: Effect.Effect<WorkerFetchSuccess, E, R>,
243
- ): Effect.Effect<Response, E, Exclude<R, Scope.Scope> | HttpServerRequest.HttpServerRequest> =>
244
+ eventLayer: Layer.Layer<REvent, EventLayerError, EventLayerRequirements> | undefined,
245
+ ): Effect.Effect<
246
+ Response,
247
+ E | EventLayerError,
248
+ Exclude<R | EventLayerRequirements, Scope.Scope> | HttpServerRequest.HttpServerRequest
249
+ > =>
244
250
  Effect.suspend(() => {
245
251
  // Native `Response` values bypass all HTTP response processing: WebSocket
246
252
  // upgrade responses and app-level `HttpEffect.toHandled` results pass
@@ -249,26 +255,64 @@ const renderFetchSuccess = <E, R>(
249
255
  let nativeResponse: Response | undefined;
250
256
  let webResponse: Response | undefined;
251
257
 
252
- const handled = HttpEffect.toHandled(
253
- Effect.flatMap(effect, (response) => {
254
- if (response instanceof Response) {
255
- nativeResponse = response;
258
+ const httpApp = Effect.flatMap(effect, (response) => {
259
+ if (response instanceof Response) {
260
+ nativeResponse = response;
256
261
 
257
- return Effect.succeed(HttpServerResponse.empty());
258
- }
262
+ return Effect.succeed(HttpServerResponse.empty());
263
+ }
259
264
 
260
- return Effect.succeed(response);
265
+ return Effect.succeed(response);
266
+ });
267
+ const handleResponse = (
268
+ request: HttpServerRequest.HttpServerRequest,
269
+ response: HttpServerResponse.HttpServerResponse,
270
+ ) =>
271
+ nativeResponse !== undefined
272
+ ? Effect.void
273
+ : Effect.map(Effect.context<never>(), (context) => {
274
+ webResponse = HttpServerResponse.toWeb(HttpEffect.scopeTransferToStream(response), {
275
+ withoutBody: request.method === "HEAD",
276
+ context,
277
+ });
278
+ });
279
+ const telemetryMiddleware = HttpMiddleware.make((self) =>
280
+ Effect.gen(function* () {
281
+ yield* Effect.addFinalizer(() => scheduleTelemetryFlush);
282
+
283
+ return yield* self;
261
284
  }),
262
- (request, response) =>
263
- nativeResponse !== undefined
264
- ? Effect.void
265
- : Effect.map(Effect.context<never>(), (context) => {
266
- webResponse = HttpServerResponse.toWeb(HttpEffect.scopeTransferToStream(response), {
267
- withoutBody: request.method === "HEAD",
268
- context,
269
- });
270
- }),
271
285
  );
286
+ const handled =
287
+ eventLayer === undefined
288
+ ? HttpEffect.toHandled(httpApp, handleResponse, telemetryMiddleware)
289
+ : Effect.flatMap(References.TracerEnabled, (tracerEnabled) =>
290
+ HttpEffect.toHandled(
291
+ httpApp,
292
+ handleResponse,
293
+ HttpMiddleware.make((self) =>
294
+ Effect.provideService(
295
+ Effect.gen(function* () {
296
+ const context = yield* Layer.build(Layer.fresh(eventLayer));
297
+
298
+ yield* Effect.addFinalizer(() =>
299
+ Effect.provideContext(scheduleTelemetryFlush, context),
300
+ );
301
+
302
+ return yield* HttpMiddleware.tracer(self).pipe(
303
+ // The tracer schedules span completion. Let it publish on
304
+ // every exit with event references still installed, before
305
+ // the exporter scope finalizes.
306
+ Effect.onExit(() => Effect.yieldNow),
307
+ Effect.provideContext(context),
308
+ );
309
+ }),
310
+ References.TracerEnabled,
311
+ tracerEnabled,
312
+ ),
313
+ ),
314
+ ).pipe(Effect.withTracerEnabled(false)),
315
+ );
272
316
 
273
317
  // `toHandled` re-fails with the original cause after rendering the error
274
318
  // response, so both branches return the response captured above.
@@ -282,13 +326,13 @@ const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<RO
282
326
  ): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> => !Effect.isEffect(options);
283
327
 
284
328
  /**
285
- * Drains buffered OTLP telemetry once the fetch handler has produced its
286
- * response.
329
+ * Drains buffered OTLP telemetry when the fetch Effect scope closes. Effect
330
+ * response streams transfer that scope to the body and schedule the flush when
331
+ * the body finishes or is canceled.
287
332
  *
288
- * The flush is handed to `ctx.waitUntil` so it never delays the response
289
- * (including streaming bodies still being consumed). It is a no-op when the
290
- * context does not contain an `OtlpExporter.Flusher`. The handed-off effect is
291
- * best-effort and silently settles within two seconds.
333
+ * The flush is handed to `ctx.waitUntil` instead of being awaited by the
334
+ * handler. It is a no-op when the context does not contain an
335
+ * `OtlpExporter.Flusher`, and silently settles within two seconds.
292
336
  */
293
337
  const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
294
338
  Telemetry.scheduleTelemetryFlush((flush) =>
@@ -296,8 +340,10 @@ const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> =
296
340
  );
297
341
 
298
342
  /**
299
- * The layer is built once per Worker instance and reused for its events.
300
- * Eviction skips finalizers, so use `eventLayer` for per-event resources.
343
+ * Cloudflare creates a new `WorkerEntrypoint` instance for each native
344
+ * invocation, so this base layer is not shared across invocations. Its managed
345
+ * runtime has no deterministic disposal point in this adapter; use
346
+ * `eventLayer` for resources whose finalizers must run with an event.
301
347
  */
302
348
  export function make<ROut, LayerError>(
303
349
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
@@ -361,12 +407,14 @@ export function make<
361
407
  effect: Effect.Effect<A, E, RuntimeContext<ROut> | REvent | Scope.Scope>,
362
408
  runOptions: RunOptions = {},
363
409
  ): Promise<A> {
364
- const eventLayer = options.eventLayer;
410
+ // Fetch builds its event layer in the HTTP scope transferred to the body.
411
+ const eventLayer = runOptions.event === "fetch" ? undefined : options.eventLayer;
365
412
  const parent = runOptions.rpc?.parent;
366
413
  const parentSpan = parent === undefined ? undefined : Tracer.externalSpan(parent);
367
414
 
368
415
  if (eventLayer === undefined) {
369
- // SAFETY: the public make overloads permit an absent event layer only when REvent is never.
416
+ // SAFETY: fetch provides REvent inside its HTTP middleware; otherwise the
417
+ // public make overloads permit an absent event layer only when REvent is never.
370
418
  return Runtime.runEventPromise(
371
419
  this.runtime,
372
420
  // @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
@@ -408,12 +456,11 @@ export function make<
408
456
  Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
409
457
  );
410
458
 
411
- // SAFETY: requestServices provides the HTTP/request contexts introduced by renderFetchSuccess.
459
+ // The request services wrap eventLayer construction so cached object handlers
460
+ // never build an event layer against a previous request's ExecutionContext.
461
+ // HttpEffect owns the inner scope and transfers it to streaming response bodies.
412
462
  return this[RunSymbol](
413
- renderFetchSuccess(fetchHandler).pipe(
414
- Effect.tap(() => scheduleTelemetryFlush),
415
- Effect.provide(requestServices),
416
- ) as Effect.Effect<Response, unknown, RuntimeContext<ROut> | REvent | Scope.Scope>,
463
+ renderFetchSuccess(fetchHandler, options.eventLayer).pipe(Effect.provide(requestServices)),
417
464
  { event: "fetch" },
418
465
  );
419
466
  }
@@ -427,7 +474,12 @@ export function make<
427
474
 
428
475
  const messages = batch.messages.map((message) => fromMessage(message, message.body));
429
476
 
430
- return this[RunSymbol](queueHandler(fromMessageBatch(batch, messages)), { event: "queue" });
477
+ return this[RunSymbol](
478
+ queueHandler(fromMessageBatch(batch, messages)).pipe(
479
+ Effect.onExit(() => scheduleTelemetryFlush),
480
+ ),
481
+ { event: "queue" },
482
+ );
431
483
  }
432
484
  }
433
485
 
@@ -452,12 +504,13 @@ interface FetchCapableInstance {
452
504
  * Creates an `ExportedHandler`-compatible `{ fetch }` object backed by
453
505
  * {@link make}.
454
506
  *
455
- * The Effect runtime is cached per `env` identity, so the user layer builds
456
- * once per isolate instead of once per request. `ExecutionContext`-backed
457
- * services (`Worker.ExecutionContext`, `Worker.WorkerContext`) are still
458
- * provided fresh for every request. Layer finalizers do not run when the
459
- * isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
460
- * for resources that must be finalized per event.
507
+ * The Effect runtime is cached per `env` identity, so the base layer builds
508
+ * once for that cached handler. Request handlers and `eventLayer` receive fresh
509
+ * `ExecutionContext` and `WorkerContext` services. A service constructed by the
510
+ * cached base layer from either context instead captures the first request's
511
+ * value, so construct invocation-bound services in `eventLayer`. Cloudflare
512
+ * provides no isolate shutdown hook, so cached base-layer finalizers are not
513
+ * guaranteed to run.
461
514
  */
462
515
  export function makeFetchHandler<
463
516
  ROut,
package/src/WorkersAi.ts CHANGED
@@ -186,7 +186,7 @@ export const embeddingResponse = (value: {
186
186
  [embeddingDimensionsKey]: value[embeddingDimensionsKey] ?? [],
187
187
  });
188
188
 
189
- const decodeEmbeddingResponse = S.decodeUnknownOption(
189
+ const decodeEmbeddingResponse = S.decodeUnknownEffect(
190
190
  S.Struct({
191
191
  data: S.optional(S.Array(S.Array(S.Number))),
192
192
  [embeddingDimensionsKey]: S.optional(S.Array(S.Number)),
@@ -212,21 +212,28 @@ export const makeClient =
212
212
  () => ai.aiGatewayLogId,
213
213
  ),
214
214
  run,
215
- runEmbedding: Effect.fn("WorkersAi.runEmbedding")(
216
- (model: string, input: WorkersAiEmbeddingInput, options?: WorkersAiOptions) => {
217
- // SAFETY: every WorkersAiEmbeddingInput variant is a string-keyed Workers AI payload.
218
- const modelInput = input as Parameters<WorkersAiBinding["run"]>[1];
215
+ runEmbedding: Effect.fn("WorkersAi.runEmbedding")(function* (
216
+ model: string,
217
+ input: WorkersAiEmbeddingInput,
218
+ options?: WorkersAiOptions,
219
+ ) {
220
+ // SAFETY: every WorkersAiEmbeddingInput variant is a string-keyed Workers AI payload.
221
+ const modelInput = input as Parameters<WorkersAiBinding["run"]>[1];
219
222
 
220
- return run(model, modelInput, options).pipe(
221
- Effect.map((response) =>
222
- Option.match(decodeEmbeddingResponse(response), {
223
- onNone: () => embeddingResponse({}),
224
- onSome: embeddingResponse,
223
+ const response = yield* run(model, modelInput, options);
224
+ const decoded = yield* decodeEmbeddingResponse(response).pipe(
225
+ Effect.mapError(
226
+ (cause) =>
227
+ new WorkersAiOperationError({
228
+ binding: definition.binding,
229
+ operation: "runEmbedding",
230
+ cause,
225
231
  }),
226
- ),
227
- );
228
- },
229
- ),
232
+ ),
233
+ );
234
+
235
+ return embeddingResponse(decoded);
236
+ }),
230
237
  models: Effect.fn("WorkersAi.models")((params?: WorkersAiModelsSearchParams) =>
231
238
  tryWorkersAiPromise(definition.binding, "models", () => ai.models(params)),
232
239
  ),
package/src/Workflow.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  Context,
15
15
  Data,
16
16
  Effect,
17
+ FiberSet,
17
18
  Layer,
18
19
  type ManagedRuntime,
19
20
  Option,
@@ -40,8 +41,6 @@ export class WorkflowEvent extends Context.Service<WorkflowEvent, WorkflowEventS
40
41
  "effect-cf/WorkflowEvent",
41
42
  ) {}
42
43
 
43
- type RunWorkflowStepEffect = <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>;
44
-
45
44
  export class WorkflowStepError extends Data.TaggedError("WorkflowStepError")<{
46
45
  readonly step?: string;
47
46
  readonly operation: string;
@@ -135,52 +134,52 @@ const exposeCloudflareNonRetryableError = <A, E, R>(
135
134
  : Effect.failCause(cause);
136
135
  });
137
136
 
138
- const fromWorkflowStep = (
139
- step: CloudflareWorkflowStep,
140
- runPromise: RunWorkflowStepEffect,
141
- ): WorkflowStepService => ({
137
+ const fromWorkflowStep = (step: CloudflareWorkflowStep): WorkflowStepService => ({
142
138
  raw: step,
143
139
  do: <A, E, R>(name: string, effect: Effect.Effect<A, E, R>, config?: WorkflowStepConfig) =>
144
- Effect.context<Exclude<R, WorkflowStepContext>>().pipe(
145
- Effect.flatMap((context) =>
146
- Effect.tryPromise({
147
- try: () => {
148
- const run = (stepContext: CloudflareWorkflowStepContext) =>
149
- runPromise(
150
- exposeCloudflareNonRetryableError(
151
- Effect.scoped(
152
- Effect.provideService(
153
- Effect.provideContext(
154
- // SAFETY: WorkflowStepContext is provided immediately below; context supplies every other R service.
155
- // @effect-diagnostics-next-line unsafeEffectTypeAssertion:off
156
- effect as Effect.Effect<A, E, Exclude<R, WorkflowStepContext>>,
157
- context,
158
- ),
159
- WorkflowStepContext,
160
- stepContext,
161
- ),
162
- ),
163
- ),
164
- );
165
- // SAFETY: these overloads reproduce the Cloudflare WorkflowStep.do runtime signatures used here.
166
- const rawStep = step as {
167
- do(
168
- name: string,
169
- callback: (context: CloudflareWorkflowStepContext) => Promise<A>,
170
- ): Promise<A>;
171
- do(
172
- name: string,
173
- config: WorkflowStepConfig,
174
- callback: (context: CloudflareWorkflowStepContext) => Promise<A>,
175
- ): Promise<A>;
176
- };
177
-
178
- return config === undefined ? rawStep.do(name, run) : rawStep.do(name, config, run);
179
- },
180
- catch: (cause) => new WorkflowStepError({ step: name, operation: "do", cause }),
140
+ Effect.gen(function* () {
141
+ const context = yield* Effect.context<Exclude<R, WorkflowStepContext>>();
142
+
143
+ return yield* Effect.scoped(
144
+ Effect.gen(function* () {
145
+ // Cloudflare owns retry timing and exposes no step cancellation API.
146
+ // Join the current callback on interruption and prevent later retry
147
+ // callbacks from starting, without waiting for native retry delays.
148
+ const runPromise = yield* FiberSet.makeRuntimePromise<never, A, E>();
149
+
150
+ return yield* Effect.tryPromise({
151
+ try: () => {
152
+ const run = (stepContext: CloudflareWorkflowStepContext) => {
153
+ const stepEffect: Effect.Effect<
154
+ A,
155
+ E,
156
+ Exclude<R, WorkflowStepContext>
157
+ > = Effect.scoped(Effect.provideService(effect, WorkflowStepContext, stepContext));
158
+
159
+ return runPromise(
160
+ exposeCloudflareNonRetryableError(Effect.provideContext(stepEffect, context)),
161
+ );
162
+ };
163
+ // SAFETY: these overloads reproduce the Cloudflare WorkflowStep.do runtime signatures used here.
164
+ const rawStep = step as {
165
+ do(
166
+ name: string,
167
+ callback: (context: CloudflareWorkflowStepContext) => Promise<A>,
168
+ ): Promise<A>;
169
+ do(
170
+ name: string,
171
+ config: WorkflowStepConfig,
172
+ callback: (context: CloudflareWorkflowStepContext) => Promise<A>,
173
+ ): Promise<A>;
174
+ };
175
+
176
+ return config === undefined ? rawStep.do(name, run) : rawStep.do(name, config, run);
177
+ },
178
+ catch: (cause) => new WorkflowStepError({ step: name, operation: "do", cause }),
179
+ });
181
180
  }),
182
- ),
183
- ),
181
+ );
182
+ }),
184
183
  sleep: (name, duration) =>
185
184
  Effect.tryPromise({
186
185
  try: () => step.sleep(name, duration),
@@ -263,10 +262,7 @@ export const make = <ROut, LayerError, Payload = unknown, Result = unknown>(
263
262
  ): Promise<Result> {
264
263
  const workflowServices = Layer.mergeAll(
265
264
  Layer.succeed(WorkflowEvent, fromWorkflowEvent(event)),
266
- Layer.succeed(
267
- WorkflowStep,
268
- fromWorkflowStep(step, (effect) => this.runtime.runPromise(effect)),
269
- ),
265
+ Layer.succeed(WorkflowStep, fromWorkflowStep(step)),
270
266
  );
271
267
 
272
268
  return Runtime.runEventPromise(this.runtime, options.run(event.payload), workflowServices);
@@ -0,0 +1,55 @@
1
+ import { Effect, Exit, FiberSet } from "effect";
2
+
3
+ type RunCallback<A, E, R> = (effect: Effect.Effect<A, E, R>) => Promise<Exit.Exit<A, E>>;
4
+
5
+ /**
6
+ * Runs an Effect callback owned by a native Promise operation.
7
+ *
8
+ * The callback keeps the caller's context, while a private scope owns its
9
+ * fiber. Closing that scope interrupts and joins the callback before awaiting
10
+ * the native operation's settlement.
11
+ */
12
+ export const runNativeCallback = Effect.fnUntraced(function* <A, E, R, B>(
13
+ operation: (run: RunCallback<A, E, R>) => Promise<B>,
14
+ ): Effect.fn.Return<B, unknown, R> {
15
+ const callerContext = yield* Effect.context<R>();
16
+
17
+ return yield* Effect.scoped(
18
+ Effect.gen(function* () {
19
+ let nativePromise: Promise<B> | undefined;
20
+
21
+ // Registered before the FiberSet so scope closure first interrupts and
22
+ // joins the callback, then waits for the native rollback/gate to settle.
23
+ yield* Effect.addFinalizer(() => {
24
+ const promise = nativePromise;
25
+
26
+ return promise === undefined
27
+ ? Effect.void
28
+ : Effect.promise(() =>
29
+ promise.then(
30
+ () => undefined,
31
+ () => undefined,
32
+ ),
33
+ );
34
+ });
35
+ const runPromise = yield* FiberSet.makeRuntimePromise<never, Exit.Exit<A, E>, never>();
36
+
37
+ return yield* Effect.callback<B, unknown>((resume) => {
38
+ try {
39
+ nativePromise = operation((effect) =>
40
+ runPromise(Effect.provideContext(Effect.exit(effect), callerContext)),
41
+ );
42
+ } catch (cause) {
43
+ resume(Effect.fail(cause));
44
+
45
+ return;
46
+ }
47
+
48
+ nativePromise.then(
49
+ (value) => resume(Effect.succeed(value)),
50
+ (cause) => resume(Effect.fail(cause)),
51
+ );
52
+ });
53
+ }),
54
+ );
55
+ });
@@ -17,7 +17,9 @@ const failureHandler = <E, R>(
17
17
  cause: Cause.Cause<E>,
18
18
  options: WorkerContextWaitUntilOptions<E, R> | undefined,
19
19
  ) =>
20
- (options?.onFailure?.(cause) ?? Effect.logError(`${label} failed`, Cause.pretty(cause))).pipe(
20
+ Effect.suspend(
21
+ () => options?.onFailure?.(cause) ?? Effect.logError(`${label} failed`, Cause.pretty(cause)),
22
+ ).pipe(
21
23
  Effect.catchCause((handlerCause) =>
22
24
  Effect.logError(
23
25
  `${label} failure handler failed`,
@@ -42,7 +44,7 @@ export const makeWaitUntilScheduler = (
42
44
  Effect.sync(() => {
43
45
  const runHandler = (cause: Cause.Cause<E>) =>
44
46
  runPromiseExit(
45
- Effect.scoped(Effect.provideContext(failureHandler(label, cause, options), context)),
47
+ Effect.provideContext(Effect.scoped(failureHandler(label, cause, options)), context),
46
48
  ).then((exit) => {
47
49
  if (Exit.isFailure(exit)) {
48
50
  console.error(`${label} failure handler failed`, Cause.pretty(exit.cause));
@@ -50,7 +52,7 @@ export const makeWaitUntilScheduler = (
50
52
  });
51
53
 
52
54
  register(
53
- runPromiseExit(Effect.scoped(Effect.provideContext(effect, context))).then(
55
+ runPromiseExit(Effect.provideContext(Effect.scoped(effect), context)).then(
54
56
  async (exit) => {
55
57
  if (Exit.isSuccess(exit)) {
56
58
  return;