effect-cf 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -8,6 +8,12 @@ Cloudflare services should be described as `Context`, `Layer`, and `Effect` valu
8
8
 
9
9
  The low-level Worker entrypoint factory is exported as `Worker.make`. It returns a `WorkerEntrypoint` class with a `fetch` handler and optional typed RPC methods. Durable Object support starts with `DurableObject.make`, which routes Durable Object lifecycle methods and RPC handlers through one managed runtime for each Durable Object instance.
10
10
 
11
+ Worker fetch handlers can return either web `Response` values or Effect `HttpServerResponse` values. `Worker.make` renders `HttpServerResponse` back to a web `Response` at the Cloudflare boundary, so application handlers can stay in Effect HTTP primitives.
12
+
13
+ Use `requestLayer` on `Worker.make` for per-request services such as auth/session context, tenant metadata, request IDs, or request-local clients. The request layer scope is the handler evaluation scope: it closes when the handler produces a `Response`, not when a streaming response body is later consumed by the platform.
14
+
15
+ For modules that need an `ExportedHandler` object instead of a `WorkerEntrypoint` class, use `Worker.makeFetchHandler`. This helper is fetch-only and intentionally does not accept Worker RPC methods.
16
+
11
17
  RPC contracts can be described as classes with `Worker.Tag` and `DurableObject.Tag`. Contract classes expose `.make(...)` for runtime entrypoint creation and `.Binding(...)` / `.Namespace(...)` for typed bindings. Bindings infer call types from the same code-owned contract classes. Definition-backed calls encode/decode arguments and success values with their Effect schemas at Worker and Durable Object boundaries.
12
18
 
13
19
  Binding helpers expose two RPC paths:
@@ -31,6 +37,7 @@ Client APIs use Cloudflare-style provider types, so object results expose pipeli
31
37
  - `Rpc` - Cloudflare RPC type helpers, `RpcTarget` / `RpcStub` re-exports, and scoped disposal utilities
32
38
  - `ServiceBinding` - typed Worker-to-Worker service binding helper
33
39
  - `WorkerEnvironment` - request environment service
40
+ - `WorkerConfig` - Effect `Config` helpers backed by Cloudflare worker `env`, including `layerWith` for custom env-to-config mapping
34
41
 
35
42
  ## RPC Values
36
43
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Cause, Config, ConfigProvider, Context, Effect, Layer, Option, Schema, Scope } from "effect";
2
2
  import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
3
- import { HttpServerRequest } from "effect/unstable/http";
3
+ import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
4
4
  import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
5
5
  import * as RpcServer from "effect/unstable/rpc/RpcServer";
6
6
  import * as _$effect_Types0 from "effect/Types";
@@ -63,6 +63,13 @@ declare namespace WorkerConfig {
63
63
  const integer: <const Name extends Key>(name: Name) => Config.Config<number>;
64
64
  /** Read a scalar Cloudflare var or secret as a boolean. */
65
65
  const boolean: <const Name extends Key>(name: Name) => Config.Config<boolean>;
66
+ /** Build a `ConfigProvider` from a Cloudflare worker `env` object. */
67
+ const providerFromEnv: (env: WorkerEnv) => ConfigProvider.ConfigProvider;
68
+ /**
69
+ * Build a `ConfigProvider` from the current `WorkerEnvironment` with a custom
70
+ * conversion function.
71
+ */
72
+ const providerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => Effect.Effect<ConfigProvider.ConfigProvider, never, WorkerEnvironment>;
66
73
  /** Build a `ConfigProvider` from the current `WorkerEnvironment`. */
67
74
  const provider: Effect.Effect<ConfigProvider.ConfigProvider, never, WorkerEnvironment>;
68
75
  /**
@@ -72,6 +79,8 @@ declare namespace WorkerConfig {
72
79
  const providerLayer: _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
73
80
  /** Alias for `providerLayer` for concise use in worker layers. */
74
81
  const layer: _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
82
+ /** Build a `ConfigProvider` layer with a custom `env` conversion function. */
83
+ const layerWith: (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) => _$effect_Layer0.Layer<never, never, WorkerEnvironment>;
75
84
  }
