effect-cf 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +8 -6
  3. package/dist/{Artifacts-DcoXcRP5.d.mts → Artifacts-DnHQA-Ax.d.mts} +42 -25
  4. package/dist/Artifacts-rq3hUKQE.mjs +257 -0
  5. package/dist/{Binding-COCtoJXa.d.mts → Binding-xJJJPmiD.d.mts} +6 -5
  6. package/dist/ComputerArtifacts.d.mts +55 -0
  7. package/dist/ComputerArtifacts.mjs +95 -0
  8. package/dist/ComputerWorkspace.d.mts +279 -0
  9. package/dist/ComputerWorkspace.mjs +577 -0
  10. package/dist/ComputerWorkspaceHost.d.mts +1 -1
  11. package/dist/ComputerWorkspaceHost.mjs +18 -16
  12. package/dist/{WorkflowBinding-D5iWzCUa.d.mts → DurableObjectState-B3uFY1eM.d.mts} +90 -141
  13. package/dist/{DurableObjectState-CEh0DVw7.mjs → DurableObjectState-BjkK2kwH.mjs} +4 -49
  14. package/dist/ErrorMessage-BM0K-Nik.mjs +46 -0
  15. package/dist/{Hyperdrive-6p59yyVk.d.mts → Hyperdrive-z-9fFz15.d.mts} +2 -2
  16. package/dist/HyperdrivePg.d.mts +2 -2
  17. package/dist/Vitest.d.mts +6 -4
  18. package/dist/Vitest.mjs +2 -1
  19. package/dist/WorkflowBinding-wZ-yGHow.d.mts +74 -0
  20. package/dist/index.d.mts +92 -398
  21. package/dist/index.mjs +323 -1105
  22. package/package.json +12 -1
  23. package/src/AiGateway.ts +62 -7
  24. package/src/AnalyticsEngine.ts +17 -13
  25. package/src/Artifacts.ts +111 -47
  26. package/src/Binding.ts +34 -23
  27. package/src/BrowserRendering.ts +72 -33
  28. package/src/CloudflareOtlp.ts +8 -5
  29. package/src/ComputerArtifacts.ts +17 -9
  30. package/src/ComputerWorkspace.ts +202 -98
  31. package/src/ComputerWorkspaceHost.ts +55 -27
  32. package/src/ContainerNamespace.ts +8 -9
  33. package/src/D1.ts +8 -14
  34. package/src/DurableObject.ts +27 -11
  35. package/src/DurableObjectAlarm.ts +17 -5
  36. package/src/DurableObjectDefinition.ts +89 -72
  37. package/src/DurableObjectNamespace.ts +26 -12
  38. package/src/DurableObjectRpcWebSocket.ts +9 -10
  39. package/src/DurableObjectState.ts +1 -0
  40. package/src/DurableObjectStorage.ts +5 -2
  41. package/src/DurableObjectWebSocket.ts +4 -1
  42. package/src/Email.ts +26 -25
  43. package/src/Hyperdrive.ts +4 -10
  44. package/src/Images.ts +12 -14
  45. package/src/Kv.ts +15 -17
  46. package/src/Queue.ts +2 -1
  47. package/src/QueueBinding.ts +7 -12
  48. package/src/QueueDefinition.ts +7 -2
  49. package/src/R2.ts +33 -24
  50. package/src/Rpc.ts +15 -21
  51. package/src/RpcDefinition.ts +33 -38
  52. package/src/ServiceBinding.ts +26 -8
  53. package/src/Vectorize.ts +45 -18
  54. package/src/Vitest.ts +9 -3
  55. package/src/WebTransport.ts +11 -5
  56. package/src/Worker.ts +91 -21
  57. package/src/WorkerDefinition.ts +143 -63
  58. package/src/WorkersAi.ts +50 -28
  59. package/src/Workflow.ts +66 -13
  60. package/src/WorkflowBinding.ts +18 -25
  61. package/src/WorkflowDefinition.ts +4 -2
  62. package/src/index.ts +0 -2
  63. package/src/internal/Entrypoint.ts +29 -13
  64. package/src/internal/ErrorMessage.ts +7 -6
  65. package/src/internal/RpcInvocation.ts +34 -11
  66. package/src/internal/Runtime.ts +33 -22
  67. package/src/internal/WorkerContext.ts +3 -3
@@ -125,32 +125,45 @@ const toWorkspaceOptions = (config: ComputerWorkspaceHostConfig): WorkspaceOptio
125
125
  );
126
126
  }
127
127
 
