effect-cf 0.20.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/{Hyperdrive-RTjgirMV.d.mts → Hyperdrive-COyg7R26.d.mts} +40 -13
  4. package/dist/HyperdrivePg.d.mts +13 -5
  5. package/dist/HyperdrivePg.mjs +12 -7
  6. package/dist/index.d.mts +708 -549
  7. package/dist/index.mjs +924 -587
  8. package/package.json +10 -5
  9. package/src/AiGateway.ts +82 -47
  10. package/src/AnalyticsEngine.ts +74 -58
  11. package/src/Binding.ts +66 -13
  12. package/src/BrowserRendering.ts +30 -7
  13. package/src/Cache.ts +32 -8
  14. package/src/CloudflareOtlp.ts +32 -9
  15. package/src/ContainerNamespace.ts +87 -55
  16. package/src/D1.ts +13 -3
  17. package/src/DurableObject.ts +27 -164
  18. package/src/DurableObjectAlarm.ts +30 -9
  19. package/src/DurableObjectDefinition.ts +58 -54
  20. package/src/DurableObjectNamespace.ts +99 -96
  21. package/src/DurableObjectRpcWebSocket.ts +14 -6
  22. package/src/DurableObjectSqlite.ts +6 -3
  23. package/src/DurableObjectState.ts +6 -9
  24. package/src/DurableObjectStorage.ts +7 -1
  25. package/src/DurableObjectWebSocket.ts +51 -41
  26. package/src/Email.ts +40 -13
  27. package/src/Environment.ts +26 -6
  28. package/src/Hyperdrive.ts +2 -2
  29. package/src/HyperdrivePg.ts +25 -13
  30. package/src/Images.ts +77 -82
  31. package/src/Kv.ts +37 -27
  32. package/src/Queue.ts +6 -1
  33. package/src/QueueBinding.ts +18 -21
  34. package/src/QueueDefinition.ts +15 -14
  35. package/src/R2.ts +107 -29
  36. package/src/Rpc.ts +3 -1
  37. package/src/RpcDefinition.ts +177 -133
  38. package/src/ServiceBinding.ts +112 -108
  39. package/src/Vectorize.ts +59 -14
  40. package/src/Worker.ts +172 -192
  41. package/src/WorkerDefinition.ts +5 -10
  42. package/src/WorkersAi.ts +27 -17
  43. package/src/Workflow.ts +54 -49
  44. package/src/WorkflowBinding.ts +19 -11
  45. package/src/WorkflowDefinition.ts +24 -27
  46. package/src/index.ts +4 -3
  47. package/src/internal/Entrypoint.ts +3 -1
  48. package/src/internal/ErrorMessage.ts +24 -0
  49. package/src/internal/RpcInvocation.ts +11 -8
  50. package/src/internal/Runtime.ts +52 -0
  51. package/src/internal/WorkerContext.ts +14 -2
  52. package/src/Entry.ts +0 -259
  53. package/src/internal/Clock.ts +0 -37
@@ -1,44 +1,127 @@
1
- import { Data, Effect, Schema as S } from "effect";
1
+ import { Data, Effect, Predicate, Schema as S } from "effect";
2
2
 
3
3
  import type * as Rpc from "./Rpc";
4
+ import * as ErrorMessage from "./internal/ErrorMessage";
4
5
 
5
6
  export class RpcReservedMethodNameError extends Data.TaggedError("RpcReservedMethodNameError")<{
6
7
  readonly definition: string;
7
8
  readonly method: string;
8
- }> {}
9
+ }> {
10
+ override get message() {
11
+ return `${this.definition} RPC method "${this.method}" is reserved by Cloudflare Workers RPC`;
12
+ }
13
+ }
9
14
 