76
85
  declare namespace Binding_d_exports {
77
86
  export { BindingNotFoundError, BindingValidationError, Service$3 as Service, TypeId$2 as TypeId };
@@ -798,7 +807,7 @@ type BoundaryHandlers<ROut, Self extends Definition$2.Any> = { readonly [Key in
798
807
  /**
799
808
  * Worker constructor options for a specific RPC definition.
800
809
  */
801
- interface Options$2<ROut, Self extends Definition$2.Any> extends Omit<WorkerOptions<ROut, Handlers$2<ROut, Self>>, "rpc"> {
810
+ interface Options$2<ROut, Self extends Definition$2.Any, RRequest = never, RequestLayerError = never> extends Omit<WorkerOptions<ROut, Handlers$2<ROut, Self>, RRequest, RequestLayerError>, "rpc"> {
802
811
  readonly rpc: Handlers$2<ROut, Self>;
803
812
  }
804
813
  /**
@@ -806,7 +815,7 @@ interface Options$2<ROut, Self extends Definition$2.Any> extends Omit<WorkerOpti
806
815
  */
807
816
  declare const method$2: typeof method$4;
808
817
  declare const make$3: <Id extends string, const MethodsShape extends Methods>(id: Id, methods: MethodsShape & NoReservedMethods<MethodsShape, ReservedMethodName$2>) => Definition$2<Id, MethodsShape> & {
809
- make: <ROut, LayerError>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>>) => WorkerClass<BoundaryHandlers<ROut, Definition$2<Id, MethodsShape>>, ROut>;
818
+ make: <ROut, LayerError, RRequest = never, RequestLayerError = never>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>, RRequest, RequestLayerError>) => WorkerClass<BoundaryHandlers<ROut, Definition$2<Id, MethodsShape>>, ROut>;
810
819
  Binding: <Self>() => <BindingId extends string>(bindingId: BindingId, binding: Omit<ServiceBindingDefinition<Definition$2<Id, MethodsShape>>, "definition">) => _$effect_Context0.ServiceClass<Self, BindingId, ServiceBindingClient<[Api$2<Definition$2<Id, MethodsShape>>] extends [never] ? Definition$2<Id, MethodsShape> extends infer T ? T extends Definition$2<Id, MethodsShape> ? T extends Definition$4.Any ? ServerApi$3<T> : never : never : never : Api$2<Definition$2<Id, MethodsShape>>>> & {
811
820
  "effect-cf/Binding": "effect-cf/Binding";
812
821
  binding: string;
@@ -833,7 +842,7 @@ declare const make$3: <Id extends string, const MethodsShape extends Methods>(id
833
842
  };
834
843
  };
835
844
  declare const Tag$2: <_Self>() => <Id extends string, const MethodsShape extends Methods>(id: Id, methods: MethodsShape & NoReservedMethods<MethodsShape, ReservedMethodName$2>) => (abstract new () => object) & (Definition$2<Id, MethodsShape> & {
836
- make: <ROut, LayerError>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>>) => WorkerClass<BoundaryHandlers<ROut, Definition$2<Id, MethodsShape>>, ROut>;
845
+ make: <ROut, LayerError, RRequest = never, RequestLayerError = never>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>, RRequest, RequestLayerError>) => WorkerClass<BoundaryHandlers<ROut, Definition$2<Id, MethodsShape>>, ROut>;
837
846
  Binding: <Self>() => <BindingId extends string>(bindingId: BindingId, binding: Omit<ServiceBindingDefinition<Definition$2<Id, MethodsShape>>, "definition">) => _$effect_Context0.ServiceClass<Self, BindingId, ServiceBindingClient<[Api$2<Definition$2<Id, MethodsShape>>] extends [never] ? Definition$2<Id, MethodsShape> extends infer T ? T extends Definition$2<Id, MethodsShape> ? T extends Definition$4.Any ? ServerApi$3<T> : never : never : never : Api$2<Definition$2<Id, MethodsShape>>>> & {
838
847
  "effect-cf/Binding": "effect-cf/Binding";
839
848
  binding: string;
@@ -860,7 +869,7 @@ declare const Tag$2: <_Self>() => <Id extends string, const MethodsShape extends
860
869
  };
861
870
  });
862
871
  declare const Worker: <_Self>() => <Id extends string, const MethodsShape extends Methods>(id: Id, methods: MethodsShape & NoReservedMethods<MethodsShape, ReservedMethodName$2>) => (abstract new () => object) & (Definition$2<Id, MethodsShape> & {
863
- make: <ROut, LayerError>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>>) => WorkerClass<BoundaryHandlers<ROut, Definition$2<Id, MethodsShape>>, ROut>;
872
+ make: <ROut, LayerError, RRequest = never, RequestLayerError = never>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>, RRequest, RequestLayerError>) => WorkerClass<BoundaryHandlers<ROut, Definition$2<Id, MethodsShape>>, ROut>;
864
873
  Binding: <Self>() => <BindingId extends string>(bindingId: BindingId, binding: Omit<ServiceBindingDefinition<Definition$2<Id, MethodsShape>>, "definition">) => _$effect_Context0.ServiceClass<Self, BindingId, ServiceBindingClient<[Api$2<Definition$2<Id, MethodsShape>>] extends [never] ? Definition$2<Id, MethodsShape> extends infer T ? T extends Definition$2<Id, MethodsShape> ? T extends Definition$4.Any ? ServerApi$3<T> : never : never : never : Api$2<Definition$2<Id, MethodsShape>>>> & {
865
874
  "effect-cf/Binding": "effect-cf/Binding";
866
875
  binding: string;
@@ -985,7 +994,7 @@ declare const Service$1: <Self, Api extends object = never>() => <Id extends str
985
994
  readonly scopedCall: <Method extends ServiceMethodKey<ApiOrDefinition<Api, Definition>>>(method: Method, ...args: ServiceMethodArgs<ApiOrDefinition<Api, Definition>, Method>) => Effect.Effect<Awaited<Result<AsyncMethodSuccess<ApiOrDefinition<Api, Definition>, Method>>>, unknown, Self | _$effect_Scope0.Scope>;
986
995
  };
987
996
  declare namespace Worker_d_exports {
988
- export { Api$1 as Api, Definition$1 as Definition, ExecutionContext, HandlerEffect$1 as HandlerEffect, Handlers$1 as Handlers, NativeRequest, Options$1 as Options, ReservedMethodName, RpcHandlers$1 as RpcHandlers, ServerApi$2 as ServerApi, Tag$1 as Tag, WorkerClass, WorkerContext, WorkerContextService, WorkerContextWaitUntilOptions, WorkerHandler, WorkerOptions, WorkerRpc, WorkerRpcHandler, WorkerRpcShape, implement$1 as implement, isWebSocketUpgrade, make$2 as make, method$1 as method };
997
+ export { Api$1 as Api, Definition$1 as Definition, ExecutionContext, FetchHandler, FetchWorkerOptions, HandlerEffect$1 as HandlerEffect, Handlers$1 as Handlers, NativeRequest, Options$1 as Options, ReservedMethodName, RpcHandlers$1 as RpcHandlers, ServerApi$2 as ServerApi, Tag$1 as Tag, WorkerClass, WorkerContext, WorkerContextService, WorkerContextWaitUntilOptions, WorkerFetchSuccess, WorkerHandler, WorkerOptions, WorkerRpc, WorkerRpcHandler, WorkerRpcShape, implement$1 as implement, isWebSocketUpgrade, make$2 as make, makeFetchHandler, method$1 as method, renderHttpResponse };
989
998
  }
990
999
  declare const ExecutionContext_base: Context.ServiceClass<ExecutionContext, "effect-cf/ExecutionContext", globalThis.ExecutionContext<unknown>>;
991
1000
  declare class ExecutionContext extends ExecutionContext_base {}
@@ -1006,21 +1015,37 @@ type ReservedMethodName = ReservedMethodName$5 | "fetch" | "connect" | "alarm" |
1006
1015
  type WorkerBaseContext<ROut> = ExecutionContext | WorkerContext | WorkerEnvironment | ROut;
1007
1016
  type WorkerFetchContext<ROut> = WorkerBaseContext<ROut> | NativeRequest | HttpServerRequest.HttpServerRequest | Scope.Scope;
1008
1017
  type WorkerRpcContext<ROut> = WorkerBaseContext<ROut> | Scope.Scope;
1009
- type WorkerHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerFetchContext<ROut>>;
1018
+ type WorkerFetchSuccess = Response | HttpServerResponse.HttpServerResponse;
1019
+ type WorkerHandler<ROut, A = WorkerFetchSuccess> = Effect.Effect<A, unknown, WorkerFetchContext<ROut>>;
1010
1020
  type WorkerRpcHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerRpcContext<ROut>>;
1011
1021
  type WorkerRpc<ROut> = Record<string, (...args: Array<any>) => WorkerRpcHandler<ROut>>;
1012
1022
  type WorkerRpcShape<Rpc extends WorkerRpc<ROut>, ROut> = { readonly [Key in keyof Rpc]: Rpc[Key] extends ((...args: infer Args) => Effect.Effect<infer A, unknown, WorkerRpcContext<ROut>>) ? (...args: Args) => Promise<A> : never };
1013
1023
  type RpcHandlers$1<ROut, Api> = { readonly [Key in keyof Api as Key extends keyof WorkerEntrypoint ? never : Key extends string ? Key extends ReservedMethodName ? never : [Api[Key]] extends [never] ? never : Api[Key] extends ((...args: Array<any>) => Promise<unknown>) ? Key : never : never]: Api[Key] extends ((...args: infer Args) => Promise<infer A>) ? (...args: Args) => WorkerRpcHandler<ROut, A> : never };
1014
- interface WorkerOptions<ROut, Rpc extends WorkerRpc<ROut>> {
1015
- readonly fetch?: Effect.Effect<Response, unknown, WorkerFetchContext<ROut>>;
1024
+ interface WorkerOptions<ROut, Rpc extends WorkerRpc<ROut>, RRequest = never, RequestLayerError = never> {
1025
+ readonly fetch?: Effect.Effect<WorkerFetchSuccess, unknown, WorkerFetchContext<ROut | RRequest>>;
1026
+ /**
1027
+ * Per-request services provided while evaluating the fetch handler.
1028
+ *
1029
+ * This scope closes when the handler produces a `Response`; it is not kept
1030
+ * open until a streaming response body is consumed by the platform.
1031
+ */
1032
+ readonly requestLayer?: Layer.Layer<RRequest, RequestLayerError, WorkerFetchContext<ROut>>;
1016
1033
  readonly rpc?: Rpc;
1017
1034
  }
1035
+ type FetchWorkerOptions<ROut, RRequest = never, RequestLayerError = never> = Omit<WorkerOptions<ROut, Record<never, never>, RRequest, RequestLayerError>, "rpc"> & {
1036
+ readonly rpc?: never;
1037
+ };
1018
1038
  type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (ctx: globalThis.ExecutionContext, env: WorkerEnv) => WorkerEntrypoint<WorkerEnv> & {
1019
1039
  fetch(request: Request): Promise<Response>;
1020
1040
  } & WorkerRpcShape<Rpc, ROut>;
1021
- declare const make$2: <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: WorkerOptions<ROut, Rpc>) => WorkerClass<Rpc, ROut>;
1041
+ interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
1042
+ readonly fetch: (request: Request, env: Env, ctx: globalThis.ExecutionContext) => Promise<Response>;
1043
+ }
1044
+ declare const renderHttpResponse: <A extends HttpServerResponse.HttpServerResponse, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<Response, E, R>;
1045
+ declare const make$2: <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>, RRequest = never, RequestLayerError = never>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: WorkerOptions<ROut, Rpc, RRequest, RequestLayerError>) => WorkerClass<Rpc, ROut>;
1046
+ declare const makeFetchHandler: <ROut, LayerError, RRequest = never, RequestLayerError = never, Env extends WorkerEnv = WorkerEnv>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: FetchWorkerOptions<ROut, RRequest, RequestLayerError>) => FetchHandler<Env>;
1022
1047
  declare const Tag$1: <_Self>() => <Id extends string, const MethodsShape extends Methods>(id: Id, methods: MethodsShape & NoReservedMethods<MethodsShape, ReservedMethodName$2>) => (abstract new () => object) & (Definition$2<Id, MethodsShape> & {
1023
- make: <ROut, LayerError>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>>) => WorkerClass<{ readonly [Key in keyof MethodsShape]: (...args: Array<unknown>) => WorkerRpcHandler<ROut, _$effect_Schema0.Codec.Encoded<MethodsShape[Key]["success"]>> }, ROut>;
1048
+ make: <ROut, LayerError, RRequest = never, RequestLayerError = never>(layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>, options: Options$2<ROut, Definition$2<Id, MethodsShape>, RRequest, RequestLayerError>) => WorkerClass<{ readonly [Key in keyof MethodsShape]: (...args: Array<unknown>) => WorkerRpcHandler<ROut, _$effect_Schema0.Codec.Encoded<MethodsShape[Key]["success"]>> }, ROut>;
1024
1049
  Binding: <Self>() => <BindingId extends string>(bindingId: BindingId, binding: Omit<ServiceBindingDefinition<Definition$2<Id, MethodsShape>>, "definition">) => Context.ServiceClass<Self, BindingId, ServiceBindingClient<[Api$2<Definition$2<Id, MethodsShape>>] extends [never] ? Definition$2<Id, MethodsShape> extends infer T ? T extends Definition$2<Id, MethodsShape> ? T extends Definition$4.Any ? ServerApi$3<T> : never : never : never : Api$2<Definition$2<Id, MethodsShape>>>> & {
1025
1050
  "effect-cf/Binding": "effect-cf/Binding";
1026
1051
  binding: string;
@@ -1055,7 +1080,7 @@ declare namespace Definition$1 {
1055
1080
  type ServerApi$2<Self extends Definition$2.Any> = ServerApi$3<Self>;
1056
1081
  type Api$1<Self extends Definition$2.Any> = Api$2<Self>;
1057
1082
  type Handlers$1<ROut, Self extends Definition$2.Any> = Handlers$2<ROut, Self>;
1058
- type Options$1<ROut, Self extends Definition$2.Any> = Options$2<ROut, Self>;
1083
+ type Options$1<ROut, Self extends Definition$2.Any, RRequest = never, RequestLayerError = never> = Options$2<ROut, Self, RRequest, RequestLayerError>;
1059
1084
  type HandlerEffect$1<ROut, Self extends Definition$2.Any, Key extends keyof Self["methods"]> = HandlerEffect$2<ROut, Self, Key>;
1060
1085
  declare namespace DurableObject_d_exports {
1061
1086
  export { Api, Definition, DurableObjectClass, DurableObjectHandler, DurableObjectOptions, DurableObjectRpc, DurableObjectRpcShape, HandlerEffect, Handlers, Options, RpcHandlers, ServerApi$1 as ServerApi, Tag, implement, make$1 as make, method };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
2
2
  import { Cause, Config, ConfigProvider, Context, Data, Effect, Exit, Layer, ManagedRuntime, Option, Queue, Schema } from "effect";
3
3
  import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
4
- import { HttpServerRequest } from "effect/unstable/http";
4
+ import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
5
5
  import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
6
6
  import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
7
7
  import * as RpcServer from "effect/unstable/rpc/RpcServer";
@@ -17,8 +17,11 @@ let WorkerConfig;
17
17
  _WorkerConfig.number = (name) => Config.number(name);
18
18
  _WorkerConfig.integer = (name) => Config.int(name);
19
19
  _WorkerConfig.boolean = (name) => Config.boolean(name);
20
- const provider = _WorkerConfig.provider = Effect.map(WorkerEnvironment.asEffect(), ConfigProvider.fromUnknown);
20
+ const providerFromEnv = _WorkerConfig.providerFromEnv = (env) => ConfigProvider.fromUnknown(env);
21
+ const providerWith = _WorkerConfig.providerWith = (makeProvider) => Effect.map(WorkerEnvironment.asEffect(), makeProvider);
22
+ const provider = _WorkerConfig.provider = providerWith(providerFromEnv);
21
23
  _WorkerConfig.layer = _WorkerConfig.providerLayer = ConfigProvider.layer(provider);
24
+ _WorkerConfig.layerWith = (makeProvider) => ConfigProvider.layer(providerWith(makeProvider));
22
25
  })(WorkerConfig || (WorkerConfig = {}));
23
26
  //#endregion
24
27
  //#region src/Binding.ts
@@ -430,7 +433,9 @@ var Worker_exports = /* @__PURE__ */ __exportAll({
430
433
  implement: () => implement$2,
431
434
  isWebSocketUpgrade: () => isWebSocketUpgrade,
432
435
  make: () => make$3,
433
- method: () => method$2
436
+ makeFetchHandler: () => makeFetchHandler,
437
+ method: () => method$2,
438
+ renderHttpResponse: () => renderHttpResponse
434
439
  });
435
440
  var ExecutionContext = class extends Context.Service()("effect-cf/ExecutionContext") {};
436
441
  var WorkerContext = class extends Context.Service()("effect-cf/WorkerContext") {};
@@ -460,13 +465,15 @@ const reservedMethodNames$2 = new Set([
460
465
  "webSocketError"
461
466
  ]);
462
467
  const RunSymbol$1 = Symbol.for("effect-cf/Worker/run");
468
+ const renderHttpResponse = (effect) => Effect.flatMap(effect, (response) => Effect.map(Effect.context(), (context) => HttpServerResponse.toWeb(response, { context })));
469
+ const renderFetchSuccess = (effect) => Effect.flatMap(effect, (response) => response instanceof Response ? Effect.succeed(response) : Effect.map(Effect.context(), (context) => HttpServerResponse.toWeb(response, { context })));
463
470
  const make$3 = (layer, options) => {
464
471
  class EffectWorker extends WorkerEntrypoint {
465
472
  runtime;
466
473
  constructor(ctx, env) {
467
474
  super(ctx, env);
468
475
  let runWaitUntilEffect = () => Promise.resolve(Exit.die(/* @__PURE__ */ new Error("WorkerContext runtime is not initialized")));
469
- const runtimeLayer = provideEntrypointServices(layer, Layer.mergeAll(Layer.succeed(ExecutionContext, ctx), Layer.succeed(WorkerContext, fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect))), Layer.succeed(WorkerEnvironment, env)));
476
+ const runtimeLayer = provideEntrypointServices(layer, Layer.mergeAll(Layer.succeed(ExecutionContext, ctx), ConfigProvider.layer(Effect.succeed(WorkerConfig.providerFromEnv(env))), Layer.succeed(WorkerContext, fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect))), Layer.succeed(WorkerEnvironment, env)));
470
477
  this.runtime = ManagedRuntime.make(runtimeLayer);
471
478
  runWaitUntilEffect = (effect) => this.runtime.runPromiseExit(effect);
472
479
  }