128
- return {
128
+ // SAFETY: Cloudflare's native DurableObjectStorage implements the narrower storage methods used
129
+ // by Computer; the union also accepts Computer's structural test/storage contract directly.
130
+ const options: WorkspaceOptions = {
129
131
  storage: config.storage as DurableObjectStorageLike,
130
132
  git: config.git ?? createGitClient(),
131
- ...(config.gitIdentity === undefined ? {} : { defaultGitIdentity: config.gitIdentity }),
132
- ...(backends.length === 0 ? {} : { backends }),
133
- ...(config.sessionId === undefined ? {} : { sessionId: config.sessionId }),
134
- ...(config.mounts === undefined ? {} : { mounts: config.mounts }),
135
- ...(config.observer === undefined ? {} : { observer: config.observer }),
136
- ...(config.retryScheduler === undefined ? {} : { retryScheduler: config.retryScheduler }),
137
- ...(config.retry === undefined ? {} : { retry: config.retry }),
138
- ...(config.assets === undefined ? {} : { assets: config.assets }),
139
- ...(config.artifacts === undefined
140
- ? {}
141
- : {
142
- artifacts: {
143
- binding: config.artifacts.binding as unknown as NonNullable<
144
- WorkspaceOptions["artifacts"]
145
- >["binding"],
146
- ...(config.artifacts.sessionId === undefined
147
- ? {}
148
- : { sessionId: config.artifacts.sessionId }),
149
- },
150
- }),
151
- ...(config.now === undefined ? {} : { now: config.now }),
152
- ...(config.useThink === undefined ? {} : { useThink: config.useThink }),
153
133
  };
134
+
135
+ if (config.gitIdentity !== undefined) options.defaultGitIdentity = config.gitIdentity;
136
+ if (backends.length !== 0) options.backends = backends;
137
+ if (config.sessionId !== undefined) options.sessionId = config.sessionId;
138
+ if (config.mounts !== undefined) options.mounts = config.mounts;
139
+ if (config.observer !== undefined) options.observer = config.observer;
140
+ if (config.retryScheduler !== undefined) options.retryScheduler = config.retryScheduler;
141
+ if (config.retry !== undefined) options.retry = config.retry;
142
+ if (config.assets !== undefined) options.assets = config.assets;
143
+
144
+ if (config.artifacts !== undefined) {
145
+ const erasedArtifactsBinding: unknown = config.artifacts.binding;
146
+ // SAFETY: the local binding intentionally models the same Cloudflare Artifacts RPC surface;
147
+ // its readonly result arrays are valid runtime inputs to Computer, whose dependency declaration
148
+ // unnecessarily requires mutable arrays.
149
+ const artifactsBinding = erasedArtifactsBinding as NonNullable<
150
+ WorkspaceOptions["artifacts"]
151
+ >["binding"];
152
+ const artifacts: NonNullable<WorkspaceOptions["artifacts"]> = {
153
+ binding: artifactsBinding,
154
+ };
155
+
156
+ if (config.artifacts.sessionId !== undefined) {
157
+ artifacts.sessionId = config.artifacts.sessionId;
158
+ }
159
+
160
+ options.artifacts = artifacts;
161
+ }
162
+
163
+ if (config.now !== undefined) options.now = config.now;
164
+ if (config.useThink !== undefined) options.useThink = config.useThink;
165
+
166
+ return options;
154
167
  };
155
168
 
156
169
  // Cloudflare constructs Durable Objects with `(state, env)`. Keeping the
@@ -185,11 +198,13 @@ export const withComputerWorkspace = <TBase extends DurableObjectConstructor>(
185
198
  env: ConstructorParameters<TBase>[1],
186
199
  ) => ComputerWorkspaceHostConfig,
