effect-cf 0.5.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 +14 -0
- package/README.md +36 -29
- package/dist/index.d.mts +854 -1216
- package/dist/index.mjs +469 -441
- package/package.json +1 -1
- package/src/Binding.ts +36 -13
- package/src/DurableObject.ts +117 -23
- package/src/DurableObjectDefinition.ts +251 -72
- package/src/DurableObjectNamespace.ts +305 -158
- package/src/Kv.ts +181 -154
- package/src/Queue.ts +18 -4
- package/src/QueueBinding.ts +43 -62
- package/src/QueueDefinition.ts +87 -41
- package/src/ServiceBinding.ts +288 -81
- 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 } 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,86 +74,177 @@ 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
|
-
type ServiceCall<
|
|
82
|
+
type ServiceCall<R, Api> = <Method extends ServiceMethodKey<Api>>(
|
|
79
83
|
method: Method,
|
|
80
84
|
...args: ServiceMethodArgs<Api, Method>
|
|
81
|
-
) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError,
|
|
85
|
+
) => Effect.Effect<ServiceMethodSuccess<Api, Method>, ServiceBindingRpcError, R>;
|
|
82
86
|
|
|
83
|
-
type DefinitionDirectMethods<
|
|
87
|
+
type DefinitionDirectMethods<R, Definition extends WorkerDefinition.Definition.Any> = {
|
|
84
88
|
readonly [Method in RpcDefinition.Definition.MethodNames<Definition>]: (
|
|
85
89
|
...args: RpcDefinition.Method.Args<Definition["methods"][Method]>
|
|
86
90
|
) => Effect.Effect<
|
|
87
91
|
RpcDefinition.Method.Success<Definition["methods"][Method]>,
|
|
88
92
|
ServiceBindingRpcError,
|
|
89
|
-
|
|
93
|
+
R
|
|
90
94
|
>;
|
|
91
95
|
};
|
|
92
96
|
|
|
93
|
-
type DirectMethods<
|
|
94
|
-
? DefinitionDirectMethods<
|
|
97
|
+
type DirectMethods<R, Definition> = Definition extends WorkerDefinition.Definition.Any
|
|
98
|
+
? DefinitionDirectMethods<R, Definition>
|
|
95
99
|
: {};
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
101
|
+
export type ServiceBindingEffectClient<
|
|
102
|
+
Api extends object,
|
|
103
|
+
Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
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
|
+
*/
|
|
121
|
+
readonly fetch: (
|
|
122
|
+
input: RequestInfo | URL,
|
|
123
|
+
init?: RequestInit,
|
|
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
|
+
*/
|
|
148
|
+
readonly rpc: <Method extends ServiceMethodKey<Api>>(
|
|
149
|
+
method: Method,
|
|
150
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
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
|
+
*/
|
|
170
|
+
readonly call: <Method extends ServiceMethodKey<Api>>(
|
|
171
|
+
method: Method,
|
|
172
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
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
|
+
*/
|
|
196
|
+
readonly scopedCall: <Method extends ServiceMethodKey<Api>>(
|
|
197
|
+
method: Method,
|
|
198
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
199
|
+
) => Effect.Effect<Awaited<ServiceMethodSuccess<Api, Method>>, unknown, Scope.Scope>;
|
|
200
|
+
};
|
|
133
201
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
};
|
|
140
224
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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";
|
|
144
229
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 }),
|
|
149
241
|
});
|
|
150
242
|
|
|
151
|
-
const rpc = <Method extends ServiceMethodKey<
|
|
243
|
+
const rpc = <Method extends ServiceMethodKey<Api>>(
|
|
152
244
|
method: Method,
|
|
153
|
-
...args: ServiceMethodArgs<
|
|
245
|
+
...args: ServiceMethodArgs<Api, Method>
|
|
154
246
|
) =>
|
|
155
247
|
Effect.gen(function* () {
|
|
156
|
-
const service = yield* tag;
|
|
157
248
|
const methodName = String(method);
|
|
158
249
|
const encodedArgs =
|
|
159
250
|
definition.definition === undefined
|
|
@@ -176,7 +267,7 @@ export const Service =
|
|
|
176
267
|
return yield* RpcInvocation.invokeRpcMethod(
|
|
177
268
|
service,
|
|
178
269
|
method,
|
|
179
|
-
encodedArgs as ServiceMethodArgs<
|
|
270
|
+
encodedArgs as ServiceMethodArgs<Api, Method>,
|
|
180
271
|
(cause) =>
|
|
181
272
|
new ServiceBindingRpcError({
|
|
182
273
|
binding: definition.binding,
|
|
@@ -186,13 +277,20 @@ export const Service =
|
|
|
186
277
|
);
|
|
187
278
|
});
|
|
188
279
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
280
|
+
const decodeSuccess = <Method extends ServiceMethodKey<Api>>(
|
|
281
|
+
methodName: string,
|
|
282
|
+
value: Awaited<ServiceMethodCloudflareReturn<Api, Method>>,
|
|
192
283
|
) =>
|
|
193
284
|
Effect.gen(function* () {
|
|
194
|
-
|
|
195
|
-
|
|
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(
|
|
196
294
|
Effect.mapError(
|
|
197
295
|
(cause) =>
|
|
198
296
|
new ServiceBindingRpcError({
|
|
@@ -203,15 +301,16 @@ export const Service =
|
|
|
203
301
|
),
|
|
204
302
|
);
|
|
205
303
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
304
|
+
return decoded as ServiceMethodSuccess<Api, Method>;
|
|
305
|
+
});
|
|
209
306
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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(
|
|
215
314
|
Effect.mapError(
|
|
216
315
|
(cause) =>
|
|
217
316
|
new ServiceBindingRpcError({
|
|
@@ -222,7 +321,115 @@ export const Service =
|
|
|
222
321
|
),
|
|
223
322
|
);
|
|
224
323
|
|
|
225
|
-
return
|
|
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
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Creates a typed Effect service for a Worker service binding.
|
|
366
|
+
*
|
|
367
|
+
* Returned value includes:
|
|
368
|
+
* - a Context tag for dependency injection
|
|
369
|
+
* - `fetch(...)` for raw HTTP forwarding
|
|
370
|
+
* - `rpc(...)` for raw Cloudflare RPC results
|
|
371
|
+
* - `call(...)` for resolved and decoded RPC results
|
|
372
|
+
* - `scopedCall(...)` for scoped and decoded disposable RPC results
|
|
373
|
+
* - direct RPC methods when `definition` is provided
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```ts
|
|
377
|
+
* const Counter = WorkerDefinition.make("Counter", {
|
|
378
|
+
* increment: WorkerDefinition.method({
|
|
379
|
+
* args: [Schema.Number],
|
|
380
|
+
* success: Schema.Number,
|
|
381
|
+
* }),
|
|
382
|
+
* });
|
|
383
|
+
*
|
|
384
|
+
* const CounterLive = Counter.layer({ binding: "COUNTER" });
|
|
385
|
+
*
|
|
386
|
+
* const program = Effect.gen(function* () {
|
|
387
|
+
* const counter = yield* Counter;
|
|
388
|
+
* const next = yield* counter.increment(1);
|
|
389
|
+
* return next;
|
|
390
|
+
* });
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
export const Service =
|
|
394
|
+
<Self, Api extends object = never>() =>
|
|
395
|
+
<
|
|
396
|
+
Id extends string,
|
|
397
|
+
const Definition extends WorkerDefinition.Definition.Any | undefined = undefined,
|
|
398
|
+
>(
|
|
399
|
+
id: Id,
|
|
400
|
+
definition: ServiceBindingDefinition<Definition>,
|
|
401
|
+
) => {
|
|
402
|
+
type ServiceApi = ApiOrDefinition<Api, Definition>;
|
|
403
|
+
|
|
404
|
+
const tag = Binding.Service<Self>()(
|
|
405
|
+
id,
|
|
406
|
+
definition.binding,
|
|
407
|
+
(value): value is ServiceBindingClient<ServiceApi> => isServiceBindingClient(value),
|
|
408
|
+
makeClient<ServiceApi, Definition>(definition),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
|
|
412
|
+
Effect.gen(function* () {
|
|
413
|
+
const service = yield* tag;
|
|
414
|
+
return yield* service.fetch(input, init);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const rpc = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
418
|
+
method: Method,
|
|
419
|
+
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
420
|
+
) =>
|
|
421
|
+
Effect.gen(function* () {
|
|
422
|
+
const service = yield* tag;
|
|
423
|
+
return yield* service.rpc(method, ...args);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const call = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
427
|
+
method: Method,
|
|
428
|
+
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
429
|
+
) =>
|
|
430
|
+
Effect.gen(function* () {
|
|
431
|
+
const service = yield* tag;
|
|
432
|
+
return yield* service.call(method, ...args);
|
|
226
433
|
});
|
|
227
434
|
|
|
228
435
|
const scopedCall = <Method extends ServiceMethodKey<ServiceApi>>(
|
|
@@ -230,8 +437,8 @@ export const Service =
|
|
|
230
437
|
...args: ServiceMethodArgs<ServiceApi, Method>
|
|
231
438
|
) =>
|
|
232
439
|
Effect.gen(function* () {
|
|
233
|
-
const
|
|
234
|
-
return yield*
|
|
440
|
+
const service = yield* tag;
|
|
441
|
+
return yield* service.scopedCall(method, ...args);
|
|
235
442
|
});
|
|
236
443
|
|
|
237
444
|
const directMethods = makeDirectMethods<Self, ServiceApi, Definition>(
|
|
@@ -257,14 +464,14 @@ export const Service =
|
|
|
257
464
|
};
|
|
258
465
|
};
|
|
259
466
|
|
|
260
|
-
const makeDirectMethods = <
|
|
261
|
-
|
|
467
|
+
export const makeDirectMethods = <
|
|
468
|
+
R,
|
|
262
469
|
Api,
|
|
263
470
|
Definition extends WorkerDefinition.Definition.Any | undefined,
|
|
264
471
|
>(
|
|
265
472
|
rpcDefinition: Definition | undefined,
|
|
266
|
-
call: ServiceCall<
|
|
267
|
-
): DirectMethods<
|
|
473
|
+
call: ServiceCall<R, Api>,
|
|
474
|
+
): DirectMethods<R, Definition> => {
|
|
268
475
|
const methods = {} as Record<string, unknown>;
|
|
269
476
|
|
|
270
477
|
if (rpcDefinition !== undefined) {
|
|
@@ -274,5 +481,5 @@ const makeDirectMethods = <
|
|
|
274
481
|
}
|
|
275
482
|
}
|
|
276
483
|
|
|
277
|
-
return methods as DirectMethods<
|
|
484
|
+
return methods as DirectMethods<R, Definition>;
|
|
278
485
|
};
|