alchemy 0.91.2 → 0.92.2

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 (68) hide show
  1. package/bin/alchemy.js +2 -12
  2. package/bin/commands/util.ts +0 -3
  3. package/lib/cloudflare/auth.d.ts +0 -3
  4. package/lib/cloudflare/auth.d.ts.map +1 -1
  5. package/lib/cloudflare/auth.js +0 -3
  6. package/lib/cloudflare/auth.js.map +1 -1
  7. package/lib/cloudflare/bindings.d.ts +10 -2
  8. package/lib/cloudflare/bindings.d.ts.map +1 -1
  9. package/lib/cloudflare/bindings.js.map +1 -1
  10. package/lib/cloudflare/bound.d.ts +2 -1
  11. package/lib/cloudflare/bound.d.ts.map +1 -1
  12. package/lib/cloudflare/email-address.d.ts +11 -2
  13. package/lib/cloudflare/email-address.d.ts.map +1 -1
  14. package/lib/cloudflare/email-address.js +20 -8
  15. package/lib/cloudflare/email-address.js.map +1 -1
  16. package/lib/cloudflare/email-catch-all.d.ts +13 -5
  17. package/lib/cloudflare/email-catch-all.d.ts.map +1 -1
  18. package/lib/cloudflare/email-catch-all.js +21 -4
  19. package/lib/cloudflare/email-catch-all.js.map +1 -1
  20. package/lib/cloudflare/email-common.d.ts +4 -0
  21. package/lib/cloudflare/email-common.d.ts.map +1 -0
  22. package/lib/cloudflare/email-common.js +7 -0
  23. package/lib/cloudflare/email-common.js.map +1 -0
  24. package/lib/cloudflare/email-routing.d.ts +1 -0
  25. package/lib/cloudflare/email-routing.d.ts.map +1 -1
  26. package/lib/cloudflare/email-routing.js +6 -2
  27. package/lib/cloudflare/email-routing.js.map +1 -1
  28. package/lib/cloudflare/email-rule.d.ts +4 -3
  29. package/lib/cloudflare/email-rule.d.ts.map +1 -1
  30. package/lib/cloudflare/email-rule.js +33 -4
  31. package/lib/cloudflare/email-rule.js.map +1 -1
  32. package/lib/cloudflare/email-sender.d.ts +38 -0
  33. package/lib/cloudflare/email-sender.d.ts.map +1 -0
  34. package/lib/cloudflare/email-sender.js +23 -0
  35. package/lib/cloudflare/email-sender.js.map +1 -0
  36. package/lib/cloudflare/index.d.ts +1 -0
  37. package/lib/cloudflare/index.d.ts.map +1 -1
  38. package/lib/cloudflare/index.js +1 -0
  39. package/lib/cloudflare/index.js.map +1 -1
  40. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  41. package/lib/cloudflare/miniflare/build-worker-options.js +29 -0
  42. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  43. package/lib/cloudflare/miniflare/miniflare-controller.js +14 -1
  44. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  45. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  46. package/lib/cloudflare/worker-metadata.js +9 -0
  47. package/lib/cloudflare/worker-metadata.js.map +1 -1
  48. package/lib/cloudflare/wrangler.json.js +13 -0
  49. package/lib/cloudflare/wrangler.json.js.map +1 -1
  50. package/package.json +3 -3
  51. package/src/cloudflare/auth.ts +0 -3
  52. package/src/cloudflare/bindings.ts +11 -0
  53. package/src/cloudflare/bound.ts +56 -53
  54. package/src/cloudflare/email-address.ts +39 -12
  55. package/src/cloudflare/email-catch-all.ts +41 -10
  56. package/src/cloudflare/email-common.ts +11 -0
  57. package/src/cloudflare/email-routing.ts +7 -2
  58. package/src/cloudflare/email-rule.ts +43 -7
  59. package/src/cloudflare/email-sender.ts +49 -0
  60. package/src/cloudflare/index.ts +1 -0
  61. package/src/cloudflare/miniflare/build-worker-options.ts +69 -1
  62. package/src/cloudflare/miniflare/miniflare-controller.ts +18 -1
  63. package/src/cloudflare/worker-metadata.ts +8 -0
  64. package/src/cloudflare/wrangler.json.ts +17 -0
  65. package/workers/cloudflare-state-store.js +57 -57
  66. package/workers/remote-binding-proxy.js +7 -3
  67. package/workers/remote-binding-proxy.ts +20 -3
  68. package/workers/tunnel-proxy.js +1 -1