10
- export class RpcArgumentCountError extends Data.TaggedError("RpcArgumentCountError")<{
11
- readonly definition: string;
12
- readonly method: string;
13
- readonly expected: number;
14
- readonly actual: number;
15
- }> {}
15
+ export class RpcArgumentCountError extends S.TaggedError<RpcArgumentCountError>()(
16
+ "RpcArgumentCountError",
17
+ {
18
+ definition: S.String,
19
+ method: S.String,
20
+ expected: S.Number,
21
+ actual: S.Number,
22
+ },
23
+ ) {
24
+ override get message(): string {
25
+ return `${this.definition} RPC method "${this.method}" expected ${this.expected} arguments but received ${this.actual}`;
26
+ }
27
+ }
16
28
 
17
- export class RpcArgumentDecodeError extends Data.TaggedError("RpcArgumentDecodeError")<{
18
- readonly definition: string;
19
- readonly method: string;
20
- readonly index: number;
21
- readonly cause: unknown;
22
- }> {}
29
+ export class RpcArgumentDecodeError extends S.TaggedError<RpcArgumentDecodeError>()(
30
+ "RpcArgumentDecodeError",
31
+ {
32
+ definition: S.String,
33
+ method: S.String,
34
+ cause: S.Defect(),
35
+ },
36
+ ) {
37
+ override get message(): string {
38
+ return `${this.definition} RPC method "${this.method}" argument decode failed: ${ErrorMessage.causeMessage(this.cause)}`;
39
+ }
40
+ }
23
41
 
24
42
  export class RpcArgumentEncodeError extends Data.TaggedError("RpcArgumentEncodeError")<{
25
43
  readonly definition: string;
26
44
  readonly method: string;
27
- readonly index: number;
28
45
  readonly cause: unknown;
29
- }> {}
46
+ }> {
47
+ override get message(): string {
48
+ return `${this.definition} RPC method "${this.method}" argument encode failed: ${ErrorMessage.causeMessage(this.cause)}`;
49
+ }
50
+ }
30
51
 
31
52
  export class RpcSuccessDecodeError extends Data.TaggedError("RpcSuccessDecodeError")<{
32
53
  readonly definition: string;
33
54
  readonly method: string;
34
55
  readonly cause: unknown;
35
- }> {}
56
+ }> {
57
+ override get message(): string {
58
+ return `${this.definition} RPC method "${this.method}" success decode failed: ${ErrorMessage.causeMessage(this.cause)}`;
59
+ }
60
+ }
36
61
 
37
- export class RpcSuccessEncodeError extends Data.TaggedError("RpcSuccessEncodeError")<{
38
- readonly definition: string;
39
- readonly method: string;
40
- readonly cause: unknown;
41
- }> {}
62
+ export class RpcSuccessEncodeError extends S.TaggedError<RpcSuccessEncodeError>()(
63
+ "RpcSuccessEncodeError",
64
+ {
65
+ definition: S.String,
66
+ method: S.String,
67
+ cause: S.Defect(),
68
+ },
69
+ ) {
70
+ override get message(): string {
71
+ return `${this.definition} RPC method "${this.method}" success encode failed: ${ErrorMessage.causeMessage(this.cause)}`;
72
+ }
73
+ }
74
+
75
+ /** Errors raised server-side that must survive the Cloudflare RPC wire. */
76
+ export type WireError = RpcArgumentCountError | RpcArgumentDecodeError | RpcSuccessEncodeError;
77
+
78
+ const WireErrorSchema = S.Union([
79
+ RpcArgumentCountError,
80
+ RpcArgumentDecodeError,
81
+ RpcSuccessEncodeError,
82
+ ]);
83
+
84
+ const wireErrorKey = "effect-cf/RpcDefinition/wireError";
85
+
86
+ export const isWireError = (error: unknown): error is WireError =>
87
+ error instanceof RpcArgumentCountError ||
88
+ error instanceof RpcArgumentDecodeError ||
89
+ error instanceof RpcSuccessEncodeError;
90
+
91
+ /**
92
+ * Encodes package RPC errors into a plain `Error` envelope so their tags
93
+ * survive Cloudflare RPC serialization, which drops custom error properties.
94
+ */
95
+ export const encodeWireError = (error: unknown): unknown => {
96
+ if (!isWireError(error)) {
97
+ return error;
98
+ }
99
+
100
+ try {
101
+ return new Error(JSON.stringify({ [wireErrorKey]: S.encodeSync(WireErrorSchema)(error) }));
102
+ } catch {
103
+ return error;
104
+ }
105
+ };
106
+
107
+ /** Restores package RPC errors from the {@link encodeWireError} envelope. */
108
+ export const decodeWireError = (cause: unknown): unknown => {
109
+ if (!(cause instanceof Error)) {
110
+ return cause;
111
+ }
112
+
113
+ try {
114
+ const parsed: unknown = JSON.parse(cause.message);
115
+
116
+ if (!Predicate.hasProperty(parsed, wireErrorKey)) {
117
+ return cause;
118
+ }
119
+
120
+ return S.decodeUnknownSync(WireErrorSchema)(parsed[wireErrorKey]);
121
+ } catch {
122
+ return cause;
123
+ }
124
+ };
42
125
 