187
200
  ): WithWorkspaceCtor<TBase> => {
188
- const CapturedBase = class extends (Base as DurableObjectConstructor) {
201
+ const CapturedBase = class extends Base {
189
202
  readonly [HostArguments]: CapturedHostArguments;
190
203
 
191
204
  constructor(...args: Array<any>) {
192
205
  super(...args);
206
+ // SAFETY: Cloudflare constructs Durable Objects with DurableObjectState as constructor
207
+ // argument zero; the open base tuple retains arbitrary trailing constructor arguments.
193
208
  this[HostArguments] = {
194
209
  state: args[0] as globalThis.DurableObjectState,
195
210
  env: args[1],
@@ -197,20 +212,33 @@ export const withComputerWorkspace = <TBase extends DurableObjectConstructor>(
197
212
  }
198
213
  };
199
214
 
200
- const WithWorkspace = withWorkspace(CapturedBase, (self) => {
215
+ const workspaceConstructor: unknown = withWorkspace(CapturedBase, (self) => {
216
+ // SAFETY: CapturedBase initializes this private symbol for every constructed instance before
217
+ // withWorkspace evaluates its options callback.
201
218
  const { env, state } = (self as HostArgumentsInstance)[HostArguments];
202
219
 
203
220
  return toWorkspaceOptions(
221
+ // SAFETY: CapturedBase is an identity subclass of Base, so its instance retains Base's full
222
+ // public instance contract. The captured environment is constructor argument one.
204
223
  config(self as InstanceType<TBase>, state, env as ConstructorParameters<TBase>[1]),
205
224
  );
206
- }) as unknown as DurableObjectConstructor;
225
+ });
226
+ // SAFETY: withWorkspace returns a constructable identity subclass; this local constructor view
227
+ // intentionally hides its generic intersection so TypeScript can extend it.
228
+ const WithWorkspace = workspaceConstructor as DurableObjectConstructor;
207
229
 
208
230
  class WithComputerWorkspace extends WithWorkspace {
209
231
  constructor(...args: Array<any>) {
210
232
  super(...args);
233
+ // SAFETY: Cloudflare supplies DurableObjectState at constructor argument zero, and
234
+ // withWorkspace adds the WorkspaceHandle methods implemented by this instance.
211
235
  HostRegistry.register(args[0] as globalThis.DurableObjectState, this as WorkspaceHandle);
212
236
  }
213
237
  }
214
238
 
215
- return WithComputerWorkspace as unknown as WithWorkspaceCtor<TBase>;
239
+ const erasedConstructor: unknown = WithComputerWorkspace;
240
+
241
+ // SAFETY: WithComputerWorkspace only adds registration to the constructor produced by
242
+ // withWorkspace, preserving Base's constructor and instance members plus the workspace host.
243
+ return erasedConstructor as WithWorkspaceCtor<TBase>;
216
244
  };
@@ -1,4 +1,4 @@
1
- import { Context, Data, Effect, Schema as S, type Layer } from "effect";
1
+ import { Context, Data, Effect, Predicate, Schema as S, type Layer } from "effect";
2
2
 
3
3
  import * as Binding from "./Binding";
4
4
  import type { WorkerEnvironment } from "./Environment";
@@ -344,10 +344,10 @@ const makeInstanceClient = <Stub extends ContainerStub>(
344
344
  };
345
345
  };
346
346
 
347
- export const isContainerNamespaceResource = (value: unknown): value is ContainerNamespaceResource =>
348
- typeof value === "object" &&
349
- value !== null &&
350
- typeof Reflect.get(value, "getByName") === "function";
347
+ export const isContainerNamespaceResource = <Candidate>(
348
+ value: Candidate,
349
+ ): value is Candidate & ContainerNamespaceResource =>
350
+ Predicate.hasProperty(value, "getByName") && Predicate.isFunction(value.getByName);
351
351
 
352
352
  export const makeClient =
353
353
  (definition: ContainerNamespaceDefinition) =>
@@ -363,7 +363,7 @@ export const makeClient =
363
363
  makeInstanceClient(
364
364
  definition,
365
365
  name,
366
- // getByName has a single call signature, so its return type is
366
+ // SAFETY: getByName has a single call signature, so its return type is
367
367
  // exactly the namespace's stub type.
368
368
  namespace.getByName(name, options) as ContainerStubOf<Namespace>,
369
369
  ),
@@ -422,9 +422,7 @@ export const layer = <Self, Namespace extends ContainerNamespaceResource>(
422
422
  Binding.layer(
423
423
  tag,
424
424
  definition.binding,
425
- // Runtime validation stays structural; the exact namespace type is
426
- // asserted by the consumer's binding declaration, like env typing.
427
- isContainerNamespaceResource as (value: unknown) => value is Namespace,
425
+ (value): value is Namespace => isContainerNamespaceResource(value),
428
426
  makeClient(definition),
429
427
  {
430
428
  expected: expectedContainerNamespace,
@@ -499,6 +497,7 @@ export const Tag =
499
497
 
500
498
  const rawUnsafe = Effect.flatMap(tag, (namespace) => namespace.rawUnsafe);
501
499
 
500
+ // SAFETY: the assigned namespace helpers exactly implement TagClass for this service tag.
502
501
  return Object.assign(tag, {
503
502
  id,
504
503
  layer: makeLayer,
package/src/D1.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { D1Client } from "@effect/sql-d1";
2
- import { Effect, Layer, type Config } from "effect";
2
+ import { Effect, Layer, Predicate, type Config } from "effect";
3
3
  import type { SqlClient } from "effect/unstable/sql";
4
4
 
5
5
  import * as Binding from "./Binding";
@@ -28,19 +28,13 @@ export interface D1Service<Id extends string> {
28
28
  };
29
29
  }
30
30
 
31
- const isD1Database = (value: unknown): value is D1Database => {
32
- if (typeof value !== "object" || value === null) {
33
- return false;
34
- }
35
-
36
- const resource = value as Record<string, unknown>;
37
-
38
- return (
39
- typeof resource.prepare === "function" &&
40
- typeof resource.batch === "function" &&
41
- typeof resource.exec === "function"
42
- );
43
- };
31
+ const isD1Database = <Candidate>(value: Candidate): value is Candidate & D1Database =>
32
+ Predicate.hasProperty(value, "prepare") &&
33
+ Predicate.isFunction(value.prepare) &&
34
+ Predicate.hasProperty(value, "batch") &&
35
+ Predicate.isFunction(value.batch) &&
36
+ Predicate.hasProperty(value, "exec") &&
37
+ Predicate.isFunction(value.exec);
44
38
 
45
39
  /**
46
40
  * Creates a typed D1 service tag plus Effect helpers.
@@ -52,7 +52,7 @@ export type DurableObjectRpc<ROut> = Record<
52
52
  (...args: Array<any>) => DurableObjectHandler<ROut>
53
53
  >;
54
54
 
55
- export type DurableObjectRpcShape<Rpc extends DurableObjectRpc<ROut>, ROut> = {
55
+ export type DurableObjectRpcApi<Rpc extends DurableObjectRpc<ROut>, ROut> = {
56
56
  readonly [Key in keyof Rpc]: Rpc[Key] extends (
57
57
  ...args: infer Args
58
58
  ) => Effect.Effect<infer A, unknown, HandlerContext<ROut>>
@@ -66,7 +66,7 @@ export type RpcHandlers<ROut, Api> = {
66
66
  : Key extends string
67
67
  ? [Api[Key]] extends [never]
68
68
  ? never
69
- : Api[Key] extends (...args: Array<any>) => Promise<unknown>
69
+ : Api[Key] extends (...args: Array<any>) => Promise<any>
70
70
  ? Key
71
71
  : never
72
72
  : never]: Api[Key] extends (...args: infer Args) => Promise<infer A>
@@ -142,7 +142,7 @@ export interface DurableObjectOptions<
142
142
  ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
143
143
  readonly webSocketError?: (
144
144
  socket: DurableWebSocket,
145
- error: unknown,
145
+ cause: unknown,
146
146
  ) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
147
147
  }
148
148
 
@@ -152,7 +152,7 @@ export interface DurableObjectOptions<
152
152
  export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
153
153
  state: globalThis.DurableObjectState,
154
154
  env: WorkerEnv,
155
- ) => CloudflareDurableObject<WorkerEnv> & DurableObjectRpcShape<Rpc, ROut>;
155
+ ) => CloudflareDurableObject<WorkerEnv> & DurableObjectRpcApi<Rpc, ROut>;
156
156
 
157
157
  /**
158
158
  * Creates a Durable Object class backed by a single managed Effect runtime.
@@ -190,11 +190,24 @@ export const make = <
190
190
  effect: Effect.Effect<A, E, HandlerContext<ROut | REvent>>,
191
191
  runOptions: RunOptions = {},
192
192
  ): Promise<A> {
193
- return Runtime.runEventPromise(
194
- this.runtime,
195
- effect,
196
- runOptions.eventLayer === false ? undefined : options.eventLayer,
197
- );
193
+ const eventLayer = runOptions.eventLayer === false ? undefined : options.eventLayer;
194
+
195
+ if (eventLayer === undefined) {
196
+ // SAFETY: event-layer bypass is used only for initialize; otherwise an absent layer means REvent is never.
197
+ return Runtime.runEventPromise(
198
+ this.runtime,
199
+ effect as Effect.Effect<A, E, HandlerContext<ROut>>,
200
+ );
201
+ }
202
+
203
+ return Runtime.runEventPromise<
204
+ A,
205
+ E,
206
+ RuntimeContext<ROut>,
207
+ REvent,
208
+ EventLayerError,
209
+ LayerError
210
+ >(this.runtime, effect, eventLayer);
198
211
  }
199
212
 
200
213
  fetch(request: Request): Promise<Response> {
@@ -242,9 +255,9 @@ export const make = <
242
255
  }
243
256
  }
244
257
 
245
- webSocketError(socket: WebSocket, error: unknown): Promise<void> | void {
258
+ webSocketError(socket: WebSocket, cause: unknown): Promise<void> | void {
246
259
  if (options.webSocketError !== undefined) {
247
- return this[RunSymbol](options.webSocketError(fromWebSocket(socket), error));
260
+ return this[RunSymbol](options.webSocketError(fromWebSocket(socket), cause));
248
261
  }
249
262
  }
250
263
  }
@@ -280,3 +293,6 @@ export type {
280
293
  } from "./DurableObjectDefinition";
281
294
 
282
295
  export { implement, method, Tag } from "./DurableObjectDefinition";
296
+
297
+ // Preserve the original public type export while using a domain-role name internally.
298
+ export type { DurableObjectRpcApi as "DurableObjectRpcShape" };
@@ -1,4 +1,15 @@
1
- import { Clock, Context, Data, DateTime, Duration, Effect, Exit, Layer, Schema as S } from "effect";
1
+ import {
2
+ Clock,
3
+ Context,
4
+ Data,
5
+ DateTime,
6
+ Duration,
7
+ Effect,
8
+ Exit,
9
+ Layer,
10
+ Predicate,
11
+ Schema as S,
12
+ } from "effect";
2
13
 
3
14
  import { DurableObjectState } from "./DurableObjectState";
4
15
  import type { SqlStorageValue, StorageOperationError } from "./DurableObjectStorage";
@@ -442,10 +453,10 @@ const getFailureRetryDelay = (options: ProcessDueAlarmsOptions<unknown, unknown>
442
453
  );
443
454
 
444
455
  const getFailureActionMode = (action: ProcessDueAlarmsFailureAction) =>
445
- typeof action === "string" ? action : action.mode;
456
+ Predicate.isString(action) ? action : action.mode;
446
457
 
447
458
  const getFailureActionRetryDelay = (action: ProcessDueAlarmsFailureAction) =>
448
- typeof action === "string" || action.mode !== "retry" ? undefined : action.retryFailedAfter;
459
+ Predicate.isString(action) || action.mode !== "retry" ? undefined : action.retryFailedAfter;
449
460
 
450
461
  export const processDue = <R = never, E = never, OnFailureR = never, OnFailureE = never>(
451
462
  handle: ProcessDueAlarmsHandler<R, E>,
@@ -497,8 +508,7 @@ export type DefinedAlarmHandlers<Definitions extends AlarmDefinitions, R = never
497
508
 
498
509
  const isAlarmDefinitionConfig = (
499
510
  definition: AlarmDefinitionEntry,
500
- ): definition is AlarmDefinitionConfig =>
501
- typeof definition === "object" && definition !== null && "payload" in definition;
511
+ ): definition is AlarmDefinitionConfig => Predicate.isObject(definition) && "payload" in definition;
502
512
 
503
513
  const getAlarmDefinitionSchema = (definition: AlarmDefinitionEntry) =>
504
514
  isAlarmDefinitionConfig(definition) ? definition.payload : definition;
@@ -534,6 +544,7 @@ export const define = <const Definitions extends AlarmDefinitions>(definitions:
534
544
  }
535
545
 
536
546
  const schema = getAlarmDefinitionSchema(definition);
547
+ // SAFETY: schema is selected from the same tagged definition used to select its handler.
537
548
  const payload = yield* (
538
549
  S.decodeUnknownEffect(schema)(event.payload) as Effect.Effect<
539
550
  AlarmDefinitionPayload<Definitions[keyof Definitions & string]>,
@@ -550,6 +561,7 @@ export const define = <const Definitions extends AlarmDefinitions>(definitions:
550
561
  );
551
562
  const handler = handlers[event.tag];
552
563
 
564
+ // SAFETY: event.tag indexes the matching definition and handler, whose payload schema was decoded above.
553
565
  yield* handler({ ...event, payload } as never);
554
566
  }),
555
567
  {
@@ -9,12 +9,8 @@ import type * as Rpc from "./Rpc";
9
9
  import * as RpcDefinition from "./RpcDefinition";
10
10
  import type { WorkerEnvironment } from "./Environment";
11
11
 
12
- /**
13
- * The client tuple types are re-established by the final `TagClass` cast, so
14
- * these internal invocations erase the binding client's generic argument
15
- * tuples instead of instantiating them at `never`.
16
- */
17
- type UnsafeInvoke<E> = (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, E>;
12
+ /** Internal dynamic invocation used only before the public client contract is restored. */
13
+ type ErasedInvoke<E> = (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, E>;
18
14
 
19
15
  export type ServiceFreeSchema = S.Codec<any, any, never, never>;
20
16
 
@@ -49,7 +45,7 @@ export namespace Method {
49
45
 
50
46
  export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
51
47
 
52
- export type EncodedSuccess<Self extends Any> = S.Json;
48
+ export type EncodedSuccess<_Self extends Any> = S.Json;
53
49
  }
54
50
 
55
51
  export type Methods = Record<string, Method.Any>;
@@ -57,9 +53,12 @@ export type Methods = Record<string, Method.Any>;
57
53
  /**
58
54
  * RPC contract for a Durable Object class.
59
55
  */
60
- export interface Definition<Id extends string = string, MethodsShape extends Methods = Methods> {
56
+ export interface Definition<
57
+ Id extends string = string,
58
+ MethodDefinitions extends Methods = Methods,
59
+ > {
61
60
  readonly id: Id;
62
- readonly methods: MethodsShape;
61
+ readonly methods: MethodDefinitions;
63
62
  }
64
63
 
65
64
  export namespace Definition {
@@ -74,8 +73,8 @@ export type ReservedMethodName =
74
73
  | "webSocketClose"
75
74
  | "webSocketError";
76
75
 
77
- export type NoReservedMethods<MethodsShape extends Methods> =
78
- Extract<keyof MethodsShape, ReservedMethodName> extends never ? MethodsShape : never;
76
+ export type NoReservedMethods<MethodDefinitions extends Methods> =
77
+ Extract<keyof MethodDefinitions, ReservedMethodName> extends never ? MethodDefinitions : never;
79
78
 
80
79
  const reservedMethodNames = new Set<string>([
81
80
  "constructor",
@@ -113,6 +112,10 @@ type BoundaryHandlers<ROut, Self extends Definition.Any> = {
113
112
  ) => DurableObjectHandler<ROut, Method.EncodedSuccess<Self["methods"][Key]>>;
114
113
  };
115
114
 
115
+ type MutableBoundaryHandlers<ROut, Self extends Definition.Any> = {
116
+ -readonly [Key in keyof BoundaryHandlers<ROut, Self>]: BoundaryHandlers<ROut, Self>[Key];
117
+ };
118
+
116
119
  /**
117
120
  * Durable Object constructor options for a specific RPC definition.
118
121
  */
@@ -137,26 +140,30 @@ export type LayerOptions = {
137
140
  readonly binding: string;
138
141
  };
139
142
 
140
- export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Context.ServiceClass<
143
+ export type TagClass<
144
+ Self,
145
+ Id extends string,
146
+ MethodDefinitions extends Methods,
147
+ > = Context.ServiceClass<
141
148
  Self,
142
149
  `effect-cf/DurableObject/${Id}`,
143
150
  DurableObjectNamespace.DurableObjectNamespaceEffectClient<
144
- Api<Definition<Id, MethodsShape>>,
145
- Definition<Id, MethodsShape>
151
+ Api<Definition<Id, MethodDefinitions>>,
152
+ Definition<Id, MethodDefinitions>
146
153
  >
147
154
  > &
148
155
  DurableObjectNamespace.DurableObjectNamespaceStaticClient<
149
156
  Self,
150
- Api<Definition<Id, MethodsShape>>,
151
- Definition<Id, MethodsShape>
157
+ Api<Definition<Id, MethodDefinitions>>,
158
+ Definition<Id, MethodDefinitions>
152
159
  > & {
153
160
  readonly id: Id;
154
- readonly methods: MethodsShape;
161
+ readonly methods: MethodDefinitions;
155
162
  readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
156
163
  layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
157
- options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
164
+ options: Options<ROut, Definition<Id, MethodDefinitions>, REvent, EventLayerError>,
158
165
  ) => DurableObjectEntrypoint.DurableObjectClass<
159
- Handlers<ROut | REvent, Definition<Id, MethodsShape>>,
166
+ Handlers<ROut | REvent, Definition<Id, MethodDefinitions>>,
160
167
  ROut | REvent
161
168
  >;
162
169
  readonly layer: (
@@ -171,18 +178,19 @@ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Co
171
178
  /**
172
179
  * Defines a single RPC method schema in a Durable Object definition.
173
180
  */
174
- export const method = RpcDefinition.method as {
175
- <Success extends ServiceFreeSchema>(definition: {
176
- readonly success: Success;
177
- }): Method<readonly [], Success>;
178
- <
179
- const Args extends ReadonlyArray<ServiceFreeSchema>,
180
- Success extends ServiceFreeSchema,
181
- >(definition: {
182
- readonly args: Args;
183
- readonly success: Success;
184
- }): Method<Args, Success>;
185
- };
181
+ export function method<Success extends ServiceFreeSchema>(definition: {
182
+ readonly success: Success;
183
+ }): Method<readonly [], Success>;
184
+ export function method<
185
+ const Args extends ReadonlyArray<ServiceFreeSchema>,
186
+ Success extends ServiceFreeSchema,
187
+ >(definition: { readonly args: Args; readonly success: Success }): Method<Args, Success>;
188
+ export function method(definition: {
189
+ readonly args?: ReadonlyArray<ServiceFreeSchema>;
190
+ readonly success: ServiceFreeSchema;
191
+ }): Method {
192
+ return RpcDefinition.method(definition);
193
+ }
186
194
 
187
195
  /**
188
196
  * Creates a Durable Object RPC definition plus implementation/binding helpers.
@@ -197,11 +205,11 @@ export const method = RpcDefinition.method as {
197
205
  * });
198
206
  * ```
199
207
  */
200
- const makeDefinition = <Id extends string, const MethodsShape extends Methods>(
208
+ const makeDefinition = <Id extends string, const MethodDefinitions extends Methods>(
201
209
  id: Id,
202
- methods: MethodsShape & NoReservedMethods<MethodsShape>,
210
+ methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
203
211
  ) => {
204
- type SelfDefinition = Definition<Id, MethodsShape>;
212
+ type SelfDefinition = Definition<Id, MethodDefinitions>;
205
213
  RpcDefinition.assertNoReservedMethods("Durable Object", methods, reservedMethodNames);
206
214
  const definition: SelfDefinition = RpcDefinition.make(id, methods);
207
215
 
@@ -217,24 +225,20 @@ const makeDefinition = <Id extends string, const MethodsShape extends Methods>(
217
225
  });
218
226
  };
219
227
 
220
- export const make = <Id extends string, const MethodsShape extends Methods>(
228
+ export const make = <Id extends string, const MethodDefinitions extends Methods>(
221
229
  id: Id,
222
- methods: MethodsShape & NoReservedMethods<MethodsShape>,
223
- ) =>
224
- Tag<Definition<Id, MethodsShape>>()<Id, MethodsShape>(
225
- id,
226
- methods as MethodsShape & NoReservedMethods<MethodsShape>,
227
- );
230
+ methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
231
+ ) => Tag<Definition<Id, MethodDefinitions>>()<Id, MethodDefinitions>(id, methods);
228
232
 
229
233
  export const Tag =
230
234
  <Self>() =>
231
- <Id extends string, const MethodsShape extends Methods>(
235
+ <Id extends string, const MethodDefinitions extends Methods>(
232
236
  id: Id,
233
- methods: MethodsShape & NoReservedMethods<MethodsShape>,
234
- ) => {
235
- const definition = makeDefinition<Id, MethodsShape>(id, methods);
237
+ methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
238
+ ): TagClass<Self, Id, MethodDefinitions> => {
239
+ const definition = makeDefinition<Id, MethodDefinitions>(id, methods);
236
240
 
237
- type SelfDefinition = Definition<Id, MethodsShape>;
241
+ type SelfDefinition = Definition<Id, MethodDefinitions>;
238
242
  type ClientApi = Api<SelfDefinition>;
239
243
  const serviceKey: `effect-cf/DurableObject/${Id}` = `effect-cf/DurableObject/${id}`;
240
244
  const tag = Context.Service<
@@ -315,46 +319,50 @@ export const Tag =
315
319
  return yield* namespace.fetch(stub, input, init);
316
320
  });
317
321
 
318
- const rpc = Effect.fn("DurableObject.rpc")(function* <Method extends keyof ClientApi>(
322
+ const rpc = Effect.fn("DurableObject.rpc")(function* <MethodName extends keyof ClientApi>(
319
323
  stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
320
- method: Method,
321
- ...args: ClientApi[Method] extends (...args: infer Args) => unknown ? Args : never
324
+ methodName: MethodName,
325
+ ...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
322
326
  ) {
323
327
  const namespace = yield* tag;
328
+ // SAFETY: ClientApi derives every key and argument tuple from the definition. This internal
329
+ // erased view only bypasses TypeScript's expansion of Rpc.Provider's callable intersection;
330
+ // TagClass restores the exact generic return contract before this helper is exported.
331
+ const invokeRpc = namespace.rpc as ErasedInvoke<DurableObjectNamespace.DurableObjectRpcError>;
324
332
 
325
- return yield* (namespace.rpc as UnsafeInvoke<DurableObjectNamespace.DurableObjectRpcError>)(
326
- stub,
327
- method,
328
- ...args,
329
- );
333
+ return yield* invokeRpc(stub, methodName, ...args);
330
334
  });
331
335
 
332
- const call = Effect.fn("DurableObject.call")(function* <Method extends keyof ClientApi>(
336
+ const call = Effect.fn("DurableObject.call")(function* <MethodName extends keyof ClientApi>(
333
337
  stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
334
- method: Method,
335
- ...args: ClientApi[Method] extends (...args: infer Args) => unknown ? Args : never
338
+ methodName: MethodName,
339
+ ...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
336
340
  ) {
337
341
  const namespace = yield* tag;
342
+ // SAFETY: ClientApi derives every key and argument tuple from the definition. This internal
343
+ // erased view only bypasses TypeScript's expansion of Rpc.Provider's callable intersection;
344
+ // TagClass restores the exact generic return contract before this helper is exported.
345
+ const invokeCall =
346
+ namespace.call as ErasedInvoke<DurableObjectNamespace.DurableObjectRpcError>;
338
347
 
339
- return yield* (namespace.call as UnsafeInvoke<DurableObjectNamespace.DurableObjectRpcError>)(
340
- stub,
341
- method,
342
- ...args,
343
- );
348
+ return yield* invokeCall(stub, methodName, ...args);
344
349
  });
345
350
 
346
351
  const scopedCall = Effect.fn("DurableObject.scopedCall")(function* <
347
- Method extends keyof ClientApi,
352
+ MethodName extends keyof ClientApi,
348
353
  >(
349
354
  stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
350
- method: Method,
351
- ...args: ClientApi[Method] extends (...args: infer Args) => unknown ? Args : never
355
+ methodName: MethodName,
356
+ ...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
352
357
  ) {
353
358
  const namespace = yield* tag;
359
+ // SAFETY: ClientApi derives every key and argument tuple from the definition. This internal
360
+ // erased view only bypasses TypeScript's expansion of Rpc.Provider's callable intersection;
361
+ // TagClass restores the exact generic return contract before this helper is exported.
362
+ const invokeScopedCall =
363
+ namespace.scopedCall as ErasedInvoke<DurableObjectNamespace.DurableObjectRpcError>;
354
364
 
355
- return yield* (
356
- namespace.scopedCall as UnsafeInvoke<DurableObjectNamespace.DurableObjectRpcError>
357
- )(stub, method, ...args);
365
+ return yield* invokeScopedCall(stub, methodName, ...args);
358
366
  });
359
367
 
360
368
  const rawUnsafe = Effect.fnUntraced(function* () {
@@ -366,6 +374,8 @@ export const Tag =
366
374
  const directMethods = DurableObjectNamespace.makeDirectMethods<Self, ClientApi, SelfDefinition>(
367
375
  definition,
368
376
  {
377
+ // SAFETY: direct methods call this helper only with method names and tuples obtained from
378
+ // the same definition; the public direct-method contract restores the result type.
369
379
  call: call as never,
370
380
  fetch,
371
381
  get,
@@ -373,7 +383,7 @@ export const Tag =
373
383
  },
374
384
  );
375
385
 
376
- return Object.assign(tag, directMethods, {
386
+ const service: unknown = Object.assign(tag, directMethods, {
377
387
  id: definition.id,
378
388
  methods: definition.methods,
379
389
  make: definition.make,
@@ -389,7 +399,12 @@ export const Tag =
389
399
  call,
390
400
  scopedCall,
391
401
  rawUnsafe,
392
- }) as unknown as TagClass<Self, Id, MethodsShape>;
402
+ });
403
+
404
+ // SAFETY: every assigned member above is derived from the same definition and service tag;
405
+ // the erased internal invocation results are exposed only through TagClass's schema-derived
406
+ // client contract.
407
+ return service as TagClass<Self, Id, MethodDefinitions>;
393
408
  };
394
409
 
395
410
  export const DurableObject = Tag;
@@ -398,8 +413,10 @@ const wrapHandlers = <ROut, const Self extends Definition.Any>(
398
413
  definition: Self,
399
414
  handlers: Handlers<ROut, Self>,
400
415
  ): BoundaryHandlers<ROut, Self> => {
401
- const wrapped = {} as Record<string, unknown>;
416
+ const wrapped: MutableBoundaryHandlers<ROut, Self> = Object.create(Object.prototype);
402
417
 
418
+ // SAFETY: definition.methods owns the complete method-name domain for Self; Object.keys only
419
+ // erases that key information and cannot introduce additional names.
403
420
  for (const key of Object.keys(definition.methods) as Array<
404
421
  RpcDefinition.Definition.MethodNames<Self>
405
422
  >) {
@@ -414,7 +431,7 @@ const wrapHandlers = <ROut, const Self extends Definition.Any>(
414
431
  });
415
432
  }
416
433
 
417
- return wrapped as BoundaryHandlers<ROut, Self>;
434
+ return wrapped;
418
435
  };
419
436
 
420
437
  /**