@@ -476,12 +483,19 @@ const make$3 = (layer, options) => {
476
483
  fetch(request) {
477
484
  const fetchHandler = options.fetch;
478
485
  if (fetchHandler === void 0) return Promise.resolve(new Response("Not Found", { status: 404 }));
479
- return this[RunSymbol$1](fetchHandler.pipe(Effect.provideService(NativeRequest, request), Effect.provideService(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request))));
486
+ const requestServices = Layer.mergeAll(Layer.succeed(NativeRequest, request), Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)));
487
+ const fetchEffect = renderFetchSuccess(fetchHandler);
488
+ const requestEffect = options.requestLayer === void 0 ? fetchEffect : fetchEffect.pipe(Effect.provide(options.requestLayer));
489
+ return this[RunSymbol$1](requestEffect.pipe(Effect.provide(requestServices)));
480
490
  }
481
491
  }
482
492
  defineEntrypointRpcMethods("Worker", EffectWorker.prototype, options.rpc, reservedMethodNames$2, (self, effect) => self[RunSymbol$1](effect));
483
493
  return assumeEntrypointClass(EffectWorker);
484
494
  };
495
+ const makeFetchHandler = (layer, options) => {
496
+ const WorkerClass = make$3(layer, options);
497
+ return { fetch: (request, env, ctx) => Promise.resolve(new WorkerClass(ctx, env).fetch(request)) };
498
+ };
485
499
  const Tag$2 = Tag$3;
