effect-cf 0.40.3 → 0.41.1
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 +12 -0
- package/dist/ContainerNamespace.mjs +23 -20
- package/dist/DurableObjectNamespace.mjs +17 -8
- package/dist/RpcTargets.d.mts +31 -0
- package/dist/RpcTargets.mjs +72 -0
- package/dist/Sandbox.mjs +87 -65
- package/dist/Worker.mjs +5 -4
- package/dist/Workflow.mjs +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/internal/RpcTargets.mjs +5 -0
- package/dist/internal/Runtime.mjs +3 -2
- package/package.json +1 -1
- package/src/ContainerNamespace.ts +32 -39
- package/src/DurableObjectNamespace.ts +13 -4
- package/src/RpcTargets.ts +94 -0
- package/src/Sandbox.ts +131 -146
- package/src/Worker.ts +27 -22
- package/src/Workflow.ts +6 -1
- package/src/index.ts +1 -0
- package/src/internal/RpcTargets.ts +10 -0
- package/src/internal/Runtime.ts +5 -2
|
@@ -11,6 +11,7 @@ import * as RpcDefinition from "./RpcDefinition";
|
|
|
11
11
|
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
12
12
|
import * as RpcInvocation from "./internal/RpcInvocation";
|
|
13
13
|
import * as RpcTracing from "./RpcTracing";
|
|
14
|
+
import * as RpcTargets from "./RpcTargets";
|
|
14
15
|
|
|
15
16
|
const expectedDurableObjectNamespace =
|
|
16
17
|
"Durable Object namespace binding with getByName(), get(), idFromName(), idFromString(), newUniqueId(), and jurisdiction()";
|
|
@@ -348,12 +349,18 @@ export const makeClient = <
|
|
|
348
349
|
const get = (
|
|
349
350
|
id: globalThis.DurableObjectId,
|
|
350
351
|
options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
|
|
351
|
-
) =>
|
|
352
|
+
) =>
|
|
353
|
+
RpcTargets.get(namespace, JSON.stringify(["id", id.toString(), options]), () =>
|
|
354
|
+
namespace.get(id, options),
|
|
355
|
+
).pipe(Effect.orDie);
|
|
352
356
|
|
|
353
357
|
const getByName = (
|
|
354
358
|
name: string,
|
|
355
359
|
options?: globalThis.DurableObjectNamespaceGetDurableObjectOptions,
|
|
356
|
-
) =>
|
|
360
|
+
) =>
|
|
361
|
+
RpcTargets.get(namespace, JSON.stringify(["name", name, options]), () =>
|
|
362
|
+
namespace.getByName(name, options),
|
|
363
|
+
).pipe(Effect.orDie);
|
|
357
364
|
|
|
358
365
|
const jurisdiction = (value: globalThis.DurableObjectJurisdiction) =>
|
|
359
366
|
Effect.sync(() => namespace.jurisdiction(value));
|
|
@@ -377,7 +384,7 @@ export const makeClient = <
|
|
|
377
384
|
});
|
|
378
385
|
},
|
|
379
386
|
catch: (cause) => new DurableObjectFetchError({ binding: definition.binding, cause }),
|
|
380
|
-
});
|
|
387
|
+
}).pipe(Effect.tapCause(() => RpcTargets.invalidate(stub)));
|
|
381
388
|
|
|
382
389
|
const rpc = Effect.fnUntraced(function* <Method extends StubMethodKey<Api>>(
|
|
383
390
|
stub: StubClient,
|
|
@@ -419,7 +426,7 @@ export const makeClient = <
|
|
|
419
426
|
method: methodName,
|
|
420
427
|
cause,
|
|
421
428
|
}),
|
|
422
|
-
);
|
|
429
|
+
).pipe(Effect.tapCause(() => RpcTargets.invalidate(stub)));
|
|
423
430
|
});
|
|
424
431
|
|
|
425
432
|
const decodeSuccess = Effect.fnUntraced(function* <Method extends StubMethodKey<Api>>(
|
|
@@ -459,6 +466,7 @@ export const makeClient = <
|
|
|
459
466
|
): Effect.fn.Return<StubMethodSuccess<Api, Method>, DurableObjectRpcError> {
|
|
460
467
|
const methodName = String(method);
|
|
461
468
|
const value = yield* CloudflareRpc.resolve(yield* rpc(stub, method, ...args)).pipe(
|
|
469
|
+
Effect.tapCause(() => RpcTargets.invalidate(stub)),
|
|
462
470
|
Effect.mapError(
|
|
463
471
|
(cause) =>
|
|
464
472
|
new DurableObjectRpcError({
|
|
@@ -484,6 +492,7 @@ export const makeClient = <
|
|
|
484
492
|
const methodName = String(method);
|
|
485
493
|
const result = yield* rpc(stub, method, ...args);
|
|
486
494
|
const value = yield* CloudflareRpc.scoped(result).pipe(
|
|
495
|
+
Effect.tapCause(() => RpcTargets.invalidate(stub)),
|
|
487
496
|
Effect.mapError(
|
|
488
497
|
(cause) =>
|
|
489
498
|
new DurableObjectRpcError({
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
import type * as Scope from "effect/Scope";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
|
|
6
|
+
import { CurrentTargets, type Targets } from "./internal/RpcTargets";
|
|
7
|
+
|
|
8
|
+
/** A native RPC target could not be constructed. */
|
|
9
|
+
export class RpcTargetError extends Data.TaggedError("RpcTargetError")<{
|
|
10
|
+
readonly cause: unknown;
|
|
11
|
+
}> {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reuse a native RPC target within the current invocation. Native Durable Object
|
|
15
|
+
* stubs capture the calling request's channel when created. Recreating a stub
|
|
16
|
+
* after a callback can extend that callback's call chain until Cloudflare's
|
|
17
|
+
* subrequest depth limit is reached, even when an alarm owns the work.
|
|
18
|
+
*
|
|
19
|
+
* The owner and address must identify one target and its construction options.
|
|
20
|
+
* Outside an effect-cf invocation (or withScope), each lookup constructs a target.
|
|
21
|
+
*/
|
|
22
|
+
export const get = <A extends object, Owner extends object>(
|
|
23
|
+
owner: Owner,
|
|
24
|
+
address: string,
|
|
25
|
+
create: () => A,
|
|
26
|
+
) =>
|
|
27
|
+
Effect.flatMap(Effect.serviceOption(CurrentTargets), (targets) =>
|
|
28
|
+
Effect.try({
|
|
29
|
+
try: () => (Option.isSome(targets) ? targets.value.get(owner, address, create) : create()),
|
|
30
|
+
catch: (cause) => new RpcTargetError({ cause }),
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** Discard a failed native target so a subsequent call can acquire a fresh channel. */
|
|
35
|
+
export const invalidate = <Target extends object>(target: Target): Effect.Effect<void> =>
|
|
36
|
+
Effect.map(Effect.serviceOption(CurrentTargets), (targets) => {
|
|
37
|
+
if (Option.isSome(targets)) targets.value.invalidate(target);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Own RPC targets in the current Scope, including a transferred response stream,
|
|
42
|
+
* never across incoming requests or durable retries. effect-cf entrypoints install this boundary automatically.
|
|
43
|
+
*/
|
|
44
|
+
export const withScope = <A, E, R>(
|
|
45
|
+
effect: Effect.Effect<A, E, R>,
|
|
46
|
+
): Effect.Effect<A, E, R | Scope.Scope> =>
|
|
47
|
+
Effect.gen(function* () {
|
|
48
|
+
let active = true;
|
|
49
|
+
let owners = new WeakMap<object, Map<string, object>>();
|
|
50
|
+
let addresses = new WeakMap<object, { entries: Map<string, object>; address: string }>();
|
|
51
|
+
const targets: Targets = {
|
|
52
|
+
get: <T extends object, Owner extends object>(
|
|
53
|
+
owner: Owner,
|
|
54
|
+
address: string,
|
|
55
|
+
create: () => T,
|
|
56
|
+
): T => {
|
|
57
|
+
if (!active) return create();
|
|
58
|
+
let entries = owners.get(owner);
|
|
59
|
+
|
|
60
|
+
if (entries === undefined) {
|
|
61
|
+
entries = new Map();
|
|
62
|
+
owners.set(owner, entries);
|
|
63
|
+
}
|
|
64
|
+
const existing = entries.get(address);
|
|
65
|
+
|
|
66
|
+
if (existing !== undefined) {
|
|
67
|
+
// SAFETY: the caller's owner/address identifies the same native target type.
|
|
68
|
+
return existing as T;
|
|
69
|
+
}
|
|
70
|
+
const target = create();
|
|
71
|
+
|
|
72
|
+
entries.set(address, target);
|
|
73
|
+
addresses.set(target, { entries, address });
|
|
74
|
+
|
|
75
|
+
return target;
|
|
76
|
+
},
|
|
77
|
+
invalidate: (target) => {
|
|
78
|
+
const entry = addresses.get(target);
|
|
79
|
+
|
|
80
|
+
if (entry?.entries.get(entry.address) === target) entry.entries.delete(entry.address);
|
|
81
|
+
addresses.delete(target);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
yield* Effect.addFinalizer(() =>
|
|
86
|
+
Effect.sync(() => {
|
|
87
|
+
active = false;
|
|
88
|
+
owners = new WeakMap();
|
|
89
|
+
addresses = new WeakMap();
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return yield* effect.pipe(Effect.provideService(CurrentTargets, targets));
|
|
94
|
+
});
|