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,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";
@@ -6,8 +6,6 @@ import type * as DurableObjectDefinition from "./DurableObjectDefinition";
6
6
  import * as RpcDefinition from "./RpcDefinition";
7
7
  import * as RpcInvocation from "./internal/RpcInvocation";
8
8
 
9
- const TypeId = "effect-cf/DurableObjectNamespace" as const;
10
-
11
9
  interface DurableObjectFetcher {
12
10
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
13
11
  }
@@ -66,14 +64,6 @@ export interface DurableObjectNamespaceDefinition {
66
64
  readonly binding: string;
67
65
  }
68
66
 
69
- type ApiFromDefinition<Definition> = Definition extends DurableObjectDefinition.Definition.Any
70
- ? DurableObjectDefinition.ServerApi<Definition>
71
- : never;
72
-
73
- type ApiOrDefinition<Api extends object, Definition> = [Api] extends [never]
74
- ? ApiFromDefinition<Definition>
75
- : Api;
76
-
77
67
  /**
78
68
  * Namespace binding metadata with optional RPC schema.
79
69
  */
@@ -109,25 +99,30 @@ type StubMethodSuccess<Api, Method extends keyof Api> = RpcInvocation.AsyncMetho
109
99
  Api,
110
100
  Method
111
101
  >;
102
+ type StubMethodCloudflareReturn<
103
+ Api,
104
+ Method extends keyof Api,
105
+ > = RpcInvocation.AsyncMethodCloudflareReturn<Api, Method>;
112
106
 
