alchemy 0.44.3 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/bin/alchemy.mjs +0 -13
  2. package/bin/commands/create.ts +1 -10
  3. package/bin/services/vibe-rules.ts +0 -14
  4. package/lib/alchemy.d.ts +3 -3
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +5 -2
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/build-date.d.ts +1 -1
  9. package/lib/build-date.js +1 -1
  10. package/lib/cloudflare/auth.js +1 -1
  11. package/lib/cloudflare/auth.js.map +1 -1
  12. package/lib/cloudflare/bundle/esbuild.d.ts +16 -0
  13. package/lib/cloudflare/bundle/esbuild.d.ts.map +1 -0
  14. package/lib/cloudflare/bundle/esbuild.js +159 -0
  15. package/lib/cloudflare/bundle/esbuild.js.map +1 -0
  16. package/lib/cloudflare/bundle/fs.d.ts +18 -0
  17. package/lib/cloudflare/bundle/fs.d.ts.map +1 -0
  18. package/lib/cloudflare/bundle/fs.js +75 -0
  19. package/lib/cloudflare/bundle/fs.js.map +1 -0
  20. package/lib/cloudflare/bundle/index.d.ts +3 -0
  21. package/lib/cloudflare/bundle/index.d.ts.map +1 -0
  22. package/lib/cloudflare/bundle/index.js +2 -0
  23. package/lib/cloudflare/bundle/index.js.map +1 -0
  24. package/lib/cloudflare/bundle/inline.d.ts +12 -0
  25. package/lib/cloudflare/bundle/inline.d.ts.map +1 -0
  26. package/lib/cloudflare/bundle/inline.js +32 -0
  27. package/lib/cloudflare/bundle/inline.js.map +1 -0
  28. package/lib/cloudflare/bundle/normalize.d.ts +21 -0
  29. package/lib/cloudflare/bundle/normalize.d.ts.map +1 -0
  30. package/lib/cloudflare/bundle/normalize.js +49 -0
  31. package/lib/cloudflare/bundle/normalize.js.map +1 -0
  32. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.d.ts → plugin-compat-warning.d.ts} +2 -2
  33. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +1 -0
  34. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.js → plugin-compat-warning.js} +3 -4
  35. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -0
  36. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +8 -0
  37. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -0
  38. package/lib/cloudflare/bundle/plugin-hot-reload.js +19 -0
  39. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -0
  40. package/lib/cloudflare/bundle/{nodejs-compat.d.ts → plugin-hybrid-node-compat.d.ts} +2 -2
  41. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -0
  42. package/lib/cloudflare/bundle/{nodejs-compat.js → plugin-hybrid-node-compat.js} +10 -10
  43. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -0
  44. package/lib/cloudflare/bundle/plugin-wasm.d.ts +3 -0
  45. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -0
  46. package/lib/cloudflare/bundle/plugin-wasm.js +55 -0
  47. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -0
  48. package/lib/cloudflare/bundle/shared.d.ts +30 -0
  49. package/lib/cloudflare/bundle/shared.d.ts.map +1 -0
  50. package/lib/cloudflare/bundle/shared.js +34 -0
  51. package/lib/cloudflare/bundle/shared.js.map +1 -0
  52. package/lib/cloudflare/bundle/{nodejs-compat-mode.d.ts → validate-node-compat.d.ts} +8 -4
  53. package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -0
  54. package/lib/cloudflare/bundle/{nodejs-compat-mode.js → validate-node-compat.js} +27 -7
  55. package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -0
  56. package/lib/cloudflare/container.d.ts +231 -0
  57. package/lib/cloudflare/container.d.ts.map +1 -1
  58. package/lib/cloudflare/container.js +7 -0
  59. package/lib/cloudflare/container.js.map +1 -1
  60. package/lib/cloudflare/d1-migrations.d.ts +1 -0
  61. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  62. package/lib/cloudflare/d1-migrations.js +18 -6
  63. package/lib/cloudflare/d1-migrations.js.map +1 -1
  64. package/lib/cloudflare/d1-state-store.d.ts +10 -0
  65. package/lib/cloudflare/d1-state-store.d.ts.map +1 -0
  66. package/lib/cloudflare/d1-state-store.js +70 -0
  67. package/lib/cloudflare/d1-state-store.js.map +1 -0
  68. package/lib/cloudflare/do-state-store/internal.js +1 -1
  69. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  70. package/lib/cloudflare/do-state-store/store.js +1 -1
  71. package/lib/cloudflare/do-state-store/store.js.map +1 -1
  72. package/lib/cloudflare/index.d.ts +3 -2
  73. package/lib/cloudflare/index.d.ts.map +1 -1
  74. package/lib/cloudflare/index.js +3 -2
  75. package/lib/cloudflare/index.js.map +1 -1
  76. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -0
  77. package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.js → local-dev-cloudflare-shim.js} +1 -1
  78. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -0
  79. package/lib/cloudflare/miniflare/http-server.d.ts.map +1 -0
  80. package/lib/cloudflare/miniflare/http-server.js.map +1 -0
  81. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.d.ts +4 -3
  82. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -0
  83. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.js +6 -3
  84. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -0
  85. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -0
  86. package/lib/cloudflare/{worker → miniflare}/miniflare.js +1 -1
  87. package/lib/cloudflare/miniflare/miniflare.js.map +1 -0
  88. package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +1 -0
  89. package/lib/cloudflare/{worker → miniflare}/mixed-mode.js +1 -2
  90. package/lib/cloudflare/miniflare/mixed-mode.js.map +1 -0
  91. package/lib/cloudflare/user.js +1 -1
  92. package/lib/cloudflare/user.js.map +1 -1
  93. package/lib/cloudflare/worker/shared.d.ts +4 -0
  94. package/lib/cloudflare/worker/shared.d.ts.map +1 -0
  95. package/lib/cloudflare/worker/{get-worker-template.js → shared.js} +10 -1
  96. package/lib/cloudflare/worker/shared.js.map +1 -0
  97. package/lib/cloudflare/worker/tail.d.ts +18 -0
  98. package/lib/cloudflare/worker/tail.d.ts.map +1 -0
  99. package/lib/cloudflare/worker/tail.js +140 -0
  100. package/lib/cloudflare/worker/tail.js.map +1 -0
  101. package/lib/cloudflare/worker-metadata.d.ts +6 -2
  102. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  103. package/lib/cloudflare/worker-metadata.js +7 -23
  104. package/lib/cloudflare/worker-metadata.js.map +1 -1
  105. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  106. package/lib/cloudflare/worker-stub.js +8 -4
  107. package/lib/cloudflare/worker-stub.js.map +1 -1
  108. package/lib/cloudflare/worker-subdomain.d.ts +31 -0
  109. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -0
  110. package/lib/cloudflare/worker-subdomain.js +55 -0
  111. package/lib/cloudflare/worker-subdomain.js.map +1 -0
  112. package/lib/cloudflare/worker.d.ts +30 -28
  113. package/lib/cloudflare/worker.d.ts.map +1 -1
  114. package/lib/cloudflare/worker.js +641 -554
  115. package/lib/cloudflare/worker.js.map +1 -1
  116. package/lib/fs/file-system-state-store.js +1 -1
  117. package/lib/fs/file-system-state-store.js.map +1 -1
  118. package/lib/scope.d.ts +2 -2
  119. package/lib/scope.d.ts.map +1 -1
  120. package/lib/scope.js +23 -8
  121. package/lib/scope.js.map +1 -1
  122. package/lib/serde.d.ts +1 -1
  123. package/lib/serde.d.ts.map +1 -1
  124. package/lib/serde.js +4 -4
  125. package/lib/serde.js.map +1 -1
  126. package/lib/sqlite/base-sqlite-state-store.d.ts +31 -0
  127. package/lib/sqlite/base-sqlite-state-store.d.ts.map +1 -0
  128. package/lib/sqlite/base-sqlite-state-store.js +124 -0
  129. package/lib/sqlite/base-sqlite-state-store.js.map +1 -0
  130. package/lib/sqlite/index.d.ts +2 -0
  131. package/lib/sqlite/index.d.ts.map +1 -0
  132. package/lib/sqlite/index.js +2 -0
  133. package/lib/sqlite/index.js.map +1 -0
  134. package/lib/sqlite/schema.d.ts +198 -0
  135. package/lib/sqlite/schema.d.ts.map +1 -0
  136. package/lib/sqlite/schema.js +23 -0
  137. package/lib/sqlite/schema.js.map +1 -0
  138. package/lib/sqlite/sqlite-state-store.d.ts +60 -0
  139. package/lib/sqlite/sqlite-state-store.d.ts.map +1 -0
  140. package/lib/sqlite/sqlite-state-store.js +83 -0
  141. package/lib/sqlite/sqlite-state-store.js.map +1 -0
  142. package/lib/test/bun.d.ts.map +1 -1
  143. package/lib/test/bun.js +0 -6
  144. package/lib/test/bun.js.map +1 -1
  145. package/lib/test/vitest.d.ts.map +1 -1
  146. package/lib/test/vitest.js +19 -5
  147. package/lib/test/vitest.js.map +1 -1
  148. package/lib/util/memoize.d.ts +1 -1
  149. package/lib/util/memoize.d.ts.map +1 -1
  150. package/lib/util/memoize.js +11 -1
  151. package/lib/util/memoize.js.map +1 -1
  152. package/package.json +12 -3
  153. package/src/alchemy.ts +8 -6
  154. package/src/build-date.ts +1 -1
  155. package/src/cloudflare/auth.ts +1 -1
  156. package/src/cloudflare/bundle/esbuild.ts +194 -0
  157. package/src/cloudflare/bundle/fs.ts +98 -0
  158. package/src/cloudflare/bundle/index.ts +6 -0
  159. package/src/cloudflare/bundle/inline.ts +42 -0
  160. package/src/cloudflare/bundle/normalize.ts +76 -0
  161. package/src/cloudflare/bundle/{nodejs-import-warning-plugin.ts → plugin-compat-warning.ts} +4 -3
  162. package/src/cloudflare/bundle/plugin-hot-reload.ts +27 -0
  163. package/src/cloudflare/bundle/{nodejs-compat.ts → plugin-hybrid-node-compat.ts} +10 -10
  164. package/src/cloudflare/bundle/plugin-wasm.ts +62 -0
  165. package/src/cloudflare/bundle/shared.ts +73 -0
  166. package/src/cloudflare/bundle/{nodejs-compat-mode.ts → validate-node-compat.ts} +41 -16
  167. package/src/cloudflare/container.ts +296 -0
  168. package/src/cloudflare/d1-migrations.ts +29 -16
  169. package/src/cloudflare/d1-state-store.ts +105 -0
  170. package/src/cloudflare/do-state-store/internal.ts +1 -1
  171. package/src/cloudflare/do-state-store/store.ts +1 -1
  172. package/src/cloudflare/index.ts +3 -2
  173. package/src/cloudflare/{bundle/local-dev-cloudflare-shim.ts → local-dev-cloudflare-shim.ts} +1 -1
  174. package/src/cloudflare/{worker → miniflare}/miniflare-worker-options.ts +14 -6
  175. package/src/cloudflare/{worker → miniflare}/miniflare.ts +1 -1
  176. package/src/cloudflare/{worker → miniflare}/mixed-mode.ts +1 -2
  177. package/src/cloudflare/user.ts +1 -1
  178. package/src/cloudflare/worker/{subdomain.ts → shared.ts} +14 -0
  179. package/src/cloudflare/worker/tail.ts +287 -0
  180. package/src/cloudflare/worker-metadata.ts +18 -26
  181. package/src/cloudflare/worker-stub.ts +9 -4
  182. package/src/cloudflare/worker-subdomain.ts +123 -0
  183. package/src/cloudflare/worker.ts +863 -734
  184. package/src/fs/file-system-state-store.ts +1 -1
  185. package/src/scope.ts +30 -11
  186. package/src/serde.ts +4 -4
  187. package/src/sqlite/base-sqlite-state-store.ts +199 -0
  188. package/src/sqlite/index.ts +1 -0
  189. package/src/sqlite/schema.ts +37 -0
  190. package/src/sqlite/sqlite-state-store.ts +174 -0
  191. package/src/test/bun.ts +0 -8
  192. package/src/test/vitest.ts +19 -8
  193. package/src/util/memoize.ts +13 -1
  194. package/templates/astro/types/env.d.ts +1 -1
  195. package/templates/nuxt/types/env.d.ts +1 -1
  196. package/templates/react-router/types/env.d.ts +1 -1
  197. package/templates/rwsdk/types/env.d.ts +1 -1
  198. package/templates/sveltekit/types/env.d.ts +1 -1
  199. package/templates/tanstack-start/types/env.d.ts +1 -1
  200. package/templates/typescript/types/env.d.ts +1 -1
  201. package/templates/vite/types/env.d.ts +1 -1
  202. package/lib/cloudflare/bundle/alias-plugin.d.ts +0 -6
  203. package/lib/cloudflare/bundle/alias-plugin.d.ts.map +0 -1
  204. package/lib/cloudflare/bundle/alias-plugin.js +0 -36
  205. package/lib/cloudflare/bundle/alias-plugin.js.map +0 -1
  206. package/lib/cloudflare/bundle/build-failures.d.ts +0 -12
  207. package/lib/cloudflare/bundle/build-failures.d.ts.map +0 -1
  208. package/lib/cloudflare/bundle/build-failures.js +0 -45
  209. package/lib/cloudflare/bundle/build-failures.js.map +0 -1
  210. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +0 -26
  211. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +0 -1
  212. package/lib/cloudflare/bundle/bundle-worker-dev.js +0 -88
  213. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +0 -1
  214. package/lib/cloudflare/bundle/bundle-worker.d.ts +0 -13
  215. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +0 -1
  216. package/lib/cloudflare/bundle/bundle-worker.js +0 -98
  217. package/lib/cloudflare/bundle/bundle-worker.js.map +0 -1
  218. package/lib/cloudflare/bundle/external.d.ts +0 -4
  219. package/lib/cloudflare/bundle/external.d.ts.map +0 -1
  220. package/lib/cloudflare/bundle/external.js +0 -40
  221. package/lib/cloudflare/bundle/external.js.map +0 -1
  222. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts.map +0 -1
  223. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js.map +0 -1
  224. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +0 -1
  225. package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +0 -1
  226. package/lib/cloudflare/bundle/nodejs-compat.d.ts.map +0 -1
  227. package/lib/cloudflare/bundle/nodejs-compat.js.map +0 -1
  228. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +0 -1
  229. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +0 -1
  230. package/lib/cloudflare/bundle/wasm-plugin.d.ts +0 -3
  231. package/lib/cloudflare/bundle/wasm-plugin.d.ts.map +0 -1
  232. package/lib/cloudflare/bundle/wasm-plugin.js +0 -53
  233. package/lib/cloudflare/bundle/wasm-plugin.js.map +0 -1
  234. package/lib/cloudflare/worker/get-worker-template.d.ts +0 -2
  235. package/lib/cloudflare/worker/get-worker-template.d.ts.map +0 -1
  236. package/lib/cloudflare/worker/get-worker-template.js.map +0 -1
  237. package/lib/cloudflare/worker/http-server.d.ts.map +0 -1
  238. package/lib/cloudflare/worker/http-server.js.map +0 -1
  239. package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +0 -1
  240. package/lib/cloudflare/worker/miniflare-worker-options.js.map +0 -1
  241. package/lib/cloudflare/worker/miniflare.d.ts.map +0 -1
  242. package/lib/cloudflare/worker/miniflare.js.map +0 -1
  243. package/lib/cloudflare/worker/mixed-mode.d.ts.map +0 -1
  244. package/lib/cloudflare/worker/mixed-mode.js.map +0 -1
  245. package/lib/cloudflare/worker/subdomain.d.ts +0 -3
  246. package/lib/cloudflare/worker/subdomain.d.ts.map +0 -1
  247. package/lib/cloudflare/worker/subdomain.js +0 -10
  248. package/lib/cloudflare/worker/subdomain.js.map +0 -1
  249. package/src/cloudflare/bundle/alias-plugin.ts +0 -48
  250. package/src/cloudflare/bundle/build-failures.ts +0 -55
  251. package/src/cloudflare/bundle/bundle-worker-dev.ts +0 -129
  252. package/src/cloudflare/bundle/bundle-worker.ts +0 -150
  253. package/src/cloudflare/bundle/external.ts +0 -41
  254. package/src/cloudflare/bundle/wasm-plugin.ts +0 -57
  255. package/src/cloudflare/worker/get-worker-template.ts +0 -14
  256. /package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.d.ts → local-dev-cloudflare-shim.d.ts} +0 -0
  257. /package/lib/cloudflare/{worker → miniflare}/http-server.d.ts +0 -0
  258. /package/lib/cloudflare/{worker → miniflare}/http-server.js +0 -0
  259. /package/lib/cloudflare/{worker → miniflare}/miniflare.d.ts +0 -0
  260. /package/lib/cloudflare/{worker → miniflare}/mixed-mode.d.ts +0 -0
  261. /package/src/cloudflare/{worker → miniflare}/http-server.ts +0 -0
