effect-cf 0.0.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/README.md +79 -0
- package/dist/chunk-CfYAbeIz.mjs +13 -0
- package/dist/index.d.mts +1362 -0
- package/dist/index.mjs +1198 -0
- package/package.json +47 -0
- package/src/Binding.ts +70 -0
- package/src/DurableObject.ts +220 -0
- package/src/DurableObjectDefinition.ts +214 -0
- package/src/DurableObjectNamespace.ts +483 -0
- package/src/DurableObjectRpcWebSocket.ts +222 -0
- package/src/DurableObjectState.ts +127 -0
- package/src/DurableObjectStorage.ts +381 -0
- package/src/DurableObjectWebSocket.ts +62 -0
- package/src/Entry.ts +259 -0
- package/src/Environment.ts +86 -0
- package/src/Kv.ts +244 -0
- package/src/Rpc.ts +127 -0
- package/src/RpcDefinition.ts +354 -0
- package/src/ServiceBinding.ts +278 -0
- package/src/Worker.ts +264 -0
- package/src/WorkerDefinition.ts +206 -0
- package/src/index.ts +15 -0
- package/src/internal/Entrypoint.ts +40 -0
- package/src/internal/RpcInvocation.ts +73 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Data, Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
import * as Binding from "./Binding";
|
|
4
|
+
import * as CloudflareRpc from "./Rpc";
|
|
5
|
+
import * as RpcDefinition from "./RpcDefinition";
|
|
6
|
+
import type * as WorkerDefinition from "./WorkerDefinition";
|
|
7
|
+
import * as RpcInvocation from "./internal/RpcInvocation";
|
|
8
|
+
|
|
9
|
+
const TypeId = "effect-cf/ServiceBinding" as const;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Minimum shape for a Cloudflare service binding.
|
|
13
|
+
*/
|
|
14
|
+
export interface ServiceFetcher {
|
|
15
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type RpcClient<Api> = {
|
|
19
|
+
readonly [Key in keyof Api as Key extends string
|
|
20
|
+
? Api[Key] extends (...args: Array<any>) => unknown
|
|
21
|
+
? Key
|
|
22
|
+
: never
|
|
23
|
+
: never]: Api[Key];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ReservedMethodName = WorkerDefinition.ReservedMethodName | "fetch";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Native Cloudflare service object including optional RPC methods.
|
|
30
|
+
*/
|
|
31
|
+
export type ServiceBindingClient<Api extends object> = ServiceFetcher &
|
|
32
|
+
RpcClient<CloudflareRpc.Provider<Api, ReservedMethodName>>;
|
|
33
|
+
|
|
34
|
+
type ApiFromDefinition<Definition> = Definition extends WorkerDefinition.Definition.Any
|
|
35
|
+
? WorkerDefinition.ServerApi<Definition>
|
|
36
|
+
: never;
|
|
37
|
+
|
|
38
|
+
type ApiOrDefinition<Api extends object, Definition> = [Api] extends [never]
|
|
39
|
+
? ApiFromDefinition<Definition>
|
|
40
|
+
: Api;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Binding metadata used to create an Effect service from a Worker binding.
|
|
44
|
+
*/
|
|
45
|
+
export interface ServiceBindingDefinition<
|
|
46
|
+
Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
47
|
+
> {
|
|
48
|
+
/** Binding name as configured in `wrangler.jsonc`. */
|
|
49
|
+
readonly binding: string;
|
|
50
|
+
/** Optional RPC schema used for argument/result encoding. */
|
|
51
|
+
readonly definition?: Definition;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Failure raised when calling `fetch` on a service binding.
|
|
56
|
+
*/
|
|
57
|
+
export class ServiceBindingFetchError extends Data.TaggedError("ServiceBindingFetchError")<{
|
|
58
|
+
readonly binding: string;
|
|
59
|
+
readonly cause: unknown;
|
|
60
|
+
}> {}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Failure raised when invoking an RPC method on a service binding.
|
|
64
|
+
*/
|
|
65
|
+
export class ServiceBindingRpcError extends Data.TaggedError("ServiceBindingRpcError")<{
|
|
66
|
+
readonly binding: string;
|
|
67
|
+
readonly method: string;
|
|
68
|
+
readonly cause: unknown;
|
|
69
|
+
}> {}
|
|
70
|
+
|
|
71
|
+
type ServiceMethodKey<Api> = RpcInvocation.AsyncMethodKey<Api>;
|
|
72
|
+
type ServiceMethodArgs<Api, Method extends keyof Api> = RpcInvocation.AsyncMethodArgs<Api, Method>;
|
|
73
|
+
type ServiceMethodSuccess<Api, Method extends keyof Api> = RpcInvocation.AsyncMethodSuccess<
|
|
74
|
+
Api,
|
|
75
|
+
Method
|
|
76
|
+
>;
|
|
77
|
+
|
|
78
|
+
type ServiceCall<Self, Api> = <Method extends ServiceMethodKey<Api>>(
|
|
79
|
+
method: Method,
|
|
80
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
81
|
+
) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError, Self>;
|
|
82
|
+
|
|
83
|
+
type DefinitionDirectMethods<Self, Definition extends WorkerDefinition.Definition.Any> = {
|
|
84
|
+
readonly [Method in RpcDefinition.Definition.MethodNames<Definition>]: (
|
|
85
|
+
...args: RpcDefinition.Method.Args<Definition["methods"][Method]>
|
|
86
|
+
) => Effect.Effect<
|
|
87
|
+
RpcDefinition.Method.Success<Definition["methods"][Method]>,
|
|
88
|
+
ServiceBindingRpcError,
|
|
89
|
+
Self
|
|
90
|
+
>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type DirectMethods<Self, Definition> = Definition extends WorkerDefinition.Definition.Any
|
|
94
|
+
? DefinitionDirectMethods<Self, Definition>
|
|
95
|
+
: {};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Creates a typed Effect service for a Worker service binding.
|
|
99
|
+
*
|
|
100
|
+
* Returned value includes:
|
|
101
|
+
* - a Context tag for dependency injection
|
|
102
|
+
* - `fetch(...)` for raw HTTP forwarding
|
|
103
|
+
* - `call(...)` for generic RPC invocation
|
|
104
|
+
* - direct RPC methods when `definition` is provided
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const Counter = WorkerDefinition.make("Counter", {
|
|
109
|
+
* increment: WorkerDefinition.method({
|
|
110
|
+
* args: [Schema.Number],
|
|
111
|
+
* success: Schema.Number,
|
|
112
|
+
* }),
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* const CounterService = Counter.binding("COUNTER", { binding: "COUNTER" });
|
|
116
|
+
*
|
|
117
|
+
* const program = Effect.gen(function* () {
|
|
118
|
+
* const next = yield* CounterService.increment(1);
|
|
119
|
+
* return next;
|
|
120
|
+
* });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export const Service =
|
|
124
|
+
<Self, Api extends object = never>() =>
|
|
125
|
+
<
|
|
126
|
+
Id extends string,
|
|
127
|
+
const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
128
|
+
>(
|
|
129
|
+
id: Id,
|
|
130
|
+
definition: ServiceBindingDefinition<Definition>,
|
|
131
|
+
) => {
|
|
132
|
+
type ServiceApi = ApiOrDefinition<Api, Definition>;
|
|
133
|
+
|
|
134
|
+
const tag = Binding.Service<Self>()(
|
|
135
|
+
id,
|
|
136
|
+
definition.binding,
|
|
137
|
+
(value): value is ServiceBindingClient<ServiceApi> =>
|
|
138
|
+
typeof value === "object" && value !== null && "fetch" in value,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
|
|
142
|
+
Effect.gen(function* () {
|
|
143
|
+
const service = yield* tag;
|
|
144
|
+
|
|
145
|
+
return yield* Effect.tryPromise({
|
|
146
|
+
try: () => service.fetch(input, init),
|
|
147
|
+
catch: (cause) => new ServiceBindingFetchError({ binding: definition.binding, cause }),
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const rpc = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
152
|
+
method: Method,
|
|
153
|
+
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
154
|
+
) =>
|
|
155
|
+
Effect.gen(function* () {
|
|
156
|
+
const service = yield* tag;
|
|
157
|
+
const methodName = String(method);
|
|
158
|
+
const encodedArgs =
|
|
159
|
+
definition.definition === undefined
|
|
160
|
+
? args
|
|
161
|
+
: yield* RpcDefinition.encodeArgs(
|
|
162
|
+
definition.definition,
|
|
163
|
+
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
164
|
+
args as never,
|
|
165
|
+
).pipe(
|
|
166
|
+
Effect.mapError(
|
|
167
|
+
(cause) =>
|
|
168
|
+
new ServiceBindingRpcError({
|
|
169
|
+
binding: definition.binding,
|
|
170
|
+
method: methodName,
|
|
171
|
+
cause,
|
|
172
|
+
}),
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return yield* RpcInvocation.invokeRpcMethod(
|
|
177
|
+
service,
|
|
178
|
+
method,
|
|
179
|
+
encodedArgs as ServiceMethodArgs<ServiceApi, Method>,
|
|
180
|
+
(cause) =>
|
|
181
|
+
new ServiceBindingRpcError({
|
|
182
|
+
binding: definition.binding,
|
|
183
|
+
method: methodName,
|
|
184
|
+
cause,
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const call = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
190
|
+
method: Method,
|
|
191
|
+
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
192
|
+
) =>
|
|
193
|
+
Effect.gen(function* () {
|
|
194
|
+
const methodName = String(method);
|
|
195
|
+
const value = yield* CloudflareRpc.resolve(yield* rpc(method, ...args)).pipe(
|
|
196
|
+
Effect.mapError(
|
|
197
|
+
(cause) =>
|
|
198
|
+
new ServiceBindingRpcError({
|
|
199
|
+
binding: definition.binding,
|
|
200
|
+
method: methodName,
|
|
201
|
+
cause,
|
|
202
|
+
}),
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (definition.definition === undefined) {
|
|
207
|
+
return value as ServiceMethodSuccess<ServiceApi, Method>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const decoded = yield* RpcDefinition.decodeSuccess(
|
|
211
|
+
definition.definition,
|
|
212
|
+
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
213
|
+
value,
|
|
214
|
+
).pipe(
|
|
215
|
+
Effect.mapError(
|
|
216
|
+
(cause) =>
|
|
217
|
+
new ServiceBindingRpcError({
|
|
218
|
+
binding: definition.binding,
|
|
219
|
+
method: methodName,
|
|
220
|
+
cause,
|
|
221
|
+
}),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return decoded as ServiceMethodSuccess<ServiceApi, Method>;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const scopedCall = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
229
|
+
method: Method,
|
|
230
|
+
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
231
|
+
) =>
|
|
232
|
+
Effect.gen(function* () {
|
|
233
|
+
const result = yield* rpc(method, ...args);
|
|
234
|
+
return yield* CloudflareRpc.scoped(result);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const directMethods = makeDirectMethods<Self, ServiceApi, Definition>(
|
|
238
|
+
definition.definition,
|
|
239
|
+
call,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
return Object.assign(tag, directMethods, {
|
|
243
|
+
[TypeId]: TypeId,
|
|
244
|
+
definition,
|
|
245
|
+
fetch,
|
|
246
|
+
rpc,
|
|
247
|
+
call,
|
|
248
|
+
scopedCall,
|
|
249
|
+
}) as typeof tag &
|
|
250
|
+
DirectMethods<Self, Definition> & {
|
|
251
|
+
readonly [TypeId]: typeof TypeId;
|
|
252
|
+
readonly definition: ServiceBindingDefinition<Definition>;
|
|
253
|
+
readonly fetch: typeof fetch;
|
|
254
|
+
readonly rpc: typeof rpc;
|
|
255
|
+
readonly call: typeof call;
|
|
256
|
+
readonly scopedCall: typeof scopedCall;
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const makeDirectMethods = <
|
|
261
|
+
Self,
|
|
262
|
+
Api,
|
|
263
|
+
Definition extends WorkerDefinition.Definition.Any | undefined,
|
|
264
|
+
>(
|
|
265
|
+
rpcDefinition: Definition | undefined,
|
|
266
|
+
call: ServiceCall<Self, Api>,
|
|
267
|
+
): DirectMethods<Self, Definition> => {
|
|
268
|
+
const methods = {} as Record<string, unknown>;
|
|
269
|
+
|
|
270
|
+
if (rpcDefinition !== undefined) {
|
|
271
|
+
for (const methodName of Object.keys(rpcDefinition.methods)) {
|
|
272
|
+
methods[methodName] = (...args: Array<unknown>) =>
|
|
273
|
+
(call as (method: string, ...args: Array<unknown>) => unknown)(methodName, ...args);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return methods as DirectMethods<Self, Definition>;
|
|
278
|
+
};
|
package/src/Worker.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
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
|
+
import type * as RpcDefinition from "./RpcDefinition";
|
|
7
|
+
import * as WorkerDefinition from "./WorkerDefinition";
|
|
8
|
+
import * as Entrypoint from "./internal/Entrypoint";
|
|
9
|
+
|
|
10
|
+
export class ExecutionContext extends Context.Service<
|
|
11
|
+
ExecutionContext,
|
|
12
|
+
globalThis.ExecutionContext
|
|
13
|
+
>()("effect-cf/ExecutionContext") {}
|
|
14
|
+
|
|
15
|
+
export interface WorkerContextWaitUntilOptions<E, R> {
|
|
16
|
+
readonly onFailure?: (cause: Cause.Cause<E>) => Effect.Effect<void, never, R>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WorkerContextService {
|
|
20
|
+
readonly raw: globalThis.ExecutionContext;
|
|
21
|
+
waitUntil<A, E, R, R2 = never>(
|
|
22
|
+
effect: Effect.Effect<A, E, R>,
|
|
23
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
24
|
+
): Effect.Effect<void, never, R | R2>;
|
|
25
|
+
readonly passThroughOnException: Effect.Effect<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class WorkerContext extends Context.Service<WorkerContext, WorkerContextService>()(
|
|
29
|
+
"effect-cf/WorkerContext",
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
type RunWaitUntilEffect = <A, E>(
|
|
33
|
+
effect: Effect.Effect<A, E, never>,
|
|
34
|
+
) => Promise<Exit.Exit<A, unknown>>;
|
|
35
|
+
|
|
36
|
+
const fromExecutionContext = (
|
|
37
|
+
ctx: globalThis.ExecutionContext,
|
|
38
|
+
runPromiseExit: RunWaitUntilEffect,
|
|
39
|
+
): WorkerContextService => ({
|
|
40
|
+
raw: ctx,
|
|
41
|
+
waitUntil: <A, E, R, R2 = never>(
|
|
42
|
+
effect: Effect.Effect<A, E, R>,
|
|
43
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
44
|
+
) =>
|
|
45
|
+
Effect.context<R | R2>().pipe(
|
|
46
|
+
Effect.flatMap((context) =>
|
|
47
|
+
Effect.sync(() => {
|
|
48
|
+
const observed = Effect.exit(effect).pipe(
|
|
49
|
+
Effect.flatMap((exit) => {
|
|
50
|
+
if (Exit.isSuccess(exit)) {
|
|
51
|
+
return Effect.void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const handleFailure =
|
|
55
|
+
options?.onFailure?.(exit.cause) ??
|
|
56
|
+
Effect.logError("WorkerContext.waitUntil failed", Cause.pretty(exit.cause));
|
|
57
|
+
|
|
58
|
+
return handleFailure.pipe(
|
|
59
|
+
Effect.catchCause((handlerCause) =>
|
|
60
|
+
Effect.logError(
|
|
61
|
+
"WorkerContext.waitUntil failure handler failed",
|
|
62
|
+
Cause.pretty(exit.cause),
|
|
63
|
+
Cause.pretty(handlerCause),
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
ctx.waitUntil(
|
|
71
|
+
runPromiseExit(Effect.scoped(Effect.provideContext(observed, context))).then((exit) => {
|
|
72
|
+
if (Exit.isFailure(exit)) {
|
|
73
|
+
console.error(
|
|
74
|
+
"WorkerContext.waitUntil failure handler failed",
|
|
75
|
+
Cause.pretty(exit.cause),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
passThroughOnException: Effect.sync(() => ctx.passThroughOnException()),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export class NativeRequest extends Context.Service<NativeRequest, Request>()(
|
|
87
|
+
"effect-cf/NativeRequest",
|
|
88
|
+
) {}
|
|
89
|
+
|
|
90
|
+
export const isWebSocketUpgrade = (request: Request): boolean =>
|
|
91
|
+
request.headers.get("Upgrade")?.toLowerCase() === "websocket";
|
|
92
|
+
|
|
93
|
+
export type ReservedMethodName =
|
|
94
|
+
| RpcDefinition.ReservedMethodName
|
|
95
|
+
| "fetch"
|
|
96
|
+
| "connect"
|
|
97
|
+
| "alarm"
|
|
98
|
+
| "webSocketMessage"
|
|
99
|
+
| "webSocketClose"
|
|
100
|
+
| "webSocketError";
|
|
101
|
+
|
|
102
|
+
const reservedMethodNames = new Set<string>([
|
|
103
|
+
"constructor",
|
|
104
|
+
"dup",
|
|
105
|
+
"fetch",
|
|
106
|
+
"connect",
|
|
107
|
+
"alarm",
|
|
108
|
+
"webSocketMessage",
|
|
109
|
+
"webSocketClose",
|
|
110
|
+
"webSocketError",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
type WorkerBaseContext<ROut> = ExecutionContext | WorkerContext | WorkerEnvironment | ROut;
|
|
114
|
+
type WorkerFetchContext<ROut> =
|
|
115
|
+
| WorkerBaseContext<ROut>
|
|
116
|
+
| NativeRequest
|
|
117
|
+
| HttpServerRequest.HttpServerRequest
|
|
118
|
+
| Scope.Scope;
|
|
119
|
+
type WorkerRpcContext<ROut> = WorkerBaseContext<ROut> | Scope.Scope;
|
|
120
|
+
|
|
121
|
+
type RuntimeContext<ROut> = WorkerBaseContext<ROut>;
|
|
122
|
+
|
|
123
|
+
const RunSymbol = Symbol.for("effect-cf/Worker/run");
|
|
124
|
+
|
|
125
|
+
export type WorkerHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerFetchContext<ROut>>;
|
|
126
|
+
|
|
127
|
+
export type WorkerRpcHandler<ROut, A = unknown> = Effect.Effect<A, unknown, WorkerRpcContext<ROut>>;
|
|
128
|
+
|
|
129
|
+
export type WorkerRpc<ROut> = Record<string, (...args: Array<any>) => WorkerRpcHandler<ROut>>;
|
|
130
|
+
|
|
131
|
+
export type WorkerRpcShape<Rpc extends WorkerRpc<ROut>, ROut> = {
|
|
132
|
+
readonly [Key in keyof Rpc]: Rpc[Key] extends (
|
|
133
|
+
...args: infer Args
|
|
134
|
+
) => Effect.Effect<infer A, unknown, WorkerRpcContext<ROut>>
|
|
135
|
+
? (...args: Args) => Promise<A>
|
|
136
|
+
: never;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export type RpcHandlers<ROut, Api> = {
|
|
140
|
+
readonly [Key in keyof Api as Key extends keyof CloudflareWorkerEntrypoint
|
|
141
|
+
? never
|
|
142
|
+
: Key extends string
|
|
143
|
+
? Key extends ReservedMethodName
|
|
144
|
+
? never
|
|
145
|
+
: [Api[Key]] extends [never]
|
|
146
|
+
? never
|
|
147
|
+
: Api[Key] extends (...args: Array<any>) => Promise<unknown>
|
|
148
|
+
? Key
|
|
149
|
+
: never
|
|
150
|
+
: never]: Api[Key] extends (...args: infer Args) => Promise<infer A>
|
|
151
|
+
? (...args: Args) => WorkerRpcHandler<ROut, A>
|
|
152
|
+
: never;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export interface WorkerOptions<ROut, Rpc extends WorkerRpc<ROut>> {
|
|
156
|
+
readonly fetch?: Effect.Effect<Response, unknown, WorkerFetchContext<ROut>>;
|
|
157
|
+
readonly rpc?: Rpc;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type WorkerClass<Rpc extends WorkerRpc<ROut>, ROut> = new (
|
|
161
|
+
ctx: globalThis.ExecutionContext,
|
|
162
|
+
env: WorkerEnv,
|
|
163
|
+
) => CloudflareWorkerEntrypoint<WorkerEnv> & {
|
|
164
|
+
fetch(request: Request): Promise<Response>;
|
|
165
|
+
} & WorkerRpcShape<Rpc, ROut>;
|
|
166
|
+
|
|
167
|
+
export const make = <ROut, LayerError, const Rpc extends WorkerRpc<ROut> = Record<never, never>>(
|
|
168
|
+
layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
|
|
169
|
+
options: WorkerOptions<ROut, Rpc>,
|
|
170
|
+
): WorkerClass<Rpc, ROut> => {
|
|
171
|
+
class EffectWorker extends CloudflareWorkerEntrypoint<WorkerEnv> {
|
|
172
|
+
readonly runtime: ManagedRuntime.ManagedRuntime<RuntimeContext<ROut>, LayerError>;
|
|
173
|
+
|
|
174
|
+
constructor(ctx: globalThis.ExecutionContext, env: WorkerEnv) {
|
|
175
|
+
super(ctx, env);
|
|
176
|
+
|
|
177
|
+
let runWaitUntilEffect: RunWaitUntilEffect = () =>
|
|
178
|
+
Promise.resolve(Exit.die(new Error("WorkerContext runtime is not initialized")));
|
|
179
|
+
|
|
180
|
+
const services = Layer.mergeAll(
|
|
181
|
+
Layer.succeed(ExecutionContext, ctx),
|
|
182
|
+
Layer.succeed(
|
|
183
|
+
WorkerContext,
|
|
184
|
+
fromExecutionContext(ctx, (effect) => runWaitUntilEffect(effect)),
|
|
185
|
+
),
|
|
186
|
+
Layer.succeed(WorkerEnvironment, env),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const runtimeLayer = Entrypoint.provideEntrypointServices(layer, services);
|
|
190
|
+
|
|
191
|
+
this.runtime = ManagedRuntime.make(runtimeLayer);
|
|
192
|
+
runWaitUntilEffect = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
193
|
+
this.runtime.runPromiseExit(effect as Effect.Effect<A, E, RuntimeContext<ROut>>);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[RunSymbol]<A, E>(effect: Effect.Effect<A, E, RuntimeContext<ROut> | Scope.Scope>): Promise<A> {
|
|
197
|
+
return this.runtime.runPromise(Effect.scoped(effect));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fetch(request: Request): Promise<Response> {
|
|
201
|
+
const fetchHandler = options.fetch;
|
|
202
|
+
|
|
203
|
+
if (fetchHandler === undefined) {
|
|
204
|
+
return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return this[RunSymbol](
|
|
208
|
+
fetchHandler.pipe(
|
|
209
|
+
Effect.provideService(NativeRequest, request),
|
|
210
|
+
Effect.provideService(
|
|
211
|
+
HttpServerRequest.HttpServerRequest,
|
|
212
|
+
HttpServerRequest.fromWeb(request),
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Entrypoint.defineEntrypointRpcMethods<EffectWorker>(
|
|
220
|
+
"Worker",
|
|
221
|
+
EffectWorker.prototype,
|
|
222
|
+
options.rpc,
|
|
223
|
+
reservedMethodNames,
|
|
224
|
+
(self, effect) => self[RunSymbol](effect),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return Entrypoint.assumeEntrypointClass<WorkerClass<Rpc, ROut>>(EffectWorker);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const Tag = WorkerDefinition.Tag;
|
|
231
|
+
|
|
232
|
+
export const method = WorkerDefinition.method;
|
|
233
|
+
|
|
234
|
+
export const implement = WorkerDefinition.implement;
|
|
235
|
+
|
|
236
|
+
export type Definition<
|
|
237
|
+
Id extends string = string,
|
|
238
|
+
Methods extends RpcDefinition.Methods = RpcDefinition.Methods,
|
|
239
|
+
> = WorkerDefinition.Definition<Id, Methods>;
|
|
240
|
+
|
|
241
|
+
export namespace Definition {
|
|
242
|
+
export type Any = WorkerDefinition.Definition.Any;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export type ServerApi<Self extends WorkerDefinition.Definition.Any> =
|
|
246
|
+
WorkerDefinition.ServerApi<Self>;
|
|
247
|
+
|
|
248
|
+
export type Api<Self extends WorkerDefinition.Definition.Any> = WorkerDefinition.Api<Self>;
|
|
249
|
+
|
|
250
|
+
export type Handlers<
|
|
251
|
+
ROut,
|
|
252
|
+
Self extends WorkerDefinition.Definition.Any,
|
|
253
|
+
> = WorkerDefinition.Handlers<ROut, Self>;
|
|
254
|
+
|
|
255
|
+
export type Options<ROut, Self extends WorkerDefinition.Definition.Any> = WorkerDefinition.Options<
|
|
256
|
+
ROut,
|
|
257
|
+
Self
|
|
258
|
+
>;
|
|
259
|
+
|
|
260
|
+
export type HandlerEffect<
|
|
261
|
+
ROut,
|
|
262
|
+
Self extends WorkerDefinition.Definition.Any,
|
|
263
|
+
Key extends keyof Self["methods"],
|
|
264
|
+
> = WorkerDefinition.HandlerEffect<ROut, Self, Key>;
|