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.
- package/CHANGELOG.md +22 -0
- package/README.md +31 -1079
- package/dist/{Artifacts-DnHQA-Ax.d.mts → Artifacts-B3r3RuTB.d.mts} +1 -48
- package/dist/{Artifacts-CSIk1Oml.mjs → Artifacts-CBxWGeZB.mjs} +2 -7
- package/dist/{Binding-pfilNJmP.mjs → Binding-C82jRhdr.mjs} +1 -2
- package/dist/{Binding-xJJJPmiD.d.mts → Binding-DKcLrQCM.d.mts} +0 -45
- package/dist/ComputerArtifacts.d.mts +1 -3
- package/dist/ComputerArtifacts.mjs +1 -2
- package/dist/ComputerWorkspace.d.mts +11 -12
- package/dist/ComputerWorkspace.mjs +31 -10
- package/dist/ComputerWorkspaceHost.d.mts +1 -9
- package/dist/{ContainerNamespace-CFjiLw8b.d.mts → ContainerNamespace-DbLaeFhN.d.mts} +1 -14
- package/dist/{DurableObjectState-CVLLse69.mjs → DurableObjectState-DcqElJ4e.mjs} +82 -89
- package/dist/{DurableObjectState-BpRjz6-q.d.mts → DurableObjectState-DpYdNZgT.d.mts} +102 -266
- package/dist/{ErrorMessage-BM0K-Nik.mjs → ErrorMessage-Byx2OgNd.mjs} +0 -1
- package/dist/{Hyperdrive-z-9fFz15.d.mts → Hyperdrive-HrwFlZ6x.d.mts} +1 -4
- package/dist/HyperdrivePg.d.mts +2 -11
- package/dist/HyperdrivePg.mjs +0 -8
- package/dist/Sandbox.d.mts +2 -32
- package/dist/Sandbox.mjs +2 -11
- package/dist/Vitest.d.mts +3 -31
- package/dist/Vitest.mjs +8 -26
- package/dist/{WorkflowBinding-lBR4_-op.d.mts → WorkflowBinding-Cn3AuUyP.d.mts} +2 -5
- package/dist/index.d.mts +42 -480
- package/dist/index.mjs +267 -380
- package/package.json +1 -1
- package/src/AiGateway.ts +0 -6
- package/src/AnalyticsEngine.ts +8 -10
- package/src/Artifacts.ts +0 -47
- package/src/Binding.ts +0 -8
- package/src/BrowserRendering.ts +0 -4
- package/src/Cache.ts +0 -19
- package/src/CloudflareOtlp.ts +6 -39
- package/src/ComputerArtifacts.ts +0 -2
- package/src/ComputerWorkspace.ts +53 -35
- package/src/ComputerWorkspaceHost.ts +0 -8
- package/src/ContainerNamespace.ts +0 -13
- package/src/D1.ts +0 -22
- package/src/DurableObject.ts +51 -32
- package/src/DurableObjectAlarm.ts +6 -210
- package/src/DurableObjectDefinition.ts +8 -20
- package/src/DurableObjectNamespace.ts +88 -115
- package/src/DurableObjectRpcWebSocket.ts +5 -46
- package/src/DurableObjectSqlite.ts +0 -12
- package/src/DurableObjectState.ts +18 -38
- package/src/DurableObjectStorage.ts +55 -70
- package/src/DurableObjectWebSocket.ts +23 -72
- package/src/Email.ts +0 -12
- package/src/Environment.ts +0 -37
- package/src/Hyperdrive.ts +0 -3
- package/src/HyperdrivePg.ts +0 -9
- package/src/Images.ts +0 -5
- package/src/Kv.ts +0 -15
- package/src/QueueBinding.ts +0 -2
- package/src/R2.ts +0 -5
- package/src/RpcDefinition.ts +5 -17
- package/src/RpcTracing.ts +133 -0
- package/src/Sandbox.ts +0 -30
- package/src/ServiceBinding.ts +83 -128
- package/src/Vectorize.ts +0 -5
- package/src/Vitest.ts +18 -43
- package/src/WebTransport.ts +0 -15
- package/src/Worker.ts +140 -108
- package/src/WorkerDefinition.ts +5 -14
- package/src/WorkersAi.ts +21 -18
- package/src/Workflow.ts +45 -54
- package/src/WorkflowBinding.ts +0 -3
- package/src/index.ts +1 -0
- package/src/internal/Entrypoint.ts +23 -4
- package/src/internal/ErrorMessage.ts +0 -1
- package/src/internal/NativeCallback.ts +55 -0
- package/src/internal/RpcInvocation.ts +18 -1
- package/src/internal/Runtime.ts +12 -7
- 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>>,
|
|
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<
|
|
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: () =>
|
|
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.
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
470
|
-
|
|
471
|
-
|
|
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.
|
|
498
|
-
Method extends StubMethodKey<Api
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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(() =>
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
147
|
-
|
|
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
|
|
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);
|