486
500
  const method$2 = method$3;
487
501
  const implement$2 = implement$3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -72,8 +72,18 @@ export namespace WorkerConfig {
72
72
  /** Read a scalar Cloudflare var or secret as a boolean. */
73
73
  export const boolean = <const Name extends Key>(name: Name) => Config.boolean(name);
74
74
 
75
+ /** Build a `ConfigProvider` from a Cloudflare worker `env` object. */
76
+ export const providerFromEnv = (env: WorkerEnv) => ConfigProvider.fromUnknown(env);
77
+
78
+ /**
79
+ * Build a `ConfigProvider` from the current `WorkerEnvironment` with a custom
80
+ * conversion function.
81
+ */
82
+ export const providerWith = (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) =>
83
+ Effect.map(WorkerEnvironment.asEffect(), makeProvider);
84
+
75
85
  /** Build a `ConfigProvider` from the current `WorkerEnvironment`. */
76
- export const provider = Effect.map(WorkerEnvironment.asEffect(), ConfigProvider.fromUnknown);
86
+ export const provider = providerWith(providerFromEnv);
77
87
 
78
88
  /**
79
89
  * Replace the active Effect `ConfigProvider` with one backed by the current
@@ -83,4 +93,8 @@ export namespace WorkerConfig {
83
93
 
84
94
  /** Alias for `providerLayer` for concise use in worker layers. */
85
95
  export const layer = providerLayer;
96
+
97
+ /** Build a `ConfigProvider` layer with a custom `env` conversion function. */
98
+ export const layerWith = (makeProvider: (env: WorkerEnv) => ConfigProvider.ConfigProvider) =>
99
+ ConfigProvider.layer(providerWith(makeProvider));
86
100
  }
