alchemy 0.90.1 → 0.92.1

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 (133) hide show
  1. package/bin/alchemy.js +3 -13
  2. package/bin/commands/util.ts +0 -3
  3. package/lib/cloudflare/astro/astro.d.ts +4 -4
  4. package/lib/cloudflare/astro/astro.d.ts.map +1 -1
  5. package/lib/cloudflare/astro/astro.js +2 -2
  6. package/lib/cloudflare/astro/astro.js.map +1 -1
  7. package/lib/cloudflare/auth.d.ts +0 -3
  8. package/lib/cloudflare/auth.d.ts.map +1 -1
  9. package/lib/cloudflare/auth.js +0 -3
  10. package/lib/cloudflare/auth.js.map +1 -1
  11. package/lib/cloudflare/bindings.d.ts +10 -2
  12. package/lib/cloudflare/bindings.d.ts.map +1 -1
  13. package/lib/cloudflare/bindings.js.map +1 -1
  14. package/lib/cloudflare/bound.d.ts +2 -1
  15. package/lib/cloudflare/bound.d.ts.map +1 -1
  16. package/lib/cloudflare/bucket.d.ts.map +1 -1
  17. package/lib/cloudflare/bucket.js +3 -1
  18. package/lib/cloudflare/bucket.js.map +1 -1
  19. package/lib/cloudflare/bun-spa/bun-spa.d.ts +3 -3
  20. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
  21. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
  22. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  23. package/lib/cloudflare/bundle/plugin-wasm.js +20 -2
  24. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  25. package/lib/cloudflare/container.d.ts +7 -0
  26. package/lib/cloudflare/container.d.ts.map +1 -1
  27. package/lib/cloudflare/container.js +1 -1
  28. package/lib/cloudflare/container.js.map +1 -1
  29. package/lib/cloudflare/custom-domain.d.ts +7 -0
  30. package/lib/cloudflare/custom-domain.d.ts.map +1 -1
  31. package/lib/cloudflare/custom-domain.js +3 -1
  32. package/lib/cloudflare/custom-domain.js.map +1 -1
  33. package/lib/cloudflare/email-address.d.ts +11 -2
  34. package/lib/cloudflare/email-address.d.ts.map +1 -1
  35. package/lib/cloudflare/email-address.js +20 -8
  36. package/lib/cloudflare/email-address.js.map +1 -1
  37. package/lib/cloudflare/email-catch-all.d.ts +13 -5
  38. package/lib/cloudflare/email-catch-all.d.ts.map +1 -1
  39. package/lib/cloudflare/email-catch-all.js +21 -4
  40. package/lib/cloudflare/email-catch-all.js.map +1 -1
  41. package/lib/cloudflare/email-common.d.ts +4 -0
  42. package/lib/cloudflare/email-common.d.ts.map +1 -0
  43. package/lib/cloudflare/email-common.js +7 -0
  44. package/lib/cloudflare/email-common.js.map +1 -0
  45. package/lib/cloudflare/email-routing.d.ts +1 -0
  46. package/lib/cloudflare/email-routing.d.ts.map +1 -1
  47. package/lib/cloudflare/email-routing.js +6 -2
  48. package/lib/cloudflare/email-routing.js.map +1 -1
  49. package/lib/cloudflare/email-rule.d.ts +4 -3
  50. package/lib/cloudflare/email-rule.d.ts.map +1 -1
  51. package/lib/cloudflare/email-rule.js +33 -4
  52. package/lib/cloudflare/email-rule.js.map +1 -1
  53. package/lib/cloudflare/email-sender.d.ts +38 -0
  54. package/lib/cloudflare/email-sender.d.ts.map +1 -0
  55. package/lib/cloudflare/email-sender.js +23 -0
  56. package/lib/cloudflare/email-sender.js.map +1 -0
  57. package/lib/cloudflare/index.d.ts +1 -0
  58. package/lib/cloudflare/index.d.ts.map +1 -1
  59. package/lib/cloudflare/index.js +1 -0
  60. package/lib/cloudflare/index.js.map +1 -1
  61. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  62. package/lib/cloudflare/miniflare/build-worker-options.js +29 -0
  63. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  64. package/lib/cloudflare/miniflare/miniflare-controller.js +14 -1
  65. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  66. package/lib/cloudflare/nuxt/nuxt.d.ts +4 -4
  67. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -1
  68. package/lib/cloudflare/nuxt/nuxt.js.map +1 -1
  69. package/lib/cloudflare/react-router/react-router.d.ts +4 -4
  70. package/lib/cloudflare/react-router/react-router.d.ts.map +1 -1
  71. package/lib/cloudflare/react-router/react-router.js.map +1 -1
  72. package/lib/cloudflare/redwood/redwood.d.ts +4 -4
  73. package/lib/cloudflare/redwood/redwood.d.ts.map +1 -1
  74. package/lib/cloudflare/redwood/redwood.js.map +1 -1
  75. package/lib/cloudflare/route.d.ts +7 -0
  76. package/lib/cloudflare/route.d.ts.map +1 -1
  77. package/lib/cloudflare/route.js +1 -1
  78. package/lib/cloudflare/route.js.map +1 -1
  79. package/lib/cloudflare/sveltekit/sveltekit.d.ts +4 -4
  80. package/lib/cloudflare/sveltekit/sveltekit.d.ts.map +1 -1
  81. package/lib/cloudflare/sveltekit/sveltekit.js.map +1 -1
  82. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts +4 -4
  83. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
  84. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
  85. package/lib/cloudflare/vite/vite.d.ts +4 -4
  86. package/lib/cloudflare/vite/vite.d.ts.map +1 -1
  87. package/lib/cloudflare/vite/vite.js.map +1 -1
  88. package/lib/cloudflare/website.d.ts +4 -4
  89. package/lib/cloudflare/website.d.ts.map +1 -1
  90. package/lib/cloudflare/website.js.map +1 -1
  91. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  92. package/lib/cloudflare/worker-metadata.js +9 -0
  93. package/lib/cloudflare/worker-metadata.js.map +1 -1
  94. package/lib/cloudflare/worker.d.ts +15 -0
  95. package/lib/cloudflare/worker.d.ts.map +1 -1
  96. package/lib/cloudflare/worker.js +14 -2
  97. package/lib/cloudflare/worker.js.map +1 -1
  98. package/lib/cloudflare/wrangler.json.js +13 -0
  99. package/lib/cloudflare/wrangler.json.js.map +1 -1
  100. package/package.json +4 -4
  101. package/src/cloudflare/astro/astro.ts +14 -13
  102. package/src/cloudflare/auth.ts +0 -3
  103. package/src/cloudflare/bindings.ts +11 -0
  104. package/src/cloudflare/bound.ts +56 -53
  105. package/src/cloudflare/bucket.ts +1 -0
  106. package/src/cloudflare/bun-spa/bun-spa.ts +15 -6
  107. package/src/cloudflare/bundle/plugin-wasm.ts +21 -2
  108. package/src/cloudflare/container.ts +9 -1
  109. package/src/cloudflare/custom-domain.ts +11 -1
  110. package/src/cloudflare/email-address.ts +39 -12
  111. package/src/cloudflare/email-catch-all.ts +41 -10
  112. package/src/cloudflare/email-common.ts +11 -0
  113. package/src/cloudflare/email-routing.ts +7 -2
  114. package/src/cloudflare/email-rule.ts +43 -7
  115. package/src/cloudflare/email-sender.ts +49 -0
  116. package/src/cloudflare/index.ts +1 -0
  117. package/src/cloudflare/miniflare/build-worker-options.ts +69 -1
  118. package/src/cloudflare/miniflare/miniflare-controller.ts +18 -1
  119. package/src/cloudflare/nuxt/nuxt.ts +12 -8
  120. package/src/cloudflare/react-router/react-router.ts +14 -7
  121. package/src/cloudflare/redwood/redwood.ts +12 -8
  122. package/src/cloudflare/route.ts +9 -1
  123. package/src/cloudflare/sveltekit/sveltekit.ts +14 -7
  124. package/src/cloudflare/tanstack-start/tanstack-start.ts +14 -7
  125. package/src/cloudflare/vite/vite.ts +12 -8
  126. package/src/cloudflare/website.ts +13 -12
  127. package/src/cloudflare/worker-metadata.ts +8 -0
  128. package/src/cloudflare/worker.ts +36 -2
  129. package/src/cloudflare/wrangler.json.ts +17 -0
  130. package/workers/cloudflare-state-store.js +57 -57
  131. package/workers/remote-binding-proxy.js +7 -3
  132. package/workers/remote-binding-proxy.ts +20 -3
  133. package/workers/tunnel-proxy.js +1 -1
