effect-cf 0.6.0 → 0.7.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 +8 -0
- package/README.md +30 -29
- package/dist/index.d.mts +837 -1027
- package/dist/index.mjs +328 -407
- package/package.json +1 -1
- package/src/Binding.ts +17 -10
- package/src/DurableObject.ts +117 -23
- package/src/DurableObjectDefinition.ts +251 -72
- package/src/DurableObjectNamespace.ts +239 -287
- package/src/Kv.ts +173 -199
- package/src/Queue.ts +18 -4
- package/src/QueueBinding.ts +45 -97
- package/src/QueueDefinition.ts +87 -41
- package/src/ServiceBinding.ts +260 -116
- package/src/Worker.ts +114 -20
- package/src/WorkerDefinition.ts +187 -63
- package/src/Workflow.ts +23 -4
- package/src/WorkflowBinding.ts +100 -109
- package/src/WorkflowDefinition.ts +116 -51
package/src/QueueDefinition.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Effect, Schema as S, type Layer, type Scope } from "effect";
|
|
1
|
+
import { Context, Effect, Schema as S, type Layer, type Scope } from "effect";
|
|
2
2
|
|
|
3
|
+
import * as Binding from "./Binding";
|
|
3
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
4
5
|
import * as QueueEntrypoint from "./Queue";
|
|
5
6
|
import * as QueueBinding from "./QueueBinding";
|
|
@@ -19,15 +20,6 @@ export namespace Definition {
|
|
|
19
20
|
export type Any = Definition<string, RpcDefinition.ServiceFreeSchema>;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
declare const BindingServiceTypeId: unique symbol;
|
|
23
|
-
|
|
24
|
-
export interface BindingService<Id extends string, Self extends Definition.Any> {
|
|
25
|
-
readonly [BindingServiceTypeId]: {
|
|
26
|
-
readonly id: Id;
|
|
27
|
-
readonly definition: Self;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
export type Handler<ROut, Self extends Definition.Any> = (
|
|
32
24
|
batch: QueueEntrypoint.QueueBatch<S.Schema.Type<Self["message"]>>,
|
|
33
25
|
) => Effect.Effect<
|
|
@@ -44,6 +36,44 @@ export interface Options<ROut, Self extends Definition.Any> extends Omit<
|
|
|
44
36
|
readonly rpc?: never;
|
|
45
37
|
}
|
|
46
38
|
|
|
39
|
+
export type LayerOptions = {
|
|
40
|
+
readonly binding: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface TagClass<
|
|
44
|
+
Self,
|
|
45
|
+
Id extends string,
|
|
46
|
+
Message extends RpcDefinition.ServiceFreeSchema,
|
|
47
|
+
> extends Context.ServiceClass<Self, Id, QueueBinding.QueueBindingClient<Message>> {
|
|
48
|
+
readonly id: Id;
|
|
49
|
+
readonly message: Message;
|
|
50
|
+
readonly make: <ROut, LayerError>(
|
|
51
|
+
layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
|
|
52
|
+
options: Options<ROut, Definition<Id, Message>>,
|
|
53
|
+
) => WorkerEntrypoint.WorkerClass<Record<never, never>, ROut>;
|
|
54
|
+
readonly layer: (
|
|
55
|
+
options: LayerOptions,
|
|
56
|
+
) => Layer.Layer<
|
|
57
|
+
Self,
|
|
58
|
+
Binding.BindingNotFoundError | Binding.BindingValidationError,
|
|
59
|
+
WorkerEnvironment
|
|
60
|
+
>;
|
|
61
|
+
readonly send: (
|
|
62
|
+
message: S.Schema.Type<Message>,
|
|
63
|
+
options?: QueueBinding.QueueSendOptions,
|
|
64
|
+
) => Effect.Effect<void, QueueBinding.QueueOperationError | S.SchemaError, Self>;
|
|
65
|
+
readonly sendBatch: (
|
|
66
|
+
messages: Iterable<QueueBinding.MessageSendRequest<S.Schema.Type<Message>>>,
|
|
67
|
+
options?: QueueBinding.QueueSendBatchOptions,
|
|
68
|
+
) => Effect.Effect<void, QueueBinding.QueueOperationError | S.SchemaError, Self>;
|
|
69
|
+
readonly metrics: () => Effect.Effect<
|
|
70
|
+
QueueBinding.QueueMetrics,
|
|
71
|
+
QueueBinding.QueueOperationError,
|
|
72
|
+
Self
|
|
73
|
+
>;
|
|
74
|
+
readonly unsafeRaw: () => Effect.Effect<globalThis.Queue<S.Codec.Encoded<Message>>, never, Self>;
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
const makeDefinition = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
48
78
|
id: Id,
|
|
49
79
|
definition: { readonly message: Message },
|
|
@@ -63,49 +93,65 @@ const makeDefinition = <Id extends string, Message extends RpcDefinition.Service
|
|
|
63
93
|
...options,
|
|
64
94
|
queue: wrapHandler(queueDefinition, options.queue),
|
|
65
95
|
}),
|
|
66
|
-
Binding:
|
|
67
|
-
<Self>() =>
|
|
68
|
-
<BindingId extends string>(
|
|
69
|
-
bindingId: BindingId,
|
|
70
|
-
binding: Omit<QueueBinding.QueueBindingDefinition<Message>, "message">,
|
|
71
|
-
) =>
|
|
72
|
-
QueueBinding.Service<Self>()<BindingId, Message>(bindingId, {
|
|
73
|
-
...binding,
|
|
74
|
-
message: definition.message,
|
|
75
|
-
}),
|
|
76
|
-
binding: <BindingId extends string>(
|
|
77
|
-
bindingId: BindingId,
|
|
78
|
-
binding: Omit<QueueBinding.QueueBindingDefinition<Message>, "message">,
|
|
79
|
-
) =>
|
|
80
|
-
QueueBinding.Service<BindingService<BindingId, SelfDefinition>>()<BindingId, Message>(
|
|
81
|
-
bindingId,
|
|
82
|
-
{
|
|
83
|
-
...binding,
|
|
84
|
-
message: definition.message,
|
|
85
|
-
},
|
|
86
|
-
),
|
|
87
96
|
});
|
|
88
97
|
};
|
|
89
98
|
|
|
90
|
-
export const make =
|
|
99
|
+
export const make = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
100
|
+
id: Id,
|
|
101
|
+
definition: { readonly message: Message },
|
|
102
|
+
) => Tag<Definition<Id, Message>>()(id, definition);
|
|
91
103
|
|
|
92
104
|
export const Tag =
|
|
93
|
-
<
|
|
105
|
+
<Self>() =>
|
|
94
106
|
<Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
95
107
|
id: Id,
|
|
96
108
|
definition: { readonly message: Message },
|
|
97
109
|
) => {
|
|
98
110
|
const queueDefinition = makeDefinition(id, definition);
|
|
111
|
+
const tag = Context.Service<Self, QueueBinding.QueueBindingClient<Message>>()(id);
|
|
112
|
+
|
|
113
|
+
const layer = (binding: LayerOptions) =>
|
|
114
|
+
QueueBinding.layer(tag, {
|
|
115
|
+
...binding,
|
|
116
|
+
message: definition.message,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const send = Effect.fnUntraced(function* (
|
|
120
|
+
message: S.Schema.Type<Message>,
|
|
121
|
+
options?: QueueBinding.QueueSendOptions,
|
|
122
|
+
) {
|
|
123
|
+
const queue = yield* tag;
|
|
124
|
+
yield* queue.send(message, options);
|
|
125
|
+
});
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
127
|
+
const sendBatch = Effect.fnUntraced(function* (
|
|
128
|
+
messages: Iterable<QueueBinding.MessageSendRequest<S.Schema.Type<Message>>>,
|
|
129
|
+
options?: QueueBinding.QueueSendBatchOptions,
|
|
130
|
+
) {
|
|
131
|
+
const queue = yield* tag;
|
|
132
|
+
yield* queue.sendBatch(messages, options);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const metrics = Effect.fnUntraced(function* () {
|
|
136
|
+
const queue = yield* tag;
|
|
137
|
+
return yield* queue.metrics();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const unsafeRaw = Effect.fnUntraced(function* () {
|
|
141
|
+
const queue = yield* tag;
|
|
142
|
+
return yield* queue.unsafeRaw;
|
|
143
|
+
});
|
|
107
144
|
|
|
108
|
-
return
|
|
145
|
+
return Object.assign(tag, {
|
|
146
|
+
id: queueDefinition.id,
|
|
147
|
+
message: queueDefinition.message,
|
|
148
|
+
make: queueDefinition.make,
|
|
149
|
+
layer,
|
|
150
|
+
send,
|
|
151
|
+
sendBatch,
|
|
152
|
+
metrics,
|
|
153
|
+
unsafeRaw,
|
|
154
|
+
}) as TagClass<Self, Id, Message>;
|
|
109
155
|
};
|
|
110
156
|
|
|
111
157
|
export const Queue = Tag;
|
package/src/ServiceBinding.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect, type Scope } from "effect";
|
|
1
|
+
import { Context, Data, Effect, type Scope } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import * as CloudflareRpc from "./Rpc";
|
|
@@ -74,6 +74,10 @@ type ServiceMethodSuccess<Api, Method extends keyof Api> = RpcInvocation.AsyncMe
|
|
|
74
74
|
Api,
|
|
75
75
|
Method
|
|
76
76
|
>;
|
|
77
|
+
type ServiceMethodCloudflareReturn<
|
|
78
|
+
Api,
|
|
79
|
+
Method extends keyof Api,
|
|
80
|
+
> = RpcInvocation.AsyncMethodCloudflareReturn<Api, Method>;
|
|
77
81
|
|
|
78
82
|
type ServiceCall<R, Api> = <Method extends ServiceMethodKey<Api>>(
|
|
79
83
|
method: Method,
|
|
@@ -98,31 +102,274 @@ export type ServiceBindingEffectClient<
|
|
|
98
102
|
Api extends object,
|
|
99
103
|
Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
100
104
|
> = DirectMethods<never, Definition> & {
|
|
105
|
+
/**
|
|
106
|
+
* Forwards an HTTP request to the bound Worker service.
|
|
107
|
+
*
|
|
108
|
+
* Use this when the service binding is acting as an HTTP origin rather than a
|
|
109
|
+
* Cloudflare RPC target.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { Effect } from "effect";
|
|
114
|
+
*
|
|
115
|
+
* const program = Effect.gen(function* () {
|
|
116
|
+
* const api = yield* ApiWorker;
|
|
117
|
+
* return yield* api.fetch(new Request("https://internal.example/users"));
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
101
121
|
readonly fetch: (
|
|
102
122
|
input: RequestInfo | URL,
|
|
103
123
|
init?: RequestInit,
|
|
104
124
|
) => Effect.Effect<globalThis.Response, ServiceBindingFetchError>;
|
|
125
|
+
/**
|
|
126
|
+
* Invokes a Worker RPC method and returns Cloudflare's raw RPC result.
|
|
127
|
+
*
|
|
128
|
+
* This preserves Cloudflare RPC behavior such as promise-like pipelining and
|
|
129
|
+
* transferable / disposable result objects. It does not resolve the returned
|
|
130
|
+
* promise-like value and it does not decode definition-backed success schemas.
|
|
131
|
+
*
|
|
132
|
+
* Most application code should use {@link call} instead.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { Effect } from "effect";
|
|
137
|
+
*
|
|
138
|
+
* const program = Effect.gen(function* () {
|
|
139
|
+
* const counter = yield* CounterService;
|
|
140
|
+
*
|
|
141
|
+
* const result = yield* counter.rpc("increment", 41);
|
|
142
|
+
* const value = yield* Effect.promise(() => result);
|
|
143
|
+
*
|
|
144
|
+
* return value;
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
105
148
|
readonly rpc: <Method extends ServiceMethodKey<Api>>(
|
|
106
149
|
method: Method,
|
|
107
150
|
...args: ServiceMethodArgs<Api, Method>
|
|
108
|
-
) => Effect.Effect<
|
|
151
|
+
) => Effect.Effect<ServiceMethodCloudflareReturn<Api, Method>, ServiceBindingRpcError>;
|
|
152
|
+
/**
|
|
153
|
+
* Invokes a Worker RPC method, resolves Cloudflare's RPC result, and decodes
|
|
154
|
+
* the success value when the binding was created from a definition.
|
|
155
|
+
*
|
|
156
|
+
* This is the normal choice when application code wants the final typed value.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* import { Effect } from "effect";
|
|
161
|
+
*
|
|
162
|
+
* const program = Effect.gen(function* () {
|
|
163
|
+
* const counter = yield* CounterService;
|
|
164
|
+
* const value = yield* counter.call("increment", 41);
|
|
165
|
+
*
|
|
166
|
+
* return value;
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
109
170
|
readonly call: <Method extends ServiceMethodKey<Api>>(
|
|
110
171
|
method: Method,
|
|
111
172
|
...args: ServiceMethodArgs<Api, Method>
|
|
112
173
|
) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError>;
|
|
174
|
+
/**
|
|
175
|
+
* Invokes a Worker RPC method in the current `Scope`, resolves Cloudflare's RPC
|
|
176
|
+
* result, decodes definition-backed success values, and disposes the resolved
|
|
177
|
+
* result when the scope closes if it implements `Symbol.dispose`.
|
|
178
|
+
*
|
|
179
|
+
* Use this for RPC methods that return Cloudflare RPC resources or other
|
|
180
|
+
* disposable objects whose lifetime should be tied to an Effect scope.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { Effect } from "effect";
|
|
185
|
+
*
|
|
186
|
+
* const program = Effect.scoped(
|
|
187
|
+
* Effect.gen(function* () {
|
|
188
|
+
* const files = yield* FileService;
|
|
189
|
+
* const handle = yield* files.scopedCall("open", "report.csv");
|
|
190
|
+
*
|
|
191
|
+
* return yield* handle.read();
|
|
192
|
+
* }),
|
|
193
|
+
* );
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
113
196
|
readonly scopedCall: <Method extends ServiceMethodKey<Api>>(
|
|
114
197
|
method: Method,
|
|
115
198
|
...args: ServiceMethodArgs<Api, Method>
|
|
116
199
|
) => Effect.Effect<Awaited<ServiceMethodSuccess<Api, Method>>, unknown, Scope.Scope>;
|
|
117
200
|
};
|
|
118
201
|
|
|
202
|
+
export type ServiceBindingStaticClient<
|
|
203
|
+
R,
|
|
204
|
+
Api extends object,
|
|
205
|
+
Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
206
|
+
> = DirectMethods<R, Definition> & {
|
|
207
|
+
readonly fetch: (
|
|
208
|
+
input: RequestInfo | URL,
|
|
209
|
+
init?: RequestInit,
|
|
210
|
+
) => Effect.Effect<globalThis.Response, ServiceBindingFetchError, R>;
|
|
211
|
+
readonly rpc: <Method extends ServiceMethodKey<Api>>(
|
|
212
|
+
method: Method,
|
|
213
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
214
|
+
) => Effect.Effect<ServiceMethodCloudflareReturn<Api, Method>, ServiceBindingRpcError, R>;
|
|
215
|
+
readonly call: <Method extends ServiceMethodKey<Api>>(
|
|
216
|
+
method: Method,
|
|
217
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
218
|
+
) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError, R>;
|
|
219
|
+
readonly scopedCall: <Method extends ServiceMethodKey<Api>>(
|
|
220
|
+
method: Method,
|
|
221
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
222
|
+
) => Effect.Effect<Awaited<ServiceMethodSuccess<Api, Method>>, unknown, Scope.Scope | R>;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export const isServiceBindingClient = <Api extends object>(
|
|
226
|
+
value: unknown,
|
|
227
|
+
): value is ServiceBindingClient<Api> =>
|
|
228
|
+
typeof value === "object" && value !== null && typeof Reflect.get(value, "fetch") === "function";
|
|
229
|
+
|
|
230
|
+
export const makeClient = <
|
|
231
|
+
Api extends object,
|
|
232
|
+
const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
233
|
+
>(
|
|
234
|
+
definition: ServiceBindingDefinition<Definition>,
|
|
235
|
+
): ((service: ServiceBindingClient<Api>) => ServiceBindingEffectClient<Api, Definition>) => {
|
|
236
|
+
return (service: ServiceBindingClient<Api>) => {
|
|
237
|
+
const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
|
|
238
|
+
Effect.tryPromise({
|
|
239
|
+
try: () => service.fetch(input, init),
|
|
240
|
+
catch: (cause) => new ServiceBindingFetchError({ binding: definition.binding, cause }),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const rpc = <Method extends ServiceMethodKey<Api>>(
|
|
244
|
+
method: Method,
|
|
245
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
246
|
+
) =>
|
|
247
|
+
Effect.gen(function* () {
|
|
248
|
+
const methodName = String(method);
|
|
249
|
+
const encodedArgs =
|
|
250
|
+
definition.definition === undefined
|
|
251
|
+
? args
|
|
252
|
+
: yield* RpcDefinition.encodeArgs(
|
|
253
|
+
definition.definition,
|
|
254
|
+
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
255
|
+
args as never,
|
|
256
|
+
).pipe(
|
|
257
|
+
Effect.mapError(
|
|
258
|
+
(cause) =>
|
|
259
|
+
new ServiceBindingRpcError({
|
|
260
|
+
binding: definition.binding,
|
|
261
|
+
method: methodName,
|
|
262
|
+
cause,
|
|
263
|
+
}),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return yield* RpcInvocation.invokeRpcMethod(
|
|
268
|
+
service,
|
|
269
|
+
method,
|
|
270
|
+
encodedArgs as ServiceMethodArgs<Api, Method>,
|
|
271
|
+
(cause) =>
|
|
272
|
+
new ServiceBindingRpcError({
|
|
273
|
+
binding: definition.binding,
|
|
274
|
+
method: methodName,
|
|
275
|
+
cause,
|
|
276
|
+
}),
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const decodeSuccess = <Method extends ServiceMethodKey<Api>>(
|
|
281
|
+
methodName: string,
|
|
282
|
+
value: Awaited<ServiceMethodCloudflareReturn<Api, Method>>,
|
|
283
|
+
) =>
|
|
284
|
+
Effect.gen(function* () {
|
|
285
|
+
if (definition.definition === undefined) {
|
|
286
|
+
return value as ServiceMethodSuccess<Api, Method>;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const decoded = yield* RpcDefinition.decodeSuccess(
|
|
290
|
+
definition.definition,
|
|
291
|
+
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
292
|
+
value,
|
|
293
|
+
).pipe(
|
|
294
|
+
Effect.mapError(
|
|
295
|
+
(cause) =>
|
|
296
|
+
new ServiceBindingRpcError({
|
|
297
|
+
binding: definition.binding,
|
|
298
|
+
method: methodName,
|
|
299
|
+
cause,
|
|
300
|
+
}),
|
|
301
|
+
),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return decoded as ServiceMethodSuccess<Api, Method>;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const call = <Method extends ServiceMethodKey<Api>>(
|
|
308
|
+
method: Method,
|
|
309
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
310
|
+
) =>
|
|
311
|
+
Effect.gen(function* () {
|
|
312
|
+
const methodName = String(method);
|
|
313
|
+
const value = yield* CloudflareRpc.resolve(yield* rpc(method, ...args)).pipe(
|
|
314
|
+
Effect.mapError(
|
|
315
|
+
(cause) =>
|
|
316
|
+
new ServiceBindingRpcError({
|
|
317
|
+
binding: definition.binding,
|
|
318
|
+
method: methodName,
|
|
319
|
+
cause,
|
|
320
|
+
}),
|
|
321
|
+
),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return yield* decodeSuccess<Method>(methodName, value);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const scopedCall = <Method extends ServiceMethodKey<Api>>(
|
|
328
|
+
method: Method,
|
|
329
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
330
|
+
) =>
|
|
331
|
+
Effect.gen(function* () {
|
|
332
|
+
const methodName = String(method);
|
|
333
|
+
const result = yield* rpc(method, ...args);
|
|
334
|
+
const value = yield* CloudflareRpc.scoped(result);
|
|
335
|
+
return yield* decodeSuccess<Method>(methodName, value);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const directMethods = makeDirectMethods<never, Api, Definition>(definition.definition, call);
|
|
339
|
+
|
|
340
|
+
return Object.assign(directMethods, {
|
|
341
|
+
fetch,
|
|
342
|
+
rpc,
|
|
343
|
+
call,
|
|
344
|
+
scopedCall,
|
|
345
|
+
}) as ServiceBindingEffectClient<Api, Definition>;
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export const layer = <
|
|
350
|
+
Self,
|
|
351
|
+
Api extends object,
|
|
352
|
+
const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
353
|
+
>(
|
|
354
|
+
tag: Context.Service<Self, ServiceBindingEffectClient<Api, Definition>>,
|
|
355
|
+
definition: ServiceBindingDefinition<Definition>,
|
|
356
|
+
) =>
|
|
357
|
+
Binding.layer(
|
|
358
|
+
tag,
|
|
359
|
+
definition.binding,
|
|
360
|
+
(value): value is ServiceBindingClient<Api> => isServiceBindingClient<Api>(value),
|
|
361
|
+
makeClient<Api, Definition>(definition),
|
|
362
|
+
);
|
|
363
|
+
|
|
119
364
|
/**
|
|
120
365
|
* Creates a typed Effect service for a Worker service binding.
|
|
121
366
|
*
|
|
122
367
|
* Returned value includes:
|
|
123
368
|
* - a Context tag for dependency injection
|
|
124
369
|
* - `fetch(...)` for raw HTTP forwarding
|
|
125
|
-
* - `
|
|
370
|
+
* - `rpc(...)` for raw Cloudflare RPC results
|
|
371
|
+
* - `call(...)` for resolved and decoded RPC results
|
|
372
|
+
* - `scopedCall(...)` for scoped and decoded disposable RPC results
|
|
126
373
|
* - direct RPC methods when `definition` is provided
|
|
127
374
|
*
|
|
128
375
|
* @example
|
|
@@ -134,10 +381,11 @@ export type ServiceBindingEffectClient<
|
|
|
134
381
|
* }),
|
|
135
382
|
* });
|
|
136
383
|
*
|
|
137
|
-
* const
|
|
384
|
+
* const CounterLive = Counter.layer({ binding: "COUNTER" });
|
|
138
385
|
*
|
|
139
386
|
* const program = Effect.gen(function* () {
|
|
140
|
-
* const
|
|
387
|
+
* const counter = yield* Counter;
|
|
388
|
+
* const next = yield* counter.increment(1);
|
|
141
389
|
* return next;
|
|
142
390
|
* });
|
|
143
391
|
* ```
|
|
@@ -153,119 +401,11 @@ export const Service =
|
|
|
153
401
|
) => {
|
|
154
402
|
type ServiceApi = ApiOrDefinition<Api, Definition>;
|
|
155
403
|
|
|
156
|
-
const makeClient = (
|
|
157
|
-
service: ServiceBindingClient<ServiceApi>,
|
|
158
|
-
): ServiceBindingEffectClient<ServiceApi, Definition> => {
|
|
159
|
-
const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
|
|
160
|
-
Effect.tryPromise({
|
|
161
|
-
try: () => service.fetch(input, init),
|
|
162
|
-
catch: (cause) => new ServiceBindingFetchError({ binding: definition.binding, cause }),
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const rpc = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
166
|
-
method: Method,
|
|
167
|
-
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
168
|
-
) =>
|
|
169
|
-
Effect.gen(function* () {
|
|
170
|
-
const methodName = String(method);
|
|
171
|
-
const encodedArgs =
|
|
172
|
-
definition.definition === undefined
|
|
173
|
-
? args
|
|
174
|
-
: yield* RpcDefinition.encodeArgs(
|
|
175
|
-
definition.definition,
|
|
176
|
-
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
177
|
-
args as never,
|
|
178
|
-
).pipe(
|
|
179
|
-
Effect.mapError(
|
|
180
|
-
(cause) =>
|
|
181
|
-
new ServiceBindingRpcError({
|
|
182
|
-
binding: definition.binding,
|
|
183
|
-
method: methodName,
|
|
184
|
-
cause,
|
|
185
|
-
}),
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return yield* RpcInvocation.invokeRpcMethod(
|
|
190
|
-
service,
|
|
191
|
-
method,
|
|
192
|
-
encodedArgs as ServiceMethodArgs<ServiceApi, Method>,
|
|
193
|
-
(cause) =>
|
|
194
|
-
new ServiceBindingRpcError({
|
|
195
|
-
binding: definition.binding,
|
|
196
|
-
method: methodName,
|
|
197
|
-
cause,
|
|
198
|
-
}),
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const call = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
203
|
-
method: Method,
|
|
204
|
-
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
205
|
-
) =>
|
|
206
|
-
Effect.gen(function* () {
|
|
207
|
-
const methodName = String(method);
|
|
208
|
-
const value = yield* CloudflareRpc.resolve(yield* rpc(method, ...args)).pipe(
|
|
209
|
-
Effect.mapError(
|
|
210
|
-
(cause) =>
|
|
211
|
-
new ServiceBindingRpcError({
|
|
212
|
-
binding: definition.binding,
|
|
213
|
-
method: methodName,
|
|
214
|
-
cause,
|
|
215
|
-
}),
|
|
216
|
-
),
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
if (definition.definition === undefined) {
|
|
220
|
-
return value as ServiceMethodSuccess<ServiceApi, Method>;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const decoded = yield* RpcDefinition.decodeSuccess(
|
|
224
|
-
definition.definition,
|
|
225
|
-
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
226
|
-
value,
|
|
227
|
-
).pipe(
|
|
228
|
-
Effect.mapError(
|
|
229
|
-
(cause) =>
|
|
230
|
-
new ServiceBindingRpcError({
|
|
231
|
-
binding: definition.binding,
|
|
232
|
-
method: methodName,
|
|
233
|
-
cause,
|
|
234
|
-
}),
|
|
235
|
-
),
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
return decoded as ServiceMethodSuccess<ServiceApi, Method>;
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const scopedCall = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
242
|
-
method: Method,
|
|
243
|
-
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
244
|
-
) =>
|
|
245
|
-
Effect.gen(function* () {
|
|
246
|
-
const result = yield* rpc(method, ...args);
|
|
247
|
-
return yield* CloudflareRpc.scoped(result);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
const directMethods = makeDirectMethods<never, ServiceApi, Definition>(
|
|
251
|
-
definition.definition,
|
|
252
|
-
call,
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
return Object.assign(directMethods, {
|
|
256
|
-
fetch,
|
|
257
|
-
rpc,
|
|
258
|
-
call,
|
|
259
|
-
scopedCall,
|
|
260
|
-
}) as ServiceBindingEffectClient<ServiceApi, Definition>;
|
|
261
|
-
};
|
|
262
|
-
|
|
263
404
|
const tag = Binding.Service<Self>()(
|
|
264
405
|
id,
|
|
265
406
|
definition.binding,
|
|
266
|
-
(value): value is ServiceBindingClient<ServiceApi> =>
|
|
267
|
-
|
|
268
|
-
makeClient,
|
|
407
|
+
(value): value is ServiceBindingClient<ServiceApi> => isServiceBindingClient(value),
|
|
408
|
+
makeClient<ServiceApi, Definition>(definition),
|
|
269
409
|
);
|
|
270
410
|
|
|
271
411
|
const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
|
|
@@ -324,7 +464,11 @@ export const Service =
|
|
|
324
464
|
};
|
|
325
465
|
};
|
|
326
466
|
|
|
327
|
-
const makeDirectMethods = <
|
|
467
|
+
export const makeDirectMethods = <
|
|
468
|
+
R,
|
|
469
|
+
Api,
|
|
470
|
+
Definition extends WorkerDefinition.Definition.Any | undefined,
|
|
471
|
+
>(
|
|
328
472
|
rpcDefinition: Definition | undefined,
|
|
329
473
|
call: ServiceCall<R, Api>,
|
|
330
474
|
): DirectMethods<R, Definition> => {
|