effect-cf 0.5.0 → 0.7.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.
@@ -1,5 +1,6 @@
1
- import { Effect, Schema as S, type Layer, type Scope } from "effect";
1
+ import { Context, Effect, Schema as S, type Layer, type Scope } from "effect";
2
2
 
3
+ import * as Binding from "./Binding";
3
4
  import type { WorkerEnvironment } from "./Environment";
4
5
  import * as QueueEntrypoint from "./Queue";
5
6
  import * as QueueBinding from "./QueueBinding";
@@ -19,15 +20,6 @@ export namespace Definition {
19
20
  export type Any = Definition<string, RpcDefinition.ServiceFreeSchema>;
20
21
  }
21
22
 
22
- declare const BindingServiceTypeId: unique symbol;
23
-
24
- export interface BindingService<Id extends string, Self extends Definition.Any> {
25
- readonly [BindingServiceTypeId]: {
26
- readonly id: Id;
27
- readonly definition: Self;
28
- };
29
- }
30
-
31
23
  export type Handler<ROut, Self extends Definition.Any> = (
32
24
  batch: QueueEntrypoint.QueueBatch<S.Schema.Type<Self["message"]>>,
33
25
  ) => Effect.Effect<
@@ -44,6 +36,44 @@ export interface Options<ROut, Self extends Definition.Any> extends Omit<
44
36
  readonly rpc?: never;
45
37
  }
46
38
 
39
+ export type LayerOptions = {
40
+ readonly binding: string;
41
+ };
42
+
43
+ export interface TagClass<
44
+ Self,
45
+ Id extends string,
46
+ Message extends RpcDefinition.ServiceFreeSchema,
47
+ > extends Context.ServiceClass<Self, Id, QueueBinding.QueueBindingClient<Message>> {
48
+ readonly id: Id;
49
+ readonly message: Message;
50
+ readonly make: <ROut, LayerError>(
51
+ layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
52
+ options: Options<ROut, Definition<Id, Message>>,
53
+ ) => WorkerEntrypoint.WorkerClass<Record<never, never>, ROut>;
54
+ readonly layer: (
55
+ options: LayerOptions,
56
+ ) => Layer.Layer<
57
+ Self,
58
+ Binding.BindingNotFoundError | Binding.BindingValidationError,
59
+ WorkerEnvironment
60
+ >;
61
+ readonly send: (
62
+ message: S.Schema.Type<Message>,
63
+ options?: QueueBinding.QueueSendOptions,
64
+ ) => Effect.Effect<void, QueueBinding.QueueOperationError | S.SchemaError, Self>;
65
+ readonly sendBatch: (
66
+ messages: Iterable<QueueBinding.MessageSendRequest<S.Schema.Type<Message>>>,
67
+ options?: QueueBinding.QueueSendBatchOptions,
68
+ ) => Effect.Effect<void, QueueBinding.QueueOperationError | S.SchemaError, Self>;
69
+ readonly metrics: () => Effect.Effect<
70
+ QueueBinding.QueueMetrics,
71
+ QueueBinding.QueueOperationError,
72
+ Self
73
+ >;
74
+ readonly unsafeRaw: () => Effect.Effect<globalThis.Queue<S.Codec.Encoded<Message>>, never, Self>;
75
+ }
76
+
47
77
  const makeDefinition = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
48
78
  id: Id,
49
79
  definition: { readonly message: Message },
@@ -63,49 +93,65 @@ const makeDefinition = <Id extends string, Message extends RpcDefinition.Service
63
93
  ...options,
64
94
  queue: wrapHandler(queueDefinition, options.queue),
65
95
  }),
66
- Binding:
67
- <Self>() =>
68
- <BindingId extends string>(
69
- bindingId: BindingId,
70
- binding: Omit<QueueBinding.QueueBindingDefinition<Message>, "message">,
71
- ) =>
72
- QueueBinding.Service<Self>()<BindingId, Message>(bindingId, {
73
- ...binding,
74
- message: definition.message,
75
- }),
76
- binding: <BindingId extends string>(
77
- bindingId: BindingId,
78
- binding: Omit<QueueBinding.QueueBindingDefinition<Message>, "message">,
79
- ) =>
80
- QueueBinding.Service<BindingService<BindingId, SelfDefinition>>()<BindingId, Message>(
81
- bindingId,
82
- {
83
- ...binding,
84
- message: definition.message,
85
- },
86
- ),
87
96
  });
