effect-cf 0.22.0 → 0.23.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 +14 -0
- package/README.md +26 -2
- package/dist/index.d.mts +929 -897
- package/dist/index.mjs +92 -53
- package/package.json +1 -1
- package/src/ContainerNamespace.ts +172 -33
- package/src/DurableObjectState.ts +61 -34
- package/src/WorkflowBinding.ts +5 -1
- package/src/internal/WorkerContext.ts +29 -19
|
@@ -2,6 +2,8 @@ import { Cause, Context, Effect, Exit } from "effect";
|
|
|
2
2
|
|
|
3
3
|
import { fromDurableObjectStorage, type DurableObjectStorage } from "./DurableObjectStorage";
|
|
4
4
|
import { fromWebSocket, type DurableWebSocket } from "./DurableObjectWebSocket";
|
|
5
|
+
import type { WorkerContextWaitUntilOptions } from "./Worker";
|
|
6
|
+
import { makeWaitUntilScheduler } from "./internal/WorkerContext";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Effect-friendly wrapper around Cloudflare `DurableObjectState`.
|
|
@@ -15,6 +17,16 @@ export interface DurableObjectStateService {
|
|
|
15
17
|
readonly storage: DurableObjectStorage;
|
|
16
18
|
/** Registers background work with Cloudflare's lifecycle. */
|
|
17
19
|
waitUntil(promise: Promise<unknown>): Effect.Effect<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Runs a background Effect via Cloudflare's `waitUntil` with the caller's
|
|
22
|
+
* Effect context, mirroring `WorkerContext.waitUntil` failure modes:
|
|
23
|
+
* `"observe"` (default) logs or routes failures to `onFailure`, while
|
|
24
|
+
* `"propagate"` also rejects the native `waitUntil` promise.
|
|
25
|
+
*/
|
|
26
|
+
waitUntil<A, E, R, R2 = never>(
|
|
27
|
+
effect: Effect.Effect<A, E, R>,
|
|
28
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
29
|
+
): Effect.Effect<void, never, R | R2>;
|
|
18
30
|
/**
|
|
19
31
|
* Runs an Effect inside Cloudflare's `blockConcurrencyWhile` gate.
|
|
20
32
|
*
|
|
@@ -72,45 +84,60 @@ export class DurableObjectState extends Context.Service<
|
|
|
72
84
|
*/
|
|
73
85
|
export const fromDurableObjectState = (
|
|
74
86
|
state: globalThis.DurableObjectState,
|
|
75
|
-
): DurableObjectStateService =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
waitUntil
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
): DurableObjectStateService => {
|
|
88
|
+
const scheduleWaitUntil = makeWaitUntilScheduler("DurableObjectState.waitUntil", (promise) =>
|
|
89
|
+
state.waitUntil(promise),
|
|
90
|
+
);
|
|
91
|
+
const waitUntil = (<A, E, R, R2 = never>(
|
|
92
|
+
input: Promise<unknown> | Effect.Effect<A, E, R>,
|
|
93
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
94
|
+
) =>
|
|
95
|
+
Effect.isEffect(input)
|
|
96
|
+
? scheduleWaitUntil(input, options, options?.mode ?? "observe")
|
|
97
|
+
: Effect.sync(() => state.waitUntil(input))) as DurableObjectStateService["waitUntil"];
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
raw: state,
|
|
101
|
+
id: state.id,
|
|
102
|
+
storage: fromDurableObjectStorage(state.storage),
|
|
103
|
+
waitUntil,
|
|
104
|
+
blockConcurrencyWhile: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
105
|
+
Effect.context<R>().pipe(
|
|
106
|
+
Effect.flatMap((context) =>
|
|
107
|
+
Effect.promise(() =>
|
|
108
|
+
state.blockConcurrencyWhile(() =>
|
|
109
|
+
runPromiseExitPreservingTypedFailures(context, effect),
|
|
110
|
+
),
|
|
111
|
+
),
|
|
85
112
|
),
|
|
113
|
+
Effect.flatten,
|
|
86
114
|
),
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
state.blockConcurrencyWhile(() => Effect.runPromiseWith(context)(effect)),
|
|
115
|
+
blockConcurrencyWhileOrReset: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
116
|
+
Effect.context<R>().pipe(
|
|
117
|
+
Effect.flatMap((context) =>
|
|
118
|
+
Effect.promise(() =>
|
|
119
|
+
state.blockConcurrencyWhile(() => Effect.runPromiseWith(context)(effect)),
|
|
120
|
+
),
|
|
94
121
|
),
|
|
95
122
|
),
|
|
123
|
+
acceptWebSocket: (ws: DurableWebSocket, tags?: Array<string>) =>
|
|
124
|
+
Effect.sync(() => state.acceptWebSocket(ws.raw, tags)),
|
|
125
|
+
getWebSockets: (tag?: string) =>
|
|
126
|
+
Effect.sync(() => state.getWebSockets(tag).map((socket) => fromWebSocket(socket))),
|
|
127
|
+
setWebSocketAutoResponse: (pair?: WebSocketRequestResponsePair) =>
|
|
128
|
+
Effect.sync(() => state.setWebSocketAutoResponse(pair)),
|
|
129
|
+
getWebSocketAutoResponse: Effect.sync(() => state.getWebSocketAutoResponse()),
|
|
130
|
+
getWebSocketAutoResponseTimestamp: (ws: DurableWebSocket) =>
|
|
131
|
+
Effect.sync(() => state.getWebSocketAutoResponseTimestamp(ws.raw)),
|
|
132
|
+
setHibernatableWebSocketEventTimeout: (timeoutMs?: number) =>
|
|
133
|
+
Effect.sync(() => state.setHibernatableWebSocketEventTimeout(timeoutMs)),
|
|
134
|
+
getHibernatableWebSocketEventTimeout: Effect.sync(() =>
|
|
135
|
+
state.getHibernatableWebSocketEventTimeout(),
|
|
96
136
|
),
|
|
97
|
-
|
|
98
|
-
Effect.sync(() => state.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
setWebSocketAutoResponse: (pair?: WebSocketRequestResponsePair) =>
|
|
102
|
-
Effect.sync(() => state.setWebSocketAutoResponse(pair)),
|
|
103
|
-
getWebSocketAutoResponse: Effect.sync(() => state.getWebSocketAutoResponse()),
|
|
104
|
-
getWebSocketAutoResponseTimestamp: (ws: DurableWebSocket) =>
|
|
105
|
-
Effect.sync(() => state.getWebSocketAutoResponseTimestamp(ws.raw)),
|
|
106
|
-
setHibernatableWebSocketEventTimeout: (timeoutMs?: number) =>
|
|
107
|
-
Effect.sync(() => state.setHibernatableWebSocketEventTimeout(timeoutMs)),
|
|
108
|
-
getHibernatableWebSocketEventTimeout: Effect.sync(() =>
|
|
109
|
-
state.getHibernatableWebSocketEventTimeout(),
|
|
110
|
-
),
|
|
111
|
-
getTags: (ws: DurableWebSocket) => Effect.sync(() => state.getTags(ws.raw)),
|
|
112
|
-
abort: (reason?: string) => Effect.sync(() => state.abort(reason)),
|
|
113
|
-
});
|
|
137
|
+
getTags: (ws: DurableWebSocket) => Effect.sync(() => state.getTags(ws.raw)),
|
|
138
|
+
abort: (reason?: string) => Effect.sync(() => state.abort(reason)),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
114
141
|
|
|
115
142
|
const runPromiseExitPreservingTypedFailures = async <A, E, R>(
|
|
116
143
|
context: Context.Context<R>,
|
package/src/WorkflowBinding.ts
CHANGED
|
@@ -177,8 +177,12 @@ export const makeClient = <
|
|
|
177
177
|
status: operation("status", () => raw.status()).pipe(
|
|
178
178
|
Effect.flatMap((status) =>
|
|
179
179
|
Effect.gen(function* () {
|
|
180
|
+
// Cloudflare reports `output: null` as a no-output sentinel while an
|
|
181
|
+
// instance is not complete; only a complete instance may carry a real
|
|
182
|
+
// null result (e.g. Schema.Null).
|
|
180
183
|
const output =
|
|
181
|
-
status.output === undefined
|
|
184
|
+
status.output === undefined ||
|
|
185
|
+
(status.output === null && status.status !== "complete")
|
|
182
186
|
? Option.none<ResultValue>()
|
|
183
187
|
: Option.some(
|
|
184
188
|
yield* decodeResult(status.output).pipe(
|
|
@@ -13,16 +13,14 @@ const causeError = <E>(cause: Cause.Cause<E>) => {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const failureHandler = <E, R>(
|
|
16
|
+
label: string,
|
|
16
17
|
cause: Cause.Cause<E>,
|
|
17
18
|
options: WorkerContextWaitUntilOptions<E, R> | undefined,
|
|
18
19
|
) =>
|
|
19
|
-
(
|
|
20
|
-
options?.onFailure?.(cause) ??
|
|
21
|
-
Effect.logError("WorkerContext.waitUntil failed", Cause.pretty(cause))
|
|
22
|
-
).pipe(
|
|
20
|
+
(options?.onFailure?.(cause) ?? Effect.logError(`${label} failed`, Cause.pretty(cause))).pipe(
|
|
23
21
|
Effect.catchCause((handlerCause) =>
|
|
24
22
|
Effect.logError(
|
|
25
|
-
|
|
23
|
+
`${label} failure handler failed`,
|
|
26
24
|
Cause.pretty(cause),
|
|
27
25
|
Cause.pretty(handlerCause),
|
|
28
26
|
),
|
|
@@ -30,18 +28,19 @@ const failureHandler = <E, R>(
|
|
|
30
28
|
);
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
|
-
* Builds
|
|
31
|
+
* Builds a `waitUntil` scheduler for any Cloudflare lifecycle that accepts
|
|
32
|
+
* background promises (`ExecutionContext`, `DurableObjectState`).
|
|
34
33
|
*
|
|
35
34
|
* Background effects capture the calling fiber's context via `Effect.context`
|
|
36
|
-
* before being
|
|
37
|
-
*
|
|
38
|
-
* runtime-bound runner.
|
|
35
|
+
* before being registered, so the default module-level `Effect.runPromiseExit`
|
|
36
|
+
* runner is sufficient; entrypoints may still pass a runtime-bound runner.
|
|
39
37
|
*/
|
|
40
|
-
export const
|
|
41
|
-
|
|
38
|
+
export const makeWaitUntilScheduler = (
|
|
39
|
+
label: string,
|
|
40
|
+
register: (promise: Promise<unknown>) => void,
|
|
42
41
|
runPromiseExit: RunWaitUntilEffect = (effect) => Effect.runPromiseExit(effect),
|
|
43
|
-
)
|
|
44
|
-
|
|
42
|
+
) => {
|
|
43
|
+
return <A, E, R, R2 = never>(
|
|
45
44
|
effect: Effect.Effect<A, E, R>,
|
|
46
45
|
options: WorkerContextWaitUntilOptions<E, R2> | undefined,
|
|
47
46
|
mode: "observe" | "propagate",
|
|
@@ -51,17 +50,14 @@ export const fromExecutionContext = (
|
|
|
51
50
|
Effect.sync(() => {
|
|
52
51
|
const runHandler = (cause: Cause.Cause<E>) =>
|
|
53
52
|
runPromiseExit(
|
|
54
|
-
Effect.scoped(Effect.provideContext(failureHandler(cause, options), context)),
|
|
53
|
+
Effect.scoped(Effect.provideContext(failureHandler(label, cause, options), context)),
|
|
55
54
|
).then((exit) => {
|
|
56
55
|
if (Exit.isFailure(exit)) {
|
|
57
|
-
console.error(
|
|
58
|
-
"WorkerContext.waitUntil failure handler failed",
|
|
59
|
-
Cause.pretty(exit.cause),
|
|
60
|
-
);
|
|
56
|
+
console.error(`${label} failure handler failed`, Cause.pretty(exit.cause));
|
|
61
57
|
}
|
|
62
58
|
});
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
register(
|
|
65
61
|
runPromiseExit(Effect.scoped(Effect.provideContext(effect, context))).then(
|
|
66
62
|
async (exit) => {
|
|
67
63
|
if (Exit.isSuccess(exit)) {
|
|
@@ -79,6 +75,20 @@ export const fromExecutionContext = (
|
|
|
79
75
|
}),
|
|
80
76
|
),
|
|
81
77
|
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Builds the `WorkerContext` service from a native `ExecutionContext`.
|
|
82
|
+
*/
|
|
83
|
+
export const fromExecutionContext = (
|
|
84
|
+
ctx: globalThis.ExecutionContext,
|
|
85
|
+
runPromiseExit: RunWaitUntilEffect = (effect) => Effect.runPromiseExit(effect),
|
|
86
|
+
): WorkerContextService => {
|
|
87
|
+
const schedule = makeWaitUntilScheduler(
|
|
88
|
+
"WorkerContext.waitUntil",
|
|
89
|
+
(promise) => ctx.waitUntil(promise),
|
|
90
|
+
runPromiseExit,
|
|
91
|
+
);
|
|
82
92
|
|
|
83
93
|
return {
|
|
84
94
|
raw: ctx,
|