alchemy 0.44.4 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/drizzle/0000_busy_mauler.sql +19 -0
  2. package/drizzle/0001_concerned_iceman.sql +20 -0
  3. package/drizzle/meta/0000_snapshot.json +137 -0
  4. package/drizzle/meta/0001_snapshot.json +106 -0
  5. package/drizzle/meta/_journal.json +20 -0
  6. package/lib/alchemy.d.ts +3 -3
  7. package/lib/alchemy.d.ts.map +1 -1
  8. package/lib/alchemy.js +5 -2
  9. package/lib/alchemy.js.map +1 -1
  10. package/lib/build-date.d.ts +1 -1
  11. package/lib/build-date.js +1 -1
  12. package/lib/cloudflare/auth.js +1 -1
  13. package/lib/cloudflare/auth.js.map +1 -1
  14. package/lib/cloudflare/bundle/esbuild.d.ts +16 -0
  15. package/lib/cloudflare/bundle/esbuild.d.ts.map +1 -0
  16. package/lib/cloudflare/bundle/esbuild.js +159 -0
  17. package/lib/cloudflare/bundle/esbuild.js.map +1 -0
  18. package/lib/cloudflare/bundle/fs.d.ts +18 -0
  19. package/lib/cloudflare/bundle/fs.d.ts.map +1 -0
  20. package/lib/cloudflare/bundle/fs.js +75 -0
  21. package/lib/cloudflare/bundle/fs.js.map +1 -0
  22. package/lib/cloudflare/bundle/index.d.ts +3 -0
  23. package/lib/cloudflare/bundle/index.d.ts.map +1 -0
  24. package/lib/cloudflare/bundle/index.js +2 -0
  25. package/lib/cloudflare/bundle/index.js.map +1 -0
  26. package/lib/cloudflare/bundle/inline.d.ts +12 -0
  27. package/lib/cloudflare/bundle/inline.d.ts.map +1 -0
  28. package/lib/cloudflare/bundle/inline.js +32 -0
  29. package/lib/cloudflare/bundle/inline.js.map +1 -0
  30. package/lib/cloudflare/bundle/normalize.d.ts +21 -0
  31. package/lib/cloudflare/bundle/normalize.d.ts.map +1 -0
  32. package/lib/cloudflare/bundle/normalize.js +49 -0
  33. package/lib/cloudflare/bundle/normalize.js.map +1 -0
  34. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.d.ts → plugin-compat-warning.d.ts} +2 -2
  35. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +1 -0
  36. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.js → plugin-compat-warning.js} +3 -3
  37. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -0
  38. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +8 -0
  39. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -0
  40. package/lib/cloudflare/bundle/plugin-hot-reload.js +19 -0
  41. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -0
  42. package/lib/cloudflare/bundle/{nodejs-compat.d.ts → plugin-hybrid-node-compat.d.ts} +2 -2
  43. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -0
  44. package/lib/cloudflare/bundle/{nodejs-compat.js → plugin-hybrid-node-compat.js} +10 -10
  45. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -0
  46. package/lib/cloudflare/bundle/plugin-wasm.d.ts +3 -0
  47. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -0
  48. package/lib/cloudflare/bundle/plugin-wasm.js +55 -0
  49. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -0
  50. package/lib/cloudflare/bundle/shared.d.ts +30 -0
  51. package/lib/cloudflare/bundle/shared.d.ts.map +1 -0
  52. package/lib/cloudflare/bundle/shared.js +34 -0
  53. package/lib/cloudflare/bundle/shared.js.map +1 -0
  54. package/lib/cloudflare/bundle/{nodejs-compat-mode.d.ts → validate-node-compat.d.ts} +8 -4
  55. package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -0
  56. package/lib/cloudflare/bundle/{nodejs-compat-mode.js → validate-node-compat.js} +27 -7
  57. package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -0
  58. package/lib/cloudflare/container.d.ts +231 -0
  59. package/lib/cloudflare/container.d.ts.map +1 -1
  60. package/lib/cloudflare/container.js +7 -0
  61. package/lib/cloudflare/container.js.map +1 -1
  62. package/lib/cloudflare/d1-migrations.d.ts +1 -0
  63. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  64. package/lib/cloudflare/d1-migrations.js +18 -6
  65. package/lib/cloudflare/d1-migrations.js.map +1 -1
  66. package/lib/cloudflare/d1-state-store.d.ts +10 -0
  67. package/lib/cloudflare/d1-state-store.d.ts.map +1 -0
  68. package/lib/cloudflare/d1-state-store.js +70 -0
  69. package/lib/cloudflare/d1-state-store.js.map +1 -0
  70. package/lib/cloudflare/do-state-store/internal.js +1 -1
  71. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  72. package/lib/cloudflare/do-state-store/store.js +1 -1
  73. package/lib/cloudflare/do-state-store/store.js.map +1 -1
  74. package/lib/cloudflare/index.d.ts +3 -2
  75. package/lib/cloudflare/index.d.ts.map +1 -1
  76. package/lib/cloudflare/index.js +3 -2
  77. package/lib/cloudflare/index.js.map +1 -1
  78. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -0
  79. package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.js → local-dev-cloudflare-shim.js} +1 -1
  80. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -0
  81. package/lib/cloudflare/miniflare/http-server.d.ts.map +1 -0
  82. package/lib/cloudflare/miniflare/http-server.js.map +1 -0
  83. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.d.ts +4 -3
  84. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -0
  85. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.js +6 -3
  86. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -0
  87. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -0
  88. package/lib/cloudflare/{worker → miniflare}/miniflare.js +1 -1
  89. package/lib/cloudflare/miniflare/miniflare.js.map +1 -0
  90. package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +1 -0
  91. package/lib/cloudflare/{worker → miniflare}/mixed-mode.js +1 -2
  92. package/lib/cloudflare/miniflare/mixed-mode.js.map +1 -0
  93. package/lib/cloudflare/user.js +1 -1
  94. package/lib/cloudflare/user.js.map +1 -1
  95. package/lib/cloudflare/worker/shared.d.ts +4 -0
  96. package/lib/cloudflare/worker/shared.d.ts.map +1 -0
  97. package/lib/cloudflare/worker/{get-worker-template.js → shared.js} +10 -1
  98. package/lib/cloudflare/worker/shared.js.map +1 -0
  99. package/lib/cloudflare/worker/tail.d.ts +18 -0
  100. package/lib/cloudflare/worker/tail.d.ts.map +1 -0
  101. package/lib/cloudflare/worker/tail.js +140 -0
  102. package/lib/cloudflare/worker/tail.js.map +1 -0
  103. package/lib/cloudflare/worker-metadata.d.ts +6 -2
  104. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  105. package/lib/cloudflare/worker-metadata.js +7 -23
  106. package/lib/cloudflare/worker-metadata.js.map +1 -1
  107. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  108. package/lib/cloudflare/worker-stub.js +8 -7
  109. package/lib/cloudflare/worker-stub.js.map +1 -1
  110. package/lib/cloudflare/worker-subdomain.d.ts +31 -0
  111. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -0
  112. package/lib/cloudflare/worker-subdomain.js +55 -0
  113. package/lib/cloudflare/worker-subdomain.js.map +1 -0
  114. package/lib/cloudflare/worker.d.ts +30 -36
  115. package/lib/cloudflare/worker.d.ts.map +1 -1
  116. package/lib/cloudflare/worker.js +641 -569
  117. package/lib/cloudflare/worker.js.map +1 -1
  118. package/lib/fs/file-system-state-store.js +1 -1
  119. package/lib/fs/file-system-state-store.js.map +1 -1
  120. package/lib/scope.d.ts +2 -2
  121. package/lib/scope.d.ts.map +1 -1
  122. package/lib/scope.js +23 -8
  123. package/lib/scope.js.map +1 -1
  124. package/lib/serde.d.ts +1 -1
  125. package/lib/serde.d.ts.map +1 -1
  126. package/lib/serde.js +4 -4
  127. package/lib/serde.js.map +1 -1
  128. package/lib/sqlite/base-sqlite-state-store.d.ts +31 -0
  129. package/lib/sqlite/base-sqlite-state-store.d.ts.map +1 -0
  130. package/lib/sqlite/base-sqlite-state-store.js +124 -0
  131. package/lib/sqlite/base-sqlite-state-store.js.map +1 -0
  132. package/lib/sqlite/index.d.ts +2 -0
  133. package/lib/sqlite/index.d.ts.map +1 -0
  134. package/lib/sqlite/index.js +2 -0
  135. package/lib/sqlite/index.js.map +1 -0
  136. package/lib/sqlite/schema.d.ts +198 -0
  137. package/lib/sqlite/schema.d.ts.map +1 -0
  138. package/lib/sqlite/schema.js +23 -0
  139. package/lib/sqlite/schema.js.map +1 -0
  140. package/lib/sqlite/sqlite-state-store.d.ts +60 -0
  141. package/lib/sqlite/sqlite-state-store.d.ts.map +1 -0
  142. package/lib/sqlite/sqlite-state-store.js +83 -0
  143. package/lib/sqlite/sqlite-state-store.js.map +1 -0
  144. package/lib/test/bun.d.ts.map +1 -1
  145. package/lib/test/bun.js +0 -6
  146. package/lib/test/bun.js.map +1 -1
  147. package/lib/test/vitest.d.ts.map +1 -1
  148. package/lib/test/vitest.js +19 -5
  149. package/lib/test/vitest.js.map +1 -1
  150. package/lib/util/memoize.d.ts +1 -1
  151. package/lib/util/memoize.d.ts.map +1 -1
  152. package/lib/util/memoize.js +11 -1
  153. package/lib/util/memoize.js.map +1 -1
  154. package/package.json +13 -3
  155. package/src/alchemy.ts +8 -6
  156. package/src/build-date.ts +1 -1
  157. package/src/cloudflare/auth.ts +1 -1
  158. package/src/cloudflare/bundle/esbuild.ts +194 -0
  159. package/src/cloudflare/bundle/fs.ts +98 -0
  160. package/src/cloudflare/bundle/index.ts +6 -0
  161. package/src/cloudflare/bundle/inline.ts +42 -0
  162. package/src/cloudflare/bundle/normalize.ts +76 -0
  163. package/src/cloudflare/bundle/{nodejs-import-warning-plugin.ts → plugin-compat-warning.ts} +4 -2
  164. package/src/cloudflare/bundle/plugin-hot-reload.ts +27 -0
  165. package/src/cloudflare/bundle/{nodejs-compat.ts → plugin-hybrid-node-compat.ts} +10 -10
  166. package/src/cloudflare/bundle/plugin-wasm.ts +62 -0
  167. package/src/cloudflare/bundle/shared.ts +73 -0
  168. package/src/cloudflare/bundle/{nodejs-compat-mode.ts → validate-node-compat.ts} +41 -16
  169. package/src/cloudflare/container.ts +296 -0
  170. package/src/cloudflare/d1-migrations.ts +29 -16
  171. package/src/cloudflare/d1-state-store.ts +105 -0
  172. package/src/cloudflare/do-state-store/internal.ts +1 -1
  173. package/src/cloudflare/do-state-store/store.ts +1 -1
  174. package/src/cloudflare/index.ts +3 -2
  175. package/src/cloudflare/{bundle/local-dev-cloudflare-shim.ts → local-dev-cloudflare-shim.ts} +1 -1
  176. package/src/cloudflare/{worker → miniflare}/miniflare-worker-options.ts +14 -6
  177. package/src/cloudflare/{worker → miniflare}/miniflare.ts +1 -1
  178. package/src/cloudflare/{worker → miniflare}/mixed-mode.ts +1 -2
  179. package/src/cloudflare/user.ts +1 -1
  180. package/src/cloudflare/worker/{subdomain.ts → shared.ts} +14 -0
  181. package/src/cloudflare/worker/tail.ts +287 -0
  182. package/src/cloudflare/worker-metadata.ts +18 -26
  183. package/src/cloudflare/worker-stub.ts +9 -7
  184. package/src/cloudflare/worker-subdomain.ts +123 -0
  185. package/src/cloudflare/worker.ts +863 -760
  186. package/src/fs/file-system-state-store.ts +1 -1
  187. package/src/scope.ts +30 -11
  188. package/src/serde.ts +4 -4
  189. package/src/sqlite/base-sqlite-state-store.ts +199 -0
  190. package/src/sqlite/index.ts +1 -0
  191. package/src/sqlite/schema.ts +37 -0
  192. package/src/sqlite/sqlite-state-store.ts +174 -0
  193. package/src/test/bun.ts +0 -8
  194. package/src/test/vitest.ts +19 -8
  195. package/src/util/memoize.ts +13 -1
  196. package/templates/astro/types/env.d.ts +1 -1
  197. package/templates/nuxt/types/env.d.ts +1 -1
  198. package/templates/react-router/types/env.d.ts +1 -1
  199. package/templates/rwsdk/types/env.d.ts +1 -1
  200. package/templates/sveltekit/types/env.d.ts +1 -1
  201. package/templates/tanstack-start/types/env.d.ts +1 -1
  202. package/templates/typescript/types/env.d.ts +1 -1
  203. package/templates/vite/types/env.d.ts +1 -1
  204. package/lib/cloudflare/bundle/alias-plugin.d.ts +0 -6
  205. package/lib/cloudflare/bundle/alias-plugin.d.ts.map +0 -1
  206. package/lib/cloudflare/bundle/alias-plugin.js +0 -36
  207. package/lib/cloudflare/bundle/alias-plugin.js.map +0 -1
  208. package/lib/cloudflare/bundle/build-failures.d.ts +0 -12
  209. package/lib/cloudflare/bundle/build-failures.d.ts.map +0 -1
  210. package/lib/cloudflare/bundle/build-failures.js +0 -45
  211. package/lib/cloudflare/bundle/build-failures.js.map +0 -1
  212. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +0 -26
  213. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +0 -1
  214. package/lib/cloudflare/bundle/bundle-worker-dev.js +0 -88
  215. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +0 -1
  216. package/lib/cloudflare/bundle/bundle-worker.d.ts +0 -13
  217. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +0 -1
  218. package/lib/cloudflare/bundle/bundle-worker.js +0 -98
  219. package/lib/cloudflare/bundle/bundle-worker.js.map +0 -1
  220. package/lib/cloudflare/bundle/external.d.ts +0 -4
  221. package/lib/cloudflare/bundle/external.d.ts.map +0 -1
  222. package/lib/cloudflare/bundle/external.js +0 -40
  223. package/lib/cloudflare/bundle/external.js.map +0 -1
  224. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts.map +0 -1
  225. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js.map +0 -1
  226. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +0 -1
  227. package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +0 -1
  228. package/lib/cloudflare/bundle/nodejs-compat.d.ts.map +0 -1
  229. package/lib/cloudflare/bundle/nodejs-compat.js.map +0 -1
  230. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +0 -1
  231. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +0 -1
  232. package/lib/cloudflare/bundle/wasm-plugin.d.ts +0 -3
  233. package/lib/cloudflare/bundle/wasm-plugin.d.ts.map +0 -1
  234. package/lib/cloudflare/bundle/wasm-plugin.js +0 -53
  235. package/lib/cloudflare/bundle/wasm-plugin.js.map +0 -1
  236. package/lib/cloudflare/worker/get-worker-template.d.ts +0 -2
  237. package/lib/cloudflare/worker/get-worker-template.d.ts.map +0 -1
  238. package/lib/cloudflare/worker/get-worker-template.js.map +0 -1
  239. package/lib/cloudflare/worker/http-server.d.ts.map +0 -1
  240. package/lib/cloudflare/worker/http-server.js.map +0 -1
  241. package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +0 -1
  242. package/lib/cloudflare/worker/miniflare-worker-options.js.map +0 -1
  243. package/lib/cloudflare/worker/miniflare.d.ts.map +0 -1
  244. package/lib/cloudflare/worker/miniflare.js.map +0 -1
  245. package/lib/cloudflare/worker/mixed-mode.d.ts.map +0 -1
  246. package/lib/cloudflare/worker/mixed-mode.js.map +0 -1
  247. package/lib/cloudflare/worker/subdomain.d.ts +0 -3
  248. package/lib/cloudflare/worker/subdomain.d.ts.map +0 -1
  249. package/lib/cloudflare/worker/subdomain.js +0 -10
  250. package/lib/cloudflare/worker/subdomain.js.map +0 -1
  251. package/src/cloudflare/bundle/alias-plugin.ts +0 -48
  252. package/src/cloudflare/bundle/build-failures.ts +0 -55
  253. package/src/cloudflare/bundle/bundle-worker-dev.ts +0 -129
  254. package/src/cloudflare/bundle/bundle-worker.ts +0 -150
  255. package/src/cloudflare/bundle/external.ts +0 -41
  256. package/src/cloudflare/bundle/wasm-plugin.ts +0 -57
  257. package/src/cloudflare/worker/get-worker-template.ts +0 -14
  258. /package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.d.ts → local-dev-cloudflare-shim.d.ts} +0 -0
  259. /package/lib/cloudflare/{worker → miniflare}/http-server.d.ts +0 -0
  260. /package/lib/cloudflare/{worker → miniflare}/http-server.js +0 -0
  261. /package/lib/cloudflare/{worker → miniflare}/miniflare.d.ts +0 -0
  262. /package/lib/cloudflare/{worker → miniflare}/mixed-mode.d.ts +0 -0
  263. /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,161 +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(this, api, {
1346
- ...props,
1347
- workerName,
1348
- url: props.url ?? true,
1349
- });
1350
- }
1351
-
1352
- // Get current timestamp
1353
- const now = Date.now();
1354
-
1355
- // Update cron triggers
1356
- if (props.crons) {
1357
- const res = await api.put(
1358
- `/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`,
1359
- props.crons.map((cron) => ({ cron })),
1360
- );
1361
-
1362
- if (!res.ok) {
1363
- await handleApiError(
1364
- res,
1365
- "updating cron triggers",
1366
- "worker",
1367
- workerName,
1368
- );
1369
- }
1370
- }
1371
-
1372
- return { scriptBundle, workerUrl, now, versionResult };
1373
1128
  };
