effect-cf 0.33.0 → 0.35.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 (74) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +31 -1079
  3. package/dist/{Artifacts-DnHQA-Ax.d.mts → Artifacts-B3r3RuTB.d.mts} +1 -48
  4. package/dist/{Artifacts-CSIk1Oml.mjs → Artifacts-CBxWGeZB.mjs} +2 -7
  5. package/dist/{Binding-pfilNJmP.mjs → Binding-C82jRhdr.mjs} +1 -2
  6. package/dist/{Binding-xJJJPmiD.d.mts → Binding-DKcLrQCM.d.mts} +0 -45
  7. package/dist/ComputerArtifacts.d.mts +1 -3
  8. package/dist/ComputerArtifacts.mjs +1 -2
  9. package/dist/ComputerWorkspace.d.mts +11 -12
  10. package/dist/ComputerWorkspace.mjs +31 -10
  11. package/dist/ComputerWorkspaceHost.d.mts +1 -9
  12. package/dist/{ContainerNamespace-CFjiLw8b.d.mts → ContainerNamespace-DbLaeFhN.d.mts} +1 -14
  13. package/dist/{DurableObjectState-CVLLse69.mjs → DurableObjectState-DcqElJ4e.mjs} +82 -89
  14. package/dist/{DurableObjectState-BpRjz6-q.d.mts → DurableObjectState-DpYdNZgT.d.mts} +102 -266
  15. package/dist/{ErrorMessage-BM0K-Nik.mjs → ErrorMessage-Byx2OgNd.mjs} +0 -1
  16. package/dist/{Hyperdrive-z-9fFz15.d.mts → Hyperdrive-HrwFlZ6x.d.mts} +1 -4
  17. package/dist/HyperdrivePg.d.mts +2 -11
  18. package/dist/HyperdrivePg.mjs +0 -8
  19. package/dist/Sandbox.d.mts +2 -32
  20. package/dist/Sandbox.mjs +2 -11
  21. package/dist/Vitest.d.mts +3 -31
  22. package/dist/Vitest.mjs +8 -26
  23. package/dist/{WorkflowBinding-lBR4_-op.d.mts → WorkflowBinding-Cn3AuUyP.d.mts} +2 -5
  24. package/dist/index.d.mts +42 -480
  25. package/dist/index.mjs +267 -380
  26. package/package.json +1 -1
  27. package/src/AiGateway.ts +0 -6
  28. package/src/AnalyticsEngine.ts +8 -10
  29. package/src/Artifacts.ts +0 -47
  30. package/src/Binding.ts +0 -8
  31. package/src/BrowserRendering.ts +0 -4
  32. package/src/Cache.ts +0 -19
  33. package/src/CloudflareOtlp.ts +6 -39
  34. package/src/ComputerArtifacts.ts +0 -2
  35. package/src/ComputerWorkspace.ts +53 -35
  36. package/src/ComputerWorkspaceHost.ts +0 -8
  37. package/src/ContainerNamespace.ts +0 -13
  38. package/src/D1.ts +0 -22
  39. package/src/DurableObject.ts +51 -32
  40. package/src/DurableObjectAlarm.ts +6 -210
  41. package/src/DurableObjectDefinition.ts +8 -20
  42. package/src/DurableObjectNamespace.ts +88 -115
  43. package/src/DurableObjectRpcWebSocket.ts +5 -46
  44. package/src/DurableObjectSqlite.ts +0 -12
  45. package/src/DurableObjectState.ts +18 -38
  46. package/src/DurableObjectStorage.ts +55 -70
  47. package/src/DurableObjectWebSocket.ts +23 -72
  48. package/src/Email.ts +0 -12
  49. package/src/Environment.ts +0 -37
  50. package/src/Hyperdrive.ts +0 -3
  51. package/src/HyperdrivePg.ts +0 -9
  52. package/src/Images.ts +0 -5
  53. package/src/Kv.ts +0 -15
  54. package/src/QueueBinding.ts +0 -2
  55. package/src/R2.ts +0 -5
  56. package/src/RpcDefinition.ts +5 -17
  57. package/src/RpcTracing.ts +133 -0
  58. package/src/Sandbox.ts +0 -30
  59. package/src/ServiceBinding.ts +83 -128
  60. package/src/Vectorize.ts +0 -5
  61. package/src/Vitest.ts +18 -43
  62. package/src/WebTransport.ts +0 -15
  63. package/src/Worker.ts +140 -108
  64. package/src/WorkerDefinition.ts +5 -14
  65. package/src/WorkersAi.ts +21 -18
  66. package/src/Workflow.ts +45 -54
  67. package/src/WorkflowBinding.ts +0 -3
  68. package/src/index.ts +1 -0
  69. package/src/internal/Entrypoint.ts +23 -4
  70. package/src/internal/ErrorMessage.ts +0 -1
  71. package/src/internal/NativeCallback.ts +55 -0
  72. package/src/internal/RpcInvocation.ts +18 -1
  73. package/src/internal/Runtime.ts +12 -7
  74. package/src/internal/WorkerContext.ts +5 -14
