effect-cf 0.20.1 → 0.22.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 (53) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
  4. package/dist/HyperdrivePg.d.mts +13 -5
  5. package/dist/HyperdrivePg.mjs +12 -7
  6. package/dist/index.d.mts +708 -549
  7. package/dist/index.mjs +924 -587
  8. package/package.json +10 -5
  9. package/src/AiGateway.ts +82 -47
  10. package/src/AnalyticsEngine.ts +74 -58
  11. package/src/Binding.ts +66 -13
  12. package/src/BrowserRendering.ts +30 -7
  13. package/src/Cache.ts +32 -8
  14. package/src/CloudflareOtlp.ts +32 -9
  15. package/src/ContainerNamespace.ts +87 -55
  16. package/src/D1.ts +13 -3
  17. package/src/DurableObject.ts +27 -164
  18. package/src/DurableObjectAlarm.ts +30 -9
  19. package/src/DurableObjectDefinition.ts +58 -54
  20. package/src/DurableObjectNamespace.ts +99 -96
  21. package/src/DurableObjectRpcWebSocket.ts +14 -6
  22. package/src/DurableObjectSqlite.ts +6 -3
  23. package/src/DurableObjectState.ts +6 -9
  24. package/src/DurableObjectStorage.ts +7 -1
  25. package/src/DurableObjectWebSocket.ts +51 -41
  26. package/src/Email.ts +40 -13
  27. package/src/Environment.ts +26 -6
  28. package/src/Hyperdrive.ts +2 -2
  29. package/src/HyperdrivePg.ts +25 -13
  30. package/src/Images.ts +77 -82
  31. package/src/Kv.ts +37 -27
  32. package/src/Queue.ts +6 -1
  33. package/src/QueueBinding.ts +18 -21
  34. package/src/QueueDefinition.ts +15 -14
  35. package/src/R2.ts +107 -29
  36. package/src/Rpc.ts +3 -1
  37. package/src/RpcDefinition.ts +177 -133
  38. package/src/ServiceBinding.ts +112 -108
  39. package/src/Vectorize.ts +59 -14
  40. package/src/Worker.ts +172 -192
  41. package/src/WorkerDefinition.ts +5 -10
  42. package/src/WorkersAi.ts +27 -17
  43. package/src/Workflow.ts +54 -49
  44. package/src/WorkflowBinding.ts +19 -11
  45. package/src/WorkflowDefinition.ts +24 -27
  46. package/src/index.ts +4 -3
  47. package/src/internal/Entrypoint.ts +3 -1
  48. package/src/internal/ErrorMessage.ts +24 -0
  49. package/src/internal/RpcInvocation.ts +11 -8
  50. package/src/internal/Runtime.ts +52 -0
  51. package/src/internal/WorkerContext.ts +14 -2
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
package/src/Entry.ts DELETED
@@ -1,259 +0,0 @@
1
- import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
2
- import { Cause, Context, Effect, Exit, Layer, ManagedRuntime, type Scope } from "effect";
3
- import { HttpServerRequest } from "effect/unstable/http";
4
-
5
- import { WorkerEnvironment, type WorkerEnv } from "./Environment";
6
-
7
- /**
8
- * Access to Cloudflare's native `ExecutionContext`.
9
- */
10
- export class ExecutionContext extends Context.Service<
11
- ExecutionContext,
12
- globalThis.ExecutionContext
13
- >()("effect-cf/ExecutionContext") {}
14
-
15
- /**
16
- * Options for {@link WorkerContextService.waitUntil}.
17
- */
18
- export interface WorkerContextWaitUntilOptions<E, R> {
19
- /** Custom failure handler for the background effect. */
20
- readonly onFailure?: (cause: Cause.Cause<E>) => Effect.Effect<void, never, R>;
21
- }
22
-
23
- /**
24
- * Effect wrapper around `ExecutionContext` background APIs.
25
- */
26
- export interface WorkerContextService {
27
- readonly raw: globalThis.ExecutionContext;
28
- waitUntil<A, E, R, R2 = never>(
29
- effect: Effect.Effect<A, E, R>,
30
- options?: WorkerContextWaitUntilOptions<E, R2>,
31
- ): Effect.Effect<void, never, R | R2>;
32
- readonly passThroughOnException: Effect.Effect<void>;
33
- }
34
-
35
- /**
36
- * Service used inside handlers to schedule background work via `waitUntil`.
37
- *
38
- * @example
39
- * ```ts
40
- * const handler = Effect.gen(function* () {
41
- * const ctx = yield* WorkerContext;
42
- * yield* ctx.waitUntil(Effect.log("flush analytics"));
43
- * return new Response("ok");
44
- * });
45
- * ```
46
- */
47
- export class WorkerContext extends Context.Service<WorkerContext, WorkerContextService>()(
48
- "effect-cf/WorkerContext",
49
- ) {}
50
-
51
- type RunWaitUntilEffect = <A, E>(
52
- effect: Effect.Effect<A, E, never>,
53
- ) => Promise<Exit.Exit<A, unknown>>;
54
-
55
- const fromExecutionContext = (
56
- ctx: globalThis.ExecutionContext,
57
- runPromiseExit: RunWaitUntilEffect,
58
- ): WorkerContextService => ({
59
- raw: ctx,
60
- waitUntil: <A, E, R, R2 = never>(
61
- effect: Effect.Effect<A, E, R>,
62
- options?: WorkerContextWaitUntilOptions<E, R2>,
63
- ) =>
64
- Effect.context<R | R2>().pipe(
65
- Effect.flatMap((context) =>
66
- Effect.sync(() => {
67
- const observed = Effect.exit(effect).pipe(
68
- Effect.flatMap((exit) => {
69
- if (Exit.isSuccess(exit)) {
70
- return Effect.void;
71
- }
72
-
73
- const handleFailure =
74
- options?.onFailure?.(exit.cause) ??
75
- Effect.logError("WorkerContext.waitUntil failed", Cause.pretty(exit.cause));
76
-
77
- return handleFailure.pipe(
78
- Effect.catchCause((handlerCause) =>
79
- Effect.logError(
80
- "WorkerContext.waitUntil failure handler failed",
81
- Cause.pretty(exit.cause),
82
- Cause.pretty(handlerCause),
83
- ),
84
- ),
85
- );
86
- }),
87
- );
88
-
89
- ctx.waitUntil(
90
- runPromiseExit(Effect.scoped(Effect.provideContext(observed, context))).then((exit) => {
91
- if (Exit.isFailure(exit)) {
92
- console.error(
93
- "WorkerContext.waitUntil failure handler failed",
94
- Cause.pretty(exit.cause),
95
- );
96
- }
97
- }),
98
- );
99
- }),
100
- ),
101
- ),
102
- passThroughOnException: Effect.sync(() => ctx.passThroughOnException()),
103
- });
104
-
105
- /**
106
- * Access to the incoming `Request` currently handled by a worker or Durable Object fetch.
107
- */
108
- export class NativeRequest extends Context.Service<NativeRequest, Request>()(
109
- "effect-cf/NativeRequest",
110
- ) {}
111
-
112
- /**
113
- * Returns `true` when the request is a websocket upgrade request.
114
- */
115
- export const isWebSocketUpgrade = (request: Request): boolean =>
116
- request.headers.get("Upgrade")?.toLowerCase() === "websocket";
117
-
118
- type RequestServices = ExecutionContext | WorkerContext | WorkerEnvironment;
119
-
120
- type HandlerContext<ROut> =
121
- | ExecutionContext
122
- | WorkerContext
123
- | WorkerEnvironment
124
- | NativeRequest
125
- | HttpServerRequest.HttpServerRequest
126
- | ROut
127
- | Scope.Scope;
128
-
129
- type RuntimeContext<ROut> = RequestServices | ROut;
130
- type RpcContext<ROut> = RuntimeContext<ROut> | Scope.Scope;
131
-
132
- const RunSymbol = Symbol.for("effect-cf/Worker/run");
133
-
134
- /**
135
- * Effect type for `fetch` handlers executed by {@link make}.
136
- */
137
- export type WorkerHandler<ROut, A = unknown> = Effect.Effect<A, unknown, HandlerContext<ROut>>;
138
-
139
- /**
140
- * Effect type for worker RPC handlers.
141
- */
142
- export type WorkerRpcHandler<ROut, A = unknown> = Effect.Effect<A, unknown, RpcContext<ROut>>;
143
-
144
- /**
145
- * Shape of worker RPC handlers passed to {@link make}.
146
- */
147
- export type WorkerRpc<ROut> = Record<string, (...args: Array<any>) => WorkerRpcHandler<ROut>>;
148
-
149
- export type WorkerRpcShape<Rpc extends WorkerRpc<ROut>, ROut> = {
150
- readonly [Key in keyof Rpc]: Rpc[Key] extends (
151
- ...args: infer Args
152
- ) => Effect.Effect<infer A, unknown, RpcContext<ROut>>
153
- ? (...args: Args) => Promise<A>
154
- : never;
155
- };
156
-
157
- export type RpcHandlers<ROut, Api> = {
158
- readonly [Key in keyof Api as Key extends keyof CloudflareWorkerEntrypoint
159
- ? never
160
- : Key extends string
161
- ? [Api[Key]] extends [never]
162
- ? never
163
- : Api[Key] extends (...args: Array<any>) => Promise<unknown>
164
- ? Key
165
- : never
166
- : never]: Api[Key] extends (...args: infer Args) => Promise<infer A>
167
- ? (...args: Args) => WorkerRpcHandler<ROut, A>
168
- : never;
169
- };
170
-
171
- /**
172
- * Options for creating a worker class with Effect handlers.
173
- */
174
- export interface WorkerOptions<ROut, Rpc extends WorkerRpc<ROut>> {
175
- /** Main request handler. */
176
- readonly fetch: Effect.Effect<Response, unknown, HandlerContext<ROut>>;
177
- /** Optional RPC methods exposed as class instance methods. */
178
- readonly rpc?: Rpc;
179
- }
180
-
181
- /**
182
- * Cloudflare `WorkerEntrypoint` constructor produced by {@link make}.
183
- */
184
- export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
185
- ctx: globalThis.ExecutionContext,
186
- env: WorkerEnv,
187
- ) => CloudflareWorkerEntrypoint<WorkerEnv> & WorkerRpcShape<Rpc, ROut>;
188
-
189
- /**
190
- * Creates a Cloudflare worker class backed by a single managed Effect runtime.
191
- *
192
- * @example
193
- * ```ts
194
- * const Worker = Entry.make(Layer.empty, {
195
- * fetch: Effect.succeed(new Response("ok")),
196
- * });
197
- * ```
198
- */
199
- export const make = <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>>(
200
- layer: Layer.Layer<ROut, LayerError, RequestServices>,
201
- options: WorkerOptions<ROut, Rpc>,
202
- ): WorkerClass<Rpc, ROut> => {
203
- class EffectWorker extends CloudflareWorkerEntrypoint<WorkerEnv> {
204
- readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut>, LayerError>;
205
-
206
- constructor(ctx: globalThis.ExecutionContext, env: WorkerEnv) {
207
- super(ctx, env);
208
-
209
- let runWaitUntilEffect: RunWaitUntilEffect = () =>
210
- Promise.resolve(Exit.die(new Error("WorkerContext runtime is not initialized")));
211
-
212
- const services = Layer.mergeAll(
213
- Layer.succeed(ExecutionContext, ctx),
214
- Layer.succeed(
215
- WorkerContext,
216
- fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect)),
217
- ),
218
- Layer.succeed(WorkerEnvironment, env),
219
- );
220
-
221
- const runtimeLayer = layer.pipe(Layer.provideMerge(services)) as Layer.Layer<
222
- RuntimeContext<ROut>,
223
- LayerError,
224
- never
225
- >;
226
-
227
- this.runtime = ManagedRuntime.make(runtimeLayer);
228
- runWaitUntilEffect = <A, E>(effect: Effect.Effect<A, E, never>) =>
229
- this.runtime.runPromiseExit(effect as Effect.Effect<A, E, RuntimeContext<ROut>>);
230
- }
231
-
232
- [RunSymbol]<A, E>(effect: Effect.Effect<A, E, RuntimeContext<ROut> | Scope.Scope>): Promise<A> {
233
- return this.runtime.runPromise(Effect.scoped(effect));
234
- }
235
-
236
- fetch(request: Request): Promise<Response> {
237
- return this[RunSymbol](
238
- options.fetch.pipe(
239
- Effect.provideService(NativeRequest, request),
240
- Effect.provideService(
241
- HttpServerRequest.HttpServerRequest,
242
- HttpServerRequest.fromWeb(request),
243
- ),
244
- ),
245
- );
246
- }
247
- }
248
-
249
- for (const [key, method] of Object.entries(options.rpc ?? {})) {
250
- Object.defineProperty(EffectWorker.prototype, key, {
251
- enumerable: true,
252
- value(this: EffectWorker, ...args: Array<any>) {
253
- return this[RunSymbol](Effect.suspend(() => method(...args)));
254
- },
255
- });
256
- }
257
-
258
- return EffectWorker as WorkerClass<Rpc, ROut>;
259
- };
@@ -1,37 +0,0 @@
1
- import { Clock, Duration, Effect, Layer } from "effect";
2
-
3
- const maxTimerMillis = 2 ** 31 - 1;
4
- const nanosPerMilli = BigInt(1_000_000);
5
-
6
- export const clock: Clock.Clock = {
7
- currentTimeMillisUnsafe: () => Date.now(),
8
- currentTimeMillis: Effect.sync(() => clock.currentTimeMillisUnsafe()),
9
- currentTimeNanosUnsafe: () => BigInt(Date.now()) * nanosPerMilli,
10
- currentTimeNanos: Effect.sync(() => clock.currentTimeNanosUnsafe()),
11
- // Workers expose `performance.now()` as time since worker start. It advances
12
- // only across I/O, like `Date.now()`, but it never moves backward.
13
- monotonicTimeNanosUnsafe: () =>
14
- typeof performance === "undefined"
15
- ? BigInt(Date.now()) * nanosPerMilli
16
- : BigInt(Math.round(performance.now() * 1_000_000)),
17
- monotonicTimeNanos: Effect.sync(() => clock.monotonicTimeNanosUnsafe()),
18
- sleep: (duration) => {
19
- const millis = Duration.toMillis(duration);
20
-
21
- if (millis <= 0) {
22
- return Effect.yieldNow;
23
- }
24
-
25
- return Effect.callback<void>((resume) => {
26
- if (millis > maxTimerMillis) {
27
- return;
28
- }
29
-
30
- const handle = setTimeout(() => resume(Effect.void), millis);
31
-
32
- return Effect.sync(() => clearTimeout(handle));
33
- });
34
- },
35
- };
36
-
37
- export const layer = Layer.effect(Clock.Clock, Effect.succeed(clock));