1374
1129
 
1375
- if (this.phase === "delete") {
1376
- // only delete the worker if it's not a version
1377
- if (!props.version) {
1378
- // Delete any queue consumers attached to this worker first
1379
- await deleteQueueConsumers(api, workerName);
1380
-
1381
- await withExponentialBackoff(
1382
- () =>
1383
- deleteWorker(api, {
1384
- workerName,
1385
- namespace:
1386
- typeof props.namespace === "string"
1387
- ? props.namespace
1388
- : props.namespace?.namespaceId,
1389
- url: this.output.url,
1390
- }),
1391
- (err) =>
1392
- (err.status === 400 &&
1393
- err.message.includes(
1394
- "is still referenced by service bindings in Workers",
1395
- )) ||
1396
- err.status === 500 ||
1397
- err.status === 503,
1398
- 10,
1399
- 100,
1400
- );
1401
- }
1402
- return this.destroy();
1403
- }
1404
-
1405
1130
  if (this.phase === "create") {
1406
1131
  if (props.version) {
1407
1132
  // When version is specified, we adopt existing workers or create them if they don't exist
1408
- const workerExists = await checkWorkerExists(api, workerName);
1409
- if (!workerExists) {
1133
+ if (!(await workerExists(api, workerName))) {
1410
1134
  // Create the base worker first if it doesn't exist
1411
1135
  const baseWorkerProps = { ...props, version: undefined };
1412
- await uploadWorkerScript(baseWorkerProps);
1136
+ await putWorkerWithAssets(baseWorkerProps, await bundle.create());
1413
1137
  }
1414
1138
  // We always "adopt" when publishing versions
1415
1139
  } else if (!props.adopt) {
@@ -1417,106 +1141,164 @@ export const _Worker = Resource(
1417
1141
  }
1418
1142
  }
1419
1143
 
1420
- 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
+ }
1421
1174
 
1422
- function ensureNoDuplicates(name: string, items: string[]) {
1423
- const duplicates = items.filter(
1424
- (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
+ }),
1425
1220
  );
1426
- if (duplicates.length > 0) {
1427
- throw new Error(`Duplicate ${name} found: ${duplicates.join(", ")}`);
1428
- }
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());
1429
1226
  }