113
- type StubCall<Api extends object> = <Method extends StubMethodKey<Api>>(
107
+ type StubCall<R, Api extends object> = <Method extends StubMethodKey<Api>>(
114
108
  stub: DurableObjectStubClient<Api>,
115
109
  method: Method,
116
110
  ...args: StubMethodArgs<Api, Method>
117
- ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError>;
111
+ ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError, R>;
118
112
 
119
- type DurableObjectDirectClient<Self, Api extends object> = {
113
+ type DurableObjectDirectClient<R, Api extends object> = {
120
114
  readonly fetch: (
121
115
  input: RequestInfo | URL,
122
116
  init?: RequestInit,
123
- ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, Self>;
117
+ ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, R>;
124
118
  } & {
125
119
  readonly [Method in StubMethodKey<Api>]: (
126
120
  ...args: StubMethodArgs<Api, Method>
127
- ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError, Self>;
121
+ ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError, R>;
128
122
  };
129
123
 
130
124
  type DefinitionNamespaceDirectMethods<
125
+ R,
131
126
  Api extends object,
132
127
  Definition extends DurableObjectDefinition.Definition.Any,
133
128
  > = {
@@ -136,141 +131,281 @@ type DefinitionNamespaceDirectMethods<
136
131
  ...args: RpcDefinition.Method.Args<Definition["methods"][Method]>
137
132
  ) => Effect.Effect<
138
133
  RpcDefinition.Method.Success<Definition["methods"][Method]>,
139
- DurableObjectRpcError
134
+ DurableObjectRpcError,
135
+ R
140
136
  >;
141
137
  };
142
138
 
143
139
  type DefinitionDurableObjectDirectClient<
144
- Self,
140
+ R,
145
141
  Definition extends DurableObjectDefinition.Definition.Any,
146
142
  > = {
147
143
  readonly fetch: (
148
144
  input: RequestInfo | URL,
149
145
  init?: RequestInit,
150
- ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, Self>;
146
+ ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, R>;
151
147
  } & {
152
148
  readonly [Method in RpcDefinition.Definition.MethodNames<Definition>]: (
153
149
  ...args: RpcDefinition.Method.Args<Definition["methods"][Method]>
154
150
  ) => Effect.Effect<
155
151
  RpcDefinition.Method.Success<Definition["methods"][Method]>,
156
152
  DurableObjectRpcError,
157
- Self
153
+ R
158
154
  >;
159
155
  };
160
156
 
161
157
  type DefinitionNamespaceDirectClientMethods<
162
- Self,
158
+ R,
163
159
  Definition extends DurableObjectDefinition.Definition.Any,
164
160
  > = {
165
161
  readonly byName: (
166
162
  name: string,
167
163
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
168
- ) => DefinitionDurableObjectDirectClient<Self, Definition>;
164
+ ) => DefinitionDurableObjectDirectClient<R, Definition>;
169
165
  readonly byId: (
170
166
  id: globalThis.DurableObjectId,
171
167
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
172
- ) => DefinitionDurableObjectDirectClient<Self, Definition>;
168
+ ) => DefinitionDurableObjectDirectClient<R, Definition>;
173
169
  };
174
170
 
175
171
  type DirectMethods<
176
- Self,
172
+ R,
177
173
  Api extends object,
178
174
  Definition,
179
175
  > = Definition extends DurableObjectDefinition.Definition.Any
180
- ? DefinitionNamespaceDirectMethods<Api, Definition> &
181
- DefinitionNamespaceDirectClientMethods<Self, Definition>
176
+ ? DefinitionNamespaceDirectMethods<R, Api, Definition> &
177
+ DefinitionNamespaceDirectClientMethods<R, Definition>
182
178
  : {};
183
179
 
184
- /**
185
- * Creates a typed Effect service for a Durable Object namespace binding.
186
- *
187
- * Returned value includes namespace helpers (`get`, `getByName`, `newUniqueId`),
188
- * raw `fetch` / `call` helpers, and direct RPC helpers when `definition` is provided.
189
- *
190
- * @example
191
- * ```ts
192
- * const ChatRoom = DurableObjectDefinition.make("ChatRoom", {
193
- * postMessage: DurableObjectDefinition.method({
194
- * args: [Schema.String],
195
- * success: Schema.Void,
196
- * }),
197
- * });
198
- *
199
- * const ChatRooms = ChatRoom.namespace("CHAT_ROOMS", { binding: "CHAT_ROOMS" });
200
- *
201
- * const program = Effect.gen(function* () {
202
- * const room = ChatRooms.byName("general");
203
- * yield* room.postMessage("hello");
204
- * });
205
- * ```
206
- */
207
- export const Service =
208
- <Self, Api extends object = never>() =>
209
- <
210
- Id extends string,
211
- const Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
212
- >(
213
- id: Id,
214
- definition: DurableObjectNamespaceBindingDefinition<Definition>,
215
- ) => {
216
- type NamespaceApi = ApiOrDefinition<Api, Definition>;
217
- type NamespaceClient = DurableObjectNamespaceClient<NamespaceApi>;
218
- type StubClient = DurableObjectStubClient<NamespaceApi>;
219
-
220
- const hasFunction = (value: object, key: string): boolean =>
221
- typeof Reflect.get(value, key) === "function";
222
-
223
- const isDurableObjectNamespaceClient = (value: unknown): value is NamespaceClient =>
224
- typeof value === "object" &&
225
- value !== null &&
226
- hasFunction(value, "getByName") &&
227
- hasFunction(value, "get") &&
228
- hasFunction(value, "idFromName") &&
229
- hasFunction(value, "idFromString") &&
230
- hasFunction(value, "newUniqueId") &&
231
- hasFunction(value, "jurisdiction");
232
-
233
- const tag = Binding.Service<Self>()(id, definition.binding, (value): value is NamespaceClient =>
234
- isDurableObjectNamespaceClient(value),
235
- );
236
-
237
- const newUniqueId = Effect.fnUntraced(function* (
238
- options?: globalThis.DurableObjectNamespaceNewUniqueIdOptions,
239
- ) {
240
- const namespace = yield* tag;
241
- return namespace.newUniqueId(options);
242
- });
180
+ export type DurableObjectNamespaceEffectClient<
181
+ Api extends object,
182
+ Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
183
+ > = DirectMethods<never, Api, Definition> & {
184
+ /** Creates a globally unique Durable Object id. */
185
+ readonly newUniqueId: (
186
+ options?: globalThis.DurableObjectNamespaceNewUniqueIdOptions,
187
+ ) => Effect.Effect<globalThis.DurableObjectId>;
188
+ /** Deterministically maps a name to a Durable Object id. */
189
+ readonly idFromName: (name: string) => Effect.Effect<globalThis.DurableObjectId>;
190
+ /** Rehydrates a Durable Object id from its string form. */
191
+ readonly idFromString: (id: string) => Effect.Effect<globalThis.DurableObjectId>;
192
+ /** Returns a stub for an existing Durable Object id. */
193
+ readonly get: (
194
+ id: globalThis.DurableObjectId,
195
+ options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
196
+ ) => Effect.Effect<DurableObjectStubClient<Api>>;
197
+ /** Returns a stub by deterministic name. */
198
+ readonly getByName: (
199
+ name: string,
200
+ options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
201
+ ) => Effect.Effect<DurableObjectStubClient<Api>>;
202
+ /** Selects a namespace pinned to a specific jurisdiction. */
203
+ readonly jurisdiction: (
204
+ jurisdiction: globalThis.DurableObjectJurisdiction,
205
+ ) => Effect.Effect<DurableObjectNamespaceClient<Api>>;
206
+ /**
207
+ * Forwards an HTTP request to a Durable Object stub.
208
+ *
209
+ * Use this for fetch-based Durable Object APIs, including WebSocket upgrade
210
+ * forwarding where the native response must be preserved.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * import { Effect } from "effect";
215
+ *
216
+ * const program = Effect.gen(function* () {
217
+ * const rooms = yield* ChatRooms;
218
+ * const room = yield* rooms.getByName("general");
219
+ *
220
+ * return yield* rooms.fetch(room, new Request("https://worker.example/room"));
221
+ * });
222
+ * ```
223
+ */
224
+ readonly fetch: (
225
+ stub: DurableObjectStubClient<Api>,
226
+ input: RequestInfo | URL,
227
+ init?: RequestInit,
228
+ ) => Effect.Effect<globalThis.Response, DurableObjectFetchError>;
229
+ /**
230
+ * Invokes a Durable Object RPC method and returns Cloudflare's raw RPC result.
231
+ *
232
+ * This preserves Cloudflare RPC behavior such as promise-like pipelining and
233
+ * transferable / disposable result objects. It does not resolve the returned
234
+ * promise-like value and it does not decode definition-backed success schemas.
235
+ *
236
+ * Most application code should use {@link call} instead.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * import { Effect } from "effect";
241
+ *
242
+ * const program = Effect.gen(function* () {
243
+ * const counters = yield* Counters;
244
+ * const counter = yield* counters.getByName("main");
245
+ *
246
+ * const result = yield* counters.rpc(counter, "get");
247
+ * const value = yield* Effect.promise(() => result);
248
+ *
249
+ * return value;
250
+ * });
251
+ * ```
252
+ */
253
+ readonly rpc: <Method extends StubMethodKey<Api>>(
254
+ stub: DurableObjectStubClient<Api>,
255
+ method: Method,
256
+ ...args: StubMethodArgs<Api, Method>
257
+ ) => Effect.Effect<StubMethodCloudflareReturn<Api, Method>, DurableObjectRpcError>;
258
+ /**
259
+ * Invokes a Durable Object RPC method, resolves Cloudflare's RPC result, and
260
+ * decodes the success value when the namespace was created from a definition.
261
+ *
262
+ * This is the normal choice when application code wants the final typed value.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * import { Effect } from "effect";
267
+ *
268
+ * const program = Effect.gen(function* () {
269
+ * const counters = yield* Counters;
270
+ * const counter = yield* counters.getByName("main");
271
+ *
272
+ * return yield* counters.call(counter, "increment", 1);
273
+ * });
274
+ * ```
275
+ */
276
+ readonly call: <Method extends StubMethodKey<Api>>(
277
+ stub: DurableObjectStubClient<Api>,
278
+ method: Method,
279
+ ...args: StubMethodArgs<Api, Method>
280
+ ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError>;
281
+ /**
282
+ * Invokes a Durable Object RPC method in the current `Scope`, resolves
283
+ * Cloudflare's RPC result, decodes definition-backed success values, and
284
+ * disposes the resolved result when the scope closes if it implements
285
+ * `Symbol.dispose`.
286
+ *
287
+ * Use this for RPC methods that return Cloudflare RPC resources or other
288
+ * disposable objects whose lifetime should be tied to an Effect scope.
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * import { Effect } from "effect";
293
+ *
294
+ * const program = Effect.scoped(
295
+ * Effect.gen(function* () {
296
+ * const rooms = yield* ChatRooms;
297
+ * const room = yield* rooms.getByName("general");
298
+ * const handle = yield* rooms.scopedCall(room, "openStream");
299
+ *
300
+ * return yield* handle.read();
301
+ * }),
302
+ * );
303
+ * ```
304
+ */
305
+ readonly scopedCall: <Method extends StubMethodKey<Api>>(
306
+ stub: DurableObjectStubClient<Api>,
307
+ method: Method,
308
+ ...args: StubMethodArgs<Api, Method>
309
+ ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, unknown, Scope.Scope>;
310
+ /**
311
+ * Exposes the underlying native Durable Object namespace binding.
312
+ *
313
+ * Prefer the typed helpers above unless Cloudflare exposes a platform feature
314
+ * that is not wrapped by effect-cf yet.
315
+ */
316
+ readonly unsafeRaw: Effect.Effect<DurableObjectNamespaceClient<Api>>;
317
+ };
243
318
 
244
- const idFromName = Effect.fnUntraced(function* (name: string) {
245
- const namespace = yield* tag;
246
- return namespace.idFromName(name);
247
- });
319
+ export type DurableObjectNamespaceStaticClient<
320
+ R,
321
+ Api extends object,
322
+ Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
323
+ > = DirectMethods<R, Api, Definition> & {
324
+ readonly newUniqueId: (
325
+ options?: globalThis.DurableObjectNamespaceNewUniqueIdOptions,
326
+ ) => Effect.Effect<globalThis.DurableObjectId, never, R>;
327
+ readonly idFromName: (name: string) => Effect.Effect<globalThis.DurableObjectId, never, R>;
328
+ readonly idFromString: (id: string) => Effect.Effect<globalThis.DurableObjectId, never, R>;
329
+ readonly get: (
330
+ id: globalThis.DurableObjectId,
331
+ options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
332
+ ) => Effect.Effect<DurableObjectStubClient<Api>, never, R>;
333
+ readonly getByName: (
334
+ name: string,
335
+ options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
336
+ ) => Effect.Effect<DurableObjectStubClient<Api>, never, R>;
337
+ readonly jurisdiction: (
338
+ jurisdiction: globalThis.DurableObjectJurisdiction,
339
+ ) => Effect.Effect<DurableObjectNamespaceClient<Api>, never, R>;
340
+ readonly fetch: (
341
+ stub: DurableObjectStubClient<Api>,
342
+ input: RequestInfo | URL,
343
+ init?: RequestInit,
344
+ ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, R>;
345
+ readonly rpc: <Method extends StubMethodKey<Api>>(
346
+ stub: DurableObjectStubClient<Api>,
347
+ method: Method,
348
+ ...args: StubMethodArgs<Api, Method>
349
+ ) => Effect.Effect<StubMethodCloudflareReturn<Api, Method>, DurableObjectRpcError, R>;
350
+ readonly call: <Method extends StubMethodKey<Api>>(
351
+ stub: DurableObjectStubClient<Api>,
352
+ method: Method,
353
+ ...args: StubMethodArgs<Api, Method>
354
+ ) => Effect.Effect<StubMethodSuccess<Api, Method>, DurableObjectRpcError, R>;
355
+ readonly scopedCall: <Method extends StubMethodKey<Api>>(
356
+ stub: DurableObjectStubClient<Api>,
357
+ method: Method,
358
+ ...args: StubMethodArgs<Api, Method>
359
+ ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, unknown, Scope.Scope | R>;
360
+ readonly unsafeRaw: () => Effect.Effect<DurableObjectNamespaceClient<Api>, never, R>;
361
+ };
248
362
 
249
- const idFromString = Effect.fnUntraced(function* (id: string) {
250
- const namespace = yield* tag;
251
- return namespace.idFromString(id);
252
- });
363
+ const hasFunction = (value: object, key: string): boolean =>
364
+ typeof Reflect.get(value, key) === "function";
365
+
366
+ export const isDurableObjectNamespaceClient = <Api extends object>(
367
+ value: unknown,
368
+ ): value is DurableObjectNamespaceClient<Api> =>
369
+ typeof value === "object" &&
370
+ value !== null &&
371
+ hasFunction(value, "getByName") &&
372
+ hasFunction(value, "get") &&
373
+ hasFunction(value, "idFromName") &&
374
+ hasFunction(value, "idFromString") &&
375
+ hasFunction(value, "newUniqueId") &&
376
+ hasFunction(value, "jurisdiction");
377
+
378
+ export const makeClient = <
379
+ Api extends object,
380
+ const Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
381
+ >(
382
+ definition: DurableObjectNamespaceBindingDefinition<Definition>,
383
+ ): ((
384
+ namespace: DurableObjectNamespaceClient<Api>,
385
+ ) => DurableObjectNamespaceEffectClient<Api, Definition>) => {
386
+ type NamespaceClient = DurableObjectNamespaceClient<Api>;
387
+ type StubClient = DurableObjectStubClient<Api>;
388
+
389
+ return (namespace: NamespaceClient) => {
390
+ const newUniqueId = (options?: globalThis.DurableObjectNamespaceNewUniqueIdOptions) =>
391
+ Effect.sync(() => namespace.newUniqueId(options));
253
392
 
254
- const get = Effect.fnUntraced(function* (
393
+ const idFromName = (name: string) => Effect.sync(() => namespace.idFromName(name));
394
+
395
+ const idFromString = (id: string) => Effect.sync(() => namespace.idFromString(id));
396
+
397
+ const get = (
255
398
  id: globalThis.DurableObjectId,
256
399
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
257
- ) {
258
- const namespace = yield* tag;
259
- return namespace.get(id, options);
260
- });
400
+ ) => Effect.sync(() => namespace.get(id, options));
261
401
 
262
- const getByName = Effect.fnUntraced(function* (
402
+ const getByName = (
263
403
  name: string,
264
404
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
265
- ) {
266
- const namespace = yield* tag;
267
- return namespace.getByName(name, options);
268
- });
405
+ ) => Effect.sync(() => namespace.getByName(name, options));
269
406
 
270
- const jurisdiction = Effect.fnUntraced(function* (value: globalThis.DurableObjectJurisdiction) {
271
- const namespace = yield* tag;
272
- return namespace.jurisdiction(value);
273
- });
407
+ const jurisdiction = (value: globalThis.DurableObjectJurisdiction) =>
408
+ Effect.sync(() => namespace.jurisdiction(value));
274
409
 
275
410
  const fetch = (stub: StubClient, input: RequestInfo | URL, init?: RequestInit) =>
276
411
  Effect.tryPromise({
@@ -278,10 +413,10 @@ export const Service =
278
413
  catch: (cause) => new DurableObjectFetchError({ binding: definition.binding, cause }),
279
414
  });
280
415
 
281
- const rpc = <Method extends StubMethodKey<NamespaceApi>>(
416
+ const rpc = <Method extends StubMethodKey<Api>>(
282
417
  stub: StubClient,
283
418
  method: Method,
284
- ...args: StubMethodArgs<NamespaceApi, Method>
419
+ ...args: StubMethodArgs<Api, Method>
285
420
  ) =>
286
421
  Effect.gen(function* () {
287
422
  const methodName = String(method);
@@ -306,7 +441,7 @@ export const Service =
306
441
  return yield* RpcInvocation.invokeRpcMethod(
307
442
  stub,
308
443
  method,
309
- encodedArgs as StubMethodArgs<NamespaceApi, Method>,
444
+ encodedArgs as StubMethodArgs<Api, Method>,
310
445
  (cause) =>
311
446
  new DurableObjectRpcError({
312
447
  binding: definition.binding,
@@ -316,14 +451,20 @@ export const Service =
316
451
  );
317
452
  });
318
453
 
319
- const call = <Method extends StubMethodKey<NamespaceApi>>(
320
- stub: StubClient,
321
- method: Method,
322
- ...args: StubMethodArgs<NamespaceApi, Method>
454
+ const decodeSuccess = <Method extends StubMethodKey<Api>>(
455
+ methodName: string,
456
+ value: Awaited<StubMethodCloudflareReturn<Api, Method>>,
323
457
  ) =>
324
458
  Effect.gen(function* () {
325
- const methodName = String(method);
326
- const value = yield* CloudflareRpc.resolve(yield* rpc(stub, method, ...args)).pipe(
459
+ if (definition.definition === undefined) {
460
+ return value as StubMethodSuccess<Api, Method>;
461
+ }
462
+
463
+ const decoded = yield* RpcDefinition.decodeSuccess(
464
+ definition.definition,
465
+ methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
466
+ value,
467
+ ).pipe(
327
468
  Effect.mapError(
328
469
  (cause) =>
329
470
  new DurableObjectRpcError({
@@ -334,15 +475,17 @@ export const Service =
334
475
  ),
335
476
  );
336
477
 
337
- if (definition.definition === undefined) {
338
- return value as StubMethodSuccess<NamespaceApi, Method>;
339
- }
478
+ return decoded as StubMethodSuccess<Api, Method>;
479
+ });
340
480
 
341
- const decoded = yield* RpcDefinition.decodeSuccess(
342
- definition.definition,
343
- methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
344
- value,
345
- ).pipe(
481
+ const call = <Method extends StubMethodKey<Api>>(
482
+ stub: StubClient,
483
+ method: Method,
484
+ ...args: StubMethodArgs<Api, Method>
485
+ ) =>
486
+ Effect.gen(function* () {
487
+ const methodName = String(method);
488
+ const value = yield* CloudflareRpc.resolve(yield* rpc(stub, method, ...args)).pipe(
346
489
  Effect.mapError(
347
490
  (cause) =>
348
491
  new DurableObjectRpcError({
@@ -353,29 +496,29 @@ export const Service =
353
496
  ),
354
497
  );
355
498
 
356
- return decoded as StubMethodSuccess<NamespaceApi, Method>;
499
+ return yield* decodeSuccess<Method>(methodName, value);
357
500
  });
358
501
 
359
- const scopedCall = <Method extends StubMethodKey<NamespaceApi>>(
502
+ const scopedCall = <Method extends StubMethodKey<Api>>(
360
503
  stub: StubClient,
361
504
  method: Method,
362
- ...args: StubMethodArgs<NamespaceApi, Method>
505
+ ...args: StubMethodArgs<Api, Method>
363
506
  ) =>
364
507
  Effect.gen(function* () {
508
+ const methodName = String(method);
365
509
  const result = yield* rpc(stub, method, ...args);
366
- return yield* CloudflareRpc.scoped(result);
510
+ const value = yield* CloudflareRpc.scoped(result);
511
+ return yield* decodeSuccess<Method>(methodName, value);
367
512
  });
368
513
 
369
- const directMethods = makeDirectMethods<Self, NamespaceApi, Definition>(definition.definition, {
514
+ const directMethods = makeDirectMethods<never, Api, Definition>(definition.definition, {
370
515
  call,
371
516
  fetch,
372
517
  get,
373
518
  getByName,
374
519
  });
375
520
 
376
- return Object.assign(tag, directMethods, {
377
- [TypeId]: TypeId,
378
- definition,
521
+ return Object.assign(directMethods, {
379
522
  newUniqueId,
380
523
  idFromName,
381
524
  idFromString,
@@ -386,46 +529,50 @@ export const Service =
386
529
  rpc,
387
530
  call,
388
531
  scopedCall,
389
- }) as typeof tag &
390
- DirectMethods<Self, NamespaceApi, Definition> & {
391
- readonly [TypeId]: typeof TypeId;
392
- readonly definition: DurableObjectNamespaceBindingDefinition<Definition>;
393
- readonly newUniqueId: typeof newUniqueId;
394
- readonly idFromName: typeof idFromName;
395
- readonly idFromString: typeof idFromString;
396
- readonly get: typeof get;
397
- readonly getByName: typeof getByName;
398
- readonly jurisdiction: typeof jurisdiction;
399
- readonly fetch: typeof fetch;
400
- readonly rpc: typeof rpc;
401
- readonly call: typeof call;
402
- readonly scopedCall: typeof scopedCall;
403
- };
532
+ unsafeRaw: Effect.succeed(namespace),
533
+ }) as DurableObjectNamespaceEffectClient<Api, Definition>;
404
534
  };
535
+ };
405
536
 
406
- const makeDirectMethods = <
537
+ export const layer = <
407
538
  Self,
408
539
  Api extends object,
540
+ const Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
541
+ >(
542
+ tag: Context.Service<Self, DurableObjectNamespaceEffectClient<Api, Definition>>,
543
+ definition: DurableObjectNamespaceBindingDefinition<Definition>,
544
+ ) =>
545
+ Binding.layer(
546
+ tag,
547
+ definition.binding,
548
+ (value): value is DurableObjectNamespaceClient<Api> =>
549
+ isDurableObjectNamespaceClient<Api>(value),
550
+ makeClient<Api, Definition>(definition),
551
+ );
552
+
553
+ export const makeDirectMethods = <
554
+ R,
555
+ Api extends object,
409
556
  Definition extends DurableObjectDefinition.Definition.Any | undefined,
410
557
  >(
411
558
  rpcDefinition: Definition | undefined,
412
559
  helpers: {
413
- readonly call: StubCall<Api>;
560
+ readonly call: StubCall<R, Api>;
414
561
  readonly fetch: (
415
562
  stub: DurableObjectStubClient<Api>,
416
563
  input: RequestInfo | URL,
417
564
  init?: RequestInit,
418
- ) => Effect.Effect<globalThis.Response, DurableObjectFetchError>;
565
+ ) => Effect.Effect<globalThis.Response, DurableObjectFetchError, R>;
419
566
  readonly get: (
420
567
  id: globalThis.DurableObjectId,
421
568
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
422
- ) => Effect.Effect<DurableObjectStubClient<Api>, never, Self>;
569
+ ) => Effect.Effect<DurableObjectStubClient<Api>, never, R>;
423
570
  readonly getByName: (
424
571
  name: string,
425
572
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
426
- ) => Effect.Effect<DurableObjectStubClient<Api>, never, Self>;
573
+ ) => Effect.Effect<DurableObjectStubClient<Api>, never, R>;
427
574
  },
428
- ): DirectMethods<Self, Api, Definition> => {
575
+ ): DirectMethods<R, Api, Definition> => {
429
576
  const methods = {} as Record<string, unknown>;
430
577
 
431
578
  if (rpcDefinition !== undefined) {
@@ -441,8 +588,8 @@ const makeDirectMethods = <
441
588
  }
442
589
 
443
590
  const makeClient = (
444
- getStub: () => Effect.Effect<DurableObjectStubClient<Api>, never, Self>,
445
- ): DurableObjectDirectClient<Self, Api> => {
591
+ getStub: () => Effect.Effect<DurableObjectStubClient<Api>, never, R>,
592
+ ): DurableObjectDirectClient<R, Api> => {
446
593
  const client = {
447
594
  fetch: (input: RequestInfo | URL, init?: RequestInit) =>
448
595
  Effect.gen(function* () {
@@ -460,12 +607,12 @@ const makeDirectMethods = <
460
607
  stub: DurableObjectStubClient<Api>,
461
608
  method: string,
462
609
  ...args: Array<unknown>
463
- ) => Effect.Effect<unknown, DurableObjectRpcError>
610
+ ) => Effect.Effect<unknown, DurableObjectRpcError, R>
464
611
  )(stub, methodName, ...args);
465
612
  });
466
613
  }
467
614
 
468
- return client as DurableObjectDirectClient<Self, Api>;
615
+ return client as DurableObjectDirectClient<R, Api>;
469
616
  };
470
617
 
471
618
  methods.byName = (
@@ -479,5 +626,5 @@ const makeDirectMethods = <
479
626
  ) => makeClient(() => helpers.get(id, options));
480
627
  }
481
628
 
482
- return methods as DirectMethods<Self, Api, Definition>;
629
+ return methods as DirectMethods<R, Api, Definition>;
483
630
  };