effect-cf 0.20.0 → 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.
- package/CHANGELOG.md +56 -0
- package/README.md +2 -2
- package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
- package/dist/HyperdrivePg.d.mts +13 -5
- package/dist/HyperdrivePg.mjs +12 -7
- package/dist/index.d.mts +708 -549
- package/dist/index.mjs +946 -589
- package/package.json +10 -5
- package/src/AiGateway.ts +82 -47
- package/src/AnalyticsEngine.ts +74 -58
- package/src/Binding.ts +66 -13
- package/src/BrowserRendering.ts +30 -7
- package/src/Cache.ts +32 -8
- package/src/CloudflareOtlp.ts +32 -9
- package/src/ContainerNamespace.ts +87 -55
- package/src/D1.ts +13 -3
- package/src/DurableObject.ts +27 -164
- package/src/DurableObjectAlarm.ts +30 -9
- package/src/DurableObjectDefinition.ts +58 -54
- package/src/DurableObjectNamespace.ts +99 -96
- package/src/DurableObjectRpcWebSocket.ts +14 -6
- package/src/DurableObjectSqlite.ts +6 -3
- package/src/DurableObjectState.ts +6 -9
- package/src/DurableObjectStorage.ts +7 -1
- package/src/DurableObjectWebSocket.ts +51 -41
- package/src/Email.ts +40 -13
- package/src/Environment.ts +26 -6
- package/src/Hyperdrive.ts +2 -2
- package/src/HyperdrivePg.ts +25 -13
- package/src/Images.ts +77 -82
- package/src/Kv.ts +37 -27
- package/src/Queue.ts +6 -1
- package/src/QueueBinding.ts +18 -21
- package/src/QueueDefinition.ts +15 -14
- package/src/R2.ts +107 -29
- package/src/Rpc.ts +3 -1
- package/src/RpcDefinition.ts +177 -133
- package/src/ServiceBinding.ts +112 -108
- package/src/Vectorize.ts +59 -14
- package/src/Worker.ts +205 -198
- package/src/WorkerDefinition.ts +5 -10
- package/src/WorkersAi.ts +27 -17
- package/src/Workflow.ts +54 -49
- package/src/WorkflowBinding.ts +19 -11
- package/src/WorkflowDefinition.ts +24 -27
- package/src/index.ts +4 -3
- package/src/internal/Entrypoint.ts +3 -1
- package/src/internal/ErrorMessage.ts +24 -0
- package/src/internal/RpcInvocation.ts +11 -8
- package/src/internal/Runtime.ts +52 -0
- package/src/internal/WorkerContext.ts +14 -2
- package/src/Entry.ts +0 -259
- package/src/internal/Clock.ts +0 -37
package/src/Worker.ts
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
|
|
2
2
|
import {
|
|
3
3
|
type Cause,
|
|
4
|
-
ConfigProvider,
|
|
5
4
|
Context,
|
|
6
5
|
Effect,
|
|
7
6
|
Layer,
|
|
8
|
-
ManagedRuntime,
|
|
9
|
-
|
|
7
|
+
type ManagedRuntime,
|
|
8
|
+
Option,
|
|
10
9
|
type Scope,
|
|
11
10
|
} from "effect";
|
|
12
|
-
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
11
|
+
import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
12
|
+
import { OtlpExporter } from "effect/unstable/observability";
|
|
13
13
|
|
|
14
|
-
import type
|
|
15
|
-
import { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
14
|
+
import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
16
15
|
import { fromMessage, fromMessageBatch, type QueueHandler } from "./Queue";
|
|
17
|
-
import type * as Rpc from "./Rpc";
|
|
18
16
|
import type * as RpcDefinition from "./RpcDefinition";
|
|
19
|
-
import type * as ServiceBinding from "./ServiceBinding";
|
|
20
|
-
import * as WorkerDefinition from "./WorkerDefinition";
|
|
21
|
-
import * as CloudflareClock from "./internal/Clock";
|
|
22
17
|
import * as Entrypoint from "./internal/Entrypoint";
|
|
23
|
-
import
|
|
18
|
+
import * as Runtime from "./internal/Runtime";
|
|
19
|
+
import { fromExecutionContext } from "./internal/WorkerContext";
|
|
24
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Access to Cloudflare's native `ExecutionContext`.
|
|
23
|
+
*/
|
|
25
24
|
export class ExecutionContext extends Context.Service<
|
|
26
25
|
ExecutionContext,
|
|
27
26
|
globalThis.ExecutionContext
|
|
28
27
|
>()("effect-cf/ExecutionContext") {}
|
|
29
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Options for {@link WorkerContextService.waitUntil}.
|
|
31
|
+
*/
|
|
30
32
|
export interface WorkerContextWaitUntilOptions<E, R> {
|
|
33
|
+
/**
|
|
34
|
+
* Failure mode for the background effect: `"observe"` (default) logs or
|
|
35
|
+
* routes the failure to `onFailure`, while `"propagate"` also rejects the
|
|
36
|
+
* native `waitUntil` promise.
|
|
37
|
+
*/
|
|
31
38
|
readonly mode?: "observe" | "propagate";
|
|
39
|
+
/** Custom failure handler for the background effect. */
|
|
32
40
|
readonly onFailure?: (cause: Cause.Cause<E>) => Effect.Effect<void, never, R>;
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Effect wrapper around `ExecutionContext` background APIs.
|
|
45
|
+
*/
|
|
35
46
|
export interface WorkerContextService {
|
|
36
47
|
readonly raw: globalThis.ExecutionContext;
|
|
37
48
|
waitUntil<A, E, R, R2 = never>(
|
|
@@ -45,14 +56,32 @@ export interface WorkerContextService {
|
|
|
45
56
|
readonly passThroughOnException: Effect.Effect<void>;
|
|
46
57
|
}
|
|
47
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Service used inside handlers to schedule background work via `waitUntil`.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const handler = Effect.gen(function* () {
|
|
65
|
+
* const ctx = yield* Worker.WorkerContext;
|
|
66
|
+
* yield* ctx.waitUntil(Effect.log("flush analytics"));
|
|
67
|
+
* return new Response("ok");
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
48
71
|
export class WorkerContext extends Context.Service<WorkerContext, WorkerContextService>()(
|
|
49
72
|
"effect-cf/WorkerContext",
|
|
50
73
|
) {}
|
|
51
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Access to the incoming `Request` currently handled by a worker or Durable Object fetch.
|
|
77
|
+
*/
|
|
52
78
|
export class NativeRequest extends Context.Service<NativeRequest, Request>()(
|
|
53
79
|
"effect-cf/NativeRequest",
|
|
54
80
|
) {}
|
|
55
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Returns `true` when the request is a websocket upgrade request.
|
|
84
|
+
*/
|
|
56
85
|
export const isWebSocketUpgrade = (request: Request): boolean =>
|
|
57
86
|
request.headers.get("Upgrade")?.toLowerCase() === "websocket";
|
|
58
87
|
|
|
@@ -102,17 +131,32 @@ type RunOptions = {
|
|
|
102
131
|
};
|
|
103
132
|
|
|
104
133
|
const RunSymbol = Symbol.for("effect-cf/Worker/run");
|
|
134
|
+
const FetchSymbol = Symbol.for("effect-cf/Worker/fetch");
|
|
105
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Successful result of a worker fetch handler: either a native `Response`
|
|
138
|
+
* (returned to Cloudflare untouched) or an Effect `HttpServerResponse`
|
|
139
|
+
* (rendered through the Effect HTTP pipeline).
|
|
140
|
+
*/
|
|
106
141
|
export type WorkerFetchSuccess = Response | HttpServerResponse.HttpServerResponse;
|
|
107
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Effect type for `fetch` handlers executed by {@link make}.
|
|
145
|
+
*/
|
|
108
146
|
export type WorkerHandler<ROut, A = WorkerFetchSuccess> = Effect.Effect<
|
|
109
147
|
A,
|
|
110
148
|
unknown,
|
|
111
149
|
WorkerFetchContext<ROut>
|
|
112
150
|
>;
|
|
113
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Effect type for worker RPC handlers.
|
|
154
|
+
*/
|
|
114
155
|
export type WorkerRpcHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerRpcContext<ROut>>;
|
|
115
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Shape of worker RPC handlers passed to {@link make}.
|
|
159
|
+
*/
|
|
116
160
|
export type WorkerRpc<ROut> = Record<string, (...args: Array<any>) => WorkerRpcHandler<ROut>>;
|
|
117
161
|
|
|
118
162
|
export type WorkerRpcShape<Rpc extends WorkerRpc<ROut>, ROut> = {
|
|
@@ -139,6 +183,9 @@ export type RpcHandlers<ROut, Api> = {
|
|
|
139
183
|
: never;
|
|
140
184
|
};
|
|
141
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Options for creating a worker class with Effect handlers.
|
|
188
|
+
*/
|
|
142
189
|
export interface WorkerOptions<
|
|
143
190
|
RRuntime,
|
|
144
191
|
REvent = never,
|
|
@@ -153,12 +200,15 @@ export interface WorkerOptions<
|
|
|
153
200
|
* trace/log exporters that should flush at event completion.
|
|
154
201
|
*/
|
|
155
202
|
readonly eventLayer?: Layer.Layer<REvent, EventLayerError, WorkerBaseContext<RRuntime>>;
|
|
203
|
+
/** Main request handler. */
|
|
156
204
|
readonly fetch?: Effect.Effect<
|
|
157
205
|
WorkerFetchSuccess,
|
|
158
206
|
unknown,
|
|
159
207
|
WorkerFetchContext<RRuntime | REvent>
|
|
160
208
|
>;
|
|
209
|
+
/** Queue consumer handler invoked per message batch. */
|
|
161
210
|
readonly queue?: QueueHandler<RRuntime | REvent>;
|
|
211
|
+
/** Optional RPC methods exposed as class instance methods. */
|
|
162
212
|
readonly rpc?: Rpc;
|
|
163
213
|
}
|
|
164
214
|
|
|
@@ -169,6 +219,9 @@ export type FetchWorkerOptions<RRuntime, REvent = never, EventLayerError = never
|
|
|
169
219
|
readonly rpc?: never;
|
|
170
220
|
};
|
|
171
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Cloudflare `WorkerEntrypoint` constructor produced by {@link make}.
|
|
224
|
+
*/
|
|
172
225
|
export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
|
|
173
226
|
ctx: globalThis.ExecutionContext,
|
|
174
227
|
env: WorkerEnv,
|
|
@@ -177,6 +230,9 @@ export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
|
|
|
177
230
|
queue(batch: globalThis.MessageBatch): Promise<void>;
|
|
178
231
|
} & WorkerRpcShape<Rpc, ROut>;
|
|
179
232
|
|
|
233
|
+
/**
|
|
234
|
+
* `ExportedHandler`-compatible fetch object produced by {@link makeFetchHandler}.
|
|
235
|
+
*/
|
|
180
236
|
export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
|
|
181
237
|
readonly fetch: (
|
|
182
238
|
request: Request,
|
|
@@ -185,33 +241,81 @@ export interface FetchHandler<Env extends WorkerEnv = WorkerEnv> {
|
|
|
185
241
|
) => Promise<Response>;
|
|
186
242
|
}
|
|
187
243
|
|
|
188
|
-
export const renderHttpResponse = <A extends HttpServerResponse.HttpServerResponse, E, R>(
|
|
189
|
-
effect: Effect.Effect<A, E, R>,
|
|
190
|
-
): Effect.Effect<Response, E, R> =>
|
|
191
|
-
Effect.flatMap(effect, (response) =>
|
|
192
|
-
Effect.map(Effect.context<never>(), (context) =>
|
|
193
|
-
HttpServerResponse.toWeb(response, { context }),
|
|
194
|
-
),
|
|
195
|
-
);
|
|
196
|
-
|
|
197
244
|
const renderFetchSuccess = <E, R>(
|
|
198
245
|
effect: Effect.Effect<WorkerFetchSuccess, E, R>,
|
|
199
|
-
): Effect.Effect<Response, E, R> =>
|
|
200
|
-
Effect.
|
|
201
|
-
response
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
246
|
+
): Effect.Effect<Response, E, Exclude<R, Scope.Scope> | HttpServerRequest.HttpServerRequest> =>
|
|
247
|
+
Effect.suspend(() => {
|
|
248
|
+
// Native `Response` values bypass all HTTP response processing: WebSocket
|
|
249
|
+
// upgrade responses and app-level `HttpEffect.toHandled` results pass
|
|
250
|
+
// through untouched. Pre-response handlers still run against a placeholder
|
|
251
|
+
// response, but their output is discarded for native `Response` results.
|
|
252
|
+
let nativeResponse: Response | undefined;
|
|
253
|
+
let webResponse: Response | undefined;
|
|
254
|
+
|
|
255
|
+
const handled = HttpEffect.toHandled(
|
|
256
|
+
Effect.flatMap(effect, (response) => {
|
|
257
|
+
if (response instanceof Response) {
|
|
258
|
+
nativeResponse = response;
|
|
259
|
+
|
|
260
|
+
return Effect.succeed(HttpServerResponse.empty());
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Effect.succeed(response);
|
|
264
|
+
}),
|
|
265
|
+
(request, response) =>
|
|
266
|
+
nativeResponse !== undefined
|
|
267
|
+
? Effect.void
|
|
268
|
+
: Effect.map(Effect.context<never>(), (context) => {
|
|
269
|
+
webResponse = HttpServerResponse.toWeb(HttpEffect.scopeTransferToStream(response), {
|
|
270
|
+
withoutBody: request.method === "HEAD",
|
|
271
|
+
context,
|
|
272
|
+
});
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// `toHandled` re-fails with the original cause after rendering the error
|
|
277
|
+
// response, so both branches return the response captured above.
|
|
278
|
+
const captured = () => nativeResponse ?? webResponse ?? new Response(null, { status: 500 });
|
|
279
|
+
|
|
280
|
+
return Effect.matchCause(handled, { onFailure: captured, onSuccess: captured });
|
|
281
|
+
});
|
|
207
282
|
|
|
208
283
|
const isWorkerOptions = <ROut, REvent, EventLayerError, Rpc extends WorkerRpc<ROut | REvent>>(
|
|
209
284
|
options: WorkerOptions<ROut, REvent, EventLayerError, Rpc> | WorkerHandler<ROut>,
|
|
210
|
-
): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> =>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
285
|
+
): options is WorkerOptions<ROut, REvent, EventLayerError, Rpc> => !Effect.isEffect(options);
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Drains buffered OTLP telemetry once the fetch handler has produced its
|
|
289
|
+
* response.
|
|
290
|
+
*
|
|
291
|
+
* The flush is handed to `ctx.waitUntil` so it never delays the response
|
|
292
|
+
* (including streaming bodies still being consumed). It is a no-op when the
|
|
293
|
+
* context does not contain an `OtlpExporter.Flusher`.
|
|
294
|
+
*/
|
|
295
|
+
const scheduleTelemetryFlush: Effect.Effect<void, never, WorkerContext> = Effect.flatMap(
|
|
296
|
+
Effect.serviceOption(OtlpExporter.Flusher),
|
|
297
|
+
Option.match({
|
|
298
|
+
onNone: () => Effect.void,
|
|
299
|
+
onSome: (flusher) =>
|
|
300
|
+
Effect.flatMap(WorkerContext, (workerContext) => workerContext.waitUntil(flusher.flush)),
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Creates a Cloudflare worker class backed by a single managed Effect runtime.
|
|
306
|
+
*
|
|
307
|
+
* The runtime layer is built when Cloudflare constructs the class and reused
|
|
308
|
+
* for every event the instance handles. Layer finalizers do not run when the
|
|
309
|
+
* isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
|
|
310
|
+
* for resources that must be finalized per event.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* export default Worker.make(Layer.empty, {
|
|
315
|
+
* fetch: Effect.succeed(new Response("ok")),
|
|
316
|
+
* });
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
215
319
|
export function make<ROut, LayerError>(
|
|
216
320
|
layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
|
|
217
321
|
fetch: WorkerHandler<ROut>,
|
|
@@ -246,69 +350,58 @@ export function make<
|
|
|
246
350
|
constructor(ctx: globalThis.ExecutionContext, env: WorkerEnv) {
|
|
247
351
|
super(ctx, env);
|
|
248
352
|
|
|
249
|
-
|
|
250
|
-
Promise.reject(new Error("WorkerContext runtime is not initialized"));
|
|
251
|
-
|
|
252
|
-
const services = Layer.mergeAll(
|
|
253
|
-
CloudflareClock.layer,
|
|
254
|
-
Layer.succeed(ExecutionContext, ctx),
|
|
255
|
-
ConfigProvider.layer(Effect.succeed(WorkerConfig.providerFromEnv(env))),
|
|
256
|
-
Layer.succeed(
|
|
257
|
-
WorkerContext,
|
|
258
|
-
fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect)),
|
|
259
|
-
),
|
|
260
|
-
Layer.succeed(WorkerEnvironment, env),
|
|
261
|
-
) as Layer.Layer<ExecutionContext | WorkerContext | WorkerEnvironment, never, never>;
|
|
262
|
-
|
|
263
|
-
const runtimeLayer = Entrypoint.provideEntrypointServices<
|
|
353
|
+
this.runtime = Runtime.makeEntrypointRuntime<
|
|
264
354
|
ROut,
|
|
265
355
|
LayerError,
|
|
266
|
-
ExecutionContext | WorkerContext
|
|
267
|
-
>(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
356
|
+
ExecutionContext | WorkerContext
|
|
357
|
+
>(
|
|
358
|
+
layer,
|
|
359
|
+
env,
|
|
360
|
+
Layer.mergeAll(
|
|
361
|
+
Layer.succeed(ExecutionContext, ctx),
|
|
362
|
+
Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
|
|
363
|
+
),
|
|
364
|
+
);
|
|
272
365
|
}
|
|
273
366
|
|
|
274
367
|
[RunSymbol]<A, E>(
|
|
275
368
|
effect: Effect.Effect<A, E, RuntimeContext<ROut> | REvent | Scope.Scope>,
|
|
276
369
|
runOptions: RunOptions = {},
|
|
277
370
|
): Promise<A> {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return this.runtime.runPromise(
|
|
284
|
-
Effect.scoped(
|
|
285
|
-
effectWithEventLayer as Effect.Effect<
|
|
286
|
-
A,
|
|
287
|
-
E | EventLayerError,
|
|
288
|
-
RuntimeContext<ROut> | Scope.Scope
|
|
289
|
-
>,
|
|
290
|
-
),
|
|
371
|
+
return Runtime.runEventPromise(
|
|
372
|
+
this.runtime,
|
|
373
|
+
effect,
|
|
374
|
+
runOptions.eventLayer === false ? undefined : options.eventLayer,
|
|
291
375
|
);
|
|
292
376
|
}
|
|
293
377
|
|
|
294
378
|
fetch(request: Request): Promise<Response> {
|
|
379
|
+
return this[FetchSymbol](request);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
[FetchSymbol](
|
|
383
|
+
request: Request,
|
|
384
|
+
requestContext?: globalThis.ExecutionContext,
|
|
385
|
+
): Promise<Response> {
|
|
295
386
|
const fetchHandler = options.fetch;
|
|
296
387
|
|
|
297
388
|
if (fetchHandler === undefined) {
|
|
298
389
|
return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
299
390
|
}
|
|
300
391
|
|
|
392
|
+
const ctx = requestContext ?? this.ctx;
|
|
301
393
|
const requestServices = Layer.mergeAll(
|
|
302
394
|
Layer.succeed(NativeRequest, request),
|
|
303
395
|
Layer.succeed(HttpServerRequest.HttpServerRequest, HttpServerRequest.fromWeb(request)),
|
|
396
|
+
Layer.succeed(ExecutionContext, ctx),
|
|
397
|
+
Layer.succeed(WorkerContext, fromExecutionContext(ctx)),
|
|
304
398
|
);
|
|
305
399
|
|
|
306
400
|
return this[RunSymbol](
|
|
307
|
-
renderFetchSuccess(fetchHandler).pipe(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
>,
|
|
401
|
+
renderFetchSuccess(fetchHandler).pipe(
|
|
402
|
+
Effect.tap(() => scheduleTelemetryFlush),
|
|
403
|
+
Effect.provide(requestServices),
|
|
404
|
+
) as Effect.Effect<Response, unknown, RuntimeContext<ROut> | REvent | Scope.Scope>,
|
|
312
405
|
);
|
|
313
406
|
}
|
|
314
407
|
|
|
@@ -336,6 +429,21 @@ export function make<
|
|
|
336
429
|
return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut | REvent>>(EffectWorker);
|
|
337
430
|
}
|
|
338
431
|
|
|
432
|
+
interface FetchCapableInstance {
|
|
433
|
+
[FetchSymbol](request: Request, requestContext: globalThis.ExecutionContext): Promise<Response>;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Creates an `ExportedHandler`-compatible `{ fetch }` object backed by
|
|
438
|
+
* {@link make}.
|
|
439
|
+
*
|
|
440
|
+
* The Effect runtime is cached per `env` identity, so the user layer builds
|
|
441
|
+
* once per isolate instead of once per request. `ExecutionContext`-backed
|
|
442
|
+
* services (`Worker.ExecutionContext`, `Worker.WorkerContext`) are still
|
|
443
|
+
* provided fresh for every request. Layer finalizers do not run when the
|
|
444
|
+
* isolate is evicted; Cloudflare provides no shutdown hook. Use `eventLayer`
|
|
445
|
+
* for resources that must be finalized per event.
|
|
446
|
+
*/
|
|
339
447
|
export const makeFetchHandler = <
|
|
340
448
|
ROut,
|
|
341
449
|
LayerError,
|
|
@@ -347,136 +455,35 @@ export const makeFetchHandler = <
|
|
|
347
455
|
options: FetchWorkerOptions<ROut, REvent, EventLayerError>,
|
|
348
456
|
): FetchHandler<Env> => {
|
|
349
457
|
const WorkerClass = make(layer, options);
|
|
458
|
+
const instances = new WeakMap<WorkerEnv, FetchCapableInstance>();
|
|
350
459
|
|
|
351
460
|
return {
|
|
352
|
-
fetch: (request, env, ctx) =>
|
|
353
|
-
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
export type ServiceFreeSchema = S.Codec<any, any, never, never>;
|
|
357
|
-
|
|
358
|
-
export interface Method<
|
|
359
|
-
Args extends ReadonlyArray<ServiceFreeSchema> = ReadonlyArray<ServiceFreeSchema>,
|
|
360
|
-
Success extends ServiceFreeSchema = ServiceFreeSchema,
|
|
361
|
-
> {
|
|
362
|
-
readonly args: Args;
|
|
363
|
-
readonly success: Success;
|
|
364
|
-
}
|
|
461
|
+
fetch: (request, env, ctx) => {
|
|
462
|
+
let instance = instances.get(env);
|
|
365
463
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
? []
|
|
371
|
-
: Args extends readonly [
|
|
372
|
-
infer Head extends ServiceFreeSchema,
|
|
373
|
-
...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
|
|
374
|
-
]
|
|
375
|
-
? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
|
|
376
|
-
: Array<S.Schema.Type<Args[number]>>;
|
|
377
|
-
|
|
378
|
-
export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
|
|
379
|
-
|
|
380
|
-
export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
export type Methods = Record<string, Method.Any>;
|
|
384
|
-
|
|
385
|
-
export type NoReservedMethods<MethodsShape extends Methods> =
|
|
386
|
-
Extract<keyof MethodsShape, ReservedMethodName> extends never ? MethodsShape : never;
|
|
387
|
-
|
|
388
|
-
export interface Definition<Id extends string = string, MethodsShape extends Methods = Methods> {
|
|
389
|
-
readonly id: Id;
|
|
390
|
-
readonly methods: MethodsShape;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export namespace Definition {
|
|
394
|
-
export type Any = Definition<string, Methods>;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export type ServerApi<Self extends Definition.Any> = {
|
|
398
|
-
readonly [Key in keyof Self["methods"]]: (
|
|
399
|
-
...args: Method.Args<Self["methods"][Key]>
|
|
400
|
-
) => Promise<Method.Success<Self["methods"][Key]>>;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
export type Api<Self extends Definition.Any> = Rpc.Provider<ServerApi<Self>, ReservedMethodName>;
|
|
404
|
-
|
|
405
|
-
export type Handlers<ROut, Self extends Definition.Any> = {
|
|
406
|
-
readonly [Key in keyof Self["methods"]]: (
|
|
407
|
-
...args: Method.Args<Self["methods"][Key]>
|
|
408
|
-
) => WorkerRpcHandler<ROut, Method.Success<Self["methods"][Key]>>;
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
export interface Options<
|
|
412
|
-
ROut,
|
|
413
|
-
Self extends Definition.Any,
|
|
414
|
-
REvent = never,
|
|
415
|
-
EventLayerError = never,
|
|
416
|
-
> extends Omit<WorkerOptions<ROut, REvent, EventLayerError, Handlers<ROut | REvent, Self>>, "rpc"> {
|
|
417
|
-
readonly rpc: Handlers<ROut | REvent, Self>;
|
|
418
|
-
}
|
|
464
|
+
if (instance === undefined) {
|
|
465
|
+
instance = new WorkerClass(ctx, env) as unknown as FetchCapableInstance;
|
|
466
|
+
instances.set(env, instance);
|
|
467
|
+
}
|
|
419
468
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Context.ServiceClass<
|
|
423
|
-
Self,
|
|
424
|
-
Id,
|
|
425
|
-
ServiceBinding.ServiceBindingEffectClient<
|
|
426
|
-
Api<Definition<Id, MethodsShape>>,
|
|
427
|
-
Definition<Id, MethodsShape>
|
|
428
|
-
>
|
|
429
|
-
> &
|
|
430
|
-
ServiceBinding.ServiceBindingStaticClient<
|
|
431
|
-
Self,
|
|
432
|
-
Api<Definition<Id, MethodsShape>>,
|
|
433
|
-
Definition<Id, MethodsShape>
|
|
434
|
-
> & {
|
|
435
|
-
readonly id: Id;
|
|
436
|
-
readonly methods: MethodsShape;
|
|
437
|
-
readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
|
|
438
|
-
layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
|
|
439
|
-
options: Options<ROut, Definition<Id, MethodsShape>, REvent, EventLayerError>,
|
|
440
|
-
) => WorkerClass<Handlers<ROut | REvent, Definition<Id, MethodsShape>>, ROut | REvent>;
|
|
441
|
-
readonly layer: (
|
|
442
|
-
options: LayerOptions,
|
|
443
|
-
) => Layer.Layer<
|
|
444
|
-
Self,
|
|
445
|
-
Binding.BindingNotFoundError | Binding.BindingValidationError,
|
|
446
|
-
WorkerEnvironment
|
|
447
|
-
>;
|
|
469
|
+
return instance[FetchSymbol](request, ctx);
|
|
470
|
+
},
|
|
448
471
|
};
|
|
449
|
-
|
|
450
|
-
export type TagFactory = <Self>() => <Id extends string, const MethodsShape extends Methods>(
|
|
451
|
-
id: Id,
|
|
452
|
-
methods: MethodsShape & NoReservedMethods<MethodsShape>,
|
|
453
|
-
) => TagClass<Self, Id, MethodsShape>;
|
|
454
|
-
|
|
455
|
-
export const Tag = WorkerDefinition.Tag as unknown as TagFactory;
|
|
456
|
-
|
|
457
|
-
export const method = WorkerDefinition.method as {
|
|
458
|
-
<Success extends ServiceFreeSchema>(definition: {
|
|
459
|
-
readonly success: Success;
|
|
460
|
-
}): Method<readonly [], Success>;
|
|
461
|
-
<
|
|
462
|
-
const Args extends ReadonlyArray<ServiceFreeSchema>,
|
|
463
|
-
Success extends ServiceFreeSchema,
|
|
464
|
-
>(definition: {
|
|
465
|
-
readonly args: Args;
|
|
466
|
-
readonly success: Success;
|
|
467
|
-
}): Method<Args, Success>;
|
|
468
472
|
};
|
|
469
473
|
|
|
470
|
-
export
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
474
|
+
export type {
|
|
475
|
+
Api,
|
|
476
|
+
Definition,
|
|
477
|
+
HandlerEffect,
|
|
478
|
+
Handlers,
|
|
479
|
+
LayerOptions,
|
|
480
|
+
Method,
|
|
481
|
+
Methods,
|
|
482
|
+
NoReservedMethods,
|
|
483
|
+
Options,
|
|
484
|
+
ServerApi,
|
|
485
|
+
ServiceFreeSchema,
|
|
486
|
+
TagClass,
|
|
487
|
+
} from "./WorkerDefinition";
|
|
488
|
+
|
|
489
|
+
export { implement, method, Tag } from "./WorkerDefinition";
|
package/src/WorkerDefinition.ts
CHANGED
|
@@ -37,15 +37,10 @@ export namespace Method {
|
|
|
37
37
|
? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
|
|
38
38
|
: Array<S.Schema.Type<Args[number]>>;
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
infer Head extends ServiceFreeSchema,
|
|
45
|
-
...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
|
|
46
|
-
]
|
|
47
|
-
? [S.Codec.Encoded<Head>, ...EncodedArgsFromSchemas<Tail>]
|
|
48
|
-
: Array<S.Codec.Encoded<Args[number]>>;
|
|
40
|
+
/** Method schemas cross the wire through their canonical JSON codec. */
|
|
41
|
+
type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
|
|
42
|
+
[Index in keyof Args]: S.Json;
|
|
43
|
+
};
|
|
49
44
|
|
|
50
45
|
export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
|
|
51
46
|
|
|
@@ -53,7 +48,7 @@ export namespace Method {
|
|
|
53
48
|
|
|
54
49
|
export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
|
|
55
50
|
|
|
56
|
-
export type EncodedSuccess<Self extends Any> = S.
|
|
51
|
+
export type EncodedSuccess<Self extends Any> = S.Json;
|
|
57
52
|
}
|
|
58
53
|
|
|
59
54
|
export type Methods = Record<string, Method.Any>;
|
package/src/WorkersAi.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
|
|
15
15
|
import * as Binding from "./Binding";
|
|
16
16
|
import type { WorkerEnvironment } from "./Environment";
|
|
17
|
+
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
17
18
|
|
|
18
19
|
const expectedWorkersAiBinding = "Workers AI binding with run(), gateway(), and models()";
|
|
19
20
|
|
|
@@ -22,7 +23,11 @@ export class WorkersAiOperationError extends Data.TaggedError("WorkersAiOperatio
|
|
|
22
23
|
readonly binding: string;
|
|
23
24
|
readonly operation: string;
|
|
24
25
|
readonly cause: unknown;
|
|
25
|
-
}> {
|
|
26
|
+
}> {
|
|
27
|
+
override get message(): string {
|
|
28
|
+
return `Workers AI ${this.operation} failed for binding "${this.binding}": ${ErrorMessage.causeMessage(this.cause)}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
26
31
|
|
|
27
32
|
/** Typed Workers AI binding definition. */
|
|
28
33
|
export interface WorkersAiDefinition {
|
|
@@ -101,7 +106,7 @@ export interface WorkersAiClient<ModelList extends AiModelListType = AiModels> {
|
|
|
101
106
|
readonly gateway: (
|
|
102
107
|
gatewayId: string,
|
|
103
108
|
) => Effect.Effect<CloudflareAiGateway, WorkersAiOperationError>;
|
|
104
|
-
readonly
|
|
109
|
+
readonly rawUnsafe: Effect.Effect<WorkersAiBinding<ModelList>>;
|
|
105
110
|
readonly definition: WorkersAiDefinition;
|
|
106
111
|
}
|
|
107
112
|
|
|
@@ -177,10 +182,12 @@ export const embeddingResponse = (value: {
|
|
|
177
182
|
export const makeClient =
|
|
178
183
|
<ModelList extends AiModelListType = AiModels>(definition: WorkersAiDefinition) =>
|
|
179
184
|
(ai: WorkersAiBinding<ModelList>): WorkersAiClient<ModelList> => {
|
|
180
|
-
const run = ((
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
const run = Effect.fn("WorkersAi.run")(
|
|
186
|
+
(model: string, input: Record<string, unknown>, options?: WorkersAiOptions) =>
|
|
187
|
+
tryWorkersAiPromise(definition.binding, "run", () =>
|
|
188
|
+
ai.run(model as string & {}, input, options),
|
|
189
|
+
),
|
|
190
|
+
) as WorkersAiClient<ModelList>["run"];
|
|
184
191
|
|
|
185
192
|
return {
|
|
186
193
|
definition,
|
|
@@ -190,22 +197,25 @@ export const makeClient =
|
|
|
190
197
|
() => ai.aiGatewayLogId,
|
|
191
198
|
),
|
|
192
199
|
run,
|
|
193
|
-
runEmbedding: (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
runEmbedding: Effect.fn("WorkersAi.runEmbedding")(
|
|
201
|
+
(model: string, input: WorkersAiEmbeddingInput, options?: WorkersAiOptions) =>
|
|
202
|
+
run(model, input as Record<string, unknown>, options).pipe(
|
|
203
|
+
Effect.map((response) =>
|
|
204
|
+
embeddingResponse(
|
|
205
|
+
response as {
|
|
206
|
+
readonly data?: ReadonlyArray<ReadonlyArray<number>>;
|
|
207
|
+
readonly shape?: ReadonlyArray<number>;
|
|
208
|
+
},
|
|
209
|
+
),
|
|
201
210
|
),
|
|
202
211
|
),
|
|
203
|
-
|
|
204
|
-
models: (params) =>
|
|
212
|
+
),
|
|
213
|
+
models: Effect.fn("WorkersAi.models")((params?: WorkersAiModelsSearchParams) =>
|
|
205
214
|
tryWorkersAiPromise(definition.binding, "models", () => ai.models(params)),
|
|
215
|
+
),
|
|
206
216
|
gateway: (gatewayId) =>
|
|
207
217
|
tryWorkersAiSync(definition.binding, "gateway", () => ai.gateway(gatewayId)),
|
|
208
|
-
|
|
218
|
+
rawUnsafe: Effect.succeed(ai),
|
|
209
219
|
};
|
|
210
220
|
};
|
|
211
221
|
|