1430
1227
 
1431
- let createdDomains: CustomDomain[] | undefined;
1432
- if (props.domains?.length) {
1433
- ensureNoDuplicates(
1434
- "Custom Domain",
1435
- props.domains.map((domain) =>
1436
- typeof domain === "string" ? domain : domain.domainName,
1437
- ),
1438
- );
1228
+ const tasks: Promise<unknown>[] = [];
1439
1229
 
1440
- createdDomains = await Promise.all(
1441
- props.domains.map(async (customDomain) => {
1442
- const domainName =
1443
- typeof customDomain === "string"
1444
- ? customDomain
1445
- : customDomain.domainName;
1446
- return await CustomDomain(domainName, {
1447
- workerName,
1448
- name: domainName,
1449
- zoneId:
1450
- typeof customDomain === "string"
1451
- ? undefined
1452
- : customDomain.zoneId,
1453
- adopt:
1454
- typeof customDomain === "string"
1455
- ? (props.adopt ?? false)
1456
- : (customDomain.adopt ?? props.adopt),
1457
- });
1458
- }),
1459
- );
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
+ }
1460
1243
  }
1461
1244
 
1462
- // Create routes if provided and capture their outputs
1463
- let createdRoutes: Route[] | undefined;
1464
- if (props.routes && props.routes.length > 0) {
1465
- ensureNoDuplicates(
1466
- "Route",
1467
- props.routes.map((route) =>
1468
- 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
+ }),
1469
1254
  ),