package/src/Worker.ts CHANGED
@@ -1,8 +1,17 @@
1
1
  import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
2
- import { Cause, Context, Effect, Exit, Layer, ManagedRuntime, type Scope } from "effect";
3
- import { HttpServerRequest } from "effect/unstable/http";
4
-
5
- import { WorkerEnvironment, type WorkerEnv } from "./Environment";
2
+ import {
3
+ Cause,
4
+ ConfigProvider,
5
+ Context,
6
+ Effect,
7
+ Exit,
8
+ Layer,
9
+ ManagedRuntime,
10
+ type Scope,
11
+ } from "effect";
12
+ import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
13
+
14
+ import { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "./Environment";
6
15
  import type * as RpcDefinition from "./RpcDefinition";
7
16
  import * as WorkerDefinition from "./WorkerDefinition";
8
17
  import * as Entrypoint from "./internal/Entrypoint";
@@ -122,7 +131,13 @@ type RuntimeContext<ROut> = WorkerBaseContext<ROut>;
122
131
 
123
132
  const RunSymbol = Symbol.for("effect-cf/Worker/run");
124
133
 
125
- export type WorkerHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerFetchContext<ROut>>;
134
+ export type WorkerFetchSuccess = Response | HttpServerResponse.HttpServerResponse;
135
+
136
+ export type WorkerHandler<ROut, A = WorkerFetchSuccess> = Effect.Effect<
137
+ A,
138
+ unknown,
139
+ WorkerFetchContext<ROut>
140
+ >;
126
141
 
127
142
  export type WorkerRpcHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerRpcContext<ROut>>;
128
143
 
@@ -152,11 +167,30 @@ export type RpcHandlers<ROut, Api> = {
152
167
  : never;
153
168
  };
154
169
 
155
- export interface WorkerOptions<ROut, Rpc extends WorkerRpc<ROut>> {
156
- readonly fetch?: Effect.Effect<Response, unknown, WorkerFetchContext<ROut>>;
170
+ export interface WorkerOptions<
171
+ ROut,
172
+ Rpc extends WorkerRpc<ROut>,
173
+ RRequest = never,
174
+ RequestLayerError = never,
175
+ > {
176
+ readonly fetch?: Effect.Effect<WorkerFetchSuccess, unknown, WorkerFetchContext<ROut | RRequest>>;
177
+ /**
178
+ * Per-request services provided while evaluating the fetch handler.
179
+ *
180
+ * This scope closes when the handler produces a `Response`; it is not kept
181
+ * open until a streaming response body is consumed by the platform.
182
+ */
183
+ readonly requestLayer?: Layer.Layer<RRequest, RequestLayerError, WorkerFetchContext<ROut>>;
157
184
  readonly rpc?: Rpc;
158
185
  }
159
186
 
187
+ export type FetchWorkerOptions<ROut, RRequest = never, RequestLayerError = never> = Omit<
188
+ WorkerOptions<ROut, Record<never, never>, RRequest, RequestLayerError>,
189
+ "rpc"
190
+ > & {
191
+ readonly rpc?: never;
192
+ };
193
+
160
194
  export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
161
195
  ctx: globalThis.ExecutionContext,
162
196
  env: WorkerEnv,
@@ -164,9 +198,43 @@ export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
164
198
  fetch(request: Request): Promise<Response>;
165
199
  } & WorkerRpcShape<Rpc, ROut>;
166
200
 
167
- export const make = <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>>(
201
+ export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
202
+ readonly fetch: (
203
+ request: Request,
204
+ env: Env,
205
+ ctx: globalThis.ExecutionContext,
206
+ ) => Promise<Response>;
207
+ }
208
+
209
+ export const renderHttpResponse = <A extends HttpServerResponse.HttpServerResponse, E, R>(
210
+ effect: Effect.Effect<A, E, R>,
211
+ ): Effect.Effect<Response, E, R> =>
212
+ Effect.flatMap(effect, (response) =>
213
+ Effect.map(Effect.context<never>(), (context) =>
214
+ HttpServerResponse.toWeb(response, { context }),
215
+ ),
216
+ );
217
+
218
+ const renderFetchSuccess = <E, R>(
219
+ effect: Effect.Effect<WorkerFetchSuccess, E, R>,
220
+ ): Effect.Effect<Response, E, R> =>
221
+ Effect.flatMap(effect, (response) =>
222
+ response instanceof Response
223
+ ? Effect.succeed(response)
224
+ : Effect.map(Effect.context<never>(), (context) =>
225
+ HttpServerResponse.toWeb(response, { context }),
226
+ ),
227
+ );
228
+
229
+ export const make = <
230
+ ROut,
231
+ LayerError,
232
+ const Rpc extends WorkerRpc<ROut> = Record<never, never>,
233
+ RRequest = never,
234
+ RequestLayerError = never,
235
+ >(
168
236
  layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
169
- options: WorkerOptions<ROut, Rpc>,
237
+ options: WorkerOptions<ROut, Rpc, RRequest, RequestLayerError>,
170
238
  ): WorkerClass<Rpc, ROut> => {
171
239
  class EffectWorker extends CloudflareWorkerEntrypoint<WorkerEnv> {
172
240
  readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut>, LayerError>;
@@ -179,14 +247,19 @@ export const make = <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
179
247
 
180
248
  const services = Layer.mergeAll(
181
249
  Layer.succeed(ExecutionContext, ctx),
250
+ ConfigProvider.layer(Effect.succeed(WorkerConfig.providerFromEnv(env))),
182
251
  Layer.succeed(
183
252
  WorkerContext,
184
253
  fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect)),
185
254
  ),
186
255
  Layer.succeed(WorkerEnvironment, env),
187
- );
256
+ ) as Layer.Layer<ExecutionContext | WorkerContext | WorkerEnvironment, never, never>;
188
257
 