@@ -1,17 +1,23 @@
1
1
  import type { Context } from "../context.ts";
2
- import { Resource } from "../resource.ts";
2
+ import { Resource, ResourceKind } from "../resource.ts";
3
3
  import { type CloudflareApiOptions, createCloudflareApi } from "./api.ts";
4
- import type { EmailAction, EmailMatcher } from "./email-rule.ts";
4
+ import { resolveEmailZoneId } from "./email-common.ts";
5
+ import type { EmailAction } from "./email-rule.ts";
5
6
  import type { CloudflareResponse } from "./response.ts";
6
7
  import type { Zone } from "./zone.ts";
7
8
 
9
+ export interface EmailCatchAllMatcher {
10
+ type: "all";
11
+ }
12
+
8
13
  /**
9
14
  * Cloudflare Email Catch All response format
10
15
  */
11
16
  interface CloudflareEmailCatchAll {
17
+ id: string;
12
18
  enabled: boolean;
13
19
  name: string;
14
- matchers: EmailMatcher[];
20
+ matchers: EmailCatchAllMatcher[];
15
21
  actions: EmailAction[];
16
22
  tag: string;
17
23
  }
@@ -43,7 +49,7 @@ export interface EmailCatchAllProps extends CloudflareApiOptions {
43
49
  * Matchers for the catch-all rule (typically matches all emails)
44
50
  * If not provided, defaults to matching all emails
45
51
  */
46
- matchers?: EmailMatcher[];
52
+ matchers?: EmailCatchAllMatcher[];
47
53
 
48
54
  /**
49
55
  * Actions to take for emails that don't match other rules
@@ -60,6 +66,11 @@ export interface EmailCatchAll {
60
66
  */
61
67
  zoneId: string;
62
68
 
69
+ /**
70
+ * Routing rule identifier.
71
+ */
72
+ ruleId: string;
73
+
63
74
  /**
64
75
  * Whether the catch-all rule is enabled
65
76
  */
@@ -73,7 +84,7 @@ export interface EmailCatchAll {
73
84
  /**
74
85
  * Matchers for the catch-all rule
75
86
  */
76
- matchers: EmailMatcher[];
87
+ matchers: EmailCatchAllMatcher[];
77
88
 
78
89
  /**
79
90
  * Actions for the catch-all rule
@@ -81,9 +92,26 @@ export interface EmailCatchAll {
81
92
  actions: EmailAction[];
82
93
 
83
94
  /**
84
- * Rule tag
95
+ * Deprecated rule tag returned by the Cloudflare API.
85
96
  */
86
- tag: string;
97
+ tag?: string;
98
+ }
99
+
100
+ export function isEmailCatchAll(resource: any): resource is EmailCatchAll {
101
+ return resource?.[ResourceKind] === "cloudflare::EmailCatchAll";
102
+ }
103
+
104
+ function validateCatchAllActions(actions: EmailAction[]) {
105
+ for (const action of actions) {
106
+ if (action.type === "drop") {
107
+ continue;
108
+ }
109
+ if (!action.value?.length) {
110
+ throw new Error(
111
+ `EmailCatchAll action "${action.type}" requires at least one value.`,
112
+ );
113
+ }
114
+ }
87
115
  }
88
116
 
89
117
  /**
@@ -151,7 +179,8 @@ export const EmailCatchAll = Resource(
151
179
  props: EmailCatchAllProps,
152
180
  ): Promise<EmailCatchAll> {
153
181
  const api = await createCloudflareApi(props);
154
- const zoneId = typeof props.zone === "string" ? props.zone : props.zone.id;
182
+ const zoneId = await resolveEmailZoneId(api, props.zone);
183
+ validateCatchAllActions(props.actions);
155
184
 
156
185
  if (this.phase === "delete") {
157
186
  // Disable the catch-all rule by setting enabled to false
@@ -205,11 +234,12 @@ export const EmailCatchAll = Resource(
205
234
 
206
235
  return {
207
236
  zoneId,
237
+ ruleId: result.result.id,
208
238
  enabled: result.result.enabled,
209
239
  name: result.result.name,
210
240
  matchers: result.result.matchers,
211
241
  actions: result.result.actions,
212
- tag: result.result.tag,
242
+ tag: result.result.tag || undefined,
213
243
  };
214
244
  }
215
245
 
@@ -238,11 +268,12 @@ export const EmailCatchAll = Resource(
238
268
 
239
269
  return {
240
270
  zoneId,
271
+ ruleId: result.result.id,
241
272
  enabled: result.result.enabled,
242
273
  name: result.result.name,
243
274
  matchers: result.result.matchers,
244
275
  actions: result.result.actions,
245
- tag: result.result.tag,
276
+ tag: result.result.tag || undefined,
246
277
  };
247
278
  },
248
279
  );
@@ -0,0 +1,11 @@
1
+ import type { CloudflareApi } from "./api.ts";
2
+ import { findZoneForHostname, type Zone } from "./zone.ts";
3
+
4
+ export async function resolveEmailZoneId(
5
+ api: CloudflareApi,
6
+ zone: string | Zone,
7
+ ): Promise<string> {
8
+ return typeof zone === "string"
9
+ ? (await findZoneForHostname(api, zone)).zoneId
10
+ : zone.id;
11
+ }
@@ -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
 
@@ -75,6 +76,10 @@ export interface EmailRouting {
75
76
  tag: string;
76
77
  }
77
78
 
79
+ export function isEmailRouting(resource: any): resource is EmailRouting {
80
+ return resource?.[ResourceKind] === "cloudflare::EmailRouting";
81
+ }
82
+
78
83
  /**
79
84
  * Configures email routing for a Cloudflare zone, allowing emails sent to the zone's domain
80
85
  * to be routed according to defined rules.
@@ -116,7 +121,7 @@ export const EmailRouting = Resource(
116
121
  props: EmailRoutingProps,
117
122
  ): Promise<EmailRouting> {
118
123
  const api = await createCloudflareApi(props);
119
- const zoneId = typeof props.zone === "string" ? props.zone : props.zone.id;
124
+ const zoneId = await resolveEmailZoneId(api, props.zone);
120
125
 
121
126
  if (this.phase === "delete") {
122
127
  // Disable email routing DNS
@@ -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
+ }
@@ -14,16 +14,20 @@ import type { Worker } from "../worker.ts";
14
14
  * Properties for creating a Nuxt resource.
15
15
  * Extends WebsiteProps, allowing customization of the underlying Website.
16
16
  */
17
- export interface NuxtProps<B extends Bindings> extends WebsiteProps<B> {}
17
+ export interface NuxtProps<
18
+ B extends Bindings,
19
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
20
+ > extends WebsiteProps<B, RPC> {}
18
21
 
19
22
  /**
20
23
  * Represents the output of a Nuxt resource deployment.
21
24
  * It resolves to the underlying Cloudflare Worker type, ensuring type safety.
22
25
  * Prevents overriding the internal ASSETS binding.
23
26
  */
24
- export type Nuxt<B extends Bindings> = B extends { ASSETS: any }
25
- ? never
26
- : Worker<B & { ASSETS: Assets }>;
27
+ export type Nuxt<
28
+ B extends Bindings,
29
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
30
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
27
31
 
28
32
  /**
29
33
  * Creates and deploys a Nuxt application using the Cloudflare Workers preset.
@@ -52,10 +56,10 @@ export type Nuxt<B extends Bindings> = B extends { ASSETS: any }
52
56
  * },
53
57
  * });
54
58
  */
55
- export async function Nuxt<B extends Bindings>(
56
- id: string,
57
- props?: Partial<NuxtProps<B>>,
58
- ): Promise<Nuxt<B>> {
59
+ export async function Nuxt<
60
+ B extends Bindings,
61
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
62
+ >(id: string, props?: Partial<NuxtProps<B, RPC>>): Promise<Nuxt<B, RPC>> {
59
63
  const runner = await getPackageManagerRunner();
60
64
  return await Website(id, {
61
65
  ...props,
@@ -5,7 +5,10 @@ import type { Bindings } from "../bindings.ts";
5
5
  import { Vite, type ViteProps } from "../vite/vite.ts";
6
6
  import type { Worker } from "../worker.ts";
7
7
 
8
- export interface ReactRouterProps<B extends Bindings> extends ViteProps<B> {
8
+ export interface ReactRouterProps<
9
+ B extends Bindings,
10
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
11
+ > extends ViteProps<B, RPC> {
9
12
  /**
10
13
  * @default workers/app.ts
11
14
  */
@@ -13,14 +16,18 @@ export interface ReactRouterProps<B extends Bindings> extends ViteProps<B> {
13
16
  }
14
17
 
15
18
  // don't allow the ASSETS to be overriden
16
- export type ReactRouter<B extends Bindings> = B extends { ASSETS: any }
17
- ? never
18
- : Worker<B & { ASSETS: Assets }>;
19
+ export type ReactRouter<
20
+ B extends Bindings,
21
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
22
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
19
23
 
20
- export async function ReactRouter<B extends Bindings>(
24
+ export async function ReactRouter<
25
+ B extends Bindings,
26
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
27
+ >(
21
28
  id: string,
22
- props: ReactRouterProps<B> = {},
23
- ): Promise<ReactRouter<B>> {
29
+ props: ReactRouterProps<B, RPC> = {},
30
+ ): Promise<ReactRouter<B, RPC>> {
24
31
  const runner = await getPackageManagerRunner();
25
32
  const cwd = path.resolve(props.cwd ?? process.cwd());
26
33
  const ssr = await detectSSREnabled(cwd);
@@ -4,12 +4,16 @@ import type { Bindings } from "../bindings.ts";
4
4
  import { Vite, type ViteProps } from "../vite/vite.ts";
5
5
  import type { Worker } from "../worker.ts";
6
6
 
7
- export interface RedwoodProps<B extends Bindings> extends ViteProps<B> {}
7
+ export interface RedwoodProps<
8
+ B extends Bindings,
9
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
10
+ > extends ViteProps<B, RPC> {}
8
11
 
9
12
  // don't allow the ASSETS to be overridden
10
- export type Redwood<B extends Bindings> = B extends { ASSETS: any }
11
- ? never
12
- : Worker<B & { ASSETS: Assets }>;
13
+ export type Redwood<
14
+ B extends Bindings,
15
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
16
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
13
17
 
14
18
  /**
15
19
  * Deploy a RedwoodJS application to Cloudflare Workers with automatically configured defaults.
@@ -37,10 +41,10 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
37
41
  * @param props - Configuration properties for the RedwoodJS deployment
38
42
  * @returns A Cloudflare Worker resource representing the deployed RedwoodJS application
39
43
  */
40
- export async function Redwood<B extends Bindings>(
41
- id: string,
42
- props?: Partial<RedwoodProps<B>>,
43
- ): Promise<Redwood<B>> {
44
+ export async function Redwood<
45
+ B extends Bindings,
46
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
47
+ >(id: string, props?: Partial<RedwoodProps<B, RPC>>): Promise<Redwood<B, RPC>> {
44
48
  return await Vite(id, {
45
49
  ...props,
46
50
  build: props?.build ?? {
@@ -49,6 +49,14 @@ export interface RouteProps extends CloudflareApiOptions {
49
49
  */
50
50
  adopt?: boolean;
51
51
 
52
+ /**
53
+ * Whether to delete the route when removed from Alchemy.
54
+ * If set to false, the route will remain but the resource will be removed from state.
55
+ *
56
+ * @default true
57
+ */
58
+ delete?: boolean;
59
+
52
60
  /**
53
61
  * If true, the route will not be created, but will be retained if it already exists.
54
62
  * This is used for local development.
@@ -151,7 +159,7 @@ export const Route = Resource(
151
159
 
152
160
  // Only delete if we have complete output data (both ID and zoneId)
153
161
  // If creation failed, we won't have proper output, so just skip deletion
154
- if (this.output?.id && this.output?.zoneId) {
162
+ if (props.delete !== false && this.output?.id && this.output?.zoneId) {
155
163
  await deleteRoute(api, this.output.zoneId, this.output.id);
156
164
  }
157
165
 
@@ -4,12 +4,16 @@ import type { Bindings } from "../bindings.ts";
4
4
  import { Vite, type ViteProps } from "../vite/vite.ts";
5
5
  import type { Worker } from "../worker.ts";
6
6
 
7
- export interface SvelteKitProps<B extends Bindings> extends ViteProps<B> {}
7
+ export interface SvelteKitProps<
8
+ B extends Bindings,
9
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
10
+ > extends ViteProps<B, RPC> {}
8
11
 
9
12
  // don't allow the ASSETS to be overriden
10
- export type SvelteKit<B extends Bindings> = B extends { ASSETS: any }
11
- ? never
12
- : Worker<B & { ASSETS: Assets }>;
13
+ export type SvelteKit<
14
+ B extends Bindings,
15
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
16
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
13
17
 
14
18
  /**
15
19
  * Deploy a SvelteKit application to Cloudflare Workers with automatically configured defaults.
@@ -47,10 +51,13 @@ export type SvelteKit<B extends Bindings> = B extends { ASSETS: any }
47
51
  * @param props - Configuration properties for the SvelteKit deployment
48
52
  * @returns A Cloudflare Worker resource representing the deployed SvelteKit application
49
53
  */
50
- export async function SvelteKit<B extends Bindings>(
54
+ export async function SvelteKit<
55
+ B extends Bindings,
56
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
57
+ >(
51
58
  id: string,
52
- props?: Partial<SvelteKitProps<B>>,
53
- ): Promise<SvelteKit<B>> {
59
+ props?: Partial<SvelteKitProps<B, RPC>>,
60
+ ): Promise<SvelteKit<B, RPC>> {
54
61
  if (props?.compatibilityDate) {
55
62
  const providedDate = new Date(props.compatibilityDate);
56
63
  const minDate = new Date("2024-09-23");
@@ -5,17 +5,24 @@ import type { Bindings } from "../bindings.ts";
5
5
  import { Vite, type ViteProps } from "../vite/vite.ts";
6
6
  import type { Worker } from "../worker.ts";
7
7
 
8
- export interface TanStackStartProps<B extends Bindings> extends ViteProps<B> {}
8
+ export interface TanStackStartProps<
9
+ B extends Bindings,
10
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
11
+ > extends ViteProps<B, RPC> {}
9
12
 
10
13
  // don't allow the ASSETS to be overriden
11
- export type TanStackStart<B extends Bindings> = B extends { ASSETS: any }
12
- ? never
13
- : Worker<B & { ASSETS: Assets }>;
14
+ export type TanStackStart<
15
+ B extends Bindings,
16
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
17
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
14
18
 
15
- export async function TanStackStart<B extends Bindings>(
19
+ export async function TanStackStart<
20
+ B extends Bindings,
21
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
22
+ >(
16
23
  id: string,
17
- props?: Partial<TanStackStartProps<B>>,
18
- ): Promise<TanStackStart<B>> {
24
+ props?: Partial<TanStackStartProps<B, RPC>>,
25
+ ): Promise<TanStackStart<B, RPC>> {
19
26
  const main =
20
27
  props?.wrangler?.main ??
21
28
  ((await exists(path.resolve(props?.cwd ?? process.cwd(), "src/server.ts")))
@@ -11,16 +11,20 @@ import {
11
11
  } from "../website.ts";
12
12
  import type { Worker } from "../worker.ts";
13
13
 
14
- export interface ViteProps<B extends Bindings> extends WebsiteProps<B> {}
14
+ export interface ViteProps<
15
+ B extends Bindings,
16
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
17
+ > extends WebsiteProps<B, RPC> {}
15
18
 
16
- export type Vite<B extends Bindings> = B extends { ASSETS: any }
17
- ? never
18
- : Worker<B & { ASSETS: Assets }>;
19
+ export type Vite<
20
+ B extends Bindings,
21
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
22
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
19
23
 
20
- export async function Vite<B extends Bindings>(
21
- id: string,
22
- props: ViteProps<B>,
23
- ): Promise<Vite<B>> {
24
+ export async function Vite<
25
+ B extends Bindings,
26
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
27
+ >(id: string, props: ViteProps<B, RPC>): Promise<Vite<B, RPC>> {
24
28
  const runner = await getPackageManagerRunner();
25
29
  let dev = spreadDevProps(props, `${runner} vite dev`);
26
30
  let domain = typeof dev === "object" ? dev.domain : undefined;