88
97
  };
89
98
 
90
- export const make = makeDefinition;
99
+ export const make = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
100
+ id: Id,
101
+ definition: { readonly message: Message },
102
+ ) => Tag<Definition<Id, Message>>()(id, definition);
91
103
 
92
104
  export const Tag =
93
- <_Self>() =>
105
+ <Self>() =>
94
106
  <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
95
107
  id: Id,
96
108
  definition: { readonly message: Message },
97
109
  ) => {
98
110
  const queueDefinition = makeDefinition(id, definition);
111
+ const tag = Context.Service<Self, QueueBinding.QueueBindingClient<Message>>()(id);
112
+
113
+ const layer = (binding: LayerOptions) =>
114
+ QueueBinding.layer(tag, {
115
+ ...binding,
116
+ message: definition.message,
117
+ });
118
+
119
+ const send = Effect.fnUntraced(function* (
120
+ message: S.Schema.Type<Message>,
121
+ options?: QueueBinding.QueueSendOptions,
122
+ ) {
123
+ const queue = yield* tag;
124
+ yield* queue.send(message, options);
125
+ });
99
126
 
100
- abstract class QueueDefinitionClass {
101
- static readonly id = queueDefinition.id;
102
- static readonly message = queueDefinition.message;
103
- static readonly make = queueDefinition.make;
104
- static readonly Binding = queueDefinition.Binding;
105
- static readonly binding = queueDefinition.binding;
106
- }
127
+ const sendBatch = Effect.fnUntraced(function* (
128
+ messages: Iterable<QueueBinding.MessageSendRequest<S.Schema.Type<Message>>>,
129
+ options?: QueueBinding.QueueSendBatchOptions,
130
+ ) {
131
+ const queue = yield* tag;
132
+ yield* queue.sendBatch(messages, options);
133
+ });
134
+
135
+ const metrics = Effect.fnUntraced(function* () {
136
+ const queue = yield* tag;
137
+ return yield* queue.metrics();
138
+ });
139
+
140
+ const unsafeRaw = Effect.fnUntraced(function* () {
141
+ const queue = yield* tag;
142
+ return yield* queue.unsafeRaw;
143
+ });
107
144
 
108
- return QueueDefinitionClass as (abstract new () => object) & typeof queueDefinition;
145
+ return Object.assign(tag, {
146
+ id: queueDefinition.id,
147
+ message: queueDefinition.message,
148
+ make: queueDefinition.make,
149
+ layer,
150
+ send,
151
+ sendBatch,
152
+ metrics,
153
+ unsafeRaw,
154
+ }) as TagClass<Self, Id, Message>;
109
155
  };
110
156
 
111
157
  export const Queue = Tag;
@@ -1,4 +1,4 @@
1
- import { Data, Effect } from "effect";
1
+ import { Context, Data, Effect, type Scope } from "effect";
2
2
 
3
3
  import * as Binding from "./Binding";
4
4
  import * as CloudflareRpc from "./Rpc";
@@ -74,86 +74,177 @@ type ServiceMethodSuccess<Api, Method extends keyof Api> = RpcInvocation.AsyncMe
74
74
  Api,
75
75
  Method
76
76
  >;
77
+ type ServiceMethodCloudflareReturn<
78
+ Api,
79
+ Method extends keyof Api,
80
+ > = RpcInvocation.AsyncMethodCloudflareReturn<Api, Method>;
77
81
 
78
- type ServiceCall<Self, Api> = <Method extends ServiceMethodKey<Api>>(
82
+ type ServiceCall<R, Api> = <Method extends ServiceMethodKey<Api>>(
79
83
  method: Method,
80
84
  ...args: ServiceMethodArgs<Api, Method>
81
- ) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError, Self>;
85
+ ) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError, R>;
82
86
 