1470
1255
  );
1471
-
1472
- // Create Route resources for each route and capture their outputs
1473
- createdRoutes = await Promise.all(
1474
- props.routes.map(async (routeConfig) => {
1475
- const pattern =
1476
- typeof routeConfig === "string" ? routeConfig : routeConfig.pattern;
1477
- return await Route(pattern, {
1478
- pattern,
1479
- script: workerName,
1480
- zoneId:
1481
- typeof routeConfig === "string" ? undefined : routeConfig.zoneId, // Route resource will handle inference if not provided
1482
- adopt:
1483
- typeof routeConfig === "string"
1484
- ? (props.adopt ?? false)
1485
- : (routeConfig.adopt ?? props.adopt),
1486
- accountId: props.accountId,
1487
- apiKey: props.apiKey,
1488
- apiToken: props.apiToken,
1489
- baseUrl: props.baseUrl,
1490
- email: props.email,
1491
- });
1492
- }),
1493
- );
1494
1256
  }
1495
1257
 
1496
- function exportBindings() {
1497
- return Object.fromEntries(
1498
- Object.entries(props.bindings ?? ({} as B)).map(
1499
- ([bindingName, binding]) => [
1500
- bindingName,
1501
- isDurableObjectNamespace(binding) &&
1502
- binding.scriptName === undefined
1503
- ? new DurableObjectNamespace(binding.id, {
1504
- ...binding,
1505
- // re-export this binding mapping to the host worker (this worker)
1506
- scriptName: workerName,
1507
- })
1508
- : isWorkflow(binding) && binding.scriptName === undefined
1509
- ? new Workflow(binding.id, {
1510
- ...binding,
1511
- // re-export this binding mapping to the host worker (this worker)
1512
- scriptName: workerName,
1513
- })
1514
- : binding,
1515
- ],
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 })) ?? [],
1516
1263
  ),