189
- const runtimeLayer = Entrypoint.provideEntrypointServices(layer, services);
258
+ const runtimeLayer = Entrypoint.provideEntrypointServices<
259
+ ROut,
260
+ LayerError,
261
+ ExecutionContext | WorkerContext | WorkerEnvironment
262
+ >(layer, services);
190
263
 
191
264
  this.runtime = ManagedRuntime.make(runtimeLayer);
192
265
  runWaitUntilEffect = <A, E>(effect: Effect.Effect<A, E, never>) =>
@@ -204,14 +277,22 @@ export const make = <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
204
277
  return Promise.resolve(new Response("Not Found", { status: 404 }));
205
278
  }
206
279
 
280
+ const requestServices = Layer.mergeAll(
281
+ Layer.succeed(NativeRequest, request),
282
+ Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)),
283
+ );
284
+ const fetchEffect = renderFetchSuccess(fetchHandler);
285
+ const requestEffect =
286
+ options.requestLayer === undefined
287
+ ? fetchEffect
288
+ : fetchEffect.pipe(Effect.provide(options.requestLayer));
289
+
207
290
  return this[RunSymbol](
208
- fetchHandler.pipe(
209
- Effect.provideService(NativeRequest, request),
210
- Effect.provideService(
211
- HttpServerRequest.HttpServerRequest,
212
- HttpServerRequest.fromWeb(request),
213
- ),
214
- ),
291
+ requestEffect.pipe(Effect.provide(requestServices)) as Effect.Effect<
292
+ Response,
293
+ unknown,
294
+ RuntimeContext<ROut> | Scope.Scope
295
+ >,
215
296
  );
