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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.41.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#161](https://github.com/danieljvdm/effect-cf/pull/161) [`a627f80`](https://github.com/danieljvdm/effect-cf/commit/a627f80449bf40227bddf917640aae9574faa563) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Retain Sandbox and Container RPC channels for each invocation so repeated lookups after callbacks do not exhaust Cloudflare's subrequest depth. Sandbox configuration continues to apply on each lookup, and failed channels are replaced on subsequent acquisition.
|
|
8
|
+
|
|
9
|
+
## 0.41.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#159](https://github.com/danieljvdm/effect-cf/pull/159) [`8cbc534`](https://github.com/danieljvdm/effect-cf/commit/8cbc5344ffa2aa031b6d98a2845ca3b945cf7d5f) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Reuse Durable Object RPC targets within each invocation so callbacks cannot grow a new subrequest chain on every call. Incoming requests and durable retries retain separate target lifetimes. Expose RpcTargets for native RPC adapters sharing the same invocation boundary.
|
|
14
|
+
|
|
3
15
|
## 0.40.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
2
|
import { layer as layer$1 } from "./Binding.mjs";
|
|
3
3
|
import { causeMessage } from "./internal/ErrorMessage.mjs";
|
|
4
|
+
import { get, invalidate } from "./RpcTargets.mjs";
|
|
4
5
|
import * as Context from "effect/Context";
|
|
5
6
|
import * as Data from "effect/Data";
|
|
6
7
|
import * as Effect from "effect/Effect";
|
|
@@ -37,7 +38,7 @@ var ContainerOperationError = class extends Data.TaggedError("ContainerOperation
|
|
|
37
38
|
return `Container ${this.operation} failed for binding "${this.binding}" instance "${this.instance}": ${causeMessage(this.cause)}`;
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
|
-
const tryContainerPromise = (definition, instance, operation, evaluate) => Effect.tryPromise({
|
|
41
|
+
const tryContainerPromise = (definition, instance, operation, evaluate, target) => Effect.tryPromise({
|
|
41
42
|
try: evaluate,
|
|
42
43
|
catch: (cause) => new ContainerOperationError({
|
|
43
44
|
binding: definition.binding,
|
|
@@ -45,43 +46,44 @@ const tryContainerPromise = (definition, instance, operation, evaluate) => Effec
|
|
|
45
46
|
operation,
|
|
46
47
|
cause
|
|
47
48
|
})
|
|
48
|
-
});
|
|
49
|
+
}).pipe(Effect.tapCause(() => invalidate(target)));
|
|
49
50
|
const makeInstanceClient = (definition, instance, stub) => {
|
|
51
|
+
const attempt = (operation, evaluate) => tryContainerPromise(definition, instance, operation, evaluate, stub);
|
|
50
52
|
const spanOptions = (operation) => ({ attributes: {
|
|
51
53
|
binding: definition.binding,
|
|
52
54
|
instance,
|
|
53
55
|
operation
|
|
54
56
|
} });
|
|
55
57
|
const hostListOperation = (operation) => Effect.fn(`ContainerNamespace.${operation}`, spanOptions(operation))(function* (hosts) {
|
|
56
|
-
return yield*
|
|
58
|
+
return yield* attempt(operation, () => stub[operation]([...hosts]));
|
|
57
59
|
});
|
|
58
60
|
const hostnameOperation = (operation) => Effect.fn(`ContainerNamespace.${operation}`, spanOptions(operation))(function* (hostname) {
|
|
59
|
-
return yield*
|
|
61
|
+
return yield* attempt(operation, () => stub[operation](hostname));
|
|
60
62
|
});
|
|
61
63
|
return {
|
|
62
64
|
rawUnsafe: Effect.succeed(stub),
|
|
63
|
-
state:
|
|
65
|
+
state: attempt("state", () => stub.getState()).pipe(Effect.flatMap(S.decodeUnknownEffect(ContainerState)), Effect.mapError((cause) => cause instanceof ContainerOperationError ? cause : new ContainerOperationError({
|
|
64
66
|
binding: definition.binding,
|
|
65
67
|
instance,
|
|
66
68
|
operation: "state",
|
|
67
69
|
cause
|
|
68
70
|
})), Effect.withSpan("ContainerNamespace.state", spanOptions("state"))),
|
|
69
71
|
fetch: Effect.fn("ContainerNamespace.fetch", spanOptions("fetch"))(function* (input, init) {
|
|
70
|
-
return yield*
|
|
72
|
+
return yield* attempt("fetch", () => stub.fetch(input, init));
|
|
71
73
|
}),
|
|
72
74
|
start: Effect.fn("ContainerNamespace.start", spanOptions("start"))(function* (options, waitOptions) {
|
|
73
|
-
return yield*
|
|
75
|
+
return yield* attempt("start", () => stub.start(options, waitOptions));
|
|
74
76
|
}),
|
|
75
77
|
startAndWaitForPorts: Effect.fn("ContainerNamespace.startAndWaitForPorts", spanOptions("startAndWaitForPorts"))(function* (options) {
|
|
76
|
-
return yield*
|
|
78
|
+
return yield* attempt("startAndWaitForPorts", () => stub.startAndWaitForPorts(options));
|
|
77
79
|
}),
|
|
78
80
|
waitForPort: Effect.fn("ContainerNamespace.waitForPort", spanOptions("waitForPort"))(function* (options) {
|
|
79
|
-
return yield*
|
|
81
|
+
return yield* attempt("waitForPort", () => stub.waitForPort(options));
|
|
80
82
|
}),
|
|
81
83
|
stop: Effect.fn("ContainerNamespace.stop", spanOptions("stop"))(function* (signal) {
|
|
82
|
-
return yield*
|
|
84
|
+
return yield* attempt("stop", () => stub.stop(signal));
|
|
83
85
|
}),
|
|
84
|
-
destroy:
|
|
86
|
+
destroy: attempt("destroy", () => stub.destroy()).pipe(Effect.withSpan("ContainerNamespace.destroy", spanOptions("destroy"))),
|
|
85
87
|
setAllowedHosts: hostListOperation("setAllowedHosts"),
|
|
86
88
|
setDeniedHosts: hostListOperation("setDeniedHosts"),
|
|
87
89
|
allowHost: hostnameOperation("allowHost"),
|
|
@@ -92,15 +94,16 @@ const makeInstanceClient = (definition, instance, stub) => {
|
|
|
92
94
|
};
|
|
93
95
|
const isContainerNamespaceResource = (value) => Predicate.hasProperty(value, "getByName") && Predicate.isFunction(value.getByName);
|
|
94
96
|
const makeClient = (definition) => (namespace) => {
|
|
95
|
-
const getByName = (name, options) =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
const getByName = (name, options) => get(namespace, JSON.stringify([
|
|
98
|
+
"container",
|
|
99
|
+
name,
|
|
100
|
+
options
|
|
101
|
+
]), () => namespace.getByName(name, options)).pipe(Effect.map((stub) => makeInstanceClient(definition, name, stub)), Effect.mapError((error) => new ContainerOperationError({
|
|
102
|
+
binding: definition.binding,
|
|
103
|
+
instance: name,
|
|
104
|
+
operation: "getByName",
|
|
105
|
+
cause: error.cause
|
|
106
|
+
})));
|
|
104
107
|
const byName = (name, options) => {
|
|
105
108
|
const instance = getByName(name, options);
|
|
106
109
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
2
|
import { layer as layer$1 } from "./Binding.mjs";
|
|
3
3
|
import { causeMessage } from "./internal/ErrorMessage.mjs";
|
|
4
|
+
import { get, invalidate } from "./RpcTargets.mjs";
|
|
4
5
|
import { decodeSuccess, encodeArgs } from "./RpcDefinition.mjs";
|
|
5
6
|
import { withRpcClientSpan, withRpcTraceContext } from "./RpcTracing.mjs";
|
|
6
7
|
import { invokeRpcMethod } from "./internal/RpcInvocation.mjs";
|
|
@@ -41,8 +42,16 @@ const makeClient = (definition) => {
|
|
|
41
42
|
const newUniqueId = (options) => Effect.sync(() => namespace.newUniqueId(options));
|
|
42
43
|
const idFromName = (name) => Effect.sync(() => namespace.idFromName(name));
|
|
43
44
|
const idFromString = (id) => Effect.sync(() => namespace.idFromString(id));
|
|
44
|
-
const get = (id, options) =>
|
|
45
|
-
|
|
45
|
+
const get$1 = (id, options) => get(namespace, JSON.stringify([
|
|
46
|
+
"id",
|
|
47
|
+
id.toString(),
|
|
48
|
+
options
|
|
49
|
+
]), () => namespace.get(id, options)).pipe(Effect.orDie);
|
|
50
|
+
const getByName = (name, options) => get(namespace, JSON.stringify([
|
|
51
|
+
"name",
|
|
52
|
+
name,
|
|
53
|
+
options
|
|
54
|
+
]), () => namespace.getByName(name, options)).pipe(Effect.orDie);
|
|
46
55
|
const jurisdiction = (value) => Effect.sync(() => namespace.jurisdiction(value));
|
|
47
56
|
const fetch = (stub, input, init) => Effect.tryPromise({
|
|
48
57
|
try: (signal) => {
|
|
@@ -56,7 +65,7 @@ const makeClient = (definition) => {
|
|
|
56
65
|
binding: definition.binding,
|
|
57
66
|
cause
|
|
58
67
|
})
|
|
59
|
-
});
|
|
68
|
+
}).pipe(Effect.tapCause(() => invalidate(stub)));
|
|
60
69
|
const rpc = Effect.fnUntraced(function* (stub, method, ...args) {
|
|
61
70
|
const methodName = String(method);
|
|
62
71
|
const encodedArgs = definition.definition === void 0 ? args : yield* encodeArgs(definition.definition, methodName, args).pipe(Effect.mapError((cause) => new DurableObjectRpcError({
|
|
@@ -69,7 +78,7 @@ const makeClient = (definition) => {
|
|
|
69
78
|
binding: definition.binding,
|
|
70
79
|
method: methodName,
|
|
71
80
|
cause
|
|
72
|
-
}));
|
|
81
|
+
})).pipe(Effect.tapCause(() => invalidate(stub)));
|
|
73
82
|
});
|
|
74
83
|
const decodeSuccess$1 = Effect.fnUntraced(function* (methodName, value) {
|
|
75
84
|
if (definition.definition === void 0) return value;
|
|
@@ -81,7 +90,7 @@ const makeClient = (definition) => {
|
|
|
81
90
|
});
|
|
82
91
|
const call = Effect.fnUntraced(function* (stub, method, ...args) {
|
|
83
92
|
const methodName = String(method);
|
|
84
|
-
const value = yield* resolve(yield* rpc(stub, method, ...args)).pipe(Effect.mapError((cause) => new DurableObjectRpcError({
|
|
93
|
+
const value = yield* resolve(yield* rpc(stub, method, ...args)).pipe(Effect.tapCause(() => invalidate(stub)), Effect.mapError((cause) => new DurableObjectRpcError({
|
|
85
94
|
binding: definition.binding,
|
|
86
95
|
method: methodName,
|
|
87
96
|
cause
|
|
@@ -91,7 +100,7 @@ const makeClient = (definition) => {
|
|
|
91
100
|
const scopedCall = Effect.fnUntraced(function* (stub, method, ...args) {
|
|
92
101
|
const methodName = String(method);
|
|
93
102
|
const result = yield* rpc(stub, method, ...args);
|
|
94
|
-
const value = yield* scoped(result).pipe(Effect.mapError((cause) => new DurableObjectRpcError({
|
|
103
|
+
const value = yield* scoped(result).pipe(Effect.tapCause(() => invalidate(stub)), Effect.mapError((cause) => new DurableObjectRpcError({
|
|
95
104
|
binding: definition.binding,
|
|
96
105
|
method: methodName,
|
|
97
106
|
cause
|
|
@@ -101,14 +110,14 @@ const makeClient = (definition) => {
|
|
|
101
110
|
const directMethods = makeDirectMethods(definition.definition, {
|
|
102
111
|
call,
|
|
103
112
|
fetch,
|
|
104
|
-
get,
|
|
113
|
+
get: get$1,
|
|
105
114
|
getByName
|
|
106
115
|
});
|
|
107
116
|
return Object.assign(directMethods, {
|
|
108
117
|
newUniqueId,
|
|
109
118
|
idFromName,
|
|
110
119
|
idFromString,
|
|
111
|
-
get,
|
|
120
|
+
get: get$1,
|
|
112
121
|
getByName,
|
|
113
122
|
jurisdiction,
|
|
114
123
|
fetch,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Scope from "effect/Scope";
|
|
3
|
+
declare namespace RpcTargets_d_exports {
|
|
4
|
+
export { RpcTargetError, get, invalidate, withScope };
|
|
5
|
+
}
|
|
6
|
+
declare const RpcTargetError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
7
|
+
readonly _tag: "RpcTargetError";
|
|
8
|
+
} & Readonly<A>;
|
|
9
|
+
/** A native RPC target could not be constructed. */
|
|
10
|
+
declare class RpcTargetError extends RpcTargetError_base<{
|
|
11
|
+
readonly cause: unknown;
|
|
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
|
+
declare const get: <A extends object, Owner extends object>(owner: Owner, address: string, create: () => A) => Effect.Effect<A, RpcTargetError, never>;
|
|
23
|
+
/** Discard a failed native target so a subsequent call can acquire a fresh channel. */
|
|
24
|
+
declare const invalidate: <Target extends object>(target: Target) => Effect.Effect<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Own RPC targets in the current Scope, including a transferred response stream,
|
|
27
|
+
* never across incoming requests or durable retries. effect-cf entrypoints install this boundary automatically.
|
|
28
|
+
*/
|
|
29
|
+
declare const withScope: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | Scope.Scope>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { RpcTargetError, RpcTargets_d_exports, get, invalidate, withScope };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { CurrentTargets } from "./internal/RpcTargets.mjs";
|
|
3
|
+
import * as Data from "effect/Data";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
//#region src/RpcTargets.ts
|
|
7
|
+
var RpcTargets_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
RpcTargetError: () => RpcTargetError,
|
|
9
|
+
get: () => get,
|
|
10
|
+
invalidate: () => invalidate,
|
|
11
|
+
withScope: () => withScope
|
|
12
|
+
});
|
|
13
|
+
/** A native RPC target could not be constructed. */
|
|
14
|
+
var RpcTargetError = class extends Data.TaggedError("RpcTargetError") {};
|
|
15
|
+
/**
|
|
16
|
+
* Reuse a native RPC target within the current invocation. Native Durable Object
|
|
17
|
+
* stubs capture the calling request's channel when created. Recreating a stub
|
|
18
|
+
* after a callback can extend that callback's call chain until Cloudflare's
|
|
19
|
+
* subrequest depth limit is reached, even when an alarm owns the work.
|
|
20
|
+
*
|
|
21
|
+
* The owner and address must identify one target and its construction options.
|
|
22
|
+
* Outside an effect-cf invocation (or withScope), each lookup constructs a target.
|
|
23
|
+
*/
|
|
24
|
+
const get = (owner, address, create) => Effect.flatMap(Effect.serviceOption(CurrentTargets), (targets) => Effect.try({
|
|
25
|
+
try: () => Option.isSome(targets) ? targets.value.get(owner, address, create) : create(),
|
|
26
|
+
catch: (cause) => new RpcTargetError({ cause })
|
|
27
|
+
}));
|
|
28
|
+
/** Discard a failed native target so a subsequent call can acquire a fresh channel. */
|
|
29
|
+
const invalidate = (target) => Effect.map(Effect.serviceOption(CurrentTargets), (targets) => {
|
|
30
|
+
if (Option.isSome(targets)) targets.value.invalidate(target);
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Own RPC targets in the current Scope, including a transferred response stream,
|
|
34
|
+
* never across incoming requests or durable retries. effect-cf entrypoints install this boundary automatically.
|
|
35
|
+
*/
|
|
36
|
+
const withScope = (effect) => Effect.gen(function* () {
|
|
37
|
+
let active = true;
|
|
38
|
+
let owners = /* @__PURE__ */ new WeakMap();
|
|
39
|
+
let addresses = /* @__PURE__ */ new WeakMap();
|
|
40
|
+
const targets = {
|
|
41
|
+
get: (owner, address, create) => {
|
|
42
|
+
if (!active) return create();
|
|
43
|
+
let entries = owners.get(owner);
|
|
44
|
+
if (entries === void 0) {
|
|
45
|
+
entries = /* @__PURE__ */ new Map();
|
|
46
|
+
owners.set(owner, entries);
|
|
47
|
+
}
|
|
48
|
+
const existing = entries.get(address);
|
|
49
|
+
if (existing !== void 0) return existing;
|
|
50
|
+
const target = create();
|
|
51
|
+
entries.set(address, target);
|
|
52
|
+
addresses.set(target, {
|
|
53
|
+
entries,
|
|
54
|
+
address
|
|
55
|
+
});
|
|
56
|
+
return target;
|
|
57
|
+
},
|
|
58
|
+
invalidate: (target) => {
|
|
59
|
+
const entry = addresses.get(target);
|
|
60
|
+
if (entry?.entries.get(entry.address) === target) entry.entries.delete(entry.address);
|
|
61
|
+
addresses.delete(target);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
65
|
+
active = false;
|
|
66
|
+
owners = /* @__PURE__ */ new WeakMap();
|
|
67
|
+
addresses = /* @__PURE__ */ new WeakMap();
|
|
68
|
+
}));
|
|
69
|
+
return yield* effect.pipe(Effect.provideService(CurrentTargets, targets));
|
|
70
|
+
});
|
|
71
|
+
//#endregion
|
|
72
|
+
export { RpcTargetError, RpcTargets_exports, get, invalidate, withScope };
|