@@ -1,7 +1,8 @@
1
1
  import type { Context } from "../context.ts";
2
- import { Resource } from "../resource.ts";
2
+ import { Resource, ResourceKind } from "../resource.ts";
3
3
  import { handleApiError } from "./api-error.ts";
4
4
  import { type CloudflareApiOptions, createCloudflareApi } from "./api.ts";
5
+ import { resolveEmailZoneId } from "./email-common.ts";
5
6
  import type { CloudflareResponse } from "./response.ts";
6
7
  import type { Zone } from "./zone.ts";
7
8
 
@@ -19,7 +20,7 @@ export interface EmailMatcher {
19
20
  /**
20
21
  * Field to match against (required for literal matchers)
21
22
  */
22
- field?: "to" | "from" | "subject";
23
+ field?: "to";
23
24
 
24
25
  /**
25
26
  * Value to match (required for literal matchers)
@@ -141,9 +142,42 @@ export interface EmailRule {
141
142
  actions: EmailAction[];
142
143
 
143
144
  /**
144
- * Rule tag
145
+ * Deprecated rule tag returned by the Cloudflare API.
145
146
  */
146
- tag: string;
147
+ tag?: string;
148
+ }
149
+
150
+ export function isEmailRule(resource: any): resource is EmailRule {
151
+ return resource?.[ResourceKind] === "cloudflare::EmailRule";
152
+ }
153
+
154
+ function validateMatchers(matchers: EmailMatcher[]) {
155
+ for (const matcher of matchers) {
156
+ if (matcher.type === "all") {
157
+ continue;
158
+ }
159
+ if (matcher.field !== "to") {
160
+ throw new Error(
161
+ 'EmailRule literal matchers currently only support field: "to".',
162
+ );
163
+ }
164
+ if (!matcher.value) {
165
+ throw new Error("EmailRule literal matchers require a value.");
166
+ }
167
+ }
168
+ }
169
+
170
+ function validateActions(actions: EmailAction[]) {
171
+ for (const action of actions) {
172
+ if (action.type === "drop") {
173
+ continue;
174
+ }
175
+ if (!action.value?.length) {
176
+ throw new Error(
177
+ `EmailRule action "${action.type}" requires at least one value.`,
178
+ );
179
+ }
180
+ }
147
181
  }
148
182
 
149
183
  /**
@@ -235,7 +269,9 @@ export const EmailRule = Resource(
235
269
  props: EmailRuleProps,
236
270
  ): Promise<EmailRule> {
237
271
  const api = await createCloudflareApi(props);
238
- const zoneId = typeof props.zone === "string" ? props.zone : props.zone.id;
272
+ const zoneId = await resolveEmailZoneId(api, props.zone);
273
+ validateMatchers(props.matchers);
274
+ validateActions(props.actions);
239
275
 
240
276
  if (this.phase === "delete") {
241
277
  if (this.output?.ruleId) {
@@ -282,7 +318,7 @@ export const EmailRule = Resource(
282
318
  priority: result.result.priority,
283
319
  matchers: result.result.matchers,
284
320
  actions: result.result.actions,
285
- tag: result.result.tag,
321
+ tag: result.result.tag || undefined,
286
322
  };
287
323
  }
288
324
  }
@@ -316,7 +352,7 @@ export const EmailRule = Resource(
316
352
  priority: result.result.priority,
317
353
  matchers: result.result.matchers,
318
354
  actions: result.result.actions,
319
- tag: result.result.tag,
355
+ tag: result.result.tag || undefined,
320
356
  };
321
357
  },
322
358
  );
@@ -0,0 +1,49 @@
1
+ export interface BaseEmailSenderProps {
2
+ allowedSenderAddresses?: string[];
3
+ dev?: {
4
+ remote?: boolean;
5
+ };
6
+ }
7
+
8
+ export type EmailSenderProps = BaseEmailSenderProps &
9
+ (
10
+ | {
11
+ destinationAddress?: string;
12
+ allowedDestinationAddresses?: never;
13
+ }
14
+ | {
15
+ destinationAddress?: never;
16
+ allowedDestinationAddresses?: string[];
17
+ }
18
+ );
19
+
20
+ /**
21
+ * Type representing a Cloudflare Email Service send binding.
22
+ * @see https://developers.cloudflare.com/email-service/api/send-emails/workers-api/
23
+ */
24
+ export type EmailSender = EmailSenderProps & {
25
+ type: "send_email";
26
+ };
27
+
28
+ /**
29
+ * Creates a Cloudflare Email Service binding for sending emails from Workers.
30
+ *
31
+ * Set `dev.remote` to `true` to use the real Cloudflare email binding while
32
+ * running the Worker locally.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const email = EmailSender({
37
+ * allowedSenderAddresses: ["noreply@example.com"],
38
+ * dev: { remote: true },
39
+ * });
40
+ * ```
41
+ *
42
+ * @see https://developers.cloudflare.com/email-service/api/send-emails/workers-api/
43
+ */
44
+ export function EmailSender(props: EmailSenderProps = {}): EmailSender {
45
+ return {
46
+ type: "send_email",
47
+ ...props,
48
+ };
49
+ }
@@ -38,6 +38,7 @@ export * from "./email-address.ts";
38
38
  export * from "./email-catch-all.ts";
