alchemy 0.90.0 → 0.91.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 (64) hide show
  1. package/bin/alchemy.js +1 -1
  2. package/lib/cloudflare/astro/astro.d.ts +4 -4
  3. package/lib/cloudflare/astro/astro.d.ts.map +1 -1
  4. package/lib/cloudflare/astro/astro.js +2 -2
  5. package/lib/cloudflare/astro/astro.js.map +1 -1
  6. package/lib/cloudflare/bun-spa/bun-spa.d.ts +3 -3
  7. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
  8. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
  9. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  10. package/lib/cloudflare/bundle/plugin-wasm.js +20 -2
  11. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  12. package/lib/cloudflare/container.d.ts +7 -0
  13. package/lib/cloudflare/container.d.ts.map +1 -1
  14. package/lib/cloudflare/container.js +1 -1
  15. package/lib/cloudflare/container.js.map +1 -1
  16. package/lib/cloudflare/custom-domain.d.ts +7 -0
  17. package/lib/cloudflare/custom-domain.d.ts.map +1 -1
  18. package/lib/cloudflare/custom-domain.js +3 -1
  19. package/lib/cloudflare/custom-domain.js.map +1 -1
  20. package/lib/cloudflare/nuxt/nuxt.d.ts +4 -4
  21. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -1
  22. package/lib/cloudflare/nuxt/nuxt.js.map +1 -1
  23. package/lib/cloudflare/react-router/react-router.d.ts +4 -4
  24. package/lib/cloudflare/react-router/react-router.d.ts.map +1 -1
  25. package/lib/cloudflare/react-router/react-router.js.map +1 -1
  26. package/lib/cloudflare/redwood/redwood.d.ts +4 -4
  27. package/lib/cloudflare/redwood/redwood.d.ts.map +1 -1
  28. package/lib/cloudflare/redwood/redwood.js.map +1 -1
  29. package/lib/cloudflare/route.d.ts +7 -0
  30. package/lib/cloudflare/route.d.ts.map +1 -1
  31. package/lib/cloudflare/route.js +1 -1
  32. package/lib/cloudflare/route.js.map +1 -1
  33. package/lib/cloudflare/sveltekit/sveltekit.d.ts +4 -4
  34. package/lib/cloudflare/sveltekit/sveltekit.d.ts.map +1 -1
  35. package/lib/cloudflare/sveltekit/sveltekit.js.map +1 -1
  36. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts +4 -4
  37. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
  38. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
  39. package/lib/cloudflare/vite/vite.d.ts +4 -4
  40. package/lib/cloudflare/vite/vite.d.ts.map +1 -1
  41. package/lib/cloudflare/vite/vite.js.map +1 -1
  42. package/lib/cloudflare/website.d.ts +4 -4
  43. package/lib/cloudflare/website.d.ts.map +1 -1
  44. package/lib/cloudflare/website.js.map +1 -1
  45. package/lib/cloudflare/worker.d.ts +15 -0
  46. package/lib/cloudflare/worker.d.ts.map +1 -1
  47. package/lib/cloudflare/worker.js +37 -22
  48. package/lib/cloudflare/worker.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/cloudflare/astro/astro.ts +14 -13
  51. package/src/cloudflare/bun-spa/bun-spa.ts +15 -6
  52. package/src/cloudflare/bundle/plugin-wasm.ts +21 -2
  53. package/src/cloudflare/container.ts +9 -1
  54. package/src/cloudflare/custom-domain.ts +11 -1
  55. package/src/cloudflare/nuxt/nuxt.ts +12 -8
  56. package/src/cloudflare/react-router/react-router.ts +14 -7
  57. package/src/cloudflare/redwood/redwood.ts +12 -8
  58. package/src/cloudflare/route.ts +9 -1
  59. package/src/cloudflare/sveltekit/sveltekit.ts +14 -7
  60. package/src/cloudflare/tanstack-start/tanstack-start.ts +14 -7
  61. package/src/cloudflare/vite/vite.ts +12 -8
  62. package/src/cloudflare/website.ts +13 -12
  63. package/src/cloudflare/worker.ts +71 -33
  64. package/workers/tunnel-proxy.js +1 -1