@@ -1,3 +1,5 @@
1
+ import type esbuild from "esbuild";
2
+ import kleur from "kleur";
1
3
  import { spawn } from "node:child_process";
2
4
  import {
3
5
  existsSync,
@@ -6,7 +8,8 @@ import {
6
8
  unlinkSync,
7
9
  writeFileSync,
8
10
  } from "node:fs";
9
- import path from "node:path";
11
+ import { isDeepStrictEqual } from "node:util";
12
+ import path from "pathe";
10
13
  import { BUILD_DATE } from "../build-date.ts";
11
14
  import type { Context } from "../context.ts";
12
15
  import type { BundleProps } from "../esbuild/bundle.ts";
@@ -18,7 +21,6 @@ import { Scope } from "../scope.ts";
18
21
  import { Secret, secret } from "../secret.ts";
19
22
  import { serializeScope } from "../serde.ts";
20
23
  import type { type } from "../type.ts";
21
- import { getContentType } from "../util/content-type.ts";
22
24
  import { DeferredPromise } from "../util/deferred-promise.ts";
23
25
  import { logger } from "../util/logger.ts";
24
26
  import { withExponentialBackoff } from "../util/retry.ts";
@@ -39,11 +41,13 @@ import {
39
41
  } from "./bindings.ts";
40
42
  import type { Bound } from "./bound.ts";
41
43
  import { isBucket } from "./bucket.ts";
42
- import { createWorkerDevContext } from "./bundle/bundle-worker-dev.ts";
43
44
  import {
44
- type NoBundleResult,
45
- bundleWorkerScript,
46
- } from "./bundle/bundle-worker.ts";
45
+ type WorkerBundle,
46
+ type WorkerBundleChunk,
47
+ type WorkerBundleProvider,
48
+ normalizeWorkerBundle,
49
+ } from "./bundle/index.ts";
50
+ import { wrap } from "./bundle/normalize.ts";
47
51
  import { type Container, ContainerApplication } from "./container.ts";
48
52
  import { CustomDomain } from "./custom-domain.ts";
49
53
  import { isD1Database } from "./d1-database.ts";
@@ -58,6 +62,8 @@ import {
58
62
  isQueueEventSource,
59
63
  } from "./event-source.ts";
60
64
  import { isKVNamespace } from "./kv-namespace.ts";
65
+ import type { MiniflareWorkerOptions } from "./miniflare/miniflare-worker-options.ts";
66
+ import { miniflareServer } from "./miniflare/miniflare.ts";
61
67
  import { isPipeline } from "./pipeline.ts";