216
297
  }
217
298
  }
@@ -227,6 +308,23 @@ export const make = <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Recor
227
308
  return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut>>(EffectWorker);
228
309
  };
229
310
 
311
+ export const makeFetchHandler = <
312
+ ROut,
313
+ LayerError,
314
+ RRequest = never,
315
+ RequestLayerError = never,
316
+ Env extends WorkerEnv = WorkerEnv,
317
+ >(
318
+ layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
319
+ options: FetchWorkerOptions<ROut, RRequest, RequestLayerError>,
320
+ ): FetchHandler<Env> => {
321
+ const WorkerClass = make(layer, options);
322
+
323
+ return {
324
+ fetch: (request, env, ctx) => Promise.resolve(new WorkerClass(ctx, env).fetch(request)),
325
+ };
326
+ };
327
+
230
328
  export const Tag = WorkerDefinition.Tag;
231
329
 
232
330
  export const method = WorkerDefinition.method;
@@ -252,10 +350,12 @@ export type Handlers<
252
350
  Self extends WorkerDefinition.Definition.Any,
253
351
  > = WorkerDefinition.Handlers<ROut, Self>;
254
352
 
255
- export type Options<ROut, Self extends WorkerDefinition.Definition.Any> = WorkerDefinition.Options<
353
+ export type Options<
256
354
  ROut,