39
39
  export * from "./email-routing.ts";
40
40
  export * from "./email-rule.ts";
41
+ export * from "./email-sender.ts";
41
42
  export * from "./health-check.ts";
42
43
  export * from "./hyperdrive-ref.ts";
43
44
  export * from "./hyperdrive.ts";
@@ -50,7 +50,10 @@ type RemoteBinding =
50
50
  }
51
51
  > & { raw: true })
52
52
  // Fetcher type bindings do not require the `raw` flag and will throw an error if it is present.
53
- | Extract<WorkerBindingSpec, { type: "service" | "vpc_service" }>;
53
+ | Extract<
54
+ WorkerBindingSpec,
55
+ { type: "send_email" | "service" | "vpc_service" }
56
+ >;
54
57
 
55
58
  type BaseWorkerOptions = {
56
59
  [K in keyof miniflare.WorkerOptions]: K extends
@@ -261,6 +264,36 @@ export const buildWorkerOptions = async (
261
264
  (options.bindings ??= {})[key] = binding.unencrypted;
262
265
  break;
263
266
  }
267
+ case "send_email": {
268
+ const config = {
269
+ name: key,
270
+ destination_address: binding.destinationAddress,
271
+ allowed_destination_addresses: binding.allowedDestinationAddresses,
272
+ allowed_sender_addresses: binding.allowedSenderAddresses,
273
+ };
274
+ if (isRemoteBinding(binding)) {
275
+ remoteBindings.push({
276
+ type: "send_email",
277
+ ...config,
278
+ });
279
+ } else {
280
+ ((
281
+ options as BaseWorkerOptions & {
282
+ email?: {
283
+ send_email?: Array<typeof config>;
284
+ };
285
+ }
286
+ ).email ??= {}).send_email ??= [];
287
+ (
288
+ options as BaseWorkerOptions & {
289
+ email: {
290
+ send_email: Array<typeof config>;
291
+ };
292
+ }
293
+ ).email.send_email.push(config);
294
+ }
295
+ break;
296
+ }
264
297
  case "secret_key": {
265
298
  throw new Error("Secret keys are not supported in local mode");
266
299
  }
@@ -441,6 +474,41 @@ export const buildWorkerOptions = async (
441
474
  remoteProxyConnectionString: remoteProxy.connectionString,
442
475
  };
443
476
  break;
477
+ case "send_email":
478
+ ((
479
+ options as BaseWorkerOptions & {
480
+ email?: {
481
+ send_email?: Array<{
482
+ name: string;
483
+ destination_address?: string;
484
+ allowed_destination_addresses?: string[];
485
+ allowed_sender_addresses?: string[];
486
+ remoteProxyConnectionString: typeof remoteProxy.connectionString;
487
+ }>;
488
+ };
489
+ }
490
+ ).email ??= {}).send_email ??= [];
491
+ (
492
+ options as BaseWorkerOptions & {
493
+ email: {
494
+ send_email: Array<{
495
+ name: string;
496
+ destination_address?: string;
497
+ allowed_destination_addresses?: string[];
498
+ allowed_sender_addresses?: string[];
499
+ remoteProxyConnectionString: typeof remoteProxy.connectionString;
500
+ }>;
501
+ };
502
+ }
503
+ ).email.send_email.push({
504
+ name: binding.name,
505
+ destination_address: binding.destination_address,
506
+ allowed_destination_addresses:
507
+ binding.allowed_destination_addresses,
508
+ allowed_sender_addresses: binding.allowed_sender_addresses,
509
+ remoteProxyConnectionString: remoteProxy.connectionString,
510
+ });
511
+ break;
444
512
  case "service":
445
513
  (options.serviceBindings ??= {})[binding.name] = {
446
514
  name: binding.name,
@@ -101,7 +101,7 @@ export class MiniflareController {
101
101
  unsafeDevRegistryPath: miniflare.getDefaultDevRegistryPath(),
102
102
  log: process.env.DEBUG
103
103
  ? new miniflare.Log(miniflare.LogLevel.DEBUG)
104
- : undefined,
104
+ : new DefaultLogger(),
105
105
  // This is required to allow websites and other separate processes
106
106
  // to detect Alchemy-managed Durable Objects via the Wrangler dev registry.
107
107
  unsafeDevRegistryDurableObjectProxy: true,
@@ -170,3 +170,20 @@ export class MiniflareController {
170
170
  ]);
171
171
  }
172
172
  }