43
126
  export const reservedMethodNames = new Set([
44
127
  "constructor",
@@ -71,6 +154,14 @@ export type ReservedMethodName =
71
154
 
72
155
  export type ServiceFreeSchema = S.Codec<any, any, never, never>;
73
156
 
157
+ /**
158
+ * Workers RPC structured-clones every value that crosses an isolate boundary
159
+ * and rejects class instances. Declaration schemas such as `Schema.Result`
160
+ * keep their container instance in their `Encoded` form, so the declared
161
+ * schemas are lowered to their canonical JSON codec before touching the wire.
162
+ */
163
+ const wireCodec = <Schema extends S.Constraint>(schema: Schema) => S.toCodecJson(schema);
164
+
74
165
  export interface Method<
75
166
  Args extends ReadonlyArray<ServiceFreeSchema> = ReadonlyArray<ServiceFreeSchema>,
76
167
  Success extends ServiceFreeSchema = ServiceFreeSchema,
@@ -91,15 +182,10 @@ export namespace Method {
91
182
  ? [S.Schema.Type<Head>, ...ArgsFromSchemas<Tail>]
92
183
  : Array<S.Schema.Type<Args[number]>>;
93
184
 
94
- type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> =
95
- Args extends readonly []
96
- ? []
97
- : Args extends readonly [
98
- infer Head extends ServiceFreeSchema,
99
- ...infer Tail extends ReadonlyArray<ServiceFreeSchema>,
100
- ]
101
- ? [S.Codec.Encoded<Head>, ...EncodedArgsFromSchemas<Tail>]
102
- : Array<S.Codec.Encoded<Args[number]>>;
185
+ /** Method schemas cross the wire through their canonical JSON codec. */
186
+ type EncodedArgsFromSchemas<Args extends ReadonlyArray<ServiceFreeSchema>> = {
187
+ [Index in keyof Args]: S.Json;
188
+ };
103
189
 
104
190
  export type Args<Self extends Any> = ArgsFromSchemas<Self["args"]>;
105
191
 
@@ -107,7 +193,7 @@ export namespace Method {
107
193
 
108
194
  export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
109
195
 
110
- export type EncodedSuccess<Self extends Any> = S.Codec.Encoded<Self["success"]>;
196
+ export type EncodedSuccess<Self extends Any> = S.Json;
111
197
  }
112
198
 
113
199
  export type Methods = Record<string, Method.Any>;
@@ -139,18 +225,6 @@ export namespace Definition {
139
225
  export type MethodNames<Self extends Any> = Extract<keyof Self["methods"], string>;
140
226
  }
141
227
 
142
- export class ReservedMethodNameError extends Error {
143
- readonly method: string;
144
- readonly target: string;
145
-
146
- constructor(target: string, method: string) {
147
- super(`${target} RPC method "${method}" is reserved by Cloudflare Workers RPC`);
148
- this.name = "ReservedMethodNameError";
149
- this.target = target;
150
- this.method = method;
151
- }
152
- }
153
-
154
228
  export const assertNoReservedMethods = (
155
229
  target: string,
156
230
  methods: Record<string, unknown>,
@@ -158,7 +232,7 @@ export const assertNoReservedMethods = (
158
232
  ) => {
159
233
  for (const method of Object.keys(methods)) {
160
234
  if (reserved.has(method)) {
161
- throw new ReservedMethodNameError(target, method);
235
+ throw new RpcReservedMethodNameError({ definition: target, method });
162
236
  }
163
237
  }
164
238
  };
@@ -180,110 +254,87 @@ export function method(definition: {
180
254
  };
181
255
  }
182
256
 
183
- export const assertNoReservedMethodNames = (
184
- definition: Definition.Any,
185
- ): Effect.Effect<void, RpcReservedMethodNameError> =>
186
- Effect.forEach(Object.keys(definition.methods), (method) =>
187
- reservedMethodNames.has(method)
188
- ? Effect.fail(new RpcReservedMethodNameError({ definition: definition.id, method }))
189
- : Effect.void,
190
- ).pipe(Effect.asVoid);
191
-
192
- export const decodeArgs = <
257
+ export const decodeArgs = Effect.fnUntraced(function* <
193
258
  const Self extends Definition.Any,
194
259
  MethodName extends Definition.MethodNames<Self>,
195
260
  >(
196
261
  definition: Self,
197
262
  methodName: MethodName,
198
263
  args: ReadonlyArray<unknown>,
199
- ): Effect.Effect<
264
+ ): Effect.fn.Return<
200
265
  Method.Args<Self["methods"][MethodName]>,
201
266
  RpcArgumentCountError | RpcArgumentDecodeError
202
- > =>
203
- Effect.gen(function* () {
204
- const methodDefinition = definition.methods[methodName];
267
+ > {
268
+ const methodDefinition = definition.methods[methodName];
269
+
270
+ if (args.length !== methodDefinition.args.length) {
271
+ return yield* new RpcArgumentCountError({
272
+ definition: definition.id,
273
+ method: methodName,
274
+ expected: methodDefinition.args.length,
275
+ actual: args.length,
276
+ });
277
+ }
205
278
 
206
- if (args.length !== methodDefinition.args.length) {
207
- return yield* Effect.fail(
208
- new RpcArgumentCountError({
279
+ const decoded = yield* (
280
+ S.decodeUnknownEffect(wireCodec(S.Tuple(methodDefinition.args)))(args) as Effect.Effect<
281
+ unknown,
282
+ unknown
283
+ >
284
+ ).pipe(
285
+ Effect.mapError(
286
+ (cause) =>
287
+ new RpcArgumentDecodeError({
209
288
  definition: definition.id,
210
289
  method: methodName,
211
- expected: methodDefinition.args.length,
212
- actual: args.length,
290
+ cause,
213
291
  }),
214
- );
215
- }
216
-
217
- const decoded: Array<unknown> = [];
218
-
219
- for (let index = 0; index < methodDefinition.args.length; index++) {
220
- const schema = methodDefinition.args[index];
221
-
222
- decoded.push(
223
- yield* (S.decodeUnknownEffect(schema)(args[index]) as Effect.Effect<unknown, unknown>).pipe(
224
- Effect.mapError(
225
- (cause) =>
226
- new RpcArgumentDecodeError({
227
- definition: definition.id,
228
- method: methodName,
229
- index,
230
- cause,
231
- }),
232
- ),
233
- ),
234
- );
235
- }
292
+ ),
293
+ );
236
294
 
237
- return decoded as Method.Args<Self["methods"][MethodName]>;
238
- });
295
+ return decoded as Method.Args<Self["methods"][MethodName]>;
296
+ });
239
297
 
240
- export const encodeArgs = <
298
+ export const encodeArgs = Effect.fnUntraced(function* <
241
299
  const Self extends Definition.Any,
242
300
  MethodName extends Definition.MethodNames<Self>,
243
301
  >(
244
302
  definition: Self,
245
303
  methodName: MethodName,
246
304
  args: Method.Args<Self["methods"][MethodName]>,
247
- ): Effect.Effect<
305
+ ): Effect.fn.Return<
248
306
  Method.EncodedArgs<Self["methods"][MethodName]>,
249
307
  RpcArgumentCountError | RpcArgumentEncodeError
250
- > =>
251
- Effect.gen(function* () {
252
- const methodDefinition = definition.methods[methodName];
308
+ > {
309
+ const methodDefinition = definition.methods[methodName];
310
+
311
+ if (args.length !== methodDefinition.args.length) {
312
+ return yield* new RpcArgumentCountError({
313
+ definition: definition.id,
314
+ method: methodName,
315
+ expected: methodDefinition.args.length,
316
+ actual: args.length,
317
+ });
318
+ }
253
319
 
254
- if (args.length !== methodDefinition.args.length) {
255
- return yield* Effect.fail(
256
- new RpcArgumentCountError({
320
+ const encoded = yield* (
321
+ S.encodeUnknownEffect(wireCodec(S.Tuple(methodDefinition.args)))(args) as Effect.Effect<
322
+ unknown,
323
+ unknown
324
+ >
325
+ ).pipe(
326
+ Effect.mapError(
327
+ (cause) =>
328
+ new RpcArgumentEncodeError({
257
329
  definition: definition.id,
258
330
  method: methodName,
259
- expected: methodDefinition.args.length,
260
- actual: args.length,
331
+ cause,
261
332
  }),
262
- );
263
- }
264
-
265
- const encoded: Array<unknown> = [];
266
-
267
- for (let index = 0; index < methodDefinition.args.length; index++) {
268
- const schema = methodDefinition.args[index];
269
-
270
- encoded.push(
271
- yield* (S.encodeEffect(schema)(args[index]) as Effect.Effect<unknown, unknown>).pipe(
272
- Effect.mapError(
273
- (cause) =>
274
- new RpcArgumentEncodeError({
275
- definition: definition.id,
276
- method: methodName,
277
- index,
278
- cause,
279
- }),
280
- ),
281
- ),
282
- );
283
- }
333
+ ),
334
+ );
284
335
 
285
- return encoded as Method.EncodedArgs<Self["methods"][MethodName]>;
286
- });
336
+ return encoded as Method.EncodedArgs<Self["methods"][MethodName]>;
337
+ });
287
338
 
288
339
  export const encodeSuccess = <
289
340
  const Self extends Definition.Any,
@@ -296,7 +347,7 @@ export const encodeSuccess = <
296
347
  const methodDefinition = definition.methods[methodName];
297
348
 
298
349
  return (
299
- S.encodeEffect(methodDefinition.success)(value) as Effect.Effect<
350
+ S.encodeUnknownEffect(wireCodec(methodDefinition.success))(value) as Effect.Effect<
300
351
  Method.EncodedSuccess<Self["methods"][MethodName]>,
301
352
  unknown
302
353
  >
@@ -323,7 +374,7 @@ export const decodeSuccess = <
323
374
  const methodDefinition = definition.methods[methodName];
324
375
 
325
376
  return (
326
- S.decodeUnknownEffect(methodDefinition.success)(value) as Effect.Effect<
377
+ S.decodeUnknownEffect(wireCodec(methodDefinition.success))(value) as Effect.Effect<
327
378
  Method.Success<Self["methods"][MethodName]>,
328
379
  unknown
329
380
  >
@@ -343,14 +394,7 @@ export const make = <Id extends string, const MethodsShape extends Methods>(
343
394
  id: Id,
344
395
  methods: MethodsShape,
345
396
  ): Definition<Id, MethodsShape> => {
346
- const definition = { id, methods } as Definition<Id, MethodsShape>;
347
- const reserved = Object.keys(definition.methods).find((methodName) =>
348
- reservedMethodNames.has(methodName),
349
- );
350
-
351
- if (reserved !== undefined) {
352
- throw new RpcReservedMethodNameError({ definition: id, method: reserved });
353
- }
397
+ assertNoReservedMethods(id, methods, reservedMethodNames);
354
398
 
355
- return definition;
399
+ return { id, methods } as Definition<Id, MethodsShape>;
356
400
  };