257
- Self
258
- >;
355
+ Self extends WorkerDefinition.Definition.Any,
356
+ RRequest = never,
357
+ RequestLayerError = never,
358
+ > = WorkerDefinition.Options<ROut, Self, RRequest, RequestLayerError>;
259
359
 
260
360
  export type HandlerEffect<
261
361
  ROut,
@@ -74,8 +74,13 @@ type BoundaryHandlers<ROut, Self extends Definition.Any> = {
74
74
  /**
75
75
  * Worker constructor options for a specific RPC definition.
76
76
  */
77
- export interface Options<ROut, Self extends Definition.Any> extends Omit<
78
- WorkerEntrypoint.WorkerOptions<ROut, Handlers<ROut, Self>>,
77
+ export interface Options<
78
+ ROut,
79
+ Self extends Definition.Any,
80
+ RRequest = never,
81
+ RequestLayerError = never,
82
+ > extends Omit<
83
+ WorkerEntrypoint.WorkerOptions<ROut, Handlers<ROut, Self>, RRequest, RequestLayerError>,
79
84
  "rpc"
80
85
  > {
81
86
  readonly rpc: Handlers<ROut, Self>;
@@ -108,13 +113,13 @@ const makeDefinition = <Id extends string, const MethodsShape extends RpcDefinit
108
113
  const definition: SelfDefinition = RpcDefinition.make(id, methods);
109
114
 
110
115
  return Object.assign(definition, {
111
- make: <ROut, LayerError>(
116
+ make: <ROut, LayerError, RRequest = never, RequestLayerError = never>(
112
117
  layer: Layer.Layer<
113
118
  ROut,
114
119
  LayerError,
115
120
  WorkerEntrypoint.ExecutionContext | WorkerEntrypoint.WorkerContext | WorkerEnvironment
116
121
  >,
117
- options: Options<ROut, SelfDefinition>,
122
+ options: Options<ROut, SelfDefinition, RRequest, RequestLayerError>,
118
123
  ) =>
119
124
  WorkerEntrypoint.make(layer, {
120
125
  ...options,