effect-cf 0.18.0 → 0.20.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.
@@ -46,6 +46,7 @@ export const fromWebSocket = <Attachment = unknown>(
46
46
  raw: WebSocket,
47
47
  ): DurableWebSocket<Attachment> => {
48
48
  const existing = wrappers.get(raw);
49
+
49
50
  if (existing !== undefined) {
50
51
  return existing as DurableWebSocket<Attachment>;
51
52
  }
@@ -74,6 +75,7 @@ export const fromWebSocket = <Attachment = unknown>(
74
75
  };
75
76
 
76
77
  wrappers.set(raw, socket);
78
+
77
79
  return socket as DurableWebSocket<Attachment>;
78
80
  };
79
81
 
@@ -189,7 +191,8 @@ export const attachment = <const AttachmentSchema extends S.Codec<any, any, neve
189
191
  const deserialize = (socket: DurableWebSocket<unknown>) =>
190
192
  Effect.gen(function* () {
191
193
  const value = yield* socket.deserializeAttachment;
192
- if (value == null) {
194
+
195
+ if (value === null || value === undefined) {
193
196
  return Option.none<Attachment>();
194
197
  }
195
198
 
package/src/Email.ts CHANGED
@@ -11,12 +11,71 @@ import * as Binding from "./Binding";
11
11
  import type { WorkerEnvironment } from "./Environment";
12
12
 
13
13
  const expectedSendEmailBinding = "Send Email binding with send()";
14
+ const textEncoder = new TextEncoder();
15
+
16
+ /** Documented Cloudflare Email Sending limits. */
17
+ export const sendLimits = {
18
+ /** Combined `to`, `cc`, and `bcc` addresses per message. */
19
+ maxRecipients: 50,
20
+ maxAttachments: 32,
21
+ /** Total message size, including attachments. Not enforced client side. */
22
+ maxMessageBytes: 5 * 1024 * 1024,
23
+ maxHeaderNameBytes: 100,
24
+ maxHeaderValueBytes: 2048,
25
+ maxHeadersBytes: 16 * 1024,
26
+ /** Allowlisted custom headers, excluding `X-` prefixed headers. */
27
+ maxAllowlistHeaders: 20,
28
+ } as const;
29
+
30
+ /** Error codes reported by Cloudflare Email Sending. */
31
+ export const emailErrorCodes = [
32
+ "E_VALIDATION_ERROR",
33
+ "E_FIELD_MISSING",
34
+ "E_TOO_MANY_RECIPIENTS",
35
+ "E_TOO_MANY_ATTACHMENTS",
36
+ "E_SENDER_NOT_VERIFIED",
37
+ "E_RECIPIENT_NOT_ALLOWED",
38
+ "E_RECIPIENT_SUPPRESSED",
39
+ "E_SENDER_DOMAIN_NOT_AVAILABLE",
40
+ "E_CONTENT_TOO_LARGE",
41
+ "E_DELIVERY_FAILED",
42
+ "E_RATE_LIMIT_EXCEEDED",
43
+ "E_DAILY_LIMIT_EXCEEDED",
44
+ "E_INTERNAL_SERVER_ERROR",
45
+ "E_HEADER_NOT_ALLOWED",
46
+ "E_HEADER_USE_API_FIELD",
47
+ "E_HEADER_VALUE_INVALID",
48
+ "E_HEADER_VALUE_TOO_LONG",
49
+ "E_HEADER_NAME_INVALID",
50
+ "E_HEADERS_TOO_LARGE",
51
+ "E_HEADERS_TOO_MANY",
52
+ ] as const;
53
+
54
+ /** A documented Cloudflare Email Sending error code. */
55
+ export type EmailErrorCode = (typeof emailErrorCodes)[number];
14
56
 
15
57
  /** Error raised when a Cloudflare Send Email operation fails. */
16
58
  export class EmailOperationError extends Data.TaggedError("EmailOperationError")<{
17
59
  readonly binding: string;
18
60
  readonly operation: string;
19
61
  readonly cause: unknown;
62
+ /** Cloudflare error code taken from the thrown error, when present. */
63
+ readonly code?: EmailErrorCode | (string & {});
64
+ }> {}
65
+
66
+ /** A single message field that violates a documented Email Sending limit. */
67
+ export interface EmailViolation {
68
+ readonly path: string;
69
+ readonly message: string;
70
+ readonly limit?: number;
71
+ readonly actual?: number;
72
+ }
73
+
74
+ /** Error raised when a message violates documented Cloudflare Email Sending limits. */
75
+ export class EmailValidationError extends Data.TaggedError("EmailValidationError")<{
76
+ readonly binding: string;
77
+ readonly operation: string;
78
+ readonly violations: ReadonlyArray<EmailViolation>;
20
79
  }> {}
21
80
 
22
81
  /** Typed Cloudflare Send Email binding definition. */
@@ -32,6 +91,13 @@ export type EmailMessageBuilder = Parameters<CloudflareSendEmail["send"]>[0];
32
91
  export type EmailSendInput = EmailMessage | EmailMessageBuilder;
33
92
  export type EmailSendResult = CloudflareEmailSendResult;
34
93
  export type EmailBinding = CloudflareSendEmail;
94
+ export type EmailRecipients = string | EmailAddress | ReadonlyArray<string | EmailAddress>;
95
+ export type EmailSendError = EmailOperationError | EmailValidationError;
96
+
97
+ export interface EmailSendOptions {
98
+ /** Validates builder messages against documented limits. Defaults to `true`. */
99
+ readonly validate?: boolean;
100
+ }
35
101
 
36
102
  interface EmailRuntimeBinding {
37
103
  readonly send: (message: EmailSendInput) => Promise<EmailSendResult>;
@@ -39,8 +105,8 @@ interface EmailRuntimeBinding {
39
105
 
40
106
  export interface EmailClient {
41
107
  readonly send: {
42
- (message: EmailMessage): Effect.Effect<EmailSendResult, EmailOperationError>;
43
- (builder: EmailMessageBuilder): Effect.Effect<EmailSendResult, EmailOperationError>;
108
+ (message: EmailMessage): Effect.Effect<EmailSendResult, EmailSendError>;
109
+ (builder: EmailMessageBuilder): Effect.Effect<EmailSendResult, EmailSendError>;
44
110
  };
45
111
  readonly unsafeRaw: Effect.Effect<EmailBinding>;
46
112
  readonly definition: EmailDefinition;
@@ -57,6 +123,7 @@ export interface EmailService<Id extends string> {
57
123
 
58
124
  export type LayerOptions = {
59
125
  readonly binding: string;
126
+ readonly send?: EmailSendOptions;
60
127
  };
61
128
 
62
129
  export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
@@ -74,8 +141,24 @@ export interface TagClass<Self, Id extends string> extends Context.ServiceClass<
74
141
  >;
75
142
  }
76
143
 
144
+ const emailErrorCode = (cause: unknown): EmailOperationError["code"] => {
145
+ if (typeof cause !== "object" || cause === null) {
146
+ return undefined;
147
+ }
148
+
149
+ const code = Reflect.get(cause, "code");
150
+
151
+ return typeof code === "string" ? code : undefined;
152
+ };
153
+
77
154
  const emailError = (binding: string, operation: string, cause: unknown) =>
78
- new EmailOperationError({ binding, operation, cause });
155
+ new EmailOperationError({ binding, operation, cause, code: emailErrorCode(cause) });
156
+
157
+ const emailValidationError = (
158
+ binding: string,
159
+ operation: string,
160
+ violations: ReadonlyArray<EmailViolation>,
161
+ ) => new EmailValidationError({ binding, operation, violations });
79
162
 
80
163
  const tryEmailPromise = <A>(
81
164
  binding: string,
@@ -87,6 +170,260 @@ const tryEmailPromise = <A>(
87
170
  catch: (cause) => emailError(binding, operation, cause),
88
171
  });
89
172
 
173
+ const builderFields = [
174
+ "subject",
175
+ "html",
176
+ "text",
177
+ "cc",
178
+ "bcc",
179
+ "replyTo",
180
+ "attachments",
181
+ "headers",
182
+ ] as const;
183
+
184
+ /**
185
+ * Distinguishes structured Email Sending messages from raw MIME `EmailMessage`
186
+ * values, which only carry envelope `from` and `to` addresses.
187
+ */
188
+ export const isEmailMessageBuilder = (message: EmailSendInput): message is EmailMessageBuilder => {
189
+ if (typeof message !== "object" || message === null) {
190
+ return false;
191
+ }
192
+
193
+ return builderFields.some((field) => Reflect.get(message, field) !== undefined);
194
+ };
195
+
196
+ const byteLength = (value: string) => textEncoder.encode(value).byteLength;
197
+
198
+ const limitViolation = (
199
+ path: string,
200
+ label: string,
201
+ actual: number,
202
+ limit: number,
203
+ ): EmailViolation => ({
204
+ path,
205
+ message: `${label} exceeds Cloudflare Email Sending limit`,
206
+ actual,
207
+ limit,
208
+ });
209
+
210
+ const addressList = (value: EmailRecipients | undefined): ReadonlyArray<string | EmailAddress> => {
211
+ if (value === undefined) {
212
+ return [];
213
+ }
214
+
215
+ return Array.isArray(value) ? value : [value as string | EmailAddress];
216
+ };
217
+
218
+ const addressViolations = (path: string, value: unknown): ReadonlyArray<EmailViolation> => {
219
+ if (typeof value === "string") {
220
+ return value.includes("@") ? [] : [{ path, message: "Email address must contain an @ symbol" }];
221
+ }
222
+
223
+ if (typeof value !== "object" || value === null) {
224
+ return [{ path, message: "Email address must be a string or { name, email } object" }];
225
+ }
226
+
227
+ const email = Reflect.get(value, "email");
228
+
229
+ if (typeof email !== "string" || !email.includes("@")) {
230
+ return [{ path: `${path}.email`, message: "Email address must contain an @ symbol" }];
231
+ }
232
+
233
+ return [];
234
+ };
235
+
236
+ const recipientViolations = (
237
+ field: "to" | "cc" | "bcc",
238
+ value: EmailRecipients | undefined,
239
+ ): ReadonlyArray<EmailViolation> =>
240
+ addressList(value).flatMap((address, index) => addressViolations(`${field}[${index}]`, address));
241
+
242
+ const attachmentViolations = (
243
+ attachments: ReadonlyArray<EmailAttachment>,
244
+ ): ReadonlyArray<EmailViolation> => {
245
+ const violations: Array<EmailViolation> = [];
246
+
247
+ if (attachments.length > sendLimits.maxAttachments) {
248
+ violations.push(
249
+ limitViolation("attachments", "attachments", attachments.length, sendLimits.maxAttachments),
250
+ );
251
+ }
252
+
253
+ for (let index = 0; index < attachments.length; index++) {
254
+ const attachment = attachments[index];
255
+ const path = `attachments[${index}]`;
256
+
257
+ if (typeof attachment !== "object" || attachment === null) {
258
+ violations.push({ path, message: "Attachment must be an object" });
259
+ continue;
260
+ }
261
+
262
+ if (typeof attachment.filename !== "string" || attachment.filename === "") {
263
+ violations.push({ path: `${path}.filename`, message: "Attachment filename is required" });
264
+ }
265
+
266
+ if (typeof attachment.type !== "string" || attachment.type === "") {
267
+ violations.push({ path: `${path}.type`, message: "Attachment MIME type is required" });
268
+ }
269
+
270
+ if (attachment.content === undefined || attachment.content === null) {
271
+ violations.push({ path: `${path}.content`, message: "Attachment content is required" });
272
+ }
273
+
274
+ if (attachment.disposition === "inline" && typeof attachment.contentId !== "string") {
275
+ violations.push({
276
+ path: `${path}.contentId`,
277
+ message: "Inline attachments require a contentId",
278
+ });
279
+ }
280
+ }
281
+
282
+ return violations;
283
+ };
284
+
285
+ const headerViolations = (headers: Record<string, string>): ReadonlyArray<EmailViolation> => {
286
+ const violations: Array<EmailViolation> = [];
287
+ const entries = Object.entries(headers);
288
+ let totalBytes = 0;
289
+ let allowlistHeaders = 0;
290
+
291
+ for (const [name, value] of entries) {
292
+ const path = `headers["${name}"]`;
293
+ const nameBytes = byteLength(name);
294
+
295
+ if (nameBytes > sendLimits.maxHeaderNameBytes) {
296
+ violations.push(
297
+ limitViolation(path, "header name bytes", nameBytes, sendLimits.maxHeaderNameBytes),
298
+ );
299
+ }
300
+
301
+ if (typeof value !== "string") {
302
+ violations.push({ path, message: "Header value must be a string" });
303
+ continue;
304
+ }
305
+
306
+ const valueBytes = byteLength(value);
307
+
308
+ if (valueBytes > sendLimits.maxHeaderValueBytes) {
309
+ violations.push(
310
+ limitViolation(path, "header value bytes", valueBytes, sendLimits.maxHeaderValueBytes),
311
+ );
312
+ }
313
+
314
+ totalBytes += nameBytes + valueBytes;
315
+
316
+ if (!name.toLowerCase().startsWith("x-")) {
317
+ allowlistHeaders += 1;
318
+ }
319
+ }
320
+
321
+ if (totalBytes > sendLimits.maxHeadersBytes) {
322
+ violations.push(
323
+ limitViolation("headers", "header bytes", totalBytes, sendLimits.maxHeadersBytes),
324
+ );
325
+ }
326
+
327
+ if (allowlistHeaders > sendLimits.maxAllowlistHeaders) {
328
+ violations.push(
329
+ limitViolation(
330
+ "headers",
331
+ "allowlisted custom headers",
332
+ allowlistHeaders,
333
+ sendLimits.maxAllowlistHeaders,
334
+ ),
335
+ );
336
+ }
337
+
338
+ return violations;
339
+ };
340
+
341
+ /**
342
+ * Validates a structured message against documented Cloudflare Email Sending
343
+ * limits and returns every violation found. Total message size is not checked
344
+ * because the encoded MIME size is only known once Cloudflare composes it.
345
+ */
346
+ export const validateMessage = (message: EmailMessageBuilder): ReadonlyArray<EmailViolation> => {
347
+ const violations: Array<EmailViolation> = [];
348
+ const fields = message as {
349
+ readonly to?: EmailRecipients;
350
+ readonly cc?: EmailRecipients;
351
+ readonly bcc?: EmailRecipients;
352
+ readonly from?: unknown;
353
+ readonly replyTo?: unknown;
354
+ readonly subject?: unknown;
355
+ readonly text?: unknown;
356
+ readonly html?: unknown;
357
+ readonly attachments?: ReadonlyArray<EmailAttachment>;
358
+ readonly headers?: Record<string, string>;
359
+ };
360
+
361
+ if (fields.from === undefined) {
362
+ violations.push({ path: "from", message: "A from address is required" });
363
+ } else {
364
+ violations.push(...addressViolations("from", fields.from));
365
+ }
366
+
367
+ if (fields.replyTo !== undefined) {
368
+ violations.push(...addressViolations("replyTo", fields.replyTo));
369
+ }
370
+
371
+ if (typeof fields.subject !== "string" || fields.subject === "") {
372
+ violations.push({ path: "subject", message: "A subject is required" });
373
+ }
374
+
375
+ if (typeof fields.text !== "string" && typeof fields.html !== "string") {
376
+ violations.push({ path: "$", message: "A message requires either text or html content" });
377
+ }
378
+
379
+ const recipients =
380
+ addressList(fields.to).length + addressList(fields.cc).length + addressList(fields.bcc).length;
381
+
382
+ if (recipients === 0) {
383
+ violations.push({
384
+ path: "$",
385
+ message: "A message requires at least one to, cc, or bcc address",
386
+ });
387
+ }
388
+
389
+ if (recipients > sendLimits.maxRecipients) {
390
+ violations.push(
391
+ limitViolation(
392
+ "$",
393
+ "combined to, cc, and bcc addresses",
394
+ recipients,
395
+ sendLimits.maxRecipients,
396
+ ),
397
+ );
398
+ }
399
+
400
+ violations.push(...recipientViolations("to", fields.to));
401
+ violations.push(...recipientViolations("cc", fields.cc));
402
+ violations.push(...recipientViolations("bcc", fields.bcc));
403
+
404
+ if (fields.attachments !== undefined) {
405
+ violations.push(...attachmentViolations(fields.attachments));
406
+ }
407
+
408
+ if (fields.headers !== undefined) {
409
+ violations.push(...headerViolations(fields.headers));
410
+ }
411
+
412
+ return violations;
413
+ };
414
+
415
+ const validateSendInput = (
416
+ binding: string,
417
+ operation: string,
418
+ message: EmailMessageBuilder,
419
+ ): Effect.Effect<EmailMessageBuilder, EmailValidationError> => {
420
+ const violations = validateMessage(message);
421
+
422
+ return violations.length > 0
423
+ ? Effect.fail(emailValidationError(binding, operation, violations))
424
+ : Effect.succeed(message);
425
+ };
426
+
90
427
  export const isEmailBinding = (value: unknown): value is EmailBinding => {
91
428
  if (typeof value !== "object" || value === null) {
92
429
  return false;
@@ -98,13 +435,20 @@ export const isEmailBinding = (value: unknown): value is EmailBinding => {
98
435
  };
99
436
 
100
437
  export const makeClient =
101
- (definition: EmailDefinition) =>
438
+ (definition: EmailDefinition, options?: EmailSendOptions) =>
102
439
  (email: EmailBinding): EmailClient => {
103
440
  const runtime = email as EmailRuntimeBinding;
104
- const send = ((message: EmailSendInput) =>
105
- tryEmailPromise(definition.binding, "send", () =>
106
- runtime.send(message),
107
- )) as EmailClient["send"];
441
+ const validate = options?.validate ?? true;
442
+ const send = ((message: EmailSendInput) => {
443
+ const dispatch = () =>
444
+ tryEmailPromise(definition.binding, "send", () => runtime.send(message));
445
+
446
+ if (!validate || !isEmailMessageBuilder(message)) {
447
+ return dispatch();
448
+ }
449
+
450
+ return validateSendInput(definition.binding, "send", message).pipe(Effect.flatMap(dispatch));
451
+ }) as EmailClient["send"];
108
452
 
109
453
  return {
110
454
  definition,
@@ -113,8 +457,8 @@ export const makeClient =
113
457
  };
114
458
  };
115
459
 
116
- export const layer = <Self>(tag: Context.Service<Self, EmailClient>, definition: EmailDefinition) =>
117
- Binding.layer(tag, definition.binding, isEmailBinding, makeClient(definition), {
460
+ export const layer = <Self>(tag: Context.Service<Self, EmailClient>, definition: LayerOptions) =>
461
+ Binding.layer(tag, definition.binding, isEmailBinding, makeClient(definition, definition.send), {
118
462
  expected: expectedSendEmailBinding,
119
463
  });
120
464
 
@@ -1,7 +1,7 @@
1
1
  import { PgClient } from "@effect/sql-pg";
2
2
  import { Effect, Layer, Redacted } from "effect";
3
3
 
4
- import * as Hyperdrive from "./Hyperdrive";
4
+ import type * as Hyperdrive from "./Hyperdrive";
5
5
 
6
6
  export type PgLayerOptions = Omit<
7
7
  PgClient.PgClientConfig & { readonly acquireForStream?: boolean },
package/src/Kv.ts CHANGED
@@ -124,7 +124,7 @@ export interface TagClass<
124
124
  }
125
125
 
126
126
  const maybeString = (value: string | null | undefined): Option.Option<string> =>
127
- value == null ? Option.none() : Option.some(value);
127
+ value === null || value === undefined ? Option.none() : Option.some(value);
128
128
 
129
129
  const maybeNumber = (value: number | undefined): Option.Option<number> =>
130
130
  value === undefined ? Option.none() : Option.some(value);
@@ -171,6 +171,7 @@ export const makeClient = <Key, Value, EncodedValue>(
171
171
  put: Effect.fnUntraced(function* (key: Key, value: Value, options?: KvPutOptions) {
172
172
  const keyEncoded = yield* encodeKey(key);
173
173
  const valueEncoded = yield* encodeValue(value);
174
+
174
175
  yield* tryKvPromise(definition.binding, "put", () =>
175
176
  kv.put(keyEncoded, valueEncoded, options),
176
177
  );
@@ -242,6 +243,7 @@ export const makeClient = <Key, Value, EncodedValue>(
242
243
  }),
243
244
  remove: Effect.fnUntraced(function* (key: Key) {
244
245
  const keyEncoded = yield* encodeKey(key);
246
+
245
247
  yield* tryKvPromise(definition.binding, "delete", () => kv.delete(keyEncoded));
246
248
  }),
247
249
  unsafeRaw: Effect.succeed(kv),
@@ -7,7 +7,7 @@ import type {
7
7
  QueueSendOptions as CloudflareQueueSendOptions,
8
8
  QueueSendResponse as CloudflareQueueSendResponse,
9
9
  } from "@cloudflare/workers-types";
10
- import { Context, Data, Effect, Schema as S } from "effect";
10
+ import { type Context, Data, Effect, Schema as S } from "effect";
11
11
 
12
12
  import * as Binding from "./Binding";
13
13
  import type * as RpcDefinition from "./RpcDefinition";
@@ -110,6 +110,7 @@ export const makeClient = <Message extends RpcDefinition.ServiceFreeSchema>(
110
110
  yield* tryQueuePromise(definition.binding, "sendBatch", () =>
111
111
  sendBatch.call(queue, encodedMessages, options),
112
112
  );
113
+
113
114
  return;
114
115
  }
115
116
 
@@ -1,6 +1,6 @@
1
1
  import { Context, Effect, Schema as S, type Layer, type Scope } from "effect";
2
2
 
3
- import * as Binding from "./Binding";
3
+ import type * as Binding from "./Binding";
4
4
  import type { WorkerEnvironment } from "./Environment";
5
5
  import * as QueueEntrypoint from "./Queue";
6
6
  import * as QueueBinding from "./QueueBinding";
@@ -121,6 +121,7 @@ export const Tag =
121
121
  options?: QueueBinding.QueueSendOptions,
122
122
  ) {
123
123
  const queue = yield* tag;
124
+
124
125
  yield* queue.send(message, options);
125
126
  });
126
127
 
@@ -129,16 +130,19 @@ export const Tag =
129
130
  options?: QueueBinding.QueueSendBatchOptions,
130
131
  ) {
131
132
  const queue = yield* tag;
133
+
132
134
  yield* queue.sendBatch(messages, options);
133
135
  });
134
136
 
135
137
  const metrics = Effect.fnUntraced(function* () {
136
138
  const queue = yield* tag;
139
+
137
140
  return yield* queue.metrics();
138
141
  });
139
142
 
140
143
  const unsafeRaw = Effect.fnUntraced(function* () {
141
144
  const queue = yield* tag;
145
+
142
146
  return yield* queue.unsafeRaw;
143
147
  });
144
148
 
@@ -165,6 +169,7 @@ const wrapHandler = <ROut, const Self extends Definition.Any>(
165
169
  return (batch) =>
166
170
  Effect.gen(function* () {
167
171
  const decoded = yield* QueueEntrypoint.decodeBatch(batch.raw, decodeBody);
172
+
168
173
  yield* handler(decoded);
169
174
  });
170
175
  };
package/src/R2.ts CHANGED
@@ -280,6 +280,7 @@ export const makeClient = (
280
280
  ),
281
281
  delete: (keys) => {
282
282
  const nativeKeys = typeof keys === "string" ? keys : [...keys];
283
+
283
284
  return tryR2Promise(definition.binding, "delete", () => bucket.delete(nativeKeys));
284
285
  },
285
286
  list: (options) => tryR2Promise(definition.binding, "list", () => bucket.list(options)),
@@ -218,6 +218,7 @@ export const decodeArgs = <
218
218
 
219
219
  for (let index = 0; index < methodDefinition.args.length; index++) {
220
220
  const schema = methodDefinition.args[index];
221
+
221
222
  decoded.push(
222
223
  yield* (S.decodeUnknownEffect(schema)(args[index]) as Effect.Effect<unknown, unknown>).pipe(
223
224
  Effect.mapError(
@@ -265,6 +266,7 @@ export const encodeArgs = <
265
266
 
266
267
  for (let index = 0; index < methodDefinition.args.length; index++) {
267
268
  const schema = methodDefinition.args[index];
269
+
268
270
  encoded.push(
269
271
  yield* (S.encodeEffect(schema)(args[index]) as Effect.Effect<unknown, unknown>).pipe(
270
272
  Effect.mapError(
@@ -1,4 +1,4 @@
1
- import { Context, Data, Effect, type Scope } from "effect";
1
+ import { Data, Effect, type Context, type Scope } from "effect";
2
2
 
3
3
  import * as Binding from "./Binding";
4
4
  import * as CloudflareRpc from "./Rpc";
@@ -333,6 +333,7 @@ export const makeClient = <
333
333
  const methodName = String(method);
334
334
  const result = yield* rpc(method, ...args);
335
335
  const value = yield* CloudflareRpc.scoped(result);
336
+
336
337
  return yield* decodeSuccess<Method>(methodName, value);
337
338
  });
338
339
 
@@ -413,6 +414,7 @@ export const Service =
413
414
  const fetch = (input: RequestInfo | URL, init?: RequestInit) =>
414
415
  Effect.gen(function* () {
415
416
  const service = yield* tag;
417
+
416
418
  return yield* service.fetch(input, init);
417
419
  });
418
420
 
@@ -422,6 +424,7 @@ export const Service =
422
424
  ) =>
423
425
  Effect.gen(function* () {
424
426
  const service = yield* tag;
427
+
425
428
  return yield* service.rpc(method, ...args);
426
429
  });
427
430
 
@@ -431,6 +434,7 @@ export const Service =
431
434
  ) =>
432
435
  Effect.gen(function* () {
433
436
  const service = yield* tag;
437
+
434
438
  return yield* service.call(method, ...args);
435
439
  });
436
440
 
@@ -440,6 +444,7 @@ export const Service =
440
444
  ) =>
441
445
  Effect.gen(function* () {
442
446
  const service = yield* tag;
447
+
443
448
  return yield* service.scopedCall(method, ...args);
444
449
  });
445
450
 
package/src/Worker.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { WorkerEntrypoint as CloudflareWorkerEntrypoint } from "cloudflare:workers";
2
- import { Cause, ConfigProvider, Context, Effect, Layer, ManagedRuntime, type Scope } from "effect";
3
- import type { Schema as S } from "effect";
2
+ import {
3
+ type Cause,
4
+ ConfigProvider,
5
+ Context,
6
+ Effect,
7
+ Layer,
8
+ ManagedRuntime,
9
+ type Schema as S,
10
+ type Scope,
11
+ } from "effect";
4
12
  import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
5
13
 
6
14
  import type * as Binding from "./Binding";