@@ -14,7 +14,10 @@ import {
14
14
  type WebsiteProps,
15
15
  } from "../website.ts";
16
16
 
17
- export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
17
+ export interface BunSPAProps<
18
+ B extends Bindings,
19
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
20
+ > extends WebsiteProps<B, RPC> {
18
21
  /**
19
22
  * The path to the frontend entrypoints that bun should bundle for deployment & serve in dev mode.
20
23
  * These are usually html files. Glob patterns are supported.
@@ -24,12 +27,18 @@ export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
24
27
  outDir?: string;
25
28
  }
26
29
 
27
- export type BunSPA<B extends Bindings> = Website<B> & { apiUrl: string };
30
+ export type BunSPA<
31
+ B extends Bindings,
32
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
33
+ > = Website<B, RPC> & { apiUrl: string };
28
34
 
29
- export async function BunSPA<B extends Bindings>(
35
+ export async function BunSPA<
36
+ B extends Bindings,
37
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
38
+ >(
30
39
  id: string,
31
- props: BunSPAProps<B>,
32
- ): Promise<BunSPA<B> & { apiUrl: string }> {
40
+ props: BunSPAProps<B, RPC>,
41
+ ): Promise<BunSPA<B, RPC> & { apiUrl: string }> {
33
42
  const frontendPaths = Array.isArray(props.frontend)
34
43
  ? props.frontend.map((p) => path.resolve(p))
35
44
  : [path.resolve(props.frontend)];
@@ -132,7 +141,7 @@ export async function BunSPA<B extends Bindings>(
132
141
  },
133
142
  });
134
143
  }
135
- return { ...website, apiUrl } as BunSPA<B>;
144
+ return { ...website, apiUrl } as BunSPA<B, RPC>;
136
145
  }
137
146
 
138
147
  async function validateBunfigToml(cwd: string): Promise<void> {
@@ -2,6 +2,7 @@ import type esbuild from "esbuild";
2
2
  import assert from "node:assert";
3
3
  import crypto from "node:crypto";
4
4
  import fs from "node:fs/promises";
5
+ import { createRequire } from "node:module";
5
6
  import path from "pathe";
6
7
  import type { WorkerBundle } from "../worker-bundle.ts";
7
8
 
@@ -22,9 +23,27 @@ export function createWasmPlugin() {
22
23
  return { path: resolved.path, external: true };
23
24
  }
24
25
 
25
- // Resolve path to source file, excluding the `?module` suffix (uses path.resolve in case args.path is already absolute)
26
+ // Resolve path to source file, excluding the `?module` suffix
26
27
  const normalizedPath = path.normalize(args.path).replace(/\?.*$/, "");
27
- const copyFrom = path.resolve(args.resolveDir, normalizedPath);
28
+
29
+ // Determine the source file: use Node module resolution for bare specifiers (npm packages),
30
+ // otherwise resolve relative to the importing file's directory
31
+ let copyFrom: string;
32
+ if (/^[./]/.test(normalizedPath)) {
33
+ copyFrom = path.resolve(args.resolveDir, normalizedPath);
34
+ } else {
35
+ try {
36
+ // createRequire needs a file path to anchor node_modules resolution from.
37
+ // The filename is a dummy — only the directory matters.
38
+ const esmRequire = createRequire(
39
+ path.join(args.resolveDir, "noop.js"),
40
+ );
41
+ copyFrom = esmRequire.resolve(normalizedPath);
42
+ } catch {
43
+ // Fall back to the original behavior if require.resolve fails
44
+ copyFrom = path.resolve(args.resolveDir, normalizedPath);
45
+ }
46
+ }
28
47
 
29
48
  // Resolve path to outdir (required for monorepos if the workdir is not the same as process.cwd())
30
49
  assert(
@@ -410,6 +410,14 @@ export interface ContainerApplicationProps extends CloudflareApiOptions {
410
410
  */
411
411
  adopt?: boolean;
412
412
 
413
+ /**
414
+ * Whether to delete the container application when removed from Alchemy.
415
+ * If set to false, the container application will remain but the resource will be removed from state.
416
+ *
417
+ * @default true
418
+ */
419
+ delete?: boolean;
420
+
413
421
  /**
414
422
  * If true, the container application will not be created, but will be retained if it already exists.
415
423
  * This is used for local development.
@@ -557,7 +565,7 @@ export const ContainerApplication = Resource(
557
565
 
558
566
  const api = await createCloudflareApi(props);
559
567
  if (this.phase === "delete") {
560
- if (this.output?.id) {
568
+ if (props.delete !== false && this.output?.id) {
561
569
  // Delete the container application
562
570
  await deleteContainerApplication(api, this.output.id);
563
571
  }
@@ -44,6 +44,14 @@ export interface CustomDomainProps extends CloudflareApiOptions {
44
44
  */
45
45
  adopt?: boolean;
46
46
 
47
+ /**
48
+ * Whether to delete the custom domain when removed from Alchemy.
49
+ * If set to false, the custom domain will remain but the resource will be removed from state.
50
+ *
51
+ * @default true
52
+ */
53
+ delete?: boolean;
54
+
47
55
  /**
48
56
  * If true, the custom domain will not be created, but will be retained if it already exists.
49
57
  * This is used for local development.
@@ -137,7 +145,9 @@ export const CustomDomain = Resource(
137
145
  const api = await createCloudflareApi(props);
138
146
 
139
147
  if (this.phase === "delete") {
140
- await deleteCustomDomain(this, api, logicalId, props);
148
+ if (props.delete !== false) {
149
+ await deleteCustomDomain(this, api, logicalId, props);
150
+ }
141
151
  return this.destroy();
142
152
  }
143
153
 
@@ -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;
@@ -18,10 +18,10 @@ import { computeWorkerDevDomain } from "./worker-subdomain.ts";
18
18
  import { type AssetsConfig, Worker, type WorkerProps } from "./worker.ts";
19
19
  import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
20
20
 
21
- export interface WebsiteProps<B extends Bindings> extends Omit<
22
- WorkerProps<B>,
23
- "assets" | "dev"
24
- > {
21
+ export interface WebsiteProps<
22
+ B extends Bindings,
23
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
24
+ > extends Omit<WorkerProps<B, RPC>, "assets" | "dev"> {
25
25
  /**
26
26
  * Configuration for the build command
27
27
  *
@@ -149,14 +149,15 @@ export interface WebsiteProps<B extends Bindings> extends Omit<
149
149
  memoize?: boolean | { patterns: string[] };
150
150
  }
151
151
 
152
- export type Website<B extends Bindings> = B extends { ASSETS: any }
153
- ? never
154
- : Worker<B & { ASSETS: Assets }>;
152
+ export type Website<
153
+ B extends Bindings,
154
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
155
+ > = B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }, RPC>;
155
156
 
156
- export async function Website<B extends Bindings>(
157
- id: string,
158
- props: WebsiteProps<B>,
159
- ) {
157
+ export async function Website<
158
+ B extends Bindings,
159
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
160
+ >(id: string, props: WebsiteProps<B, RPC>) {
160
161
  const {
161
162
  name = Scope.current.createPhysicalName(id).toLowerCase(),
162
163
  build: buildProps,
@@ -377,7 +378,7 @@ export async function Website<B extends Bindings>(
377
378
  : {}),
378
379
  },
379
380
  dev: url ? { url } : undefined,
380
- })) as Website<B>;
381
+ })) as Website<B, RPC>;
381
382
  }
382
383
 
383
384
  export const spreadBuildProps = (
@@ -6,7 +6,11 @@ import type { type } from "../type.ts";
6
6
  import { DeferredPromise } from "../util/deferred-promise.ts";
7
7
  import { logger } from "../util/logger.ts";
8
8
  import { withExponentialBackoff } from "../util/retry.ts";
9
- import { CloudflareApiError, handleApiError } from "./api-error.ts";
9
+ import {
10
+ CloudflareApiError,
11
+ handleApiError,
12
+ isCloudflareApiError,
13
+ } from "./api-error.ts";
10
14
  import {
11
15
  type CloudflareApi,
12
16
  type CloudflareApiOptions,
@@ -207,6 +211,14 @@ export interface BaseWorkerProps<
207
211
  */
208
212
  logpush?: boolean;
209
213
 
214
+ /**
215
+ * Whether to delete the worker when removed from Alchemy.
216
+ * If set to false, the worker will remain but the resource will be removed from state.
217
+ *
218
+ * @default true
219
+ */
220
+ delete?: boolean;
221
+
210
222
  /**
211
223
  * Whether to adopt the Worker if it already exists when creating
212
224
  */
@@ -939,6 +951,14 @@ export type Worker<
939
951
  * // The worker will have a preview URL for testing:
940
952
  * console.log(`Preview URL: ${previewWorker.url}`);
941
953
  * // Output: Preview URL: https://pr-123-my-worker.subdomain.workers.dev
954
+ *
955
+ * @example
956
+ * // Prevent deletion of the worker when removed from Alchemy:
957
+ * const worker = await Worker("critical-api", {
958
+ * name: "critical-api",
959
+ * entrypoint: "./src/api.ts",
960
+ * delete: false
961
+ * });
942
962
  */
943
963
  export function Worker<
944
964
  const B extends Bindings,
@@ -1104,7 +1124,7 @@ const _Worker = Resource(
1104
1124
  durableObjects: options.durableObjects,
1105
1125
  workflows: options.workflows,
1106
1126
  });