1517
1264
  );
1518
1265
  }
1519
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
+
1520
1302
  // Construct the output
1521
1303
  return this({
1522
1304
  ...props,
@@ -1528,22 +1310,22 @@ export const _Worker = Resource(
1528
1310
  compatibilityDate,
1529
1311
  compatibilityFlags,
1530
1312
  format: props.format || "esm", // Include format in the output
1531
- bindings: exportBindings(),
1313
+ bindings: normalizeExportBindings(workerName, props.bindings),
1532
1314
  env: props.env,
1533
1315
  observability: props.observability,
1534
- createdAt: now,
1316
+ createdAt: this.output?.createdAt ?? now,
1535
1317
  updatedAt: now,
1536
1318
  eventSources: props.eventSources,
1537
- url: workerUrl,
1319
+ url: subdomain?.url,
1538
1320
  dev: props.dev,
1539
1321
  // Include assets configuration in the output
1540
1322
  assets: props.assets,
1541
1323
  // Include cron triggers in the output
1542
1324
  crons: props.crons,
1543
1325
  // Include the created routes in the output
1544
- routes: createdRoutes,
1326
+ routes,
1545
1327
  // Include the created domains in the output
1546
- domains: createdDomains,
1328
+ domains,
1547
1329
  // Include the dispatch namespace in the output
1548
1330
  namespace: props.namespace,
1549
1331
  // Include version information in the output
@@ -1554,48 +1336,442 @@ export const _Worker = Resource(
1554
1336
  },
1555
1337
  );
1556
1338
 
1557
- 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(
1558
1451
  api: CloudflareApi,
1559
1452
  props: {
1560
- workerName: string;
1561
- namespace?: string | DispatchNamespaceResource;
1562
- url?: string;
1453
+ scriptName: string;
1454
+ containers?: Container[];
1455
+ bindings: WorkerBindingSpec[];
1563
1456
  },
1564
- ) {
1565
- 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
+ }
1566
1486
 
1567
- // Delete worker
1568
- const deleteResponse = await api.delete(
1569
- props.namespace
1570
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.namespace}/scripts/${workerName}?force=true`
1571
- : `/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
+ }),
1572
1519
  );
