effect-cf 0.27.0 → 0.28.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 +10 -0
- package/dist/{Artifacts-DcoXcRP5.d.mts → Artifacts-DnHQA-Ax.d.mts} +42 -25
- package/dist/{Artifacts-BhWrdyqX.mjs → Artifacts-rq3hUKQE.mjs} +46 -30
- package/dist/{Binding-COCtoJXa.d.mts → Binding-xJJJPmiD.d.mts} +6 -5
- package/dist/ComputerArtifacts.d.mts +1 -1
- package/dist/ComputerArtifacts.mjs +9 -9
- package/dist/ComputerWorkspace.d.mts +5 -5
- package/dist/ComputerWorkspace.mjs +107 -75
- package/dist/ComputerWorkspaceHost.d.mts +1 -1
- package/dist/ComputerWorkspaceHost.mjs +18 -16
- package/dist/{DurableObjectState-biHrHwrh.d.mts → DurableObjectState-B3uFY1eM.d.mts} +85 -62
- package/dist/{DurableObjectState-Dux8KfjG.mjs → DurableObjectState-BjkK2kwH.mjs} +3 -3
- package/dist/{ErrorMessage-_lg53p3W.mjs → ErrorMessage-BM0K-Nik.mjs} +1 -4
- package/dist/{Hyperdrive-6p59yyVk.d.mts → Hyperdrive-z-9fFz15.d.mts} +2 -2
- package/dist/HyperdrivePg.d.mts +2 -2
- package/dist/Vitest.d.mts +6 -5
- package/dist/Vitest.mjs +2 -2
- package/dist/{WorkflowBinding-zlRPCXVf.d.mts → WorkflowBinding-wZ-yGHow.d.mts} +5 -10
- package/dist/index.d.mts +82 -60
- package/dist/index.mjs +268 -170
- package/package.json +1 -1
- package/src/AiGateway.ts +62 -7
- package/src/AnalyticsEngine.ts +17 -13
- package/src/Artifacts.ts +111 -47
- package/src/Binding.ts +34 -23
- package/src/BrowserRendering.ts +72 -33
- package/src/CloudflareOtlp.ts +8 -5
- package/src/ComputerArtifacts.ts +17 -9
- package/src/ComputerWorkspace.ts +202 -98
- package/src/ComputerWorkspaceHost.ts +55 -27
- package/src/ContainerNamespace.ts +8 -9
- package/src/D1.ts +8 -14
- package/src/DurableObject.ts +27 -11
- package/src/DurableObjectAlarm.ts +17 -5
- package/src/DurableObjectDefinition.ts +89 -72
- package/src/DurableObjectNamespace.ts +26 -12
- package/src/DurableObjectRpcWebSocket.ts +9 -10
- package/src/DurableObjectState.ts +1 -0
- package/src/DurableObjectStorage.ts +5 -2
- package/src/DurableObjectWebSocket.ts +4 -1
- package/src/Email.ts +26 -25
- package/src/Hyperdrive.ts +4 -10
- package/src/Images.ts +12 -14
- package/src/Kv.ts +15 -17
- package/src/Queue.ts +2 -1
- package/src/QueueBinding.ts +7 -12
- package/src/QueueDefinition.ts +7 -2
- package/src/R2.ts +33 -24
- package/src/Rpc.ts +15 -21
- package/src/RpcDefinition.ts +33 -38
- package/src/ServiceBinding.ts +26 -8
- package/src/Vectorize.ts +45 -18
- package/src/Vitest.ts +9 -3
- package/src/WebTransport.ts +11 -5
- package/src/Worker.ts +91 -21
- package/src/WorkerDefinition.ts +143 -63
- package/src/WorkersAi.ts +50 -28
- package/src/Workflow.ts +66 -13
- package/src/WorkflowBinding.ts +18 -25
- package/src/WorkflowDefinition.ts +4 -2
- package/src/internal/Entrypoint.ts +29 -13
- package/src/internal/ErrorMessage.ts +7 -6
- package/src/internal/RpcInvocation.ts +34 -11
- package/src/internal/Runtime.ts +33 -22
- package/src/internal/WorkerContext.ts +3 -3
package/src/QueueDefinition.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type Handler<ROut, Self extends Definition.Any> = (
|
|
|
29
29
|
>;
|
|
30
30
|
|
|
31
31
|
export interface Options<ROut, Self extends Definition.Any> extends Omit<
|
|
32
|
-
WorkerEntrypoint.WorkerOptions<ROut, Record<never, never>>,
|
|
32
|
+
WorkerEntrypoint.WorkerOptions<ROut, never, never, Record<never, never>>,
|
|
33
33
|
"queue" | "rpc"
|
|
34
34
|
> {
|
|
35
35
|
readonly queue: Handler<ROut, Self>;
|
|
@@ -75,7 +75,11 @@ export interface TagClass<
|
|
|
75
75
|
QueueBinding.QueueOperationError,
|
|
76
76
|
Self
|
|
77
77
|
>;
|
|
78
|
-
readonly rawUnsafe: Effect.Effect<
|
|
78
|
+
readonly rawUnsafe: Effect.Effect<
|
|
79
|
+
QueueBinding.QueueProducer<S.Codec.Encoded<Message>>,
|
|
80
|
+
never,
|
|
81
|
+
Self
|
|
82
|
+
>;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const makeDefinition = <Id extends string, Message extends RpcDefinition.ServiceFreeSchema>(
|
|
@@ -148,6 +152,7 @@ export const Tag =
|
|
|
148
152
|
|
|
149
153
|
const rawUnsafe = Effect.flatMap(tag, (queue) => queue.rawUnsafe);
|
|
150
154
|
|
|
155
|
+
// SAFETY: the assigned definition helpers exactly implement TagClass for this queue schema.
|
|
151
156
|
return Object.assign(tag, {
|
|
152
157
|
id: queueDefinition.id,
|
|
153
158
|
message: queueDefinition.message,
|
package/src/R2.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
R2UploadPartOptions as CloudflareR2UploadPartOptions,
|
|
13
13
|
R2UploadedPart as CloudflareR2UploadedPart,
|
|
14
14
|
} from "@cloudflare/workers-types";
|
|
15
|
-
import { Context, Data, Effect, Option, type Layer } from "effect";
|
|
15
|
+
import { Context, Data, Effect, Option, Predicate, Schema, type Layer } from "effect";
|
|
16
16
|
|
|
17
17
|
import * as Binding from "./Binding";
|
|
18
18
|
import type { WorkerEnvironment } from "./Environment";
|
|
@@ -190,11 +190,11 @@ const isR2ObjectBody = (
|
|
|
190
190
|
value: CloudflareR2ObjectBody | CloudflareR2Object,
|
|
191
191
|
): value is CloudflareR2ObjectBody =>
|
|
192
192
|
"body" in value &&
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
Predicate.isFunction(value.arrayBuffer) &&
|
|
194
|
+
Predicate.isFunction(value.bytes) &&
|
|
195
|
+
Predicate.isFunction(value.text) &&
|
|
196
|
+
Predicate.isFunction(value.json) &&
|
|
197
|
+
Predicate.isFunction(value.blob);
|
|
198
198
|
|
|
199
199
|
const wrapObjectBody = (binding: string, object: CloudflareR2ObjectBody): R2ObjectBodyClient => ({
|
|
200
200
|
key: object.key,
|
|
@@ -244,23 +244,29 @@ const wrapGetResult = (
|
|
|
244
244
|
return wrapObjectBody(binding, object);
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
247
|
+
type R2BindingValue = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
248
|
+
|
|
249
|
+
const R2BucketSchema = Schema.declare(
|
|
250
|
+
(value: R2BindingValue): value is CloudflareR2Bucket =>
|
|
251
|
+
Predicate.hasProperty(value, "head") &&
|
|
252
|
+
Predicate.isFunction(value.head) &&
|
|
253
|
+
Predicate.hasProperty(value, "get") &&
|
|
254
|
+
Predicate.isFunction(value.get) &&
|
|
255
|
+
Predicate.hasProperty(value, "put") &&
|
|
256
|
+
Predicate.isFunction(value.put) &&
|
|
257
|
+
Predicate.hasProperty(value, "createMultipartUpload") &&
|
|
258
|
+
Predicate.isFunction(value.createMultipartUpload) &&
|
|
259
|
+
Predicate.hasProperty(value, "resumeMultipartUpload") &&
|
|
260
|
+
Predicate.isFunction(value.resumeMultipartUpload) &&
|
|
261
|
+
Predicate.hasProperty(value, "delete") &&
|
|
262
|
+
Predicate.isFunction(value.delete) &&
|
|
263
|
+
Predicate.hasProperty(value, "list") &&
|
|
264
|
+
Predicate.isFunction(value.list),
|
|
265
|
+
);
|
|
266
|
+
const decodeR2Bucket = Schema.decodeUnknownOption(R2BucketSchema);
|
|
267
|
+
|
|
268
|
+
export const isR2Bucket = (value: R2BindingValue): value is CloudflareR2Bucket =>
|
|
269
|
+
Option.isSome(decodeR2Bucket(value));
|
|
264
270
|
|
|
265
271
|
export const makeClient = (
|
|
266
272
|
definition: R2Definition,
|
|
@@ -294,6 +300,7 @@ export const makeClient = (
|
|
|
294
300
|
});
|
|
295
301
|
|
|
296
302
|
return (bucket) => {
|
|
303
|
+
// SAFETY: the implementation preserves Cloudflare's get overload distinction for onlyIf options.
|
|
297
304
|
const get = Effect.fn(
|
|
298
305
|
"R2.get",
|
|
299
306
|
spanOptions(definition.binding, "get"),
|
|
@@ -314,6 +321,7 @@ export const makeClient = (
|
|
|
314
321
|
),
|
|
315
322
|
),
|
|
316
323
|
get,
|
|
324
|
+
// SAFETY: the implementation preserves Cloudflare's put overload distinction for onlyIf options.
|
|
317
325
|
put: Effect.fn(
|
|
318
326
|
"R2.put",
|
|
319
327
|
spanOptions(definition.binding, "put"),
|
|
@@ -352,7 +360,7 @@ export const makeClient = (
|
|
|
352
360
|
"R2.delete",
|
|
353
361
|
spanOptions(definition.binding, "delete"),
|
|
354
362
|
)((keys: string | ReadonlyArray<string>) => {
|
|
355
|
-
const nativeKeys =
|
|
363
|
+
const nativeKeys = Predicate.isString(keys) ? keys : [...keys];
|
|
356
364
|
|
|
357
365
|
return tryR2Promise(definition.binding, "delete", () => bucket.delete(nativeKeys));
|
|
358
366
|
}),
|
|
@@ -381,6 +389,7 @@ export const Tag =
|
|
|
381
389
|
|
|
382
390
|
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
383
391
|
|
|
392
|
+
// SAFETY: Object.assign attaches exactly the static id/layer members declared by TagClass.
|
|
384
393
|
return Object.assign(tag, {
|
|
385
394
|
id,
|
|
386
395
|
layer: makeLayer,
|
package/src/Rpc.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RpcStub, RpcTarget } from "cloudflare:workers";
|
|
2
|
-
import { Effect, type Scope } from "effect";
|
|
2
|
+
import { Effect, Predicate, Schema, type Scope } from "effect";
|
|
3
3
|
|
|
4
4
|
import { decodeWireError } from "./RpcDefinition";
|
|
5
5
|
|
|
@@ -57,9 +57,7 @@ export type Stubify<T> = T extends Stubable
|
|
|
57
57
|
? ReadonlyArray<Stubify<Value>>
|
|
58
58
|
: T extends BaseType
|
|
59
59
|
? T
|
|
60
|
-
: T extends
|
|
61
|
-
[key: string | number]: unknown;
|
|
62
|
-
}
|
|
60
|
+
: T extends Record<string | number, infer _Value>
|
|
63
61
|
? {
|
|
64
62
|
[Key in keyof T]: Stubify<T[Key]>;
|
|
65
63
|
}
|
|
@@ -76,7 +74,7 @@ export type Result<T> = T extends Stubable
|
|
|
76
74
|
|
|
77
75
|
export type MethodKey<Api> = {
|
|
78
76
|
[Key in keyof Api]-?: Key extends string
|
|
79
|
-
? Api[Key] extends (...args: Array<any>) =>
|
|
77
|
+
? Api[Key] extends (...args: Array<any>) => any
|
|
80
78
|
? Key
|
|
81
79
|
: never
|
|
82
80
|
: never;
|
|
@@ -84,7 +82,7 @@ export type MethodKey<Api> = {
|
|
|
84
82
|
|
|
85
83
|
export type MethodArgs<Api, Method extends keyof Api> = Api[Method] extends (
|
|
86
84
|
...args: infer Args
|
|
87
|
-
) =>
|
|
85
|
+
) => any
|
|
88
86
|
? Args
|
|
89
87
|
: never;
|
|
90
88
|
|
|
@@ -98,13 +96,12 @@ export type DisposableValue = {
|
|
|
98
96
|
[Symbol.dispose](): void;
|
|
99
97
|
};
|
|
100
98
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Symbol.dispose
|
|
105
|
-
typeof (value as { readonly [Symbol.dispose]?: unknown })[Symbol.dispose] === "function";
|
|
99
|
+
type RpcBoundaryValue = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
100
|
+
|
|
101
|
+
export const isDisposable = (value: RpcBoundaryValue): value is DisposableValue =>
|
|
102
|
+
Predicate.hasProperty(value, Symbol.dispose) && Predicate.isFunction(value[Symbol.dispose]);
|
|
106
103
|
|
|
107
|
-
export const dispose = (value:
|
|
104
|
+
export const dispose = (value: RpcBoundaryValue): Effect.Effect<void> =>
|
|
108
105
|
Effect.sync(() => {
|
|
109
106
|
if (isDisposable(value)) {
|
|
110
107
|
value[Symbol.dispose]();
|
|
@@ -112,18 +109,15 @@ export const dispose = (value: unknown): Effect.Effect<void> =>
|
|
|
112
109
|
});
|
|
113
110
|
|
|
114
111
|
export const resolve = <A>(value: A): Effect.Effect<Awaited<A>, unknown> =>
|
|
115
|
-
isPromiseLike(value)
|
|
112
|
+
Predicate.isPromiseLike(value)
|
|
116
113
|
? Effect.tryPromise({
|
|
117
|
-
try: () => value,
|
|
114
|
+
try: () => Promise.resolve(value),
|
|
118
115
|
catch: (cause) => decodeWireError(cause),
|
|
119
116
|
})
|
|
120
|
-
: Effect.sync(() =>
|
|
117
|
+
: Effect.sync(() => {
|
|
118
|
+
// SAFETY: the predicate excluded PromiseLike values, so Awaited<A> is represented by A here.
|
|
119
|
+
return value as Awaited<A>;
|
|
120
|
+
});
|
|
121
121
|
|
|
122
122
|
export const scoped = <A>(value: A): Effect.Effect<Awaited<A>, unknown, Scope.Scope> =>
|
|
123
123
|
Effect.acquireRelease(resolve(value), (resolved) => dispose(resolved));
|
|
124
|
-
|
|
125
|
-
const isPromiseLike = <A>(value: A): value is A & PromiseLike<Awaited<A>> =>
|
|
126
|
-
(typeof value === "object" || typeof value === "function") &&
|
|
127
|
-
value !== null &&
|
|
128
|
-
"then" in value &&
|
|
129
|
-
typeof (value as { readonly then?: unknown }).then === "function";
|
package/src/RpcDefinition.ts
CHANGED
|
@@ -83,7 +83,9 @@ const WireErrorSchema = S.Union([
|
|
|
83
83
|
|
|
84
84
|
const wireErrorKey = "effect-cf/RpcDefinition/wireError";
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
type WireValue = S.Schema.Type<typeof S.Unknown>;
|
|
87
|
+
|
|
88
|
+
export const isWireError = (error: WireValue): error is WireError =>
|
|
87
89
|
error instanceof RpcArgumentCountError ||
|
|
88
90
|
error instanceof RpcArgumentDecodeError ||
|
|
89
91
|
error instanceof RpcSuccessEncodeError;
|
|
@@ -92,7 +94,7 @@ export const isWireError = (error: unknown): error is WireError =>
|
|
|
92
94
|
* Encodes package RPC errors into a plain `Error` envelope so their tags
|
|
93
95
|
* survive Cloudflare RPC serialization, which drops custom error properties.
|
|
94
96
|
*/
|
|
95
|
-
export const encodeWireError = (error:
|
|
97
|
+
export const encodeWireError = (error: WireValue): WireValue => {
|
|
96
98
|
if (!isWireError(error)) {
|
|
97
99
|
return error;
|
|
98
100
|
}
|
|
@@ -105,13 +107,13 @@ export const encodeWireError = (error: unknown): unknown => {
|
|
|
105
107
|
};
|
|
106
108
|
|
|
107
109
|
/** Restores package RPC errors from the {@link encodeWireError} envelope. */
|
|
108
|
-
export const decodeWireError = (cause:
|
|
110
|
+
export const decodeWireError = (cause: WireValue): WireValue => {
|
|
109
111
|
if (!(cause instanceof Error)) {
|
|
110
112
|
return cause;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
try {
|
|
114
|
-
const parsed:
|
|
116
|
+
const parsed: WireValue = S.decodeUnknownSync(S.Unknown)(JSON.parse(cause.message));
|
|
115
117
|
|
|
116
118
|
if (!Predicate.hasProperty(parsed, wireErrorKey)) {
|
|
117
119
|
return cause;
|
|
@@ -193,19 +195,22 @@ export namespace Method {
|
|
|
193
195
|
|
|
194
196
|
export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
|
|
195
197
|
|
|
196
|
-
export type EncodedSuccess<
|
|
198
|
+
export type EncodedSuccess<_Self extends Any> = S.Json;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
export type Methods = Record<string, Method.Any>;
|
|
200
202
|
|
|
201
203
|
export type NoReservedMethods<
|
|
202
|
-
|
|
204
|
+
MethodDefinitions extends Methods,
|
|
203
205
|
Reserved extends string = ReservedMethodName,
|
|
204
|
-
> = Extract<keyof
|
|
206
|
+
> = Extract<keyof MethodDefinitions, Reserved> extends never ? MethodDefinitions : never;
|
|
205
207
|
|
|
206
|
-
export interface Definition<
|
|
208
|
+
export interface Definition<
|
|
209
|
+
Id extends string = string,
|
|
210
|
+
MethodDefinitions extends Methods = Methods,
|
|
211
|
+
> {
|
|
207
212
|
readonly id: Id;
|
|
208
|
-
readonly methods:
|
|
213
|
+
readonly methods: MethodDefinitions;
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
export namespace Definition {
|
|
@@ -225,9 +230,13 @@ export namespace Definition {
|
|
|
225
230
|
export type MethodNames<Self extends Any> = Extract<keyof Self["methods"], string>;
|
|
226
231
|
}
|
|
227
232
|
|
|
228
|
-
|
|
233
|
+
type ReservedMethodValue = S.Schema.Type<typeof S.Unknown>;
|
|
234
|
+
|
|
235
|
+
export const assertNoReservedMethods = <
|
|
236
|
+
MethodDefinitions extends Readonly<Record<string, ReservedMethodValue>>,
|
|
237
|
+
>(
|
|
229
238
|
target: string,
|
|
230
|
-
methods:
|
|
239
|
+
methods: MethodDefinitions,
|
|
231
240
|
reserved: ReadonlySet<string>,
|
|
232
241
|
) => {
|
|
233
242
|
for (const method of Object.keys(methods)) {
|
|
@@ -276,11 +285,8 @@ export const decodeArgs = Effect.fnUntraced(function* <
|
|
|
276
285
|
});
|
|
277
286
|
}
|
|
278
287
|
|
|
279
|
-
const decoded = yield* (
|
|
280
|
-
|
|
281
|
-
unknown,
|
|
282
|
-
unknown
|
|
283
|
-
>
|
|
288
|
+
const decoded = yield* S.decodeUnknownEffect(wireCodec(S.Tuple(methodDefinition.args)))(
|
|
289
|
+
args,
|
|
284
290
|
).pipe(
|
|
285
291
|
Effect.mapError(
|
|
286
292
|
(cause) =>
|
|
@@ -292,6 +298,7 @@ export const decodeArgs = Effect.fnUntraced(function* <
|
|
|
292
298
|
),
|
|
293
299
|
);
|
|
294
300
|
|
|
301
|
+
// SAFETY: the tuple codec is constructed from this exact method's argument schemas.
|
|
295
302
|
return decoded as Method.Args<Self["methods"][MethodName]>;
|
|
296
303
|
});
|
|
297
304
|
|
|
@@ -317,11 +324,8 @@ export const encodeArgs = Effect.fnUntraced(function* <
|
|
|
317
324
|
});
|
|
318
325
|
}
|
|
319
326
|
|
|
320
|
-
const encoded = yield* (
|
|
321
|
-
|
|
322
|
-
unknown,
|
|
323
|
-
unknown
|
|
324
|
-
>
|
|
327
|
+
const encoded = yield* S.encodeUnknownEffect(wireCodec(S.Tuple(methodDefinition.args)))(
|
|
328
|
+
args,
|
|
325
329
|
).pipe(
|
|
326
330
|
Effect.mapError(
|
|
327
331
|
(cause) =>
|
|
@@ -333,6 +337,7 @@ export const encodeArgs = Effect.fnUntraced(function* <
|
|
|
333
337
|
),
|
|
334
338
|
);
|
|
335
339
|
|
|
340
|
+
// SAFETY: the tuple codec is constructed from this exact method's argument schemas.
|
|
336
341
|
return encoded as Method.EncodedArgs<Self["methods"][MethodName]>;
|
|
337
342
|
});
|
|
338
343
|
|
|
@@ -346,12 +351,7 @@ export const encodeSuccess = <
|
|
|
346
351
|
): Effect.Effect<Method.EncodedSuccess<Self["methods"][MethodName]>, RpcSuccessEncodeError> => {
|
|
347
352
|
const methodDefinition = definition.methods[methodName];
|
|
348
353
|
|
|
349
|
-
return (
|
|
350
|
-
S.encodeUnknownEffect(wireCodec(methodDefinition.success))(value) as Effect.Effect<
|
|
351
|
-
Method.EncodedSuccess<Self["methods"][MethodName]>,
|
|
352
|
-
unknown
|
|
353
|
-
>
|
|
354
|
-
).pipe(
|
|
354
|
+
return S.encodeUnknownEffect(wireCodec(methodDefinition.success))(value).pipe(
|
|
355
355
|
Effect.mapError(
|
|
356
356
|
(cause) =>
|
|
357
357
|
new RpcSuccessEncodeError({
|
|
@@ -369,16 +369,11 @@ export const decodeSuccess = <
|
|
|
369
369
|
>(
|
|
370
370
|
definition: Self,
|
|
371
371
|
methodName: MethodName,
|
|
372
|
-
value:
|
|
372
|
+
value: WireValue,
|
|
373
373
|
): Effect.Effect<Method.Success<Self["methods"][MethodName]>, RpcSuccessDecodeError> => {
|
|
374
374
|
const methodDefinition = definition.methods[methodName];
|
|
375
375
|
|
|
376
|
-
return (
|
|
377
|
-
S.decodeUnknownEffect(wireCodec(methodDefinition.success))(value) as Effect.Effect<
|
|
378
|
-
Method.Success<Self["methods"][MethodName]>,
|
|
379
|
-
unknown
|
|
380
|
-
>
|
|
381
|
-
).pipe(
|
|
376
|
+
return S.decodeUnknownEffect(wireCodec(methodDefinition.success))(value).pipe(
|
|
382
377
|
Effect.mapError(
|
|
383
378
|
(cause) =>
|
|
384
379
|
new RpcSuccessDecodeError({
|
|
@@ -390,11 +385,11 @@ export const decodeSuccess = <
|
|
|
390
385
|
);
|
|
391
386
|
};
|
|
392
387
|
|
|
393
|
-
export const make = <Id extends string, const
|
|
388
|
+
export const make = <Id extends string, const MethodDefinitions extends Methods>(
|
|
394
389
|
id: Id,
|
|
395
|
-
methods:
|
|
396
|
-
): Definition<Id,
|
|
390
|
+
methods: MethodDefinitions,
|
|
391
|
+
): Definition<Id, MethodDefinitions> => {
|
|
397
392
|
assertNoReservedMethods(id, methods, reservedMethodNames);
|
|
398
393
|
|
|
399
|
-
return { id, methods }
|
|
394
|
+
return { id, methods };
|
|
400
395
|
};
|
package/src/ServiceBinding.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect, type Context, type Scope } from "effect";
|
|
1
|
+
import { Data, Effect, Option, Predicate, Schema, type Context, type Scope } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import * as CloudflareRpc from "./Rpc";
|
|
@@ -19,7 +19,7 @@ export interface ServiceFetcher {
|
|
|
19
19
|
|
|
20
20
|
type RpcClient<Api> = {
|
|
21
21
|
readonly [Key in keyof Api as Key extends string
|
|
22
|
-
? Api[Key] extends (...args: Array<any>) =>
|
|
22
|
+
? Api[Key] extends (...args: Array<any>) => any
|
|
23
23
|
? Key
|
|
24
24
|
: never
|
|
25
25
|
: never]: Api[Key];
|
|
@@ -232,10 +232,17 @@ export type ServiceBindingStaticClient<
|
|
|
232
232
|
) => Effect.Effect<Awaited<ServiceMethodSuccess<Api, Method>>, unknown, Scope.Scope | R>;
|
|
233
233
|
};
|
|
234
234
|
|
|
235
|
+
type ServiceBindingValue = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
236
|
+
|
|
237
|
+
const ServiceBindingSchema = Schema.declare(
|
|
238
|
+
(value: ServiceBindingValue): value is ServiceFetcher =>
|
|
239
|
+
Predicate.hasProperty(value, "fetch") && Predicate.isFunction(value.fetch),
|
|
240
|
+
);
|
|
241
|
+
const decodeServiceBinding = Schema.decodeUnknownOption(ServiceBindingSchema);
|
|
242
|
+
|
|
235
243
|
export const isServiceBindingClient = <Api extends object>(
|
|
236
|
-
value:
|
|
237
|
-
): value is ServiceBindingClient<Api> =>
|
|
238
|
-
typeof value === "object" && value !== null && typeof Reflect.get(value, "fetch") === "function";
|
|
244
|
+
value: ServiceBindingValue,
|
|
245
|
+
): value is ServiceBindingClient<Api> => Option.isSome(decodeServiceBinding(value));
|
|
239
246
|
|
|
240
247
|
export const makeClient = <
|
|
241
248
|
Api extends object,
|
|
@@ -259,6 +266,7 @@ export const makeClient = <
|
|
|
259
266
|
...args: ServiceMethodArgs<Api, Method>
|
|
260
267
|
) {
|
|
261
268
|
const methodName = String(method);
|
|
269
|
+
// SAFETY: definition-backed clients derive Method and Args from this exact RPC definition.
|
|
262
270
|
const encodedArgs =
|
|
263
271
|
definition.definition === undefined
|
|
264
272
|
? args
|
|
@@ -277,6 +285,7 @@ export const makeClient = <
|
|
|
277
285
|
),
|
|
278
286
|
);
|
|
279
287
|
|
|
288
|
+
// SAFETY: encoding preserves the selected API method's positional argument contract.
|
|
280
289
|
return yield* RpcInvocation.invokeRpcMethod(
|
|
281
290
|
service,
|
|
282
291
|
method,
|
|
@@ -295,9 +304,11 @@ export const makeClient = <
|
|
|
295
304
|
value: Awaited<ServiceMethodCloudflareReturn<Api, Method>>,
|
|
296
305
|
) {
|
|
297
306
|
if (definition.definition === undefined) {
|
|
307
|
+
// SAFETY: without a definition the resolved native RPC value is the API method's declared success.
|
|
298
308
|
return value as ServiceMethodSuccess<Api, Method>;
|
|
299
309
|
}
|
|
300
310
|
|
|
311
|
+
// SAFETY: definition-backed clients derive the method name from this exact RPC definition.
|
|
301
312
|
const decoded = yield* RpcDefinition.decodeSuccess(
|
|
302
313
|
definition.definition,
|
|
303
314
|
methodName as RpcDefinition.Definition.MethodNames<NonNullable<Definition>>,
|
|
@@ -313,6 +324,7 @@ export const makeClient = <
|
|
|
313
324
|
),
|
|
314
325
|
);
|
|
315
326
|
|
|
327
|
+
// SAFETY: decodeSuccess validates against the selected method's declared success schema.
|
|
316
328
|
return decoded as ServiceMethodSuccess<Api, Method>;
|
|
317
329
|
});
|
|
318
330
|
|
|
@@ -347,6 +359,7 @@ export const makeClient = <
|
|
|
347
359
|
|
|
348
360
|
const directMethods = makeDirectMethods<never, Api, Definition>(definition.definition, call);
|
|
349
361
|
|
|
362
|
+
// SAFETY: Object.assign attaches the four client operations required by ServiceBindingEffectClient.
|
|
350
363
|
return Object.assign(directMethods, {
|
|
351
364
|
fetch,
|
|
352
365
|
rpc,
|
|
@@ -457,6 +470,7 @@ export const Service =
|
|
|
457
470
|
call,
|
|
458
471
|
);
|
|
459
472
|
|
|
473
|
+
// SAFETY: Object.assign attaches the definition-derived service metadata and static operations.
|
|
460
474
|
return Object.assign(tag, directMethods, {
|
|
461
475
|
[TypeId]: TypeId,
|
|
462
476
|
definition,
|
|
@@ -483,14 +497,18 @@ export const makeDirectMethods = <
|
|
|
483
497
|
rpcDefinition: Definition | undefined,
|
|
484
498
|
call: ServiceCall<R, Api>,
|
|
485
499
|
): DirectMethods<R, Definition> => {
|
|
486
|
-
|
|
500
|
+
type DynamicMethod = (...args: Array<any>) => Effect.Effect<any, ServiceBindingRpcError, R>;
|
|
501
|
+
const methods: Record<string, DynamicMethod> = {};
|
|
487
502
|
|
|
488
503
|
if (rpcDefinition !== undefined) {
|
|
504
|
+
// SAFETY: rpcDefinition limits these names and tuples to the same definition used to construct call.
|
|
505
|
+
const invoke = call as (method: string, ...args: Array<any>) => ReturnType<DynamicMethod>;
|
|
506
|
+
|
|
489
507
|
for (const methodName of Object.keys(rpcDefinition.methods)) {
|
|
490
|
-
methods[methodName] = (...args: Array<
|
|
491
|
-
(call as (method: string, ...args: Array<unknown>) => unknown)(methodName, ...args);
|
|
508
|
+
methods[methodName] = (...args: Array<any>) => invoke(methodName, ...args);
|
|
492
509
|
}
|
|
493
510
|
}
|
|
494
511
|
|
|
512
|
+
// SAFETY: every declared definition method is populated above with a forwarding Effect function.
|
|
495
513
|
return methods as DirectMethods<R, Definition>;
|
|
496
514
|
};
|
package/src/Vectorize.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Data, Effect, type Layer } from "effect";
|
|
1
|
+
import { Context, Data, Effect, Option, Predicate, Schema, type Layer } from "effect";
|
|
2
2
|
import type {
|
|
3
3
|
Vectorize as CloudflareVectorize,
|
|
4
4
|
VectorizeAsyncMutation as CloudflareVectorizeAsyncMutation,
|
|
@@ -157,22 +157,43 @@ const spanOptions = (binding: string, operation: VectorizeOperation) => ({
|
|
|
157
157
|
attributes: { binding, operation },
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
typeof Reflect.get(value, key) === "function";
|
|
160
|
+
type VectorizeBindingValue = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
const invokeVectorizeMethod = <Args extends Array<any>, Return>(
|
|
163
|
+
method: (...args: Args) => Return,
|
|
164
|
+
receiver: VectorizeRuntimeBinding,
|
|
165
|
+
args: Args,
|
|
166
|
+
): Return => {
|
|
167
|
+
const result: VectorizeBindingValue = Function.prototype.apply.call(method, receiver, args);
|
|
168
|
+
|
|
169
|
+
// SAFETY: the native apply intrinsic returns method's declared Return without reading proxy properties.
|
|
170
|
+
return result as Return;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const VectorizeBindingSchema = Schema.declare(
|
|
174
|
+
(value: VectorizeBindingValue): value is VectorizeBinding =>
|
|
175
|
+
Predicate.hasProperty(value, "describe") &&
|
|
176
|
+
Predicate.isFunction(value.describe) &&
|
|
177
|
+
Predicate.hasProperty(value, "query") &&
|
|
178
|
+
Predicate.isFunction(value.query) &&
|
|
179
|
+
Predicate.hasProperty(value, "insert") &&
|
|
180
|
+
Predicate.isFunction(value.insert) &&
|
|
181
|
+
Predicate.hasProperty(value, "upsert") &&
|
|
182
|
+
Predicate.isFunction(value.upsert) &&
|
|
183
|
+
Predicate.hasProperty(value, "deleteByIds") &&
|
|
184
|
+
Predicate.isFunction(value.deleteByIds) &&
|
|
185
|
+
Predicate.hasProperty(value, "getByIds") &&
|
|
186
|
+
Predicate.isFunction(value.getByIds),
|
|
187
|
+
);
|
|
188
|
+
const decodeVectorizeBinding = Schema.decodeUnknownOption(VectorizeBindingSchema);
|
|
189
|
+
|
|
190
|
+
export const isVectorizeBinding = (value: VectorizeBindingValue): value is VectorizeBinding =>
|
|
191
|
+
Option.isSome(decodeVectorizeBinding(value));
|
|
172
192
|
|
|
173
193
|
export const makeClient =
|
|
174
194
|
(definition: VectorizeDefinition) =>
|
|
175
195
|
(index: VectorizeBinding): VectorizeClient => {
|
|
196
|
+
// SAFETY: isVectorizeBinding validates every required runtime method before makeClient is called.
|
|
176
197
|
const runtime = index as VectorizeRuntimeBinding;
|
|
177
198
|
const deleteByIds = Effect.fn(
|
|
178
199
|
"Vectorize.deleteByIds",
|
|
@@ -190,18 +211,23 @@ export const makeClient =
|
|
|
190
211
|
"Vectorize.query",
|
|
191
212
|
spanOptions(definition.binding, "query"),
|
|
192
213
|
)((vector: VectorizeValues, options?: VectorizeQueryOptions) =>
|
|
193
|
-
tryVectorizePromise(definition.binding, "query", () =>
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
tryVectorizePromise(definition.binding, "query", () => {
|
|
215
|
+
// SAFETY: Cloudflare accepts these vector containers without mutating the readonly array variant.
|
|
216
|
+
return runtime.query(vector as Float32Array | Float64Array | number[], options);
|
|
217
|
+
}),
|
|
196
218
|
),
|
|
197
219
|
queryById: Effect.fn(
|
|
198
220
|
"Vectorize.queryById",
|
|
199
221
|
spanOptions(definition.binding, "queryById"),
|
|
200
222
|
)((vectorId: string, options?: VectorizeQueryOptions) =>
|
|
201
223
|
runtime.queryById !== undefined
|
|
202
|
-
? tryVectorizePromise(definition.binding, "queryById", () =>
|
|
203
|
-
runtime.queryById
|
|
204
|
-
|
|
224
|
+
? tryVectorizePromise(definition.binding, "queryById", () => {
|
|
225
|
+
const queryById = runtime.queryById;
|
|
226
|
+
|
|
227
|
+
return queryById === undefined
|
|
228
|
+
? Promise.reject(new TypeError("Vectorize binding does not expose queryById()"))
|
|
229
|
+
: invokeVectorizeMethod(queryById, runtime, [vectorId, options]);
|
|
230
|
+
})
|
|
205
231
|
: Effect.fail(
|
|
206
232
|
vectorizeError(
|
|
207
233
|
definition.binding,
|
|
@@ -251,6 +277,7 @@ export const Tag =
|
|
|
251
277
|
|
|
252
278
|
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
253
279
|
|
|
280
|
+
// SAFETY: Object.assign attaches exactly the static id/layer members declared by TagClass.
|
|
254
281
|
return Object.assign(tag, {
|
|
255
282
|
id,
|
|
256
283
|
layer: makeLayer,
|
package/src/Vitest.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
runInDurableObject as runInDurableObjectPromise,
|
|
25
25
|
waitOnExecutionContext,
|
|
26
26
|
} from "cloudflare:test";
|
|
27
|
-
import { ConfigProvider, Effect, Exit, Layer } from "effect";
|
|
27
|
+
import { ConfigProvider, Effect, Exit, Layer, Schema } from "effect";
|
|
28
28
|
|
|
29
29
|
import {
|
|
30
30
|
DurableObjectState,
|
|
@@ -148,6 +148,7 @@ export const pages = Effect.fn("effect-cf/vitest/pages")(function* <
|
|
|
148
148
|
>(handler: Function, init: PagesEventContextInit<Function>) {
|
|
149
149
|
return yield* Effect.acquireUseRelease(
|
|
150
150
|
Effect.sync(() =>
|
|
151
|
+
// SAFETY: PagesEventContextInit computes the same conditional initializer accepted by this Function.
|
|
151
152
|
createPagesEventContext<Function>(
|
|
152
153
|
init as Parameters<typeof createPagesEventContext<Function>>[0],
|
|
153
154
|
),
|
|
@@ -362,7 +363,10 @@ export interface WorkflowMockEvent {
|
|
|
362
363
|
export interface WorkflowInstanceModifier {
|
|
363
364
|
readonly disableSleeps: (steps?: ReadonlyArray<WorkflowStepTarget>) => Effect.Effect<void>;
|
|
364
365
|
readonly disableRetryDelays: (steps?: ReadonlyArray<WorkflowStepTarget>) => Effect.Effect<void>;
|
|
365
|
-
readonly mockStepResult: (
|
|
366
|
+
readonly mockStepResult: (
|
|
367
|
+
step: WorkflowStepTarget,
|
|
368
|
+
result: WorkflowMockResult,
|
|
369
|
+
) => Effect.Effect<void>;
|
|
366
370
|
readonly mockStepError: (
|
|
367
371
|
step: WorkflowStepTarget,
|
|
368
372
|
error: Error,
|
|
@@ -373,6 +377,8 @@ export interface WorkflowInstanceModifier {
|
|
|
373
377
|
readonly forceEventTimeout: (step: WorkflowStepTarget) => Effect.Effect<void>;
|
|
374
378
|
}
|
|
375
379
|
|
|
380
|
+
type WorkflowMockResult = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
381
|
+
|
|
376
382
|
/** Effect-native introspector for one Workflow instance. */
|
|
377
383
|
export interface WorkflowInstanceIntrospector {
|
|
378
384
|
readonly raw: NativeWorkflowInstanceIntrospector;
|
|
@@ -413,7 +419,7 @@ const wrapWorkflowModifier = (
|
|
|
413
419
|
});
|
|
414
420
|
|
|
415
421
|
const runWorkflowModifier = Effect.fnUntraced(function* <A, E, R>(
|
|
416
|
-
register: (use: (modifier: NativeWorkflowInstanceModifier) => Promise<void>) => Promise<
|
|
422
|
+
register: (use: (modifier: NativeWorkflowInstanceModifier) => Promise<void>) => Promise<any>,
|
|
417
423
|
use: (modifier: WorkflowInstanceModifier) => Effect.Effect<A, E, R>,
|
|
418
424
|
): Effect.fn.Return<A, E, R> {
|
|
419
425
|
const context = yield* Effect.context<R>();
|
package/src/WebTransport.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the edge does provide.
|
|
31
31
|
*/
|
|
32
32
|
import type { Request as CloudflareRequest } from "@cloudflare/workers-types";
|
|
33
|
-
import { Data, Effect, Option, Result, Schema as S } from "effect";
|
|
33
|
+
import { Data, Effect, Option, Predicate, Result, Schema as S } from "effect";
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Metadata Cloudflare's edge reports about the inbound client connection.
|
|
@@ -92,10 +92,16 @@ export interface Capabilities {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** Feature-detects the WebTransport capabilities of the current runtime. */
|
|
95
|
-
export const capabilities: Effect.Effect<Capabilities> = Effect.sync(() =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
export const capabilities: Effect.Effect<Capabilities> = Effect.sync(() => {
|
|
96
|
+
const runtime = globalThis;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
inboundSessions: false,
|
|
100
|
+
outboundSessions:
|
|
101
|
+
Predicate.hasProperty(runtime, "WebTransport") &&
|
|
102
|
+
Predicate.isFunction(runtime["WebTransport"]),
|
|
103
|
+
};
|
|
104
|
+
});
|
|
99
105
|
|
|
100
106
|
/** WebTransport capability missing from the current Workers runtime. */
|
|
101
107
|
export type WebTransportCapability = "inbound-sessions" | "outbound-sessions";
|