1107
- if (this.output?.dev?.hasRemote !== false) {
1127
+ if (props.delete !== false && this.output?.dev?.hasRemote !== false) {
1108
1128
  const api = await createCloudflareApi(props);
1109
1129
  if (props.version) {
1110
1130
  //* if the worker exists we deploy an empty version so we can destroy
@@ -1250,45 +1270,49 @@ const _Worker = Resource(
1250
1270
 
1251
1271
  let result: PutWorkerResult;
1252
1272
 
1273
+ // cloudflare has awkward sequencing requirements for modifying subdomains
1274
+ // because conflicts with Durable Objects and preview_enabled is stateful
1275
+ // to solve this, we try and do both in parallel and push them through
1276
+ const bruteForce = <T>(operation: () => Promise<T>) =>
1277
+ withExponentialBackoff(
1278
+ operation,
1279
+ (error) => {
1280
+ operation;
1281
+ return (
1282
+ error instanceof CloudflareApiError &&
1283
+ error.status === 400 &&
1284
+ error.message.includes(
1285
+ "Cannot use Durable Objects with Preview URLs",
1286
+ )
1287
+ );
1288
+ },
1289
+ 10,
1290
+ 100,
1291
+ 1000,
1292
+ );
1293
+
1253
1294
  if (this.scope.watch) {
1254
1295
  const controller = new AbortController();
1255
- result = await watchWorker(api, props, {
1256
- id,
1257
- name: options.name,
1258
- dispatchNamespace: options.dispatchNamespace,
1259
- bundle,
1260
- compatibilityDate: options.compatibilityDate,
1261
- compatibilityFlags: options.compatibilityFlags,
1262
- version: props.version,
1263
- assets,
1264
- controller,
1265
- });
1296
+ [result] = await Promise.all([
1297
+ watchWorker(api, props, {
1298
+ id,
1299
+ name: options.name,
1300
+ dispatchNamespace: options.dispatchNamespace,
1301
+ bundle,
1302
+ compatibilityDate: options.compatibilityDate,
1303
+ compatibilityFlags: options.compatibilityFlags,
1304
+ version: props.version,
1305
+ assets,
1306
+ controller,
1307
+ }),
1308
+ bruteForce(updateSubdomain),
1309
+ ]);
1266
1310
  this.onCleanup(() => controller.abort());
1267
1311
  const tail = await createTail(api, id, options.name).catch((error) => {
1268
1312
  logger.error(`Failed to create tail for ${options.name}`, error);
1269
1313
  });
1270
1314
  this.onCleanup(() => tail?.close());
1271
1315
  } else {
1272
- // cloudflare has awkward sequencing requirements for modifying subdomains
1273
- // because conflicts with Durable Objects and preview_enabled is stateful
1274
- // to solve this, we try and do both in parallel and push them through
1275
- const bruteForce = <T>(operation: () => Promise<T>) =>
1276
- withExponentialBackoff(
1277
- operation,
1278
- (error) => {
1279
- operation;
1280
- return (
1281
- error instanceof CloudflareApiError &&
1282
- error.status === 400 &&
1283
- error.message.includes(
1284
- "Cannot use Durable Objects with Preview URLs",
1285
- )
1286
- );
1287
- },
1288
- 10,
1289
- 100,
1290
- 1000,
1291
- );
1292
1316
  const scriptBundle = await bundle.create();
1293
1317
  const [_, _result] = await Promise.all([
1294
1318
  bruteForce(updateSubdomain),
@@ -1540,6 +1564,7 @@ async function provisionResources<B extends Bindings>(
1540
1564
  durableObjects: {
1541
1565
  namespaceId: await getContainerNamespaceId(container),
1542
1566
  },
1567
+ delete: props.delete,
1543
1568
  dev: options.local,
1544
1569
  ...input.api,
1545
1570
  });
@@ -1553,6 +1578,7 @@ async function provisionResources<B extends Bindings>(
1553
1578
  name: domain.name,
1554
1579
  zoneId: domain.zoneId,
1555
1580
  adopt: domain.adopt,
1581
+ delete: props.delete,
1556
1582
  workerName: options.name,
1557
1583
  dev: options.local,
1558
1584
  ...input.api,
@@ -1575,6 +1601,7 @@ async function provisionResources<B extends Bindings>(
1575
1601
  scriptName: options.name,
1576
1602
  settings: eventSource.settings,
1577
1603
  adopt: props.adopt,
1604
+ delete: props.delete,
1578
1605
  dev: options.local,
1579
1606
  ...input.api,
1580
1607
  });
@@ -1589,6 +1616,7 @@ async function provisionResources<B extends Bindings>(
1589
1616
  script: options.name,
1590
1617
  zoneId: route.zoneId,
1591
1618
  adopt: route.adopt,
1619
+ delete: props.delete,
1592
1620
  dev: options.local,
1593
1621
  ...input.api,
1594
1622
  });
@@ -1839,6 +1867,16 @@ export async function putWorker(
1839
1867
  });
1840
1868
  }
1841
1869
  }
1870
+ if (
1871
+ isCloudflareApiError(error, { code: 11001 }) &&
1872
+ !props.eventSources?.length
1873
+ ) {
1874
+ // Error code 11001 means that the worker has event sources, but no `queue` handler is defined.
1875
+ // Normally, queue consumers are removed automatically since we're using the `QueueConsumer` resource,
1876
+ // but if no `queue` handler is defined, we need to remove them for Cloudflare to let us deploy.
1877
+ await deleteQueueConsumers(api, workerName);
1878
+ return await putWorker(api, props);
1879
+ }
1842
1880
  throw error;
1843
1881
  }
1844
1882
  },
@@ -83,7 +83,7 @@ const renderErrorHtml = (props) => `
83
83
  Alchemy</a>.</p>
84
84
  </div>
85
85
  <div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
86
- <p class="text-sm text-slate-500">Alchemy 0.90.0</p>
86
+ <p class="text-sm text-slate-500">Alchemy 0.91.2</p>
87
87
  </div>
88
88
  </div>
89
89
  </body>