83
- type DefinitionDirectMethods<Self, Definition extends WorkerDefinition.Definition.Any> = {
87
+ type DefinitionDirectMethods<R, Definition extends WorkerDefinition.Definition.Any> = {
84
88
  readonly [Method in RpcDefinition.Definition.MethodNames<Definition>]: (
85
89
  ...args: RpcDefinition.Method.Args<Definition["methods"][Method]>
86
90
  ) => Effect.Effect<
87
91
  RpcDefinition.Method.Success<Definition["methods"][Method]>,
88
92
  ServiceBindingRpcError,
89
- Self
93
+ R
90
94
  >;
91
95
  };
92
96
 
93
- type DirectMethods<Self, Definition> = Definition extends WorkerDefinition.Definition.Any
94
- ? DefinitionDirectMethods<Self, Definition>
97
+ type DirectMethods<R, Definition> = Definition extends WorkerDefinition.Definition.Any
98
+ ? DefinitionDirectMethods<R, Definition>
95
99
  : {};
96
100
 
97
- /**
98
- * Creates a typed Effect service for a Worker service binding.
99
- *
100
- * Returned value includes:
101
- * - a Context tag for dependency injection
102
- * - `fetch(...)` for raw HTTP forwarding
103
- * - `call(...)` for generic RPC invocation
104
- * - direct RPC methods when `definition` is provided
105
- *
106
- * @example
107
- * ```ts
108
- * const Counter = WorkerDefinition.make("Counter", {
109
- * increment: WorkerDefinition.method({
110
- * args: [Schema.Number],
111
- * success: Schema.Number,
112
- * }),
113
- * });
114
- *
115
- * const CounterService = Counter.binding("COUNTER", { binding: "COUNTER" });
116
- *
117
- * const program = Effect.gen(function* () {
118
- * const next = yield* CounterService.increment(1);
119
- * return next;
120
- * });
121
- * ```
122
- */
123
- export const Service =
124
- <Self, Api extends object = never>() =>
125
- <
126
- Id extends string,
127
- const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
128
- >(
129
- id: Id,
130
- definition: ServiceBindingDefinition<Definition>,
131
- ) => {
132
- type ServiceApi = ApiOrDefinition<Api, Definition>;
101
+ export type ServiceBindingEffectClient<
102
+ Api extends object,
103
+ Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
104
+ > = DirectMethods<never, Definition> & {
105
+ /**
106
+ * Forwards an HTTP request to the bound Worker service.
107
+ *
108
+ * Use this when the service binding is acting as an HTTP origin rather than a
109
+ * Cloudflare RPC target.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * import { Effect } from "effect";
114
+ *
115
+ * const program = Effect.gen(function* () {
116
+ * const api = yield* ApiWorker;
117
+ * return yield* api.fetch(new Request("https://internal.example/users"));
118
+ * });
119
+ * ```
120
+ */
121
+ readonly fetch: (
122
+ input: RequestInfo | URL,
123
+ init?: RequestInit,
124
+ ) => Effect.Effect<globalThis.Response, ServiceBindingFetchError>;
125
+ /**
126
+ * Invokes a Worker RPC method and returns Cloudflare's raw RPC result.
127
+ *
128
+ * This preserves Cloudflare RPC behavior such as promise-like pipelining and
129
+ * transferable / disposable result objects. It does not resolve the returned
130
+ * promise-like value and it does not decode definition-backed success schemas.
131
+ *
132
+ * Most application code should use {@link call} instead.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * import { Effect } from "effect";
137
+ *
138
+ * const program = Effect.gen(function* () {
139
+ * const counter = yield* CounterService;
140
+ *
141
+ * const result = yield* counter.rpc("increment", 41);
142
+ * const value = yield* Effect.promise(() => result);
143
+ *
144
+ * return value;
145
+ * });
146
+ * ```
147
+ */
148
+ readonly rpc: <Method extends ServiceMethodKey<Api>>(
149
+ method: Method,
150
+ ...args: ServiceMethodArgs<Api, Method>
151
+ ) => Effect.Effect<ServiceMethodCloudflareReturn<Api, Method>, ServiceBindingRpcError>;
152
+ /**
153
+ * Invokes a Worker RPC method, resolves Cloudflare's RPC result, and decodes
154
+ * the success value when the binding was created from a definition.
155
+ *
156
+ * This is the normal choice when application code wants the final typed value.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * import { Effect } from "effect";
161
+ *
162
+ * const program = Effect.gen(function* () {
163
+ * const counter = yield* CounterService;
164
+ * const value = yield* counter.call("increment", 41);
165
+ *
166
+ * return value;
167
+ * });
168
+ * ```
169
+ */
170
+ readonly call: <Method extends ServiceMethodKey<Api>>(
171
+ method: Method,
172
+ ...args: ServiceMethodArgs<Api, Method>
173
+ ) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError>;
174
+ /**
175
+ * Invokes a Worker RPC method in the current `Scope`, resolves Cloudflare's RPC
176
+ * result, decodes definition-backed success values, and disposes the resolved
177
+ * result when the scope closes if it implements `Symbol.dispose`.
178
+ *
179
+ * Use this for RPC methods that return Cloudflare RPC resources or other
180
+ * disposable objects whose lifetime should be tied to an Effect scope.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * import { Effect } from "effect";
185
+ *
186
+ * const program = Effect.scoped(
187
+ * Effect.gen(function* () {
188
+ * const files = yield* FileService;
189
+ * const handle = yield* files.scopedCall("open", "report.csv");
190
+ *
191
+ * return yield* handle.read();
192
+ * }),
193
+ * );
194
+ * ```
195
+ */
196
+ readonly scopedCall: <Method extends ServiceMethodKey<Api>>(
197
+ method: Method,
198
+ ...args: ServiceMethodArgs<Api, Method>
199
+ ) => Effect.Effect<Awaited<ServiceMethodSuccess<Api, Method>>, unknown, Scope.Scope>;
200
+ };
133
201
 
134
- const tag = Binding.Service<Self>()(
135
- id,
136
- definition.binding,
137
- (value): value is ServiceBindingClient<ServiceApi> =>
138
- typeof value === "object" && value !== null && "fetch" in value,
139
- );
202
+ export type ServiceBindingStaticClient<
203
+ R,
204
+ Api extends object,
205
+ Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
206
+ > = DirectMethods<R, Definition> & {
207
+ readonly fetch: (
208
+ input: RequestInfo | URL,
209
+ init?: RequestInit,
210
+ ) => Effect.Effect<globalThis.Response, ServiceBindingFetchError, R>;
211
+ readonly rpc: <Method extends ServiceMethodKey<Api>>(
212
+ method: Method,
213
+ ...args: ServiceMethodArgs<Api, Method>
214
+ ) => Effect.Effect<ServiceMethodCloudflareReturn<Api, Method>, ServiceBindingRpcError, R>;
215
+ readonly call: <Method extends ServiceMethodKey<Api>>(
216
+ method: Method,
217
+ ...args: ServiceMethodArgs<Api, Method>
218
+ ) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError, R>;
219
+ readonly scopedCall: <Method extends ServiceMethodKey<Api>>(
220
+ method: Method,
221
+ ...args: ServiceMethodArgs<Api, Method>
222
+ ) => Effect.Effect<Awaited<ServiceMethodSuccess<Api, Method>>, unknown, Scope.Scope | R>;
223
+ };
140
224
 
141
- const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
142
- Effect.gen(function* () {
143
- const service = yield* tag;
225
+ export const isServiceBindingClient = <Api extends object>(
226
+ value: unknown,
227
+ ): value is ServiceBindingClient<Api> =>
228
+ typeof value === "object" && value !== null && typeof Reflect.get(value, "fetch") === "function";
144
229
 
145
- return yield* Effect.tryPromise({
146
- try: () => service.fetch(input, init),
147
- catch: (cause) => new ServiceBindingFetchError({ binding: definition.binding, cause }),
148
- });
230
+ export const makeClient = <
231
+ Api extends object,
232
+ const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
233
+ >(
234
+ definition: ServiceBindingDefinition<Definition>,
235
+ ): ((service: ServiceBindingClient<Api>) => ServiceBindingEffectClient<Api, Definition>) => {
236
+ return (service: ServiceBindingClient<Api>) => {
237
+ const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
238
+ Effect.tryPromise({
239
+ try: () => service.fetch(input, init),
240
+ catch: (cause) => new ServiceBindingFetchError({ binding: definition.binding, cause }),
149
241
  });
150
242
 
151
- const rpc = <Method extends ServiceMethodKey<ServiceApi>>(
243
+ const rpc = <Method extends ServiceMethodKey<Api>>(
152
244
  method: Method,
153
- ...args: ServiceMethodArgs<ServiceApi, Method>
245
+ ...args: ServiceMethodArgs<Api, Method>
154
246
  ) =>
155
247
  Effect.gen(function* () {
156
- const service = yield* tag;
157
248
  const methodName = String(method);
158
249
  const encodedArgs =
159
250
  definition.definition === undefined
@@ -176,7 +267,7 @@ export const Service =
176
267
  return yield* RpcInvocation.invokeRpcMethod(
177
268
  service,
178
269
  method,
179
- encodedArgs as ServiceMethodArgs<ServiceApi, Method>,
270
+ encodedArgs as ServiceMethodArgs<Api, Method>,
180
271
  (cause) =>
181
272
  new ServiceBindingRpcError({
182
273
  binding: definition.binding,
@@ -186,13 +277,20 @@ export const Service =
186
277
  );
187
278
  });
188
279
 
189
- const call = <Method extends ServiceMethodKey<ServiceApi>>(
190
- method: Method,
191
- ...args: ServiceMethodArgs<ServiceApi, Method>
280
+ const decodeSuccess = <Method extends ServiceMethodKey<Api>>(
281
+ methodName: string,
282
+ value: Awaited<ServiceMethodCloudflareReturn<Api, Method>>,
192
283
  ) =>
193
284
  Effect.gen(function* () {
194
- const methodName = String(method);
195
- const value = yield* CloudflareRpc.resolve(yield* rpc(method, ...args)).pipe(
285
+ if (definition.definition === undefined) {
286
+ return value as ServiceMethodSuccess<Api, Method>;
287
+ }
288
+
289
+ const decoded = yield* RpcDefinition.decodeSuccess(
290
+ definition.definition,
291
+ methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
292
+ value,
293
+ ).pipe(
196
294
  Effect.mapError(
197
295
  (cause) =>
198
296
  new ServiceBindingRpcError({
@@ -203,15 +301,16 @@ export const Service =
203
301
  ),
204
302
  );
205
303
 
206
- if (definition.definition === undefined) {
207
- return value as ServiceMethodSuccess<ServiceApi, Method>;
208
- }
304
+ return decoded as ServiceMethodSuccess<Api, Method>;
305
+ });
209
306
 
210
- const decoded = yield* RpcDefinition.decodeSuccess(
211
- definition.definition,
212
- methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
213
- value,
214
- ).pipe(
307
+ const call = <Method extends ServiceMethodKey<Api>>(
308
+ method: Method,
309
+ ...args: ServiceMethodArgs<Api, Method>
310
+ ) =>
311
+ Effect.gen(function* () {
312
+ const methodName = String(method);
313
+ const value = yield* CloudflareRpc.resolve(yield* rpc(method, ...args)).pipe(
215
314
  Effect.mapError(
216
315
  (cause) =>
217
316
  new ServiceBindingRpcError({
@@ -222,7 +321,115 @@ export const Service =
222
321
  ),
223
322
  );
224
323
 
225
- return decoded as ServiceMethodSuccess<ServiceApi, Method>;
324
+ return yield* decodeSuccess<Method>(methodName, value);
325
+ });
326
+
327
+ const scopedCall = <Method extends ServiceMethodKey<Api>>(
328
+ method: Method,
329
+ ...args: ServiceMethodArgs<Api, Method>
330
+ ) =>
331
+ Effect.gen(function* () {
332
+ const methodName = String(method);
333
+ const result = yield* rpc(method, ...args);
334
+ const value = yield* CloudflareRpc.scoped(result);
335
+ return yield* decodeSuccess<Method>(methodName, value);
336
+ });
337
+
338
+ const directMethods = makeDirectMethods<never, Api, Definition>(definition.definition, call);
339
+
340
+ return Object.assign(directMethods, {
341
+ fetch,
342
+ rpc,
343
+ call,
344
+ scopedCall,
345
+ }) as ServiceBindingEffectClient<Api, Definition>;
346
+ };
347
+ };
348
+
349
+ export const layer = <
350
+ Self,
351
+ Api extends object,
352
+ const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
353
+ >(
354
+ tag: Context.Service<Self, ServiceBindingEffectClient<Api, Definition>>,
355
+ definition: ServiceBindingDefinition<Definition>,
356
+ ) =>
357
+ Binding.layer(
358
+ tag,
359
+ definition.binding,
360
+ (value): value is ServiceBindingClient<Api> => isServiceBindingClient<Api>(value),
361
+ makeClient<Api, Definition>(definition),
362
+ );
363
+
364
+ /**
365
+ * Creates a typed Effect service for a Worker service binding.
366
+ *
367
+ * Returned value includes:
368
+ * - a Context tag for dependency injection
369
+ * - `fetch(...)` for raw HTTP forwarding
370
+ * - `rpc(...)` for raw Cloudflare RPC results
371
+ * - `call(...)` for resolved and decoded RPC results
372
+ * - `scopedCall(...)` for scoped and decoded disposable RPC results
373
+ * - direct RPC methods when `definition` is provided
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * const Counter = WorkerDefinition.make("Counter", {
378
+ * increment: WorkerDefinition.method({
379
+ * args: [Schema.Number],
380
+ * success: Schema.Number,
381
+ * }),
382
+ * });
383
+ *
384
+ * const CounterLive = Counter.layer({ binding: "COUNTER" });
385
+ *
386
+ * const program = Effect.gen(function* () {
387
+ * const counter = yield* Counter;
388
+ * const next = yield* counter.increment(1);
389
+ * return next;
390
+ * });
391
+ * ```
392
+ */
393
+ export const Service =
394
+ <Self, Api extends object = never>() =>
395
+ <
396
+ Id extends string,
397
+ const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
398
+ >(
399
+ id: Id,
400
+ definition: ServiceBindingDefinition<Definition>,
401
+ ) => {
402
+ type ServiceApi = ApiOrDefinition<Api, Definition>;
403
+
404
+ const tag = Binding.Service<Self>()(
405
+ id,
406
+ definition.binding,
407
+ (value): value is ServiceBindingClient<ServiceApi> => isServiceBindingClient(value),
408
+ makeClient<ServiceApi, Definition>(definition),
409
+ );
410
+
411
+ const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
412
+ Effect.gen(function* () {
413
+ const service = yield* tag;
414
+ return yield* service.fetch(input, init);
415
+ });
416
+
417
+ const rpc = <Method extends ServiceMethodKey<ServiceApi>>(
418
+ method: Method,
419
+ ...args: ServiceMethodArgs<ServiceApi, Method>
420
+ ) =>
421
+ Effect.gen(function* () {
422
+ const service = yield* tag;
423
+ return yield* service.rpc(method, ...args);
424
+ });
425
+
426
+ const call = <Method extends ServiceMethodKey<ServiceApi>>(
427
+ method: Method,
428
+ ...args: ServiceMethodArgs<ServiceApi, Method>
429
+ ) =>
430
+ Effect.gen(function* () {
431
+ const service = yield* tag;
432
+ return yield* service.call(method, ...args);
226
433
  });
227
434
 
228
435
  const scopedCall = <Method extends ServiceMethodKey<ServiceApi>>(
@@ -230,8 +437,8 @@ export const Service =
230
437
  ...args: ServiceMethodArgs<ServiceApi, Method>
231
438
  ) =>
232
439
  Effect.gen(function* () {
233
- const result = yield* rpc(method, ...args);
234
- return yield* CloudflareRpc.scoped(result);
440
+ const service = yield* tag;
441
+ return yield* service.scopedCall(method, ...args);
235
442
  });
236
443
 
237
444
  const directMethods = makeDirectMethods<Self, ServiceApi, Definition>(
@@ -257,14 +464,14 @@ export const Service =
257
464
  };
258
465
  };
259
466
 
260
- const makeDirectMethods = <
261
- Self,
467
+ export const makeDirectMethods = <
468
+ R,
262
469
  Api,
263
470
  Definition extends WorkerDefinition.Definition.Any | undefined,
264
471
  >(
265
472
  rpcDefinition: Definition | undefined,
266
- call: ServiceCall<Self, Api>,
267
- ): DirectMethods<Self, Definition> => {
473
+ call: ServiceCall<R, Api>,
474
+ ): DirectMethods<R, Definition> => {
268
475
  const methods = {} as Record<string, unknown>;
269
476
 
270
477
  if (rpcDefinition !== undefined) {
@@ -274,5 +481,5 @@ const makeDirectMethods = <
274
481
  }
275
482
  }
276
483
 
277
- return methods as DirectMethods<Self, Definition>;
484
+ return methods as DirectMethods<R, Definition>;
278
485
  };