62
68
  import {
63
69
  QueueConsumer,
@@ -67,16 +73,15 @@ import {
67
73
  import { type QueueResource, isQueue } from "./queue.ts";
68
74
  import { Route } from "./route.ts";
69
75
  import { isVectorizeIndex } from "./vectorize-index.ts";
70
- import { type AssetUploadResult, uploadAssets } from "./worker-assets.ts";
76
+ import { uploadAssets } from "./worker-assets.ts";
71
77
  import {
72
78
  type WorkerScriptMetadata,
73
79
  bumpMigrationTagVersion,
74
80
  prepareWorkerMetadata,
75
81
  } from "./worker-metadata.ts";
76
82
  import { WorkerStub, isWorkerStub } from "./worker-stub.ts";
77
- import type { MiniflareWorkerOptions } from "./worker/miniflare-worker-options.ts";
78
- import { miniflareServer } from "./worker/miniflare.ts";
79
- import { getAccountSubdomain } from "./worker/subdomain.ts";
83
+ import { WorkerSubdomain, disableWorkerSubdomain } from "./worker-subdomain.ts";
84
+ import { createTail } from "./worker/tail.ts";
80
85
  import { Workflow, isWorkflow, upsertWorkflow } from "./workflow.ts";
81
86
 
82
87
  /**
@@ -246,7 +251,7 @@ export interface BaseWorkerProps<
246
251
  * ],
247
252
  * });
248
253
  */
249
- routes?: Array<
254
+ routes?: (
250
255
  | string
251
256
  | {
252
257
  /**
@@ -264,7 +269,7 @@ export interface BaseWorkerProps<
264
269
  */
265
270
  adopt?: boolean;
266
271
  }
267
- >;
272
+ )[];
268
273
 
269
274
  /**
270
275
  * Custom domains to bind to the worker
@@ -335,9 +340,7 @@ export interface BaseWorkerProps<
335
340
  */
336
341
  port?: number;
337
342
  /**
338
- * EXPERIMENTAL: Whether to run the worker remotely instead of locally.
339
- *
340
- * When this is enabled, hot reloading will not work.
343
+ * Whether to run the worker remotely instead of locally.
341
344
  *
342
345
  * @default false
343
346
  */
@@ -381,9 +384,9 @@ export interface EntrypointWorkerProps<
381
384
  /**
382
385
  * Whether to upload source maps for the worker script.
383
386
  *
384
- * @default false
387
+ * @default true
385
388
  */
386
- uploadSourceMaps?: boolean;
389
+ sourceMap?: boolean;
387
390
 
388
391
  /**
389
392
  * Rules for adding additional files to the bundle.
@@ -972,193 +975,72 @@ export const _Worker = Resource(
972
975
  id: string,
973
976
  props: WorkerProps<B>,
974
977
  ): Promise<Worker<B>> {
975
- if (props.noBundle && !props.entrypoint) {
976
- throw new Error("entrypoint must be provided when noBundle is true");
977
- }
978
-
979
- // Use the provided name
980
- const workerName = props.name ?? id;
981
-
982
978
  if (props.projectRoot) {
983
979
  logger.warn("projectRoot is deprecated, use cwd instead");
984
980
  props.cwd = props.projectRoot;
985
981
  }
986
982
 
987
- const cwd = props.cwd ? path.resolve(props.cwd) : process.cwd();
983
+ const cwd = path.resolve(props.cwd ?? process.cwd());
988
984
  const relativeCwd =
989
985
  cwd === process.cwd() ? undefined : path.relative(process.cwd(), cwd);
990
986
 
987
+ const workerName = props.name ?? id;
991
988
  const compatibilityDate =
992
989
  props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
993
990
  const compatibilityFlags = props.compatibilityFlags ?? [];
991
+ const dispatchNamespace =
992
+ typeof props.namespace === "string"
993
+ ? props.namespace
994
+ : props.namespace?.namespaceName;
994
995
 
995
- if (this.scope.dev && this.phase !== "delete" && props.dev !== false) {
996
- // Get current timestamp
997
- const now = Date.now();
998
-
999
- if (typeof props.dev === "object" && "command" in props.dev) {
1000
- upsertDevCommand({
1001
- id,
1002
- command: props.dev.command,
1003
- cwd: props.dev.cwd ?? cwd,
1004
- env: props.env ?? {},
1005
- });
1006
- return this({
1007
- type: "service",
1008
- id,
1009
- entrypoint: props.entrypoint,
1010
- name: workerName,
1011
- cwd: relativeCwd,
1012
- compatibilityDate,
1013
- compatibilityFlags,
1014
- format: props.format || "esm", // Include format in the output
1015
- bindings: props.bindings ?? ({} as B),
1016
- env: props.env,
1017
- observability: props.observability,
1018
- createdAt: now,
1019
- updatedAt: now,
1020
- eventSources: props.eventSources,
1021
- url: props.dev.url,
1022
- dev: props.dev,
1023
- // Include assets configuration in the output
1024
- assets: props.assets,
1025
- // Include cron triggers in the output
1026
- crons: props.crons,
1027
- // phantom property
1028
- Env: undefined!,
1029
- } as unknown as Worker<B>);
1030
- }
996
+ const dev = normalizeDev(this, props.dev);
1031
997
 
1032
- const sharedOptions: Omit<MiniflareWorkerOptions, "script"> = {
1033
- name: workerName,
998
+ const [bundle, error] = wrap(() =>
999
+ normalizeWorkerBundle({
1000
+ entrypoint: props.entrypoint,
1001
+ script: props.script,
1002
+ format: props.format,
1003
+ noBundle: props.noBundle,
1004
+ rules: "rules" in props ? props.rules : undefined,
1005
+ bundle: props.bundle,
1006
+ cwd,
1034
1007
  compatibilityDate,
1035
1008
  compatibilityFlags,
1036
- bindings: props.bindings ?? ({} as B),
1037
- port: typeof props.dev === "object" ? props.dev.port : undefined,
1038
- };
1039
-
1040
- let url: string;
1041
-
1042
- // If entrypoint is provided, set up hot reloading with esbuild context
1043
- if (props.entrypoint) {
1044
- const startPromise = new DeferredPromise<string>();
1009
+ outdir:
1010
+ props.bundle?.outdir ??
1011
+ path.join(process.cwd(), ".alchemy", ...this.scope.chain, id),
1012
+ sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
1013
+ }),
1014
+ );
1045
1015
 
1046
- await createWorkerDevContext(
1047
- workerName,
1048
- {
1049
- ...props,
1050
- entrypoint: props.entrypoint,
1051
- cwd,
1052
- compatibilityDate,
1053
- compatibilityFlags,
1054
- },
1055
- {
1056
- onBuildStart: () => {
1057
- logger.task("miniflare-server", {
1058
- message: `${startPromise.status === "pending" ? "Building" : "Rebuilding"}`,
1059
- status: "pending",
1060
- resource: id,
1061
- prefix: "build",
1062
- prefixColor: "cyanBright",
1063
- });
1064
- },
1065
- onBuildEnd: async (newScript) => {
1066
- try {
1067
- // Hot reload callback - update the miniflare worker
1068
- const server = await miniflareServer.push({
1069
- ...sharedOptions,
1070
- script: newScript,
1071
- });
1072
- if (startPromise.status === "pending") {
1073
- logger.task("miniflare-server", {
1074
- message: `Started Miniflare server at ${server.url}`,
1075
- status: "success",
1076
- resource: id,
1077
- prefix: "ready",
1078
- prefixColor: "cyanBright",
1079
- });
1080
- startPromise.resolve(server.url);
1081
- } else {
1082
- logger.task("miniflare-server", {
1083
- message: `Updated Miniflare server at ${server.url}`,
1084
- status: "success",
1085
- resource: id,
1086
- prefix: "reload",
1087
- prefixColor: "cyanBright",
1088
- });
1089
- }
1090
- } catch (error) {
1091
- if (startPromise.status === "pending") {
1092
- logger.error(error);
1093
- logger.task("miniflare-server", {
1094
- message: "Failed to start Miniflare server",
1095
- status: "failure",
1096
- resource: id,
1097
- prefix: "error",
1098
- prefixColor: "redBright",
1099
- });
1100
- startPromise.reject(
1101
- new Error(
1102
- `Failed to start Miniflare server for worker "${workerName}"`,
1103
- { cause: error },
1104
- ),
1105
- );
1106
- } else {
1107
- logger.task("miniflare-server", {
1108
- message: "Failed to update Miniflare server",
1109
- status: "failure",
1110
- resource: id,
1111
- prefix: "error",
1112
- prefixColor: "redBright",
1113
- });
1114
- logger.error(error);
1115
- }
1116
- }
1117
- },
1118
- onBuildError: (errors) => {
1119
- if (startPromise.status === "pending") {
1120
- logger.task("miniflare-server", {
1121
- message: "Failed to build worker",
1122
- status: "failure",
1123
- resource: id,
1124
- prefix: "error",
1125
- prefixColor: "redBright",
1126
- });
1127
- startPromise.reject(errors);
1128
- } else {
1129
- logger.task("miniflare-server", {
1130
- message: "Failed to rebuild worker",
1131
- status: "failure",
1132
- resource: id,
1133
- prefix: "error",
1134
- prefixColor: "redBright",
1135
- });
1136
- logger.error(errors);
1137
- }
1138
- },
1139
- },
1140
- );
1016
+ if (dev.local) {
1017
+ let url: string | undefined;
1018
+ if (error) {
1019
+ throw error;
1020
+ }
1141
1021
 
1142
- url = await startPromise.value;
1143
- } else {
1144
- // Fallback to one-time bundling for inline scripts
1145
- const scriptContent =
1146
- props.script ??
1147
- (await bundleWorkerScript({
1148
- name: workerName,
1149
- ...props,
1150
- cwd,
1022
+ switch (dev.type) {
1023
+ case "command":
1024
+ createDevCommand({
1025
+ id,
1026
+ command: dev.command,
1027
+ cwd: dev.cwd ?? process.cwd(),
1028
+ env: props.env ?? {},
1029
+ });
1030
+ url = dev.url;
1031
+ break;
1032
+ case "miniflare": {
1033
+ url = await createMiniflare({
1034
+ id,
1035
+ workerName,
1151
1036
  compatibilityDate,
1152
1037
  compatibilityFlags,
1153
- }));
1154
- const server = await miniflareServer.push({
1155
- ...sharedOptions,
1156
- script:
1157
- typeof scriptContent === "string"
1158
- ? scriptContent
1159
- : scriptContent[props.entrypoint!].toString(),
1160
- });
1161
- url = server.url;
1038
+ bindings: props.bindings,
1039
+ bundle,
1040
+ port: dev.port,
1041
+ });
1042
+ break;
1043
+ }
1162
1044
  }
1163
1045
 
1164
1046
  return this({
@@ -1170,11 +1052,11 @@ export const _Worker = Resource(
1170
1052
  compatibilityDate,
1171
1053
  compatibilityFlags,
1172
1054
  format: props.format || "esm", // Include format in the output
1173
- bindings: props.bindings ?? ({} as B),
1055
+ bindings: normalizeExportBindings(workerName, props.bindings),
1174
1056
  env: props.env,
1175
1057
  observability: props.observability,
1176
- createdAt: now,
1177
- updatedAt: now,
1058
+ createdAt: this.output?.createdAt ?? Date.now(),
1059
+ updatedAt: Date.now(),
1178
1060
  eventSources: props.eventSources,
1179
1061
  url,
1180
1062
  dev: props.dev,
@@ -1187,65 +1069,53 @@ export const _Worker = Resource(
1187
1069
  } as unknown as Worker<B>);
1188
1070
  }
1189
1071
 
1190
- // Create Cloudflare API client with automatic account discovery
1191
1072
  const api = await createCloudflareApi(props);
1192
1073
 
1193
- const uploadWorkerScript = async (props: WorkerProps<B>) => {
1194
- // Get the script content - either from props.script, or by bundling
1195
- const scriptBundle =
1196
- props.script ??
1197
- (await bundleWorkerScript({
1198
- ...props,
1199
- name: workerName,
1200
- cwd,
1201
- compatibilityDate,
1202
- compatibilityFlags,
1203
- }));
1204
-
1205
- // Find any assets bindings
1206
- const assetsBindings: { name: string; assets: Assets }[] = [];
1207
- const workflowsBindings: Workflow[] = [];
1208
- const containersBindings: Container[] = [];
1209
-
1210
- if (props.bindings) {
1211
- for (const [bindingName, binding] of Object.entries(props.bindings)) {
1212
- if (typeof binding === "object") {
1213
- if (binding.type === "assets") {
1214
- assetsBindings.push({ name: bindingName, assets: binding });
1215
- } else if (binding.type === "workflow") {
1216
- workflowsBindings.push(binding);
1217
- } else if (binding.type === "container") {
1218
- containersBindings.push(binding);
1219
- }
1220
- }
1074
+ if (this.phase === "delete") {
1075
+ await bundle?.delete?.();
1076
+ if (!props.version) {
1077
+ await deleteQueueConsumers(api, workerName);
1078
+ await deleteWorker(api, {
1079
+ scriptName: workerName,
1080
+ dispatchNamespace,
1081
+ });
1082
+ }
1083
+ return this.destroy();
1084
+ } else if (error) {
1085
+ throw error;
1086
+ }
1087
+
1088
+ let assetsBinding: Assets | undefined;
1089
+ const workflowsBindings: Workflow[] = [];
1090
+ const containersBindings: Container[] = [];
1091
+
1092
+ for (const binding of Object.values(props.bindings ?? {})) {
1093
+ if (typeof binding === "object") {
1094
+ if (binding.type === "assets") {
1095
+ assetsBinding = binding;
1096
+ } else if (binding.type === "workflow") {
1097
+ workflowsBindings.push(binding);
1098
+ } else if (binding.type === "container") {
1099
+ containersBindings.push(binding);
1221
1100
  }
1222
1101
  }
1102
+ }
1223
1103
 
1224
- // Get dispatch namespace if specified
1225
- const dispatchNamespace = props.namespace
1226
- ? typeof props.namespace === "string"
1227
- ? props.namespace
1228
- : props.namespace.namespace
1104
+ const putWorkerWithAssets = async (
1105
+ props: WorkerProps<B>,
1106
+ scriptBundle: WorkerBundle,
1107
+ ) => {
1108
+ const assetUploadResult = assetsBinding
1109
+ ? await uploadAssets(api, {
1110
+ workerName,
1111
+ assets: assetsBinding,
1112
+ assetConfig: props.assets,
1113
+ namespace: dispatchNamespace,
1114
+ })
1229
1115
  : undefined;
1230
1116
 
1231
- // Upload any assets and get completion tokens
1232
- let assetUploadResult: AssetUploadResult | undefined;
1233
- if (assetsBindings.length > 0) {
1234
- // We'll use the first asset binding for now
1235
- // In the future, we might want to support multiple asset bindings
1236
- const assetBinding = assetsBindings[0];
1237
-
1238
- // Upload the assets and get the completion token
1239
- assetUploadResult = await uploadAssets(api, {
1240
- workerName,
1241
- assets: assetBinding.assets,
1242
- assetConfig: props.assets,
1243
- namespace: dispatchNamespace,
1244
- });
1245
- }
1246
-
1247
1117
  // Deploy worker (either as version or live worker)
1248
- const versionResult = await putWorker(api, {
1118
+ return await putWorker(api, {
1249
1119
  ...props,
1250
1120
  workerName,
1251
1121
  scriptBundle,
@@ -1255,162 +1125,15 @@ export const _Worker = Resource(
1255
1125
  compatibilityFlags,
1256
1126
  assetUploadResult,
1257
1127
  });
1258
- versionResult.versionId;
1259
-
1260
- for (const workflow of workflowsBindings) {
1261
- if (
1262
- workflow.scriptName === undefined ||
1263
- workflow.scriptName === workerName
1264
- ) {
1265
- await upsertWorkflow(api, {
1266
- workflowName: workflow.workflowName,
1267
- className: workflow.className,
1268
- scriptName: workflow.scriptName ?? workerName,
1269
- });
1270
- }
1271
- }
1272
-
1273
- for (const container of containersBindings) {
1274
- async function findNamespaceId() {
1275
- const response = await api.get(
1276
- `/accounts/${api.accountId}/workers/scripts/${workerName}/versions/${versionResult.deploymentId}`,
1277
- );
1278
- const result = (await response.json()) as {
1279
- result: {
1280
- resources: {
1281
- bindings: WorkerBindingSpec[];
1282
- };
1283
- };
1284
- };
1285
- const namespaceId = result.result.resources.bindings.find(
1286
- (binding): binding is WorkerBindingDurableObjectNamespace =>
1287
- binding.type === "durable_object_namespace" &&
1288
- binding.class_name === container.className,
1289
- )?.namespace_id;
1290
- if (!namespaceId) {
1291
- throw new Error(
1292
- `Namespace ID not found for container ${container.id}`,
1293
- );
1294
- }
1295
- return namespaceId;
1296
- }
1297
- await ContainerApplication(container.id, {
1298
- accountId: props.accountId,
1299
- apiKey: props.apiKey,
1300
- apiToken: props.apiToken,
1301
- baseUrl: props.baseUrl,
1302
- email: props.email,
1303
- image: container.image,
1304
- name: container.id,
1305
- instanceType: container.instanceType,
1306
- observability: container.observability,
1307
- durableObjects: {
1308
- namespaceId: await findNamespaceId(),
1309
- },
1310
- schedulingPolicy: container.schedulingPolicy,
1311
- });
1312
- }
1313
-
1314
- await Promise.all(
1315
- props.eventSources?.map((eventSource) => {
1316
- if (isQueue(eventSource) || isQueueEventSource(eventSource)) {
1317
- const queue = isQueue(eventSource)
1318
- ? eventSource
1319
- : eventSource.queue;
1320
- return QueueConsumer(`${queue.id}-consumer`, {
1321
- queue,
1322
- scriptName: workerName,
1323
- accountId: api.accountId,
1324
- adopt: props.adopt,
1325
- settings: isQueueEventSource(eventSource)
1326
- ? eventSource.settings
1327
- : queue.dlq
1328
- ? { deadLetterQueue: queue.dlq }
1329
- : undefined,
1330
- });
1331
- }
1332
- throw new Error(`Unsupported event source type: ${eventSource}`);
1333
- }) ?? [],
1334
- );
1335
-
1336
- // Handle worker URL if requested
1337
- let workerUrl: string | undefined;
1338
- if (props.version) {
1339
- // For versions, use the preview URL if available
1340
- workerUrl = versionResult?.previewUrl;
1341
- } else if (!props.namespace) {
1342
- // namespaces don't support URLs
1343
-
1344
- // For regular workers, use the normal URL configuration
1345
- workerUrl = await configureURL(
1346
- this,
1347
- api,
1348
- workerName,
1349
- props.url ?? true,
1350
- );
1351
- }
1352
-
1353
- // Get current timestamp
1354
- const now = Date.now();
1355
-
1356
- // Update cron triggers
1357
- if (props.crons) {
1358
- const res = await api.put(
1359
- `/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`,
1360
- props.crons.map((cron) => ({ cron })),
1361
- );
1362
-
1363
- if (!res.ok) {
1364
- await handleApiError(
1365
- res,
1366
- "updating cron triggers",
1367
- "worker",
1368
- workerName,
1369
- );
1370
- }
1371
- }
1372
-
1373
- return { scriptBundle, workerUrl, now, versionResult };
1374
1128
  };
1375
1129
 
1376
- if (this.phase === "delete") {
1377
- // only delete the worker if it's not a version
1378
- if (!props.version) {
1379
- // Delete any queue consumers attached to this worker first
1380
- await deleteQueueConsumers(api, workerName);
1381
-
1382
- await withExponentialBackoff(
1383
- () =>
1384
- deleteWorker(api, {
1385
- workerName,
1386
- namespace:
1387
- typeof props.namespace === "string"
1388
- ? props.namespace
1389
- : props.namespace?.namespaceId,
1390
- url: this.output.url,
1391
- }),
1392
- (err) =>
1393
- (err.status === 400 &&
1394
- err.message.includes(
1395
- "is still referenced by service bindings in Workers",
1396
- )) ||
1397
- err.status === 500 ||
1398
- err.status === 503,
1399
- 10,
1400
- 100,
1401
- );
1402
- }
1403
- return this.destroy();
1404
- }
1405
-
1406
1130
  if (this.phase === "create") {
1407
1131
  if (props.version) {
1408
1132
  // When version is specified, we adopt existing workers or create them if they don't exist
1409
- const workerExists = await checkWorkerExists(api, workerName);
1410
- if (!workerExists) {
1133
+ if (!(await workerExists(api, workerName))) {
1411
1134
  // Create the base worker first if it doesn't exist
1412
1135
  const baseWorkerProps = { ...props, version: undefined };
1413
- await uploadWorkerScript(baseWorkerProps);
1136
+ await putWorkerWithAssets(baseWorkerProps, await bundle.create());
1414
1137
  }
1415
1138
  // We always "adopt" when publishing versions
1416
1139
  } else if (!props.adopt) {
@@ -1418,106 +1141,164 @@ export const _Worker = Resource(
1418
1141
  }
1419
1142
  }
1420
1143
 
1421
- const { workerUrl, now } = await uploadWorkerScript(props);
1144
+ let putWorkerResult: PutWorkerResult;
1145
+ if (dev.type === "remote") {
1146
+ // todo(john): clean this up and add log tail
1147
+ const controller = new AbortController();
1148
+ cleanups.push(() => controller.abort());
1149
+ const watcher = await bundle.watch(controller.signal);
1150
+ const promise = new DeferredPromise<PutWorkerResult>();
1151
+ void watcher.pipeTo(
1152
+ new WritableStream({
1153
+ async write(chunk) {
1154
+ switch (chunk.type) {
1155
+ case "start":
1156
+ logger.task("", {
1157
+ message: "start",
1158
+ status: "pending",
1159
+ resource: id,
1160
+ prefix: "build",
1161
+ prefixColor: "cyanBright",
1162
+ });
1163
+ break;
1164
+ case "end": {
1165
+ if (promise.status === "pending") {
1166
+ await putWorkerWithAssets(props, chunk.result)
1167
+ .then((result) => promise.resolve(result))
1168
+ .catch((error) => {
1169
+ controller.abort();
1170
+ promise.reject(error);
1171
+ });
1172
+ return;
1173
+ }
1422
1174
 
1423
- function ensureNoDuplicates(name: string, items: string[]) {
1424
- const duplicates = items.filter(
1425
- (pattern, index) => items.indexOf(pattern) !== index,
1175
+ logger.task("", {
1176
+ message: "reload",
1177
+ status: "success",
1178
+ resource: id,
1179
+ prefix: "build",
1180
+ prefixColor: "cyanBright",
1181
+ });
1182
+ await putWorker(api, {
1183
+ ...props,
1184
+ workerName,
1185
+ scriptBundle: chunk.result,
1186
+ dispatchNamespace,
1187
+ version: props.version,
1188
+ compatibilityDate,
1189
+ compatibilityFlags,
1190
+ assetUploadResult: assetsBinding
1191
+ ? {
1192
+ keepAssets: true,
1193
+ assetConfig: props.assets,
1194
+ }
1195
+ : undefined,
1196
+ unstable_cacheWorkerSettings: true,
1197
+ });
1198
+ logger.task("", {
1199
+ message: "updated",
1200
+ status: "success",
1201
+ resource: id,
1202
+ prefix: "build",
1203
+ prefixColor: "greenBright",
1204
+ });
1205
+ break;
1206
+ }
1207
+ case "error":
1208
+ logger.task("", {
1209
+ message: "error",
1210
+ status: "failure",
1211
+ resource: id,
1212
+ prefix: "build",
1213
+ prefixColor: "redBright",
1214
+ });
1215
+ logger.error(chunk.errors);
1216
+ break;
1217
+ }
1218
+ },
1219
+ }),
1426
1220
  );
1427
- if (duplicates.length > 0) {
1428
- throw new Error(`Duplicate ${name} found: ${duplicates.join(", ")}`);
1429
- }
1221
+ putWorkerResult = await promise.value;
1222
+ const tail = await createTail(api, id, workerName);
1223
+ cleanups.push(() => tail.close());
1224
+ } else {
1225
+ putWorkerResult = await putWorkerWithAssets(props, await bundle.create());
1430
1226
  }
1431
1227
 
1432
- let createdDomains: CustomDomain[] | undefined;
1433
- if (props.domains?.length) {
1434
- ensureNoDuplicates(
1435
- "Custom Domain",
1436
- props.domains.map((domain) =>
1437
- typeof domain === "string" ? domain : domain.domainName,
1438
- ),
1439
- );
1228
+ const tasks: Promise<unknown>[] = [];
1440
1229
 
1441
- createdDomains = await Promise.all(
1442
- props.domains.map(async (customDomain) => {
1443
- const domainName =
1444
- typeof customDomain === "string"
1445
- ? customDomain
1446
- : customDomain.domainName;
1447
- return await CustomDomain(domainName, {
1448
- workerName,
1449
- name: domainName,
1450
- zoneId:
1451
- typeof customDomain === "string"
1452
- ? undefined
1453
- : customDomain.zoneId,
1454
- adopt:
1455
- typeof customDomain === "string"
1456
- ? (props.adopt ?? false)
1457
- : (customDomain.adopt ?? props.adopt),
1458
- });
1459
- }),
1460
- );
1230
+ for (const workflow of workflowsBindings) {
1231
+ if (
1232
+ workflow.scriptName === undefined ||
1233
+ workflow.scriptName === workerName
1234
+ ) {
1235
+ tasks.push(
1236
+ upsertWorkflow(api, {
1237
+ workflowName: workflow.workflowName,
1238
+ className: workflow.className,
1239
+ scriptName: workflow.scriptName ?? workerName,
1240
+ }),
1241
+ );
1242
+ }
1461
1243
  }
1462
1244
 
1463
- // Create routes if provided and capture their outputs
1464
- let createdRoutes: Route[] | undefined;
1465
- if (props.routes && props.routes.length > 0) {
1466
- ensureNoDuplicates(
1467
- "Route",
1468
- props.routes.map((route) =>
1469
- typeof route === "string" ? route : route.pattern,
1245
+ if (containersBindings.length > 0) {
1246
+ tasks.push(
1247
+ getVersionMetadata(api, workerName, putWorkerResult.deployment_id).then(
1248
+ (versionMetadata) =>
1249
+ provisionContainers(api, {
1250
+ scriptName: workerName,
1251
+ containers: containersBindings,
1252
+ bindings: versionMetadata.resources.bindings,
1253
+ }),
1470
1254
  ),
1471
1255
  );
1472
-
1473
- // Create Route resources for each route and capture their outputs
1474
- createdRoutes = await Promise.all(
1475
- props.routes.map(async (routeConfig) => {
1476
- const pattern =
1477
- typeof routeConfig === "string" ? routeConfig : routeConfig.pattern;
1478
- return await Route(pattern, {
1479
- pattern,
1480
- script: workerName,
1481
- zoneId:
1482
- typeof routeConfig === "string" ? undefined : routeConfig.zoneId, // Route resource will handle inference if not provided
1483
- adopt:
1484
- typeof routeConfig === "string"
1485
- ? (props.adopt ?? false)
1486
- : (routeConfig.adopt ?? props.adopt),
1487
- accountId: props.accountId,
1488
- apiKey: props.apiKey,
1489
- apiToken: props.apiToken,
1490
- baseUrl: props.baseUrl,
1491
- email: props.email,
1492
- });
1493
- }),
1494
- );
1495
1256
  }
1496
1257
 
1497
- function exportBindings() {
1498
- return Object.fromEntries(
1499
- Object.entries(props.bindings ?? ({} as B)).map(
1500
- ([bindingName, binding]) => [
1501
- bindingName,
1502
- isDurableObjectNamespace(binding) &&
1503
- binding.scriptName === undefined
1504
- ? new DurableObjectNamespace(binding.id, {
1505
- ...binding,
1506
- // re-export this binding mapping to the host worker (this worker)
1507
- scriptName: workerName,
1508
- })
1509
- : isWorkflow(binding) && binding.scriptName === undefined
1510
- ? new Workflow(binding.id, {
1511
- ...binding,
1512
- // re-export this binding mapping to the host worker (this worker)
1513
- scriptName: workerName,
1514
- })
1515
- : binding,
1516
- ],
1258
+ if (!isDeepStrictEqual(props.crons, this.output?.crons)) {
1259
+ tasks.push(
1260
+ api.put(
1261
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`,
1262
+ props.crons?.map((cron) => ({ cron })) ?? [],
1517
1263
  ),
1518
1264
  );
1519
1265
  }
1520
1266
 
1267
+ tasks.push(
1268
+ provisionEventSources(api, {
1269
+ scriptName: workerName,
1270
+ eventSources: props.eventSources,
1271
+ }),
1272
+ );
1273
+
1274
+ const [domains, routes, subdomain] = await Promise.all([
1275
+ // TODO: can you provision domains and routes in parallel, or is there a dependency?
1276
+ provisionDomains(api, {
1277
+ scriptName: workerName,
1278
+ adopt: props.adopt,
1279
+ domains: props.domains,
1280
+ }),
1281
+ provisionRoutes(api, {
1282
+ scriptName: workerName,
1283
+ adopt: props.adopt,
1284
+ routes: props.routes,
1285
+ }),
1286
+ provisionSubdomain(api, {
1287
+ scriptName: workerName,
1288
+ enable: props.url ?? dispatchNamespace === undefined,
1289
+ previewVersionId:
1290
+ props.version && putWorkerResult.metadata.has_preview
1291
+ ? putWorkerResult.id
1292
+ : undefined,
1293
+ retain: !!props.version,
1294
+ forceDelete:
1295
+ this.phase === "create" && !!props.adopt && props.url === false,
1296
+ }),
1297
+ ...tasks,
1298
+ ]);
1299
+
1300
+ const now = Date.now();
1301
+
1521
1302
  // Construct the output
1522
1303
  return this({
1523
1304
  ...props,
@@ -1529,22 +1310,22 @@ export const _Worker = Resource(
1529
1310
  compatibilityDate,
1530
1311
  compatibilityFlags,
1531
1312
  format: props.format || "esm", // Include format in the output
1532
- bindings: exportBindings(),
1313
+ bindings: normalizeExportBindings(workerName, props.bindings),
1533
1314
  env: props.env,
1534
1315
  observability: props.observability,
1535
- createdAt: now,
1316
+ createdAt: this.output?.createdAt ?? now,
1536
1317
  updatedAt: now,
1537
1318
  eventSources: props.eventSources,
1538
- url: workerUrl,
1319
+ url: subdomain?.url,
1539
1320
  dev: props.dev,
1540
1321
  // Include assets configuration in the output
1541
1322
  assets: props.assets,
1542
1323
  // Include cron triggers in the output
1543
1324
  crons: props.crons,
1544
1325
  // Include the created routes in the output
1545
- routes: createdRoutes,
1326
+ routes,
1546
1327
  // Include the created domains in the output
1547
- domains: createdDomains,
1328
+ domains,
1548
1329
  // Include the dispatch namespace in the output
1549
1330
  namespace: props.namespace,
1550
1331
  // Include version information in the output
@@ -1555,48 +1336,442 @@ export const _Worker = Resource(
1555
1336
  },
1556
1337
  );
1557
1338
 
1558
- export async function deleteWorker(
1339
+ const cleanups: (() => any)[] = [];
1340
+ let exiting = false;
1341
+
1342
+ process.on("SIGINT", async () => {
1343
+ if (cleanups.length > 0) {
1344
+ if (!exiting) {
1345
+ exiting = true;
1346
+ logger.log(`\n${kleur.gray("Exiting...")}`);
1347
+ }
1348
+ // TODO: for some reason this runs twice...
1349
+ // and this whole thing feels hacky anyway
1350
+ await Promise.allSettled(cleanups.map((cleanup) => cleanup()));
1351
+ }
1352
+ process.exit(0);
1353
+ });
1354
+
1355
+ type Dev =
1356
+ | {
1357
+ type: "none";
1358
+ local: false;
1359
+ }
1360
+ | {
1361
+ type: "miniflare";
1362
+ port?: number;
1363
+ local: true;
1364
+ }
1365
+ | {
1366
+ type: "command";
1367
+ command: string;
1368
+ url: string;
1369
+ cwd?: string;
1370
+ local: true;
1371
+ }
1372
+ | {
1373
+ type: "remote";
1374
+ local: false;
1375
+ };
1376
+
1377
+ const normalizeDev = (ctx: Context<any>, dev: WorkerProps["dev"]): Dev => {
1378
+ if (!ctx.scope.dev || ctx.phase === "delete" || dev === false) {
1379
+ return {
1380
+ type: "none",
1381
+ local: false,
1382
+ };
1383
+ }
1384
+ const devObj = dev === true ? {} : (dev ?? {});
1385
+ if ("command" in devObj) {
1386
+ // Commands are always local
1387
+ return {
1388
+ type: "command",
1389
+ ...devObj,
1390
+ local: true,
1391
+ };
1392
+ }
1393
+ if (devObj.remote === false || ctx.scope.dev === "prefer-local") {
1394
+ return {
1395
+ type: "miniflare",
1396
+ port: devObj.port,
1397
+ local: true,
1398
+ };
1399
+ }
1400
+ return {
1401
+ type: "remote",
1402
+ local: false,
1403
+ };
1404
+ };
1405
+
1406
+ const assertUnique = <T, Key extends keyof T>(
1407
+ inputs: T[],
1408
+ key: Key,
1409
+ message: string,
1410
+ ) => {
1411
+ const ids = inputs.map((input) => input[key]);
1412
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
1413
+ if (duplicates.length > 0) {
1414
+ throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
1415
+ }
1416
+ };
1417
+
1418
+ const normalizeExportBindings = (
1419
+ scriptName: string,
1420
+ bindings: Bindings = {},
1421
+ ) => {
1422
+ return Object.fromEntries(
1423
+ Object.entries(bindings).map(([bindingName, binding]) => [
1424
+ bindingName,
1425
+ isDurableObjectNamespace(binding) && binding.scriptName === undefined
1426
+ ? new DurableObjectNamespace(binding.id, {
1427
+ ...binding,
1428
+ // re-export this binding mapping to the host worker (this worker)
1429
+ scriptName,
1430
+ })
1431
+ : isWorkflow(binding) && binding.scriptName === undefined
1432
+ ? new Workflow(binding.id, {
1433
+ ...binding,
1434
+ // re-export this binding mapping to the host worker (this worker)
1435
+ scriptName,
1436
+ })
1437
+ : binding,
1438
+ ]),
1439
+ );
1440
+ };
1441
+
1442
+ const normalizeApiOptions = (api: CloudflareApi): CloudflareApiOptions => ({
1443
+ accountId: api.accountId,
1444
+ apiKey: api.apiKey,
1445
+ apiToken: api.apiToken,
1446
+ email: api.email,
1447
+ baseUrl: api.baseUrl,
1448
+ });
1449
+
1450
+ async function provisionContainers(
1559
1451
  api: CloudflareApi,
1560
1452
  props: {
1561
- workerName: string;
1562
- namespace?: string | DispatchNamespaceResource;
1563
- url?: string;
1453
+ scriptName: string;
1454
+ containers?: Container[];
1455
+ bindings: WorkerBindingSpec[];
1564
1456
  },
1565
- ) {
1566
- const workerName = props.workerName;
1457
+ ): Promise<ContainerApplication[] | undefined> {
1458
+ if (!props.containers?.length) {
1459
+ return;
1460
+ }
1461
+ return await Promise.all(
1462
+ props.containers.map((container) => {
1463
+ const namespaceId = props.bindings.find(
1464
+ (binding): binding is WorkerBindingDurableObjectNamespace =>
1465
+ binding.type === "durable_object_namespace" &&
1466
+ binding.class_name === container.className,
1467
+ )?.namespace_id;
1468
+ if (!namespaceId) {
1469
+ throw new Error(`Namespace ID not found for container ${container.id}`);
1470
+ }
1471
+ return ContainerApplication(container.id, {
1472
+ image: container.image,
1473
+ name: container.id,
1474
+ instanceType: container.instanceType,
1475
+ observability: container.observability,
1476
+ durableObjects: {
1477
+ namespaceId,
1478
+ },
1479
+ schedulingPolicy: container.schedulingPolicy,
1480
+ adopt: container.adopt,
1481
+ ...normalizeApiOptions(api),
1482
+ });
1483
+ }),
1484
+ );
1485
+ }
1567
1486
 
1568
- // Delete worker
1569
- const deleteResponse = await api.delete(
1570
- props.namespace
1571
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.namespace}/scripts/${workerName}?force=true`
1572
- : `/accounts/${api.accountId}/workers/scripts/${workerName}?force=true`,
1487
+ async function provisionEventSources(
1488
+ api: CloudflareApi,
1489
+ props: {
1490
+ scriptName: string;
1491
+ eventSources?: EventSource[];
1492
+ },
1493
+ ): Promise<QueueConsumer[] | undefined> {
1494
+ if (!props.eventSources?.length) {
1495
+ return;
1496
+ }
1497
+ return await Promise.all(
1498
+ props.eventSources.map((eventSource) => {
1499
+ if (isQueue(eventSource)) {
1500
+ return QueueConsumer(`${eventSource.id}-consumer`, {
1501
+ queue: eventSource,
1502
+ scriptName: props.scriptName,
1503
+ settings: eventSource.dlq
1504
+ ? { deadLetterQueue: eventSource.dlq }
1505
+ : undefined,
1506
+ ...normalizeApiOptions(api),
1507
+ });
1508
+ }
1509
+ if (isQueueEventSource(eventSource)) {
1510
+ return QueueConsumer(`${eventSource.queue.id}-consumer`, {
1511
+ queue: eventSource.queue,
1512
+ scriptName: props.scriptName,
1513
+ settings: eventSource.settings,
1514
+ ...normalizeApiOptions(api),
1515
+ });
1516
+ }
1517
+ throw new Error(`Unsupported event source: ${eventSource}`);
1518
+ }),
1573
1519
  );
1520
+ }
1574
1521
 
1575
- // Check for success (2xx status code)
1576
- if (!deleteResponse.ok && deleteResponse.status !== 404) {
1577
- await handleApiError(deleteResponse, "delete", "worker", workerName);
1522
+ async function provisionDomains(
1523
+ api: CloudflareApi,
1524
+ props: {
1525
+ scriptName: string;
1526
+ adopt?: boolean;
1527
+ domains?: WorkerProps["domains"];
1528
+ },
1529
+ ): Promise<CustomDomain[] | undefined> {
1530
+ if (!props.domains?.length) {
1531
+ return;
1578
1532
  }
1533
+ const domains = props.domains.map((domain) => {
1534
+ if (typeof domain === "string") {
1535
+ return {
1536
+ name: domain,
1537
+ zoneId: undefined,
1538
+ adopt: props.adopt,
1539
+ };
1540
+ }
1541
+ return {
1542
+ name: domain.domainName,
1543
+ zoneId: domain.zoneId,
1544
+ adopt: domain.adopt ?? props.adopt,
1545
+ };
1546
+ });
1547
+ assertUnique(domains, "name", "Custom Domain");
1548
+ return await Promise.all(
1549
+ domains.map(async (domain) => {
1550
+ return await CustomDomain(domain.name, {
1551
+ workerName: props.scriptName,
1552
+ name: domain.name,
1553
+ zoneId: domain.zoneId,
1554
+ adopt: domain.adopt,
1555
+ ...normalizeApiOptions(api),
1556
+ });
1557
+ }),
1558
+ );
1559
+ }
1579
1560
 
1580
- // Disable the URL if it was enabled
1581
- if (props.url) {
1582
- try {
1583
- await api.post(
1584
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1585
- JSON.stringify({ enabled: false }),
1586
- {
1587
- headers: { "Content-Type": "application/json" },
1588
- },
1589
- );
1590
- } catch (error) {
1591
- logger.warn("Failed to disable worker URL during deletion:", error);
1561
+ async function provisionRoutes(
1562
+ api: CloudflareApi,
1563
+ props: {
1564
+ scriptName: string;
1565
+ adopt?: boolean;
1566
+ routes?: WorkerProps["routes"];
1567
+ },
1568
+ ): Promise<Route[] | undefined> {
1569
+ if (!props.routes?.length) {
1570
+ return;
1571
+ }
1572
+ const routes = props.routes.map((route) => {
1573
+ if (typeof route === "string") {
1574
+ return {
1575
+ pattern: route,
1576
+ adopt: props.adopt,
1577
+ };
1592
1578
  }
1579
+ return {
1580
+ pattern: route.pattern,
1581
+ zoneId: route.zoneId,
1582
+ adopt: route.adopt ?? props.adopt,
1583
+ };
1584
+ });
1585
+ assertUnique(routes, "pattern", "Route");
1586
+ return await Promise.all(
1587
+ routes.map(async (route) => {
1588
+ return await Route(route.pattern, {
1589
+ pattern: route.pattern,
1590
+ script: props.scriptName,
1591
+ zoneId: route.zoneId,
1592
+ adopt: route.adopt,
1593
+ ...normalizeApiOptions(api),
1594
+ });
1595
+ }),
1596
+ );
1597
+ }
1598
+
1599
+ async function provisionSubdomain(
1600
+ api: CloudflareApi,
1601
+ props: {
1602
+ scriptName: string;
1603
+ enable: boolean;
1604
+ previewVersionId: string | undefined;
1605
+ retain: boolean;
1606
+ forceDelete: boolean;
1607
+ },
1608
+ ): Promise<WorkerSubdomain | undefined> {
1609
+ if (props.enable) {
1610
+ return await WorkerSubdomain("url", {
1611
+ scriptName: props.scriptName,
1612
+ previewVersionId: props.previewVersionId,
1613
+ retain: props.retain,
1614
+ ...normalizeApiOptions(api),
1615
+ });
1616
+ }
1617
+ if (props.forceDelete) {
1618
+ await disableWorkerSubdomain(api, props.scriptName);
1593
1619
  }
1620
+ }
1621
+
1622
+ async function createMiniflare(props: {
1623
+ id: string;
1624
+ workerName: string;
1625
+ compatibilityDate: string;
1626
+ compatibilityFlags: string[];
1627
+ bindings: Bindings | undefined;
1628
+ port: number | undefined;
1629
+ bundle: WorkerBundleProvider;
1630
+ }) {
1631
+ const sharedOptions: Omit<MiniflareWorkerOptions, "bundle"> = {
1632
+ name: props.workerName,
1633
+ compatibilityDate: props.compatibilityDate,
1634
+ compatibilityFlags: props.compatibilityFlags,
1635
+ bindings: props.bindings,
1636
+ port: props.port,
1637
+ };
1638
+
1639
+ const startPromise = new DeferredPromise<string>();
1640
+ // todo(john): streamline and share logging logic with remote dev
1641
+ type Events =
1642
+ | {
1643
+ type: "build-start" | "build-end";
1644
+ }
1645
+ | {
1646
+ type: "build-error";
1647
+ errors: esbuild.Message[];
1648
+ }
1649
+ | {
1650
+ type: "miniflare-ready";
1651
+ url: string;
1652
+ }
1653
+ | {
1654
+ type: "miniflare-error";
1655
+ error: Error;
1656
+ };
1657
+ const controller = new AbortController();
1658
+ cleanups.push(() => controller.abort());
1659
+ (await props.bundle.watch(controller.signal))
1660
+ .pipeThrough(
1661
+ new TransformStream<WorkerBundleChunk, Events>({
1662
+ async transform(chunk, controller) {
1663
+ switch (chunk.type) {
1664
+ case "start": {
1665
+ controller.enqueue({ type: "build-start" });
1666
+ break;
1667
+ }
1668
+ case "end": {
1669
+ controller.enqueue({ type: "build-end" });
1670
+ try {
1671
+ const server = await miniflareServer.push({
1672
+ ...sharedOptions,
1673
+ bundle: chunk.result,
1674
+ });
1675
+ controller.enqueue({
1676
+ type: "miniflare-ready",
1677
+ url: server.url,
1678
+ });
1679
+ } catch (error) {
1680
+ controller.enqueue({
1681
+ type: "miniflare-error",
1682
+ error: error as Error,
1683
+ });
1684
+ }
1685
+ break;
1686
+ }
1687
+ case "error": {
1688
+ controller.enqueue({
1689
+ type: "build-error",
1690
+ errors: chunk.errors,
1691
+ });
1692
+ break;
1693
+ }
1694
+ }
1695
+ },
1696
+ }),
1697
+ )
1698
+ .pipeTo(
1699
+ new WritableStream({
1700
+ write: async (chunk) => {
1701
+ switch (chunk.type) {
1702
+ case "build-start": {
1703
+ logger.task("", {
1704
+ message: "start",
1705
+ status: "pending",
1706
+ resource: props.id,
1707
+ prefix: "build",
1708
+ prefixColor: "cyanBright",
1709
+ });
1710
+ break;
1711
+ }
1712
+ case "build-end": {
1713
+ logger.task("", {
1714
+ message: startPromise.status === "pending" ? "load" : "reload",
1715
+ status: "success",
1716
+ resource: props.id,
1717
+ prefix: "build",
1718
+ prefixColor: "greenBright",
1719
+ });
1720
+ break;
1721
+ }
1722
+ case "build-error": {
1723
+ logger.task("", {
1724
+ message: "error",
1725
+ status: "failure",
1726
+ resource: props.id,
1727
+ prefix: "build",
1728
+ prefixColor: "redBright",
1729
+ });
1730
+ if (startPromise.status === "pending") {
1731
+ controller.abort();
1732
+ startPromise.reject(chunk.errors);
1733
+ } else {
1734
+ logger.error(chunk.errors);
1735
+ }
1736
+ break;
1737
+ }
1738
+ case "miniflare-ready": {
1739
+ logger.task("", {
1740
+ message: `ready at ${chunk.url}`,
1741
+ status: "success",
1742
+ resource: props.id,
1743
+ prefix: "miniflare",
1744
+ prefixColor: "greenBright",
1745
+ });
1746
+ if (startPromise.status === "pending") {
1747
+ startPromise.resolve(chunk.url);
1748
+ }
1749
+ break;
1750
+ }
1751
+ case "miniflare-error": {
1752
+ logger.task("", {
1753
+ message: "error",
1754
+ status: "failure",
1755
+ resource: props.id,
1756
+ prefix: "miniflare",
1757
+ prefixColor: "redBright",
1758
+ });
1759
+ if (startPromise.status === "pending") {
1760
+ startPromise.reject(chunk.error);
1761
+ } else {
1762
+ logger.error(chunk.error);
1763
+ }
1764
+ break;
1765
+ }
1766
+ }
1767
+ },
1768
+ }),
1769
+ );
1594
1770
 
1595
- // Return minimal output for deleted state
1596
- return;
1771
+ return await startPromise.value;
1597
1772
  }
1598
1773
 
1599
- function upsertDevCommand(props: {
1774
+ function createDevCommand(props: {
1600
1775
  id: string;
1601
1776
  command: string;
1602
1777
  cwd: string;
@@ -1630,14 +1805,13 @@ function upsertDevCommand(props: {
1630
1805
  },
1631
1806
  stdio: ["inherit", "inherit", "inherit"],
1632
1807
  });
1633
- process.on("SIGINT", () => {
1808
+ cleanups.push(() => {
1634
1809
  try {
1635
1810
  unlinkSync(persistFile);
1636
1811
  } catch {
1637
1812
  // ignore
1638
1813
  }
1639
- proc.kill("SIGINT");
1640
- process.exit(0);
1814
+ proc.kill();
1641
1815
  });
1642
1816
  if (proc.pid) {
1643
1817
  mkdirSync(path.dirname(persistFile), { recursive: true });
@@ -1649,124 +1823,124 @@ type PutWorkerOptions = WorkerProps & {
1649
1823
  dispatchNamespace?: string;
1650
1824
  migrationTag?: string;
1651
1825
  workerName: string;
1652
- scriptBundle: string | NoBundleResult;
1826
+ scriptBundle: WorkerBundle;
1653
1827
  version: string | undefined;
1654
1828
  compatibilityDate: string;
1655
1829
  compatibilityFlags: string[];
1656
- assetUploadResult: AssetUploadResult | undefined;
1830
+ assetUploadResult:
1831
+ | {
1832
+ completionToken?: string;
1833
+ keepAssets?: boolean;
1834
+ assetConfig?: AssetsConfig;
1835
+ }
1836
+ | undefined;
1837
+ unstable_cacheWorkerSettings?: boolean;
1657
1838
  };
1658
1839
 
1659
- export async function putWorker(
1840
+ async function prepareWorkerUpload(
1660
1841
  api: CloudflareApi,
1661
1842
  props: PutWorkerOptions,
1662
- ): Promise<{ versionId?: string; previewUrl?: string; deploymentId: string }> {
1663
- const {
1664
- //
1665
- dispatchNamespace,
1666
- migrationTag,
1667
- workerName,
1668
- scriptBundle,
1669
- version,
1670
- } = props;
1843
+ ) {
1671
1844
  const scriptMetadata = await prepareWorkerMetadata(api, props);
1672
1845
 
1673
- return withExponentialBackoff(
1674
- async () => {
1675
- const scriptName =
1676
- scriptMetadata.main_module ?? scriptMetadata.body_part!;
1677
-
1678
- // Create FormData for the upload
1679
- const formData = new FormData();
1680
-
1681
- function addFile(fileName: string, content: Buffer | string) {
1682
- const contentType = getContentType(fileName) ?? "application/null";
1683
- formData.append(
1684
- fileName,
1685
- new Blob([content], {
1686
- type:
1687
- contentType === "application/javascript" &&
1688
- scriptMetadata.main_module
1689
- ? "application/javascript+module"
1690
- : contentType,
1691
- }),
1692
- fileName,
1693
- );
1694
- }
1695
-
1696
- if (typeof scriptBundle === "string") {
1697
- addFile(scriptName, scriptBundle);
1698
- } else {
1699
- for (const [fileName, content] of Object.entries(scriptBundle)) {
1700
- addFile(fileName, content);
1701
- }
1702
- }
1846
+ if (props.format === "cjs") {
1847
+ scriptMetadata.body_part = props.scriptBundle.entrypoint;
1848
+ } else {
1849
+ scriptMetadata.main_module = props.scriptBundle.entrypoint;
1850
+ }
1851
+ const body = new FormData();
1703
1852
 
1704
- // Prepare metadata - add version annotations if this is a version
1705
- const finalMetadata = version
1706
- ? {
1707
- ...scriptMetadata,
1708
- // Exclude migrations for worker versions - they're not allowed
1709
- migrations: undefined,
1710
- annotations: {
1711
- "workers/tag": version,
1712
- "workers/message": `Version ${version}`,
1713
- },
1714
- }
1715
- : {
1716
- ...scriptMetadata,
1717
- migrations: scriptMetadata.migrations
1718
- ? {
1719
- ...scriptMetadata.migrations,
1720
- old_tag: migrationTag,
1721
- new_tag: bumpMigrationTagVersion(migrationTag),
1722
- }
1723
- : undefined,
1724
- };
1853
+ for await (const file of props.scriptBundle.files) {
1854
+ body.append(file.name, file);
1855
+ }
1725
1856
 
1726
- if (process.env.DEBUG) {
1727
- console.log(`metadata(${scriptName}):`, finalMetadata);
1857
+ // Prepare metadata - add version annotations if this is a version
1858
+ const finalMetadata = props.version
1859
+ ? {
1860
+ ...scriptMetadata,
1861
+ // Exclude migrations for worker versions - they're not allowed
1862
+ migrations: undefined,
1863
+ annotations: {
1864
+ "workers/tag": props.version,
1865
+ "workers/message": `Version ${props.version}`,
1866
+ },
1728
1867
  }
1868
+ : {
1869
+ ...scriptMetadata,
1870
+ migrations: scriptMetadata.migrations
1871
+ ? {
1872
+ ...scriptMetadata.migrations,
1873
+ old_tag: props.migrationTag,
1874
+ new_tag: bumpMigrationTagVersion(props.migrationTag),
1875
+ }
1876
+ : undefined,
1877
+ };
1729
1878
 
1730
- // Add metadata as JSON
1731
- formData.append(
1732
- "metadata",
1733
- new Blob([JSON.stringify(finalMetadata)], {
1734
- type: "application/json",
1735
- }),
1879
+ body.append(
1880
+ "metadata",
1881
+ new Blob([JSON.stringify(finalMetadata)], {
1882
+ type: "application/json",
1883
+ }),
1884
+ );
1885
+
1886
+ let endpoint: string;
1887
+ let method: "PUT" | "POST";
1888
+ if (props.version) {
1889
+ if (props.dispatchNamespace) {
1890
+ throw new Error(
1891
+ "Worker Preview Versions are not supported in Workers for Platforms",
1736
1892
  );
1893
+ }
1894
+ // Upload worker version using the versions API
1895
+ endpoint = `/accounts/${api.accountId}/workers/scripts/${props.workerName}/versions`;
1896
+ method = "POST";
1897
+ } else {
1898
+ // Upload worker script with bindings
1899
+ endpoint = props.dispatchNamespace
1900
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${props.workerName}`
1901
+ : `/accounts/${api.accountId}/workers/scripts/${props.workerName}`;
1902
+ method = "PUT";
1903
+ }
1737
1904
 
1738
- // Determine endpoint and HTTP method
1739
- let endpoint: string;
1740
- let method: "PUT" | "POST";
1741
- if (version) {
1742
- if (dispatchNamespace) {
1743
- throw new Error(
1744
- "Worker Preview Versions are not supported in Workers for Platforms",
1745
- );
1746
- }
1747
- // Upload worker version using the versions API
1748
- endpoint = `/accounts/${api.accountId}/workers/scripts/${workerName}/versions`;
1749
- method = "POST";
1750
- } else {
1751
- // Upload worker script with bindings
1752
- endpoint = dispatchNamespace
1753
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${dispatchNamespace}/scripts/${workerName}`
1754
- : `/accounts/${api.accountId}/workers/scripts/${workerName}`;
1755
- method = "PUT";
1756
- }
1905
+ return {
1906
+ body,
1907
+ endpoint,
1908
+ method,
1909
+ };
1910
+ }
1757
1911
 
1758
- const uploadResponse =
1759
- method === "PUT"
1760
- ? await api.put(endpoint, formData, {
1761
- headers: {
1762
- "Content-Type": "multipart/form-data",
1763
- },
1764
- })
1765
- : await api.post(endpoint, formData, {
1766
- headers: {
1767
- "Content-Type": "multipart/form-data",
1768
- },
1769
- });
1912
+ interface PutWorkerResult {
1913
+ id: string;
1914
+ number: number;
1915
+ metadata: {
1916
+ has_preview: boolean;
1917
+ };
1918
+ annotations?: {
1919
+ "workers/tag"?: string;
1920
+ };
1921
+ deployment_id: string;
1922
+ }
1923
+
1924
+ export async function putWorker(
1925
+ api: CloudflareApi,
1926
+ props: PutWorkerOptions,
1927
+ ): Promise<PutWorkerResult> {
1928
+ const {
1929
+ //
1930
+ workerName,
1931
+ version,
1932
+ } = props;
1933
+
1934
+ return withExponentialBackoff(
1935
+ async () => {
1936
+ const { body, endpoint, method } = await prepareWorkerUpload(api, props);
1937
+ const uploadResponse = await api.fetch(endpoint, {
1938
+ method,
1939
+ headers: {
1940
+ "Content-Type": "multipart/form-data",
1941
+ },
1942
+ body,
1943
+ });
1770
1944
 
1771
1945
  // Check if the upload was successful
1772
1946
  if (!uploadResponse.ok) {
@@ -1805,55 +1979,9 @@ export async function putWorker(
1805
1979
  }
1806
1980
  }
1807
1981
  const responseData = (await uploadResponse.json()) as {
1808
- result: {
1809
- id: string;
1810
- number: number;
1811
- metadata: {
1812
- has_preview: boolean;
1813
- };
1814
- annotations?: {
1815
- "workers/tag"?: string;
1816
- };
1817
- deployment_id: string;
1818
- };
1819
- };
1820
- const result = responseData.result;
1821
-
1822
- // Handle version response
1823
- if (props.version) {
1824
- // Get the account's workers.dev subdomain to construct preview URL
1825
- let previewUrl: string | undefined;
1826
- if (result.metadata?.has_preview) {
1827
- // Need to get the subdomain
1828
- const subdomainResponse = await api.get(
1829
- `/accounts/${api.accountId}/workers/subdomain`,
1830
- );
1831
-
1832
- if (subdomainResponse.ok) {
1833
- const subdomainData: {
1834
- result: {
1835
- subdomain: string;
1836
- };
1837
- } = await subdomainResponse.json();
1838
- const subdomain = subdomainData.result?.subdomain;
1839
- if (subdomain) {
1840
- // Preview URL format: <FIRST_8_CHARS_OF_VERSION_ID>-<WORKER_NAME>.<SUBDOMAIN>.workers.dev
1841
- const versionPrefix = result.id.substring(0, 8);
1842
- previewUrl = `https://${versionPrefix}-${workerName}.${subdomain}.workers.dev`;
1843
- }
1844
- }
1845
- }
1846
-
1847
- return {
1848
- versionId: result.id,
1849
- previewUrl,
1850
- deploymentId: result.deployment_id,
1851
- };
1852
- }
1853
-
1854
- return {
1855
- deploymentId: result.deployment_id,
1982
+ result: PutWorkerResult;
1856
1983
  };
1984
+ return responseData.result;
1857
1985
  },
1858
1986
  (err) =>
1859
1987
  err.status === 404 ||
@@ -1868,28 +1996,25 @@ export async function putWorker(
1868
1996
  );
1869
1997
  }
1870
1998
 
1871
- export async function checkWorkerExists(
1872
- api: CloudflareApi,
1873
- workerName: string,
1874
- ): Promise<boolean> {
1875
- const response = await api.get(
1876
- `/accounts/${api.accountId}/workers/scripts/${workerName}`,
1999
+ async function workerExists(api: CloudflareApi, scriptName: string) {
2000
+ const res = await api.get(
2001
+ `/accounts/${api.accountId}/workers/scripts/${scriptName}`,
1877
2002
  );
1878
- return response.status === 200;
2003
+ return res.status === 200;
1879
2004
  }
1880
2005
 
1881
- export async function assertWorkerDoesNotExist(
2006
+ async function assertWorkerDoesNotExist(
1882
2007
  api: CloudflareApi,
1883
- workerName: string,
2008
+ scriptName: string,
1884
2009
  ) {
1885
2010
  const response = await api.get(
1886
- `/accounts/${api.accountId}/workers/scripts/${workerName}`,
2011
+ `/accounts/${api.accountId}/workers/scripts/${scriptName}`,
1887
2012
  );
1888
2013
  if (response.status === 404) {
1889
2014
  return true;
1890
2015
  }
1891
2016
  if (response.status === 200) {
1892
- const metadata = await getWorkerScriptMetadata(api, workerName);
2017
+ const metadata = await getScriptMetadata(api, scriptName);
1893
2018
 
1894
2019
  if (!metadata) {
1895
2020
  throw new Error(
@@ -1898,7 +2023,7 @@ export async function assertWorkerDoesNotExist(
1898
2023
  }
1899
2024
 
1900
2025
  throw new Error(
1901
- `Worker with name '${workerName}' already exists. Please use a unique name.`,
2026
+ `Worker with name '${scriptName}' already exists. Please use a unique name.`,
1902
2027
  );
1903
2028
  }
1904
2029
  throw new Error(
@@ -1906,80 +2031,84 @@ export async function assertWorkerDoesNotExist(
1906
2031
  );
1907
2032
  }
1908
2033
 
1909
- export async function configureURL<B extends Bindings>(
1910
- ctx: Context<Worker<B>> | Context<WorkerStub>,
1911
- api: CloudflareApi,
1912
- workerName: string,
1913
- url: boolean,
1914
- ) {
1915
- let workerUrl;
1916
- if (url) {
1917
- // Enable the workers.dev subdomain for this worker
1918
- await api.post(
1919
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1920
- { enabled: true, previews_enabled: true },
1921
- {
1922
- headers: { "Content-Type": "application/json" },
1923
- },
1924
- );
1925
-
1926
- // Get the account's workers.dev subdomain
1927
- const subdomain = await getAccountSubdomain(api);
1928
-
1929
- if (subdomain) {
1930
- workerUrl = `https://${workerName}.${subdomain}.workers.dev`;
1931
- }
1932
- } else if (url === false && ctx.output?.url) {
1933
- // Explicitly disable URL if it was previously enabled
1934
- const response = await api.post(
1935
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1936
- JSON.stringify({ enabled: false }),
1937
- {
1938
- headers: { "Content-Type": "application/json" },
1939
- },
1940
- );
1941
- if (!response.ok) {
1942
- throw new Error(
1943
- `Failed to disable worker URL: ${response.status} ${response.statusText}`,
1944
- );
1945
- }
1946
- }
1947
- return workerUrl;
1948
- }
1949
-
1950
- export async function getWorkerScriptMetadata(
2034
+ async function getScriptMetadata(
1951
2035
  api: CloudflareApi,
1952
- workerName: string,
2036
+ scriptName: string,
1953
2037
  ): Promise<WorkerScriptMetadata | undefined> {
1954
- const response = await api.get(
1955
- `/accounts/${api.accountId}/workers/services/${workerName}`,
2038
+ const res = await api.get(
2039
+ `/accounts/${api.accountId}/workers/services/${scriptName}`,
1956
2040
  );
1957
- if (response.status === 404) {
1958
- return undefined;
2041
+ if (res.status === 404) {
2042
+ return;
1959
2043
  }
1960
- if (!response.ok) {
2044
+ if (!res.ok) {
1961
2045
  throw new Error(
1962
- `Error getting worker script metadata: ${response.status} ${response.statusText}`,
2046
+ `Error getting worker script metadata: ${res.status} ${res.statusText}`,
1963
2047
  );
1964
2048
  }
1965
- return ((await response.json()) as any).result as WorkerScriptMetadata;
2049
+ const json = (await res.json()) as { result: WorkerScriptMetadata };
2050
+ return json.result;
1966
2051
  }
1967
2052
 
1968
- /**
1969
- * Lists and deletes all queue consumers for a specific worker
1970
- * @param ctx Worker context containing eventSources
1971
- * @param api CloudflareApi instance
1972
- * @param workerName Name of the worker script
1973
- */
1974
- async function deleteQueueConsumers(
1975
- api: CloudflareApi,
1976
- workerName: string,
1977
- ): Promise<void> {
1978
- const consumers = await listQueueConsumersForWorker(api, workerName);
1979
-
2053
+ async function deleteQueueConsumers(api: CloudflareApi, scriptName: string) {
2054
+ const consumers = await listQueueConsumersForWorker(api, scriptName);
1980
2055
  await Promise.all(
1981
2056
  consumers.map(async (consumer) => {
1982
2057
  await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
1983
2058
  }),
1984
2059
  );
1985
2060
  }
2061
+
2062
+ export async function deleteWorker(
2063
+ api: CloudflareApi,
2064
+ props: {
2065
+ scriptName: string;
2066
+ dispatchNamespace?: string;
2067
+ },
2068
+ ) {
2069
+ await withExponentialBackoff(
2070
+ async () => {
2071
+ const deleteResponse = await api.delete(
2072
+ props.dispatchNamespace
2073
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${props.scriptName}?force=true`
2074
+ : `/accounts/${api.accountId}/workers/scripts/${props.scriptName}?force=true`,
2075
+ );
2076
+
2077
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
2078
+ await handleApiError(
2079
+ deleteResponse,
2080
+ "delete",
2081
+ "worker",
2082
+ props.scriptName,
2083
+ );
2084
+ }
2085
+ },
2086
+ (err) =>
2087
+ (err.status === 400 &&
2088
+ err.message.includes(
2089
+ "is still referenced by service bindings in Workers",
2090
+ )) ||
2091
+ err.status === 500 ||
2092
+ err.status === 503,
2093
+ 10,
2094
+ 100,
2095
+ );
2096
+ }
2097
+
2098
+ async function getVersionMetadata(
2099
+ api: CloudflareApi,
2100
+ scriptName: string,
2101
+ deploymentId: string,
2102
+ ) {
2103
+ const response = await api.get(
2104
+ `/accounts/${api.accountId}/workers/scripts/${scriptName}/versions/${deploymentId}`,
2105
+ );
2106
+ const result = (await response.json()) as {
2107
+ result: {
2108
+ resources: {
2109
+ bindings: WorkerBindingSpec[];
2110
+ };
2111
+ };
2112
+ };
2113
+ return result.result;
2114
+ }