1520
+ }
1573
1521
 
1574
- // Check for success (2xx status code)
1575
- if (!deleteResponse.ok && deleteResponse.status !== 404) {
1576
- 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;
1577
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
+ }
1578
1560
 
1579
- // Disable the URL if it was enabled
1580
- if (props.url) {
1581
- try {
1582
- await api.post(
1583
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1584
- JSON.stringify({ enabled: false }),
1585
- {
1586
- headers: { "Content-Type": "application/json" },
1587
- },
1588
- );
1589
- } catch (error) {
1590
- 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
+ };
1591
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
+ });
1592
1616
  }
1617
+ if (props.forceDelete) {
1618
+ await disableWorkerSubdomain(api, props.scriptName);
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
+ };
1593
1638
 
1594
- // Return minimal output for deleted state
1595
- return;
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
+ );
1770
+
1771
+ return await startPromise.value;
1596
1772
  }
1597
1773
 
1598
- function upsertDevCommand(props: {
1774
+ function createDevCommand(props: {
1599
1775
  id: string;
1600
1776
  command: string;
1601
1777
  cwd: string;
@@ -1629,14 +1805,13 @@ function upsertDevCommand(props: {
1629
1805
  },
1630
1806
  stdio: ["inherit", "inherit", "inherit"],
1631
1807
  });
1632
- process.on("SIGINT", () => {
1808
+ cleanups.push(() => {
1633
1809
  try {
1634
1810
  unlinkSync(persistFile);
1635
1811
  } catch {
1636
1812
  // ignore
1637
1813
  }
1638
- proc.kill("SIGINT");
1639
- process.exit(0);
1814
+ proc.kill();
1640
1815
  });
1641
1816
  if (proc.pid) {
1642
1817
  mkdirSync(path.dirname(persistFile), { recursive: true });
@@ -1648,124 +1823,124 @@ type PutWorkerOptions = WorkerProps & {
1648
1823
  dispatchNamespace?: string;
1649
1824
  migrationTag?: string;
1650
1825
  workerName: string;
1651
- scriptBundle: string | NoBundleResult;
1826
+ scriptBundle: WorkerBundle;
1652
1827
  version: string | undefined;
1653
1828
  compatibilityDate: string;
1654
1829
  compatibilityFlags: string[];
1655
- assetUploadResult: AssetUploadResult | undefined;
1830
+ assetUploadResult:
1831
+ | {
1832
+ completionToken?: string;
1833
+ keepAssets?: boolean;
1834
+ assetConfig?: AssetsConfig;
1835
+ }
1836
+ | undefined;
1837
+ unstable_cacheWorkerSettings?: boolean;
1656
1838
  };
1657
1839
 
1658
- export async function putWorker(
1840
+ async function prepareWorkerUpload(
1659
1841
  api: CloudflareApi,
1660
1842
  props: PutWorkerOptions,
1661
- ): Promise<{ versionId?: string; previewUrl?: string; deploymentId: string }> {
1662
- const {
1663
- //
1664
- dispatchNamespace,
1665
- migrationTag,
1666
- workerName,
1667
- scriptBundle,
1668
- version,
1669
- } = props;
1843
+ ) {
1670
1844
  const scriptMetadata = await prepareWorkerMetadata(api, props);
1671
1845
 
1672
- return withExponentialBackoff(
1673
- async () => {
1674
- const scriptName =
1675
- scriptMetadata.main_module ?? scriptMetadata.body_part!;
1676
-
1677
- // Create FormData for the upload
1678
- const formData = new FormData();
1679
-
1680
- function addFile(fileName: string, content: Buffer | string) {
1681
- const contentType = getContentType(fileName) ?? "application/null";
1682
- formData.append(
1683
- fileName,
1684
- new Blob([content], {
1685
- type:
1686
- contentType === "application/javascript" &&
1687
- scriptMetadata.main_module
1688
- ? "application/javascript+module"
1689
- : contentType,
1690
- }),
1691
- fileName,
1692
- );
1693
- }
1694
-
1695
- if (typeof scriptBundle === "string") {
1696
- addFile(scriptName, scriptBundle);
1697
- } else {
1698
- for (const [fileName, content] of Object.entries(scriptBundle)) {
1699
- addFile(fileName, content);
1700
- }
1701
- }
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();
1702
1852
 
1703
- // Prepare metadata - add version annotations if this is a version
1704
- const finalMetadata = version
1705
- ? {
1706
- ...scriptMetadata,
1707
- // Exclude migrations for worker versions - they're not allowed
1708
- migrations: undefined,
1709
- annotations: {
1710
- "workers/tag": version,
1711
- "workers/message": `Version ${version}`,
1712
- },
1713
- }
1714
- : {
1715
- ...scriptMetadata,
1716
- migrations: scriptMetadata.migrations
1717
- ? {
1718
- ...scriptMetadata.migrations,
1719
- old_tag: migrationTag,
1720
- new_tag: bumpMigrationTagVersion(migrationTag),
1721
- }
1722
- : undefined,
1723
- };
1853
+ for await (const file of props.scriptBundle.files) {
1854
+ body.append(file.name, file);
1855
+ }
1724
1856
 
1725
- if (process.env.DEBUG) {
1726
- 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
+ },
1727
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
+ };
1728
1878
 
1729
- // Add metadata as JSON
1730
- formData.append(
1731
- "metadata",
1732
- new Blob([JSON.stringify(finalMetadata)], {
1733
- type: "application/json",
1734
- }),
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",
1735
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
+ }
1736
1904
 
1737
- // Determine endpoint and HTTP method
1738
- let endpoint: string;
1739
- let method: "PUT" | "POST";
1740
- if (version) {
1741
- if (dispatchNamespace) {
1742
- throw new Error(
1743
- "Worker Preview Versions are not supported in Workers for Platforms",
1744
- );
1745
- }
1746
- // Upload worker version using the versions API
1747
- endpoint = `/accounts/${api.accountId}/workers/scripts/${workerName}/versions`;
1748
- method = "POST";
1749
- } else {
1750
- // Upload worker script with bindings
1751
- endpoint = dispatchNamespace
1752
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${dispatchNamespace}/scripts/${workerName}`
1753
- : `/accounts/${api.accountId}/workers/scripts/${workerName}`;
1754
- method = "PUT";
1755
- }
1905
+ return {
1906
+ body,
1907
+ endpoint,
1908
+ method,
1909
+ };
1910
+ }
1756
1911
 
1757
- const uploadResponse =
1758
- method === "PUT"
1759
- ? await api.put(endpoint, formData, {
1760
- headers: {
1761
- "Content-Type": "multipart/form-data",
1762
- },
1763
- })
1764
- : await api.post(endpoint, formData, {
1765
- headers: {
1766
- "Content-Type": "multipart/form-data",
1767
- },
1768
- });
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
+ });
1769
1944
 
1770
1945
  // Check if the upload was successful
1771
1946
  if (!uploadResponse.ok) {
@@ -1804,55 +1979,9 @@ export async function putWorker(
1804
1979
  }
1805
1980
  }
1806
1981
  const responseData = (await uploadResponse.json()) as {
1807
- result: {
1808
- id: string;
1809
- number: number;
1810
- metadata: {
1811
- has_preview: boolean;
1812
- };
1813
- annotations?: {
1814
- "workers/tag"?: string;
1815
- };
1816
- deployment_id: string;
1817
- };
1818
- };
1819
- const result = responseData.result;
1820
-
1821
- // Handle version response
1822
- if (props.version) {
1823
- // Get the account's workers.dev subdomain to construct preview URL
1824
- let previewUrl: string | undefined;
1825
- if (result.metadata?.has_preview) {
1826
- // Need to get the subdomain
1827
- const subdomainResponse = await api.get(
1828
- `/accounts/${api.accountId}/workers/subdomain`,
1829
- );
1830
-
1831
- if (subdomainResponse.ok) {
1832
- const subdomainData: {
1833
- result: {
1834
- subdomain: string;
1835
- };
1836
- } = await subdomainResponse.json();
1837
- const subdomain = subdomainData.result?.subdomain;
1838
- if (subdomain) {
1839
- // Preview URL format: <FIRST_8_CHARS_OF_VERSION_ID>-<WORKER_NAME>.<SUBDOMAIN>.workers.dev
1840
- const versionPrefix = result.id.substring(0, 8);
1841
- previewUrl = `https://${versionPrefix}-${workerName}.${subdomain}.workers.dev`;
1842
- }
1843
- }
1844
- }
1845
-
1846
- return {
1847
- versionId: result.id,
1848
- previewUrl,
1849
- deploymentId: result.deployment_id,
1850
- };
1851
- }
1852
-
1853
- return {
1854
- deploymentId: result.deployment_id,
1982
+ result: PutWorkerResult;
1855
1983
  };
1984
+ return responseData.result;
1856
1985
  },
1857
1986
  (err) =>
1858
1987
  err.status === 404 ||
@@ -1867,28 +1996,25 @@ export async function putWorker(
1867
1996
  );
1868
1997
  }
1869
1998
 
1870
- export async function checkWorkerExists(
1871
- api: CloudflareApi,
1872
- workerName: string,
1873
- ): Promise<boolean> {
1874
- const response = await api.get(
1875
- `/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}`,
1876
2002
  );
1877
- return response.status === 200;
2003
+ return res.status === 200;
1878
2004
  }
1879
2005
 
1880
- export async function assertWorkerDoesNotExist(
2006
+ async function assertWorkerDoesNotExist(
1881
2007
  api: CloudflareApi,
1882
- workerName: string,
2008
+ scriptName: string,
1883
2009
  ) {
1884
2010
  const response = await api.get(
1885
- `/accounts/${api.accountId}/workers/scripts/${workerName}`,
2011
+ `/accounts/${api.accountId}/workers/scripts/${scriptName}`,
1886
2012
  );
1887
2013
  if (response.status === 404) {
1888
2014
  return true;
1889
2015
  }
1890
2016
  if (response.status === 200) {
1891
- const metadata = await getWorkerScriptMetadata(api, workerName);
2017
+ const metadata = await getScriptMetadata(api, scriptName);
1892
2018
 
1893
2019
  if (!metadata) {
1894
2020
  throw new Error(
@@ -1897,7 +2023,7 @@ export async function assertWorkerDoesNotExist(
1897
2023
  }
1898
2024
 
1899
2025
  throw new Error(
1900
- `Worker with name '${workerName}' already exists. Please use a unique name.`,
2026
+ `Worker with name '${scriptName}' already exists. Please use a unique name.`,
1901
2027
  );
1902
2028
  }
1903
2029
  throw new Error(
@@ -1905,107 +2031,84 @@ export async function assertWorkerDoesNotExist(
1905
2031
  );
1906
2032
  }
1907
2033
 
1908
- export async function configureURL<B extends Bindings>(
1909
- ctx: Context<Worker<B>> | Context<WorkerStub>,
2034
+ async function getScriptMetadata(
1910
2035
  api: CloudflareApi,
1911
- props: {
1912
- workerName: string;
1913
- url: boolean;
1914
- adopt?: boolean;
1915
- },
1916
- ) {
1917
- const { workerName, url } = props;
1918
- let workerUrl;
1919
- if (url) {
1920
- // Enable the workers.dev subdomain for this worker
1921
- await api.post(
1922
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1923
- { enabled: true, previews_enabled: true },
1924
- {
1925
- headers: { "Content-Type": "application/json" },
1926
- },
1927
- );
1928
-
1929
- // Get the account's workers.dev subdomain
1930
- const subdomain = await getAccountSubdomain(api);
1931
-
1932
- if (subdomain) {
1933
- workerUrl = `https://${workerName}.${subdomain}.workers.dev`;
1934
- }
1935
- } else if (
1936
- url === false &&
1937
- (ctx.output?.url || (ctx.phase === "create" && props.adopt))
1938
- ) {
1939
- // Explicitly disable URL if it was previously enabled
1940
- const response = await api.post(
1941
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1942
- JSON.stringify({ enabled: false }),
1943
- {
1944
- headers: { "Content-Type": "application/json" },
1945
- },
1946
- );
1947
- if (!response.ok) {
1948
- throw new Error(
1949
- `Failed to disable worker URL: ${response.status} ${response.statusText}`,
1950
- );
1951
- }
1952
- }
1953
- return workerUrl;
1954
- }
1955
-
1956
- export async function getWorkerSubdomainConfig(
1957
- api: CloudflareApi,
1958
- workerName: string,
1959
- ) {
1960
- // Query Cloudflare API to verify subdomain is disabled
1961
- const subdomainResponse = await api.get(
1962
- `/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
1963
- );
1964
-
1965
- if (subdomainResponse.status === 404) {
1966
- return undefined;
1967
- }
1968
-
1969
- const subdomainData: any = await subdomainResponse.json();
1970
- return subdomainData.result as {
1971
- enabled: boolean;
1972
- previews_enabled: boolean;
1973
- };
1974
- }
1975
-
1976
- export async function getWorkerScriptMetadata(
1977
- api: CloudflareApi,
1978
- workerName: string,
2036
+ scriptName: string,
1979
2037
  ): Promise<WorkerScriptMetadata | undefined> {
1980
- const response = await api.get(
1981
- `/accounts/${api.accountId}/workers/services/${workerName}`,
2038
+ const res = await api.get(
2039
+ `/accounts/${api.accountId}/workers/services/${scriptName}`,
1982
2040
  );
1983
- if (response.status === 404) {
1984
- return undefined;
2041
+ if (res.status === 404) {
2042
+ return;
1985
2043
  }
1986
- if (!response.ok) {
2044
+ if (!res.ok) {
1987
2045
  throw new Error(
1988
- `Error getting worker script metadata: ${response.status} ${response.statusText}`,
2046
+ `Error getting worker script metadata: ${res.status} ${res.statusText}`,
1989
2047
  );
1990
2048
  }
1991
- return ((await response.json()) as any).result as WorkerScriptMetadata;
2049
+ const json = (await res.json()) as { result: WorkerScriptMetadata };
2050
+ return json.result;
1992
2051
  }
1993
2052
 
1994
- /**
1995
- * Lists and deletes all queue consumers for a specific worker
1996
- * @param ctx Worker context containing eventSources
1997
- * @param api CloudflareApi instance
1998
- * @param workerName Name of the worker script
1999
- */
2000
- async function deleteQueueConsumers(
2001
- api: CloudflareApi,
2002
- workerName: string,
2003
- ): Promise<void> {
2004
- const consumers = await listQueueConsumersForWorker(api, workerName);
2005
-
2053
+ async function deleteQueueConsumers(api: CloudflareApi, scriptName: string) {
2054
+ const consumers = await listQueueConsumersForWorker(api, scriptName);
2006
2055
  await Promise.all(
2007
2056
  consumers.map(async (consumer) => {
2008
2057
  await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
2009
2058
  }),
2010
2059
  );
2011
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
+ }