@@ -7,6 +7,7 @@ import type * as DurableObjectDefinition from "./DurableObjectDefinition";
7
7
  import * as RpcDefinition from "./RpcDefinition";
8
8
  import * as ErrorMessage from "./internal/ErrorMessage";
9
9
  import * as RpcInvocation from "./internal/RpcInvocation";
10
+ import * as RpcTracing from "./RpcTracing";
10
11
 
11
12
  const expectedDurableObjectNamespace =
12
13
  "Durable Object namespace binding with getByName(), get(), idFromName(), idFromString(), newUniqueId(), and jurisdiction()";
@@ -39,29 +40,20 @@ export type DurableObjectStubClient<Api extends object> = DurableObjectFetcher &
39
40
  readonly name?: string;
40
41
  };
41
42
 
42
- /**
43
- * Native Durable Object namespace binding shape.
44
- */
45
43
  export interface DurableObjectNamespaceClient<Api extends object> {
46
- /** Creates a globally unique Durable Object id. */
47
44
  newUniqueId(
48
45
  options?: globalThis.DurableObjectNamespaceNewUniqueIdOptions,
49
46
  ): globalThis.DurableObjectId;
50
- /** Deterministically maps a name to a Durable Object id. */
51
47
  idFromName(name: string): globalThis.DurableObjectId;
52
- /** Rehydrates a Durable Object id from its string form. */
53
48
  idFromString(id: string): globalThis.DurableObjectId;
54
- /** Returns a stub for an existing Durable Object id. */
55
49
  get(
56
50
  id: globalThis.DurableObjectId,
57
51
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
58
52
  ): DurableObjectStubClient<Api>;
59
- /** Returns a stub by deterministic name. */
60
53
  getByName(
61
54
  name: string,
62
55
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
63
56
  ): DurableObjectStubClient<Api>;
64
- /** Selects a namespace pinned to a specific jurisdiction. */
65
57
  jurisdiction(
66
58
  jurisdiction: globalThis.DurableObjectJurisdiction,
67
59
  ): DurableObjectNamespaceClient<Api>;
@@ -80,10 +72,10 @@ export interface DurableObjectNamespaceDefinition {
80
72
  export interface DurableObjectNamespaceBindingDefinition<
81
73
  Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
82
74
  > {
83
- /** Binding name as configured in `wrangler.jsonc`. */
84
75
  readonly binding: string;
85
- /** Optional RPC schema used for argument/result encoding. */
86
76
  readonly definition?: Definition;
77
+ /** Append live trace context only when the receiver also enables rpcTracing. */
78
+ readonly rpcTracing?: boolean;
87
79
  }
88
80
 
89
81
  /**
@@ -199,46 +191,22 @@ export type DurableObjectNamespaceEffectClient<
199
191
  Api extends object,
200
192
  Definition extends DurableObjectDefinition.Definition.Any | undefined = undefined,
201
193
  > = DirectMethods<never, Api, Definition> & {
202
- /** Creates a globally unique Durable Object id. */
203
194
  readonly newUniqueId: (
204
195
  options?: globalThis.DurableObjectNamespaceNewUniqueIdOptions,
205
196
  ) => Effect.Effect<globalThis.DurableObjectId>;
206
- /** Deterministically maps a name to a Durable Object id. */
207
197
  readonly idFromName: (name: string) => Effect.Effect<globalThis.DurableObjectId>;
208
- /** Rehydrates a Durable Object id from its string form. */
209
198
  readonly idFromString: (id: string) => Effect.Effect<globalThis.DurableObjectId>;
210
- /** Returns a stub for an existing Durable Object id. */
211
199
  readonly get: (
212
200
  id: globalThis.DurableObjectId,
213
201
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
214
202
  ) => Effect.Effect<DurableObjectStubClient<Api>>;
215
- /** Returns a stub by deterministic name. */
216
203
  readonly getByName: (
217
204
  name: string,
218
205
  options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
219
206
  ) => Effect.Effect<DurableObjectStubClient<Api>>;
220
- /** Selects a namespace pinned to a specific jurisdiction. */
221
207
  readonly jurisdiction: (
222
208
  jurisdiction: globalThis.DurableObjectJurisdiction,
223
209
  ) => Effect.Effect<DurableObjectNamespaceClient<Api>>;
224
- /**
225
- * Forwards an HTTP request to a Durable Object stub.
226
- *
227
- * Use this for fetch-based Durable Object APIs, including WebSocket upgrade
228
- * forwarding where the native response must be preserved.
229
- *
230
- * @example
231
- * ```ts
232
- * import { Effect } from "effect";
233
- *
234
- * const program = Effect.gen(function* () {
235
- * const rooms = yield* ChatRooms;
236
- * const room = yield* rooms.getByName("general");
237
- *
238
- * return yield* rooms.fetch(room, new Request("https://worker.example/room"));
239
- * });
240
- * ```
241
- */
242
210
  readonly fetch: (
243
211
  stub: DurableObjectStubClient<Api>,
244
212
  input: RequestInfo | URL,
@@ -252,21 +220,6 @@ export type DurableObjectNamespaceEffectClient<
252
220
  * promise-like value and it does not decode definition-backed success schemas.
253
221
  *
254
222
  * Most application code should use {@link call} instead.
255
- *
256
- * @example
257
- * ```ts
258
- * import { Effect } from "effect";
259
- *
260
- * const program = Effect.gen(function* () {
261
- * const counters = yield* Counters;
262
- * const counter = yield* counters.getByName("main");
263
- *
264
- * const result = yield* counters.rpc(counter, "get");
265
- * const value = yield* Effect.promise(() => result);
266
- *
267
- * return value;
268
- * });
269
- * ```
270
223
  */
271
224
  readonly rpc: <Method extends StubMethodKey<Api>>(
272
225
  stub: DurableObjectStubClient<Api>,
@@ -278,18 +231,6 @@ export type DurableObjectNamespaceEffectClient<
278
231
  * decodes the success value when the namespace was created from a definition.
279
232
  *
280
233
  * This is the normal choice when application code wants the final typed value.
281
- *
282
- * @example
283
- * ```ts
284
- * import { Effect } from "effect";
285
- *
286
- * const program = Effect.gen(function* () {
287
- * const counters = yield* Counters;
288
- * const counter = yield* counters.getByName("main");
289
- *
290
- * return yield* counters.call(counter, "increment", 1);
291
- * });
292
- * ```
293
234
  */
294
235
  readonly call: <Method extends StubMethodKey<Api>>(
295
236
  stub: DurableObjectStubClient<Api>,
@@ -304,27 +245,12 @@ export type DurableObjectNamespaceEffectClient<
304
245
  *
305
246
  * Use this for RPC methods that return Cloudflare RPC resources or other
306
247
  * disposable objects whose lifetime should be tied to an Effect scope.
307
- *
308
- * @example
309
- * ```ts
310
- * import { Effect } from "effect";
311
- *
312
- * const program = Effect.scoped(
313
- * Effect.gen(function* () {
314
- * const rooms = yield* ChatRooms;
315
- * const room = yield* rooms.getByName("general");
316
- * const handle = yield* rooms.scopedCall(room, "openStream");
317
- *
318
- * return yield* handle.read();
319
- * }),
320
- * );
321
- * ```
322
248
  */
323
249
  readonly scopedCall: <Method extends StubMethodKey<Api>>(
324
250
  stub: DurableObjectStubClient<Api>,
325
251
  method: Method,
326
252
  ...args: StubMethodArgs<Api, Method>
327
- ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, unknown, Scope.Scope>;
253
+ ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, DurableObjectRpcError, Scope.Scope>;
328
254
  /**
329
255
  * Exposes the underlying native Durable Object namespace binding.
330
256
  *
@@ -374,7 +300,11 @@ export type DurableObjectNamespaceStaticClient<
374
300
  stub: DurableObjectStubClient<Api>,
375
301
  method: Method,
376
302
  ...args: StubMethodArgs<Api, Method>
377
- ) => Effect.Effect<Awaited<StubMethodSuccess<Api, Method>>, unknown, Scope.Scope | R>;
303
+ ) => Effect.Effect<
304
+ Awaited<StubMethodSuccess<Api, Method>>,
305
+ DurableObjectRpcError,
306
+ Scope.Scope | R
307
+ >;
378
308
  readonly rawUnsafe: () => Effect.Effect<DurableObjectNamespaceClient<Api>, never, R>;
379
309
  };
380
310
 
@@ -425,13 +355,30 @@ export const makeClient = <
425
355
 
426
356
  const fetch = (stub: StubClient, input: RequestInfo | URL, init?: RequestInit) =>
427
357
  Effect.tryPromise({
428
- try: () => stub.fetch(input, init),
358
+ try: (signal) => {
359
+ const callerSignal =
360
+ init?.signal !== undefined
361
+ ? init.signal
362
+ : input instanceof Request
363
+ ? input.signal
364
+ : undefined;
365
+
366
+ return stub.fetch(input, {
367
+ ...init,
368
+ signal:
369
+ callerSignal === null || callerSignal === undefined
370
+ ? signal
371
+ : AbortSignal.any([callerSignal, signal]),
372
+ });
373
+ },
429
374
  catch: (cause) => new DurableObjectFetchError({ binding: definition.binding, cause }),
430
375
  });
431
376
 
432
- const rpc = Effect.fn("DurableObjectNamespace.rpc")(function* <
433
- Method extends StubMethodKey<Api>,
434
- >(stub: StubClient, method: Method, ...args: StubMethodArgs<Api, Method>) {
377
+ const rpc = Effect.fnUntraced(function* <Method extends StubMethodKey<Api>>(
378
+ stub: StubClient,
379
+ method: Method,
380
+ ...args: StubMethodArgs<Api, Method>
381
+ ): Effect.fn.Return<StubMethodCloudflareReturn<Api, Method>, DurableObjectRpcError> {
435
382
  const methodName = String(method);
436
383
  // SAFETY: the runtime definition and method name select the same method schemas as Method.
437
384
  const encodedArgs =
@@ -452,11 +399,15 @@ export const makeClient = <
452
399
  ),
453
400
  );
454
401
 
455
- // SAFETY: encodedArgs preserves the selected method's positional argument contract.
402
+ const nativeArgs = definition.rpcTracing
403
+ ? yield* RpcTracing.withRpcTraceContext(encodedArgs)
404
+ : encodedArgs;
405
+
406
+ // SAFETY: domain arguments retain their encoded order; an opted-in receiver strips only valid trace metadata.
456
407
  return yield* RpcInvocation.invokeRpcMethod(
457
408
  stub,
458
409
  method,
459
- encodedArgs as StubMethodArgs<Api, Method>,
410
+ nativeArgs as StubMethodArgs<Api, Method>,
460
411
  (cause) =>
461
412
  new DurableObjectRpcError({
462
413
  binding: definition.binding,
@@ -466,9 +417,10 @@ export const makeClient = <
466
417
  );
467
418
  });
468
419
 
469
- const decodeSuccess = Effect.fn("DurableObjectNamespace.decodeSuccess")(function* <
470
- Method extends StubMethodKey<Api>,
471
- >(methodName: string, value: Awaited<StubMethodCloudflareReturn<Api, Method>>) {
420
+ const decodeSuccess = Effect.fnUntraced(function* <Method extends StubMethodKey<Api>>(
421
+ methodName: string,
422
+ value: Awaited<StubMethodCloudflareReturn<Api, Method>>,
423
+ ): Effect.fn.Return<StubMethodSuccess<Api, Method>, DurableObjectRpcError> {
472
424
  if (definition.definition === undefined) {
473
425
  // SAFETY: without a schema codec, the native RPC result is already the declared success type.
474
426
  return value as StubMethodSuccess<Api, Method>;
@@ -494,33 +446,54 @@ export const makeClient = <
494
446
  return decoded as StubMethodSuccess<Api, Method>;
495
447
  });
496
448
 
497
- const call = Effect.fn("DurableObjectNamespace.call")(function* <
498
- Method extends StubMethodKey<Api>,
499
- >(stub: StubClient, method: Method, ...args: StubMethodArgs<Api, Method>) {
500
- const methodName = String(method);
501
- const value = yield* CloudflareRpc.resolve(yield* rpc(stub, method, ...args)).pipe(
502
- Effect.mapError(
503
- (cause) =>
504
- new DurableObjectRpcError({
505
- binding: definition.binding,
506
- method: methodName,
507
- cause,
508
- }),
509
- ),
510
- );
511
-
512
- return yield* decodeSuccess<Method>(methodName, value);
513
- });
514
-
515
- const scopedCall = Effect.fn("DurableObjectNamespace.scopedCall")(function* <
516
- Method extends StubMethodKey<Api>,
517
- >(stub: StubClient, method: Method, ...args: StubMethodArgs<Api, Method>) {
518
- const methodName = String(method);
519
- const result = yield* rpc(stub, method, ...args);
520
- const value = yield* CloudflareRpc.scoped(result);
521
-
522
- return yield* decodeSuccess<Method>(methodName, value);
523
- });
449
+ const call = Effect.fnUntraced(
450
+ function* <Method extends StubMethodKey<Api>>(
451
+ stub: StubClient,
452
+ method: Method,
453
+ ...args: StubMethodArgs<Api, Method>
454
+ ): Effect.fn.Return<StubMethodSuccess<Api, Method>, DurableObjectRpcError> {
455
+ const methodName = String(method);
456
+ const value = yield* CloudflareRpc.resolve(yield* rpc(stub, method, ...args)).pipe(
457
+ Effect.mapError(
458
+ (cause) =>
459
+ new DurableObjectRpcError({
460
+ binding: definition.binding,
461
+ method: methodName,
462
+ cause,
463
+ }),
464
+ ),
465
+ );
466
+
467
+ return yield* decodeSuccess<Method>(methodName, value);
468
+ },
469
+ (effect, ...args) =>
470
+ RpcTracing.withRpcClientSpan(effect, definition.binding, String(args[1])),
471
+ );
472
+
473
+ const scopedCall = Effect.fnUntraced(
474
+ function* <Method extends StubMethodKey<Api>>(
475
+ stub: StubClient,
476
+ method: Method,
477
+ ...args: StubMethodArgs<Api, Method>
478
+ ): Effect.fn.Return<StubMethodSuccess<Api, Method>, DurableObjectRpcError, Scope.Scope> {
479
+ const methodName = String(method);
480
+ const result = yield* rpc(stub, method, ...args);
481
+ const value = yield* CloudflareRpc.scoped(result).pipe(
482
+ Effect.mapError(
483
+ (cause) =>
484
+ new DurableObjectRpcError({
485
+ binding: definition.binding,
486
+ method: methodName,
487
+ cause,
488
+ }),
489
+ ),
490
+ );
491
+
492
+ return yield* decodeSuccess<Method>(methodName, value);
493
+ },
494
+ (effect, ...args) =>
495
+ RpcTracing.withRpcClientSpan(effect, definition.binding, String(args[1])),
496
+ );
524
497
 
525
498
  const directMethods = makeDirectMethods<never, Api, Definition>(definition.definition, {
526
499
  call,
@@ -22,14 +22,12 @@ const serviceRestartReason = "Durable Object RPC activation reset";
22
22
  /** Heartbeat handling for Effect's application-level RPC Ping messages. */
23
23
  export type HeartbeatPolicy = "auto-response" | "passthrough";
24
24
 
25
- /** Object-shaped application attachment fields that can coexist with adapter metadata. */
26
25
  export type RpcWebSocketAttachment = object;
27
26
 
28
27
  /** A resumable RPC stream declaration understood by the websocket transport. */
29
28
  export interface ResumableStreamDeclaration<ResumeDescriptor = unknown, Checkpoint = unknown> {
30
29
  /** Stable declaration identifier. Change it when the persisted representation changes. */
31
30
  readonly id: string;
32
- /** RPC tag matched against encoded client requests. */
33
31
  readonly rpcTag: string;
34
32
  /** Validates a resume descriptor restored from a websocket attachment. */
35
33
  readonly resumeDescriptorSchema: Schema.Decoder<ResumeDescriptor>;
@@ -45,7 +43,6 @@ export interface ResumableStreamDeclaration<ResumeDescriptor = unknown, Checkpoi
45
43
  readonly resumeDescriptor: ResumeDescriptor;
46
44
  readonly acknowledgedCheckpoint: Checkpoint;
47
45
  }>;
48
- /** Rebuilds the encoded request fields used to restart a handler. */
49
46
  readonly rebuild: (options: {
50
47
  readonly subscriptionKey: string;
51
48
  readonly resumeDescriptor: ResumeDescriptor;
@@ -80,9 +77,7 @@ export const resumableStream = <ResumeDescriptor, Checkpoint>(
80
77
  * Configuration for {@link layer}.
81
78
  */
82
79
  export interface LayerOptions {
83
- /** Tag used to select hibernated sockets. */
84
80
  readonly tag?: string | undefined;
85
- /** Socket attachment namespace used to persist adapter metadata across hibernation. */
86
81
  readonly attachmentKey?: string | undefined;
87
82
  /**
88
83
  * Heartbeat behavior. `"auto-response"` (the default) installs Cloudflare's
@@ -95,24 +90,16 @@ export interface LayerOptions {
95
90
  * when RPC and unrelated websocket protocols share one object.
96
91
  */
97
92
  readonly heartbeat?: HeartbeatPolicy | undefined;
98
- /** Explicit stream declarations that may be reconstructed after activation loss. */
99
93
  readonly resumableStreams?: ReadonlyArray<AnyResumableStreamDeclaration> | undefined;
100
94
  }
101
95
 
102
- /** Native websocket event payload accepted by Cloudflare Durable Objects. */
103
96
  export type NativeWebSocketMessage = string | ArrayBuffer;
104
97
 
105
- /**
106
- * Service API used to wire websocket lifecycle events to Effect RPC server protocol.
107
- */
108
98
  export interface DurableObjectRpcWebSocketService {
109
- /** Accepts a new, not-yet-accepted socket with the RPC tag and any application tags. */
99
+ /** Accept a server socket that has not already been accepted. */
110
100
  readonly accept: (socket: DurableWebSocket, tags?: ReadonlyArray<string>) => Effect.Effect<void>;
111
- /**
112
- * Creates and accepts a websocket upgrade without double-accepting the server
113
- * socket. Application attachment fields are shallow-merged with the adapter's
114
- * metadata namespace.
115
- */
101
+
102
+ /** Shallow-merges application attachment fields with the adapter's metadata namespace. */
116
103
  readonly acceptUpgrade: <Attachment extends RpcWebSocketAttachment = RpcWebSocketAttachment>(
117
104
  options?: AcceptUpgradeOptions<Attachment>,
118
105
  ) => Effect.Effect<AcceptedUpgrade<Attachment>, DurableWebSocketAttachmentError>;
@@ -211,9 +198,6 @@ const ResumableAttachmentMetadataSchema = Schema.Struct({
211
198
 
212
199
  const decodeResumableAttachment = Schema.decodeUnknownOption(ResumableAttachmentMetadataSchema);
213
200
 
214
- /**
215
- * Context tag for the Durable Object RPC websocket service.
216
- */
217
201
  export class DurableObjectRpcWebSocket extends Context.Service<
218
202
  DurableObjectRpcWebSocket,
219
203
  DurableObjectRpcWebSocketService
@@ -246,7 +230,6 @@ export const layer = (
246
230
  const disconnects = yield* Queue.make<number>();
247
231
  const connectionsBySocket = new Map<WebSocket, RpcConnection>();
248
232
  const connectionsById = new Map<number, RpcConnection>();
249
- const clientIds = new Set<number>();
250
233
  const resetSockets = new WeakSet<WebSocket>();
251
234
  const declarationsById = new Map<string, AnyResumableStreamDeclaration>();
252
235
  const declarationsByTag = new Map<string, AnyResumableStreamDeclaration>();
@@ -284,21 +267,6 @@ export const layer = (
284
267
  }
285
268
 
286
269
  const reserveClientId = (socket: DurableWebSocket): AttachmentMetadata => {
287
- const attachment = readAttachment(socket.raw, attachmentKey);
288
-
289
- if (attachment._tag === "Invalid") {
290
- throw new Error("Invalid Durable Object RPC websocket attachment metadata");
291
- }
292
-
293
- if (attachment._tag === "Valid") {
294
- const metadata = attachment.metadata;
295
-
296
- nextClientId = Math.max(nextClientId, metadata.clientId + 1);
297
- writeAttachment(socket.raw, attachmentKey, metadata);
298
-
299
- return metadata;
300
- }
301
-
302
270
  const id = nextClientId++;
303
271
  const created: LegacyAttachmentMetadata = {
304
272
  version: legacyAttachmentVersion,
@@ -393,7 +361,6 @@ export const layer = (
393
361
 
394
362
  connectionsBySocket.set(socket.raw, connection);
395
363
  connectionsById.set(connection.id, connection);
396
- clientIds.add(connection.id);
397
364
  nextClientId = Math.max(nextClientId, connection.id + 1);
398
365
 
399
366
  return connection;
@@ -450,7 +417,6 @@ export const layer = (
450
417
 
451
418
  connectionsBySocket.delete(socket.raw);
452
419
  connectionsById.delete(connection.id);
453
- clientIds.delete(connection.id);
454
420
  activeNonResumableRequestCount -= connection.nonResumableRequestIds.size;
455
421
  connection.requestIds.clear();
456
422
  connection.nonResumableRequestIds.clear();
@@ -863,7 +829,7 @@ export const layer = (
863
829
 
864
830
  connection?.socket.raw.close();
865
831
  }),
866
- clientIds: Effect.sync(() => clientIds),
832
+ clientIds: Effect.sync(() => new Set(connectionsById.keys())),
867
833
  initialMessage: Effect.succeedNone,
868
834
  supportsAck: true,
869
835
  supportsTransferables: false,
@@ -1157,17 +1123,10 @@ const writeAttachment = (socket: WebSocket, key: string, metadata: AttachmentMet
1157
1123
  }
1158
1124
 
1159
1125
  const attachment = Predicate.isObject(current) ? current : {};
1160
- const currentMetadata =
1161
- Predicate.hasProperty(attachment, key) && Predicate.isObject(attachment[key])
1162
- ? attachment[key]
1163
- : {};
1164
1126
 
1165
1127
  socket.serializeAttachment({
1166
1128
  ...attachment,
1167
- [key]: {
1168
- ...currentMetadata,
1169
- ...metadata,
1170
- },
1129
+ [key]: metadata,
1171
1130
  });
1172
1131
  };
1173
1132
 
@@ -4,20 +4,8 @@ import type { SqlClient } from "effect/unstable/sql";
4
4
 
5
5
  import { DurableObjectState } from "./DurableObjectState";
6
6
 
7
- /** Options forwarded to `@effect/sql-sqlite-do` when building a SQL client layer. */
8
7
  export type SqliteLayerOptions = Omit<SqliteClient.SqliteClientConfig, "db" | "storage">;
9
8
 
10
- /**
11
- * Provides `effect/unstable/sql` from the current Durable Object SQLite storage.
12
- *
13
- * @example
14
- * ```ts
15
- * const RoomLayer = Layer.mergeAll(
16
- * DurableObjectSqlite.layer(),
17
- * OtherRoomServices,
18
- * );
19
- * ```
20
- */
21
9
  export const layer = (
22
10
  options?: SqliteLayerOptions,
23
11
  ): Layer.Layer<SqliteClient.SqliteClient | SqlClient.SqlClient, never, DurableObjectState> =>
@@ -3,19 +3,16 @@ import { Cause, Context, Effect, Exit } from "effect";
3
3
  import { fromDurableObjectStorage, type DurableObjectStorage } from "./DurableObjectStorage";
4
4
  import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
5
5
  import type { WorkerContextWaitUntilOptions } from "./Worker";
6
+ import { runNativeCallback } from "./internal/NativeCallback";
6
7
  import { makeWaitUntilScheduler } from "./internal/WorkerContext";
7
8
 
8
9
  /**
9
10
  * Effect-friendly wrapper around Cloudflare `DurableObjectState`.
10
11
  */
11
12
  export interface DurableObjectStateService {
12
- /** Underlying Cloudflare state instance. */
13
13
  readonly raw: globalThis.DurableObjectState;
14
- /** Durable Object id for the current instance. */
15
14
  readonly id: globalThis.DurableObjectId;
16
- /** Wrapped storage API. */
17
15
  readonly storage: DurableObjectStorage;
18
- /** Registers background work with Cloudflare's lifecycle. */
19
16
  waitUntil(promise: Promise<unknown>): Effect.Effect<void>;
20
17
  /**
21
18
  * Runs a background Effect via Cloudflare's `waitUntil` with the caller's
@@ -47,21 +44,13 @@ export interface DurableObjectStateService {
47
44
  * documented timeout (currently 30 seconds).
48
45
  */
49
46
  blockConcurrencyWhileOrReset<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
50
- /** Accepts a websocket connection for hibernation-capable Durable Objects. */
51
47
  acceptWebSocket(ws: DurableWebSocket, tags?: Array<string>): Effect.Effect<void>;
52
- /** Lists active sockets, optionally filtered by tag. */
53
48
  getWebSockets(tag?: string): Effect.Effect<Array<DurableWebSocket>>;
54
- /** Configures automatic request/response handling for sockets. */
55
49
  setWebSocketAutoResponse(pair?: WebSocketRequestResponsePair): Effect.Effect<void>;
56
- /** Gets the configured websocket auto-response pair. */
57
50
  getWebSocketAutoResponse: Effect.Effect<WebSocketRequestResponsePair | null>;
58
- /** Timestamp of the last automatic websocket response for a socket. */
59
51
  getWebSocketAutoResponseTimestamp(ws: DurableWebSocket): Effect.Effect<Date | null>;
60
- /** Sets the timeout for hibernatable websocket events. */
61
52
  setHibernatableWebSocketEventTimeout(timeoutMs?: number): Effect.Effect<void>;
62
- /** Gets the timeout for hibernatable websocket events. */
63
53
  getHibernatableWebSocketEventTimeout: Effect.Effect<number | null>;
64
- /** Reads tags attached to a websocket. */
65
54
  getTags(ws: DurableWebSocket): Effect.Effect<Array<string>>;
66
55
  /**
67
56
  * Forcibly resets the Durable Object. Cloudflare logs an uncaught Error using
@@ -73,17 +62,11 @@ export interface DurableObjectStateService {
73
62
  abort(reason?: string, options?: globalThis.DurableObjectAbortOptions): Effect.Effect<void>;
74
63
  }
75
64
 
76
- /**
77
- * Context tag for accessing the current Durable Object state service.
78
- */
79
65
  export class DurableObjectState extends Context.Service<
80
66
  DurableObjectState,
81
67
  DurableObjectStateService
82
68
  >()("effect-cf/DurableObjectState") {}
83
69
 
84
- /**
85
- * Wraps a native Cloudflare `DurableObjectState` as a {@link DurableObjectStateService}.
86
- */
87
70
  export const fromDurableObjectState = (
88
71
  state: globalThis.DurableObjectState,
89
72
  ): DurableObjectStateService => {
@@ -105,24 +88,21 @@ export const fromDurableObjectState = (
105
88
  storage: fromDurableObjectStorage(state.storage),
106
89
  waitUntil,
107
90
  blockConcurrencyWhile: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
108
- Effect.context<R>().pipe(
109
- Effect.flatMap((context) =>
110
- Effect.promise(() =>
111
- state.blockConcurrencyWhile(() =>
112
- runPromiseExitPreservingTypedFailures(context, effect),
113
- ),
114
- ),
115
- ),
116
- Effect.flatten,
117
- ),
91
+ runNativeCallback<A, E, R, Exit.Exit<A, E>>((run) =>
92
+ state.blockConcurrencyWhile(() => runExitPreservingTypedFailures(run, effect)),
93
+ ).pipe(Effect.orDie, Effect.flatten),
118
94
  blockConcurrencyWhileOrReset: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
119
- Effect.context<R>().pipe(
120
- Effect.flatMap((context) =>
121
- Effect.promise(() =>
122
- state.blockConcurrencyWhile(() => Effect.runPromiseWith(context)(effect)),
123
- ),
124
- ),
125
- ),
95
+ runNativeCallback<A, E, R, A>((run) =>
96
+ state.blockConcurrencyWhile(async () => {
97
+ const exit = await run(effect);
98
+
99
+ if (Exit.isFailure(exit)) {
100
+ throw Cause.squash(exit.cause);
101
+ }
102
+
103
+ return exit.value;
104
+ }),
105
+ ).pipe(Effect.orDie),
126
106
  acceptWebSocket: (ws: DurableWebSocket, tags?: Array<string>) =>
127
107
  Effect.sync(() => state.acceptWebSocket(ws.raw, tags)),
128
108
  getWebSockets: (tag?: string) =>
@@ -143,11 +123,11 @@ export const fromDurableObjectState = (
143
123
  };
144
124
  };
145
125
 
146
- const runPromiseExitPreservingTypedFailures = async <A, E, R>(
147
- context: Context.Context<R>,
126
+ const runExitPreservingTypedFailures = async <A, E, R>(
127
+ run: (effect: Effect.Effect<A, E, R>) => Promise<Exit.Exit<A, E>>,
148
128
  effect: Effect.Effect<A, E, R>,
149
129
  ): Promise<Exit.Exit<A, E>> => {
150
- const exit = await Effect.runPromiseExitWith(context)(effect);
130
+ const exit = await run(effect);
151
131
 
152
132
  if (Exit.isFailure(exit) && (Cause.hasDies(exit.cause) || Cause.hasInterrupts(exit.cause))) {
153
133
  throw Cause.squash(exit.cause);