173
+
174
+ class DefaultLogger extends miniflare.Log {
175
+ constructor() {
176
+ // Miniflare emits send_email activity at info level.
177
+ super(miniflare.LogLevel.INFO);
178
+ }
179
+
180
+ override info(message: string) {
181
+ if (
182
+ message.startsWith("Ready on") ||
183
+ message.startsWith("Updated and ready on")
184
+ ) {
185
+ return;
186
+ }
187
+ super.info(message);
188
+ }
189
+ }
@@ -492,6 +492,14 @@ export async function prepareWorkerMetadata(
492
492
  jurisdiction:
493
493
  binding.jurisdiction === "default" ? undefined : binding.jurisdiction,
494
494
  });
495
+ } else if (binding.type === "send_email") {
496
+ meta.bindings.push({
497
+ type: "send_email",
498
+ name: bindingName,
499
+ destination_address: binding.destinationAddress,
500
+ allowed_destination_addresses: binding.allowedDestinationAddresses,
501
+ allowed_sender_addresses: binding.allowedSenderAddresses,
502
+ });
495
503
  } else if (binding.type === "secrets_store_secret") {
496
504
  meta.bindings.push({
497
505
  type: "secrets_store_secret",
@@ -263,6 +263,7 @@ async function processBindings(
263
263
  const durableObjects: WranglerJsonConfig["durable_objects"]["bindings"] = [];
264
264
  const r2Buckets: WranglerJsonConfig["r2_buckets"] = [];
265
265
  const services: WranglerJsonConfig["services"] = [];
266
+ const sendEmailBindings: WranglerJsonConfig["send_email"] = [];
266
267
  const secrets: string[] = [];
267
268
  const workflows: WranglerJsonConfig["workflows"] = [];
268
269
  const d1Databases: WranglerJsonConfig["d1_databases"] = [];
@@ -389,6 +390,14 @@ async function processBindings(
389
390
  binding.jurisdiction === "default" ? undefined : binding.jurisdiction,
390
391
  ...(binding.dev?.remote ? { remote: true } : {}),
391
392
  });
393
+ } else if (binding.type === "send_email") {
394
+ sendEmailBindings.push({
395
+ name: bindingName,
396
+ destination_address: binding.destinationAddress,
397
+ allowed_destination_addresses: binding.allowedDestinationAddresses,
398
+ allowed_sender_addresses: binding.allowedSenderAddresses,
399
+ ...(binding.dev?.remote ? { remote: true } : {}),
400
+ });
392
401
  } else if (binding.type === "secret") {
393
402
  // Secret binding
394
403
  secrets.push(bindingName);
@@ -569,6 +578,14 @@ async function processBindings(
569
578
  spec.services = services;
570
579
  }
571
580
 
581
+ if (sendEmailBindings.length > 0) {
582
+ (
583
+ spec as WranglerJsonSpec & {
584
+ send_email?: typeof sendEmailBindings;
585
+ }
586
+ ).send_email = sendEmailBindings;
587
+ }
588
+
572
589
  if (d1Databases.length > 0) {
573
590
  spec.d1_databases = d1Databases;
574
591
  }