alchemy 0.20.1 → 0.22.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 (323) hide show
  1. package/lib/ai/astro-file.d.ts +4 -4
  2. package/lib/ai/client.d.ts +1 -1
  3. package/lib/ai/css-file.d.ts +4 -4
  4. package/lib/ai/data.d.ts +4 -4
  5. package/lib/ai/document.d.ts +5 -5
  6. package/lib/ai/html-file.d.ts +4 -4
  7. package/lib/ai/index.d.ts +10 -10
  8. package/lib/ai/json-file.d.ts +4 -4
  9. package/lib/ai/typescript-file.d.ts +4 -4
  10. package/lib/ai/vue-file.d.ts +4 -4
  11. package/lib/ai/yaml-file.d.ts +4 -4
  12. package/lib/alchemy.d.ts +5 -5
  13. package/lib/apply.d.ts +2 -2
  14. package/lib/aws/bucket.d.ts +2 -2
  15. package/lib/aws/control/error.d.ts +1 -1
  16. package/lib/aws/control/index.d.ts +4 -4
  17. package/lib/aws/control/resource.d.ts +2 -2
  18. package/lib/aws/credentials.d.ts +1 -1
  19. package/lib/aws/function.d.ts +3 -3
  20. package/lib/aws/index.d.ts +9 -9
  21. package/lib/aws/oidc/github-oidc-provider.d.ts +1 -1
  22. package/lib/aws/oidc/index.d.ts +2 -2
  23. package/lib/aws/oidc/oidc-provider.d.ts +2 -2
  24. package/lib/aws/policy-attachment.d.ts +2 -2
  25. package/lib/aws/policy.d.ts +2 -2
  26. package/lib/aws/queue.d.ts +2 -2
  27. package/lib/aws/role.d.ts +3 -3
  28. package/lib/aws/ses.d.ts +2 -2
  29. package/lib/aws/table.d.ts +2 -2
  30. package/lib/cloudflare/account-api-token.d.ts +5 -5
  31. package/lib/cloudflare/account-id.d.ts +1 -1
  32. package/lib/cloudflare/ai-gateway.d.ts +3 -3
  33. package/lib/cloudflare/api.d.ts +2 -2
  34. package/lib/cloudflare/assets.d.ts +2 -2
  35. package/lib/cloudflare/auth.d.ts +1 -1
  36. package/lib/cloudflare/bindings.d.ts +18 -18
  37. package/lib/cloudflare/bound.d.ts +19 -16
  38. package/lib/cloudflare/bound.d.ts.map +1 -1
  39. package/lib/cloudflare/bucket.d.ts +4 -4
  40. package/lib/cloudflare/bundle/bundle-worker.d.ts +2 -2
  41. package/lib/cloudflare/custom-domain.d.ts +3 -3
  42. package/lib/cloudflare/custom-domain.js.map +1 -1
  43. package/lib/cloudflare/d1-clone.d.ts +3 -3
  44. package/lib/cloudflare/d1-database.d.ts +3 -3
  45. package/lib/cloudflare/d1-export.d.ts +1 -1
  46. package/lib/cloudflare/d1-import.d.ts +1 -1
  47. package/lib/cloudflare/d1-migrations.d.ts +1 -1
  48. package/lib/cloudflare/dns-records.d.ts +4 -4
  49. package/lib/cloudflare/durable-object-namespace.d.ts +1 -1
  50. package/lib/cloudflare/event-source.d.ts +2 -2
  51. package/lib/cloudflare/hyperdrive.d.ts +4 -4
  52. package/lib/cloudflare/index.d.ts +40 -39
  53. package/lib/cloudflare/index.d.ts.map +1 -1
  54. package/lib/cloudflare/index.js +1 -0
  55. package/lib/cloudflare/index.js.map +1 -1
  56. package/lib/cloudflare/kv-namespace.d.ts +3 -3
  57. package/lib/cloudflare/nuxt.d.ts +4 -4
  58. package/lib/cloudflare/nuxt.d.ts.map +1 -1
  59. package/lib/cloudflare/nuxt.js +2 -1
  60. package/lib/cloudflare/nuxt.js.map +1 -1
  61. package/lib/cloudflare/permission-groups.d.ts +3 -3
  62. package/lib/cloudflare/pipeline.d.ts +4 -4
  63. package/lib/cloudflare/queue-consumer.d.ts +4 -4
  64. package/lib/cloudflare/queue.d.ts +3 -3
  65. package/lib/cloudflare/r2-rest-state-store.d.ts +3 -3
  66. package/lib/cloudflare/react-router.d.ts +13 -0
  67. package/lib/cloudflare/react-router.d.ts.map +1 -0
  68. package/lib/cloudflare/react-router.js +29 -0
  69. package/lib/cloudflare/react-router.js.map +1 -0
  70. package/lib/cloudflare/redwood.d.ts +4 -4
  71. package/lib/cloudflare/route.d.ts +4 -4
  72. package/lib/cloudflare/tanstack-start.d.ts +4 -4
  73. package/lib/cloudflare/user.d.ts +2 -2
  74. package/lib/cloudflare/vectorize-index.d.ts +3 -3
  75. package/lib/cloudflare/vectorize-metadata-index.d.ts +4 -4
  76. package/lib/cloudflare/vite.d.ts +4 -4
  77. package/lib/cloudflare/website.d.ts +3 -3
  78. package/lib/cloudflare/website.d.ts.map +1 -1
  79. package/lib/cloudflare/website.js +30 -35
  80. package/lib/cloudflare/website.js.map +1 -1
  81. package/lib/cloudflare/worker-assets.d.ts +3 -3
  82. package/lib/cloudflare/worker-metadata.d.ts +6 -6
  83. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  84. package/lib/cloudflare/worker-metadata.js +110 -26
  85. package/lib/cloudflare/worker-metadata.js.map +1 -1
  86. package/lib/cloudflare/worker-stub.d.ts +3 -3
  87. package/lib/cloudflare/worker.d.ts +26 -17
  88. package/lib/cloudflare/worker.d.ts.map +1 -1
  89. package/lib/cloudflare/worker.js +21 -16
  90. package/lib/cloudflare/worker.js.map +1 -1
  91. package/lib/cloudflare/workflow.d.ts +3 -1
  92. package/lib/cloudflare/workflow.d.ts.map +1 -1
  93. package/lib/cloudflare/workflow.js +3 -0
  94. package/lib/cloudflare/workflow.js.map +1 -1
  95. package/lib/cloudflare/wrangler.json.d.ts +13 -2
  96. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  97. package/lib/cloudflare/wrangler.json.js +5 -0
  98. package/lib/cloudflare/wrangler.json.js.map +1 -1
  99. package/lib/cloudflare/zone.d.ts +4 -4
  100. package/lib/context.d.ts +3 -3
  101. package/lib/destroy.d.ts +2 -2
  102. package/lib/dns/import-dns.d.ts +3 -3
  103. package/lib/dns/index.d.ts +2 -2
  104. package/lib/esbuild/bundle.d.ts +2 -2
  105. package/lib/esbuild/index.d.ts +1 -1
  106. package/lib/fs/copy-file.d.ts +2 -2
  107. package/lib/fs/file-system-state-store.d.ts +2 -2
  108. package/lib/fs/file.d.ts +5 -5
  109. package/lib/fs/folder.d.ts +2 -2
  110. package/lib/fs/index.d.ts +14 -14
  111. package/lib/fs/static-astro-file.d.ts +1 -1
  112. package/lib/fs/static-css-file.d.ts +1 -1
  113. package/lib/fs/static-html-file.d.ts +1 -1
  114. package/lib/fs/static-json-file.d.ts +1 -1
  115. package/lib/fs/static-text-file.d.ts +1 -1
  116. package/lib/fs/static-typescript-file.d.ts +1 -1
  117. package/lib/fs/static-vue-file.d.ts +1 -1
  118. package/lib/fs/static-yaml-file.d.ts +1 -1
  119. package/lib/github/index.d.ts +2 -2
  120. package/lib/github/repository-environment.d.ts +2 -2
  121. package/lib/github/secret.d.ts +3 -3
  122. package/lib/index.d.ts +10 -9
  123. package/lib/index.d.ts.map +1 -1
  124. package/lib/index.js +1 -0
  125. package/lib/index.js.map +1 -1
  126. package/lib/internal/docs/providers.d.ts +2 -2
  127. package/lib/neon/api.d.ts +1 -1
  128. package/lib/neon/index.d.ts +3 -3
  129. package/lib/neon/project.d.ts +4 -4
  130. package/lib/os/exec.d.ts +2 -2
  131. package/lib/os/index.d.ts +1 -1
  132. package/lib/resource.d.ts +2 -2
  133. package/lib/runtime/bind.d.ts +3 -3
  134. package/lib/runtime/global.d.ts +1 -1
  135. package/lib/runtime/state.d.ts +1 -1
  136. package/lib/scope.d.ts +3 -3
  137. package/lib/sentry/api.d.ts +1 -1
  138. package/lib/sentry/client-key.d.ts +3 -3
  139. package/lib/sentry/index.d.ts +4 -4
  140. package/lib/sentry/project.d.ts +3 -3
  141. package/lib/sentry/team.d.ts +3 -3
  142. package/lib/serde.d.ts +3 -3
  143. package/lib/state.d.ts +2 -2
  144. package/lib/stripe/index.d.ts +4 -4
  145. package/lib/stripe/index.d.ts.map +1 -1
  146. package/lib/stripe/index.js +1 -1
  147. package/lib/stripe/index.js.map +1 -1
  148. package/lib/stripe/meter.d.ts +2 -2
  149. package/lib/stripe/price.d.ts +2 -2
  150. package/lib/stripe/product.d.ts +2 -2
  151. package/lib/stripe/webhook.d.ts +2 -2
  152. package/lib/test/bun.d.ts +3 -3
  153. package/lib/type.d.ts +6 -0
  154. package/lib/type.d.ts.map +1 -0
  155. package/lib/type.js +7 -0
  156. package/lib/type.js.map +1 -0
  157. package/lib/upstash/api.d.ts +1 -1
  158. package/lib/upstash/index.d.ts +3 -3
  159. package/lib/upstash/redis.d.ts +4 -4
  160. package/lib/vercel/api.d.ts +1 -1
  161. package/lib/vercel/index.d.ts +3 -3
  162. package/lib/vercel/project-domain.d.ts +3 -3
  163. package/lib/vercel/project.d.ts +4 -4
  164. package/lib/web/astro.d.ts +2 -2
  165. package/lib/web/shadcn-component.d.ts +2 -2
  166. package/lib/web/shadcn.d.ts +3 -3
  167. package/lib/web/tailwind.d.ts +2 -2
  168. package/lib/web/vite.d.ts +2 -2
  169. package/lib/web/vitepress/config.d.ts +1 -1
  170. package/lib/web/vitepress/custom-theme.d.ts +4 -4
  171. package/lib/web/vitepress/dependencies.d.ts +2 -2
  172. package/lib/web/vitepress/home-page.d.ts +3 -3
  173. package/lib/web/vitepress/index.d.ts +5 -5
  174. package/lib/web/vitepress/vitepress.d.ts +2 -2
  175. package/package.json +7 -7
  176. package/src/ai/astro-file.ts +5 -5
  177. package/src/ai/client.ts +1 -1
  178. package/src/ai/css-file.ts +5 -5
  179. package/src/ai/data.ts +5 -5
  180. package/src/ai/document.ts +5 -5
  181. package/src/ai/html-file.ts +5 -5
  182. package/src/ai/index.ts +10 -10
  183. package/src/ai/json-file.ts +6 -6
  184. package/src/ai/typescript-file.ts +5 -5
  185. package/src/ai/vue-file.ts +5 -5
  186. package/src/ai/yaml-file.ts +6 -6
  187. package/src/alchemy.ts +9 -9
  188. package/src/apply.ts +6 -6
  189. package/src/aws/bucket.ts +3 -3
  190. package/src/aws/control/client.ts +1 -1
  191. package/src/aws/control/error.ts +1 -1
  192. package/src/aws/control/index.ts +4 -4
  193. package/src/aws/control/proxy.ts +1 -1
  194. package/src/aws/control/resource.ts +5 -5
  195. package/src/aws/credentials.ts +1 -1
  196. package/src/aws/function.ts +4 -4
  197. package/src/aws/index.ts +9 -9
  198. package/src/aws/oidc/github-oidc-provider.ts +1 -1
  199. package/src/aws/oidc/index.ts +2 -2
  200. package/src/aws/oidc/oidc-provider.ts +3 -3
  201. package/src/aws/policy-attachment.ts +3 -3
  202. package/src/aws/policy.ts +2 -2
  203. package/src/aws/queue.ts +2 -2
  204. package/src/aws/role.ts +4 -4
  205. package/src/aws/ses.ts +3 -3
  206. package/src/aws/table.ts +4 -4
  207. package/src/cloudflare/account-api-token.ts +7 -7
  208. package/src/cloudflare/account-id.ts +1 -1
  209. package/src/cloudflare/ai-gateway.ts +6 -6
  210. package/src/cloudflare/api.ts +5 -5
  211. package/src/cloudflare/assets.ts +3 -3
  212. package/src/cloudflare/auth.ts +1 -1
  213. package/src/cloudflare/bindings.ts +18 -18
  214. package/src/cloudflare/bound.ts +60 -50
  215. package/src/cloudflare/bucket.ts +7 -7
  216. package/src/cloudflare/bundle/bundle-worker.ts +8 -8
  217. package/src/cloudflare/bundle/local-dev-cloudflare-shim.ts +1 -1
  218. package/src/cloudflare/bundle/nodejs-compat.ts +1 -1
  219. package/src/cloudflare/custom-domain.ts +5 -5
  220. package/src/cloudflare/d1-clone.ts +3 -3
  221. package/src/cloudflare/d1-database.ts +7 -7
  222. package/src/cloudflare/d1-export.ts +1 -1
  223. package/src/cloudflare/d1-import.ts +1 -1
  224. package/src/cloudflare/d1-migrations.ts +2 -2
  225. package/src/cloudflare/dns-records.ts +5 -5
  226. package/src/cloudflare/durable-object-namespace.ts +1 -1
  227. package/src/cloudflare/event-source.ts +2 -2
  228. package/src/cloudflare/hyperdrive.ts +7 -7
  229. package/src/cloudflare/index.ts +40 -39
  230. package/src/cloudflare/kv-namespace.ts +7 -7
  231. package/src/cloudflare/nuxt.ts +6 -5
  232. package/src/cloudflare/permission-groups.ts +3 -3
  233. package/src/cloudflare/pipeline.ts +7 -7
  234. package/src/cloudflare/queue-consumer.ts +5 -5
  235. package/src/cloudflare/queue.ts +6 -6
  236. package/src/cloudflare/r2-rest-state-store.ts +8 -8
  237. package/src/cloudflare/react-router.ts +45 -0
  238. package/src/cloudflare/redwood.ts +4 -4
  239. package/src/cloudflare/route.ts +5 -5
  240. package/src/cloudflare/tanstack-start.ts +4 -4
  241. package/src/cloudflare/user.ts +3 -3
  242. package/src/cloudflare/vectorize-index.ts +6 -6
  243. package/src/cloudflare/vectorize-metadata-index.ts +5 -5
  244. package/src/cloudflare/vite.ts +4 -4
  245. package/src/cloudflare/website.ts +42 -48
  246. package/src/cloudflare/worker-assets.ts +4 -4
  247. package/src/cloudflare/worker-metadata.ts +134 -43
  248. package/src/cloudflare/worker-stub.ts +4 -4
  249. package/src/cloudflare/worker.ts +169 -124
  250. package/src/cloudflare/workflow.ts +7 -2
  251. package/src/cloudflare/wrangler.json.ts +21 -2
  252. package/src/cloudflare/zone.ts +5 -5
  253. package/src/context.ts +4 -4
  254. package/src/destroy.ts +4 -4
  255. package/src/dns/import-dns.ts +3 -3
  256. package/src/dns/index.ts +2 -2
  257. package/src/esbuild/bundle.ts +2 -2
  258. package/src/esbuild/index.ts +1 -1
  259. package/src/fs/copy-file.ts +3 -3
  260. package/src/fs/file-system-state-store.ts +5 -5
  261. package/src/fs/file.ts +7 -7
  262. package/src/fs/folder.ts +3 -3
  263. package/src/fs/index.ts +14 -14
  264. package/src/fs/static-astro-file.ts +1 -1
  265. package/src/fs/static-css-file.ts +1 -1
  266. package/src/fs/static-html-file.ts +1 -1
  267. package/src/fs/static-json-file.ts +1 -1
  268. package/src/fs/static-text-file.ts +1 -1
  269. package/src/fs/static-typescript-file.ts +1 -1
  270. package/src/fs/static-vue-file.ts +1 -1
  271. package/src/fs/static-yaml-file.ts +1 -1
  272. package/src/github/index.ts +2 -2
  273. package/src/github/repository-environment.ts +3 -3
  274. package/src/github/secret.ts +4 -4
  275. package/src/index.ts +10 -9
  276. package/src/internal/docs/providers.ts +4 -4
  277. package/src/neon/api.ts +2 -2
  278. package/src/neon/index.ts +3 -3
  279. package/src/neon/project.ts +6 -6
  280. package/src/os/exec.ts +2 -2
  281. package/src/os/index.ts +1 -1
  282. package/src/resource.ts +3 -3
  283. package/src/runtime/bind.ts +7 -7
  284. package/src/runtime/global.ts +1 -1
  285. package/src/runtime/state.ts +1 -1
  286. package/src/scope.ts +5 -5
  287. package/src/secret.ts +1 -1
  288. package/src/sentry/api.ts +2 -2
  289. package/src/sentry/client-key.ts +4 -4
  290. package/src/sentry/index.ts +4 -4
  291. package/src/sentry/project.ts +4 -4
  292. package/src/sentry/team.ts +4 -4
  293. package/src/serde.ts +4 -4
  294. package/src/state.ts +3 -3
  295. package/src/stripe/index.ts +4 -4
  296. package/src/stripe/meter.ts +2 -2
  297. package/src/stripe/price.ts +2 -2
  298. package/src/stripe/product.ts +2 -2
  299. package/src/stripe/webhook.ts +2 -2
  300. package/src/test/bun.ts +5 -5
  301. package/src/type.ts +9 -0
  302. package/src/upstash/api.ts +1 -1
  303. package/src/upstash/index.ts +3 -3
  304. package/src/upstash/redis.ts +6 -6
  305. package/src/vercel/api.ts +2 -2
  306. package/src/vercel/index.ts +3 -3
  307. package/src/vercel/project-domain.ts +4 -4
  308. package/src/vercel/project.ts +4 -4
  309. package/src/web/astro.ts +7 -7
  310. package/src/web/shadcn-component.ts +3 -3
  311. package/src/web/shadcn.ts +6 -6
  312. package/src/web/tailwind.ts +4 -4
  313. package/src/web/vite.ts +8 -8
  314. package/src/web/vitepress/config.ts +1 -1
  315. package/src/web/vitepress/custom-theme.ts +10 -10
  316. package/src/web/vitepress/dependencies.ts +2 -2
  317. package/src/web/vitepress/home-page.ts +4 -4
  318. package/src/web/vitepress/index.ts +5 -5
  319. package/src/web/vitepress/vitepress.ts +7 -7
  320. package/lib/runtime/shims.d.ts +0 -6
  321. package/lib/runtime/shims.d.ts.map +0 -1
  322. package/lib/runtime/shims.js +0 -27
  323. package/lib/runtime/shims.js.map +0 -1
@@ -1,11 +1,18 @@
1
- import type { Context } from "../context.js";
2
- import { slugify } from "../util/slugify.js";
3
- import { Self, type Bindings, type WorkerBindingSpec } from "./bindings.js";
4
- import type { DurableObjectNamespace } from "./durable-object-namespace.js";
5
- import { createAssetConfig, type AssetUploadResult } from "./worker-assets.js";
6
- import type { SingleStepMigration } from "./worker-migration.js";
7
- import type { AssetsConfig, Worker, WorkerProps } from "./worker.js";
8
- import type { Workflow } from "./workflow.js";
1
+ import type { Context } from "../context.ts";
2
+ import { slugify } from "../util/slugify.ts";
3
+ import {
4
+ Self,
5
+ type Bindings,
6
+ type WorkerBindingDurableObjectNamespace,
7
+ type WorkerBindingSpec,
8
+ } from "./bindings.ts";
9
+ import {
10
+ isDurableObjectNamespace,
11
+ type DurableObjectNamespace,
12
+ } from "./durable-object-namespace.ts";
13
+ import { createAssetConfig, type AssetUploadResult } from "./worker-assets.ts";
14
+ import type { SingleStepMigration } from "./worker-migration.ts";
15
+ import type { AssetsConfig, Worker, WorkerProps } from "./worker.ts";
9
16
 
10
17
  /**
11
18
  * Metadata returned by Cloudflare API for a worker script
@@ -186,7 +193,8 @@ export interface WorkerMetadata {
186
193
 
187
194
  export async function prepareWorkerMetadata<B extends Bindings>(
188
195
  ctx: Context<Worker<B>>,
189
- oldBindings: Bindings | undefined,
196
+ oldBindings: WorkerBindingSpec[] | undefined,
197
+ oldTags: string[] | undefined,
190
198
  props: WorkerProps & {
191
199
  compatibilityDate: string;
192
200
  compatibilityFlags: string[];
@@ -194,20 +202,75 @@ export async function prepareWorkerMetadata<B extends Bindings>(
194
202
  },
195
203
  assetUploadResult?: AssetUploadResult,
196
204
  ): Promise<WorkerMetadata> {
197
- const deletedClasses = Object.entries(oldBindings ?? {})
198
- .filter(([key]) => !props.bindings?.[key])
199
- .flatMap(([_, binding]) => {
200
- if (
201
- binding &&
202
- typeof binding === "object" &&
203
- binding.type === "durable_object_namespace" &&
204
- (binding.scriptName === undefined ||
205
- binding.scriptName === props.workerName)
206
- ) {
207
- return [binding.className];
205
+ // we use Cloudflare Worker tags to store a mapping between Alchemy's stable identifier and the binding name
206
+ // e.g.
207
+ // {
208
+ // BINDING_NAME: new DurableObjectNamespace("stable-id")
209
+ // }
210
+ // will be stored as alchemy:do:stable-id:BINDING_NAME
211
+ // TODO(sam): should we base64 encode to ensure no `:` collision risk?
212
+ const bindingNameToStableId = Object.fromEntries(
213
+ oldTags?.flatMap((tag) => {
214
+ // alchemy:do:{stableId}:{bindingName}
215
+ if (tag.startsWith("alchemy:do:")) {
216
+ const [, , stableId, bindingName] = tag.split(":");
217
+ return [[bindingName, stableId]];
208
218
  }
209
219
  return [];
210
- });
220
+ }) ?? [],
221
+ );
222
+
223
+ const deletedClasses = oldBindings?.flatMap((oldBinding) => {
224
+ if (
225
+ oldBinding.type === "durable_object_namespace" &&
226
+ (oldBinding.script_name === undefined ||
227
+ // if this a cross-script binding, we don't need to do migrations in the remote worker
228
+ oldBinding.script_name === props.workerName)
229
+ ) {
230
+ // reverse the stableId from our tag-encoded metadata
231
+ const stableId = bindingNameToStableId[oldBinding.name];
232
+ if (stableId) {
233
+ if (props.bindings === undefined) {
234
+ // all classes are deleted
235
+ return [oldBinding.class_name];
236
+ }
237
+ // we created this worker on latest version, we can now intelligently determine migrations
238
+
239
+ // try and find the DO binding by stable id
240
+ const object = Object.values(props.bindings).find(
241
+ (binding): binding is DurableObjectNamespace<any> =>
242
+ isDurableObjectNamespace(binding) && binding.id === stableId,
243
+ );
244
+ if (object) {
245
+ // we found the corresponding object, it should not be deleted
246
+ return [];
247
+ } else {
248
+ // it was not found, we will now delete it
249
+ return [oldBinding.class_name];
250
+ }
251
+ } else {
252
+ // ok, we were unable to find the stableId, this worker must have been created by an old alchemy or outside of alchemy
253
+ // let's now apply a herusitic based on binding name (assume binding name is consistent)
254
+ // TODO(sam): this has a chance of being wrong, is that OK? Users should be encouraged to upgrade alchemy version and re-deploy
255
+ const object = props.bindings?.[oldBinding.name];
256
+ if (object && isDurableObjectNamespace(object)) {
257
+ if (object.className === oldBinding.class_name) {
258
+ // this is relatively safe to assume is the right match, do not delete
259
+ return [];
260
+ } else {
261
+ // the class name has changed, this could indicate one of:
262
+ // 1. the user has changed the class name and we should migrate it
263
+ // 2. the user deleted the DO a long time ago and this is unrelated (we should just create a new one)
264
+ return [oldBinding.class_name];
265
+ }
266
+ } else {
267
+ // we didn't find it, so delete it
268
+ return [oldBinding.class_name];
269
+ }
270
+ }
271
+ }
272
+ return [];
273
+ });
211
274
 
212
275
  // Prepare metadata with bindings
213
276
  const meta: WorkerMetadata = {
@@ -218,11 +281,22 @@ export async function prepareWorkerMetadata<B extends Bindings>(
218
281
  enabled: props.observability?.enabled !== false,
219
282
  },
220
283
  // TODO(sam): base64 encode instead? 0 collision risk vs readability.
221
- tags: [`alchemy:id:${slugify(ctx.fqn)}`],
284
+ tags: [
285
+ `alchemy:id:${slugify(ctx.fqn)}`,
286
+ // encode a mapping table of Durable Object stable ID -> binding name
287
+ // we use this to reliably compute class migrations based on server-side state
288
+ ...Object.entries(props.bindings ?? {}).flatMap(
289
+ ([bindingName, binding]) =>
290
+ isDurableObjectNamespace(binding)
291
+ ? // TODO(sam): base64 encode if contains `:`?
292
+ [`alchemy:do:${binding.id}:${bindingName}`]
293
+ : [],
294
+ ),
295
+ ],
222
296
  migrations: {
223
297
  new_classes: props.migrations?.new_classes ?? [],
224
298
  deleted_classes: [
225
- ...deletedClasses,
299
+ ...(deletedClasses ?? []),
226
300
  ...(props.migrations?.deleted_classes ?? []),
227
301
  ],
228
302
  renamed_classes: props.migrations?.renamed_classes ?? [],
@@ -303,7 +377,7 @@ export async function prepareWorkerMetadata<B extends Bindings>(
303
377
  binding.scriptName === props.workerName
304
378
  ) {
305
379
  // we do not need configure class migrations for cross-script bindings
306
- configureClassMigration(binding, binding.id, binding.className);
380
+ configureClassMigration(bindingName, binding);
307
381
  }
308
382
  } else if (binding.type === "r2_bucket") {
309
383
  meta.bindings.push({
@@ -393,29 +467,46 @@ export async function prepareWorkerMetadata<B extends Bindings>(
393
467
  }
394
468
 
395
469
  function configureClassMigration(
396
- binding: DurableObjectNamespace<any> | Workflow,
397
- stableId: string,
398
- className: string,
470
+ bindingName: string,
471
+ newBinding: DurableObjectNamespace<any>,
399
472
  ) {
400
- const oldBinding: DurableObjectNamespace<any> | Workflow | undefined =
401
- Object.values(oldBindings ?? {})
402
- ?.filter(
403
- (b) =>
404
- typeof b === "object" &&
405
- (b.type === "durable_object_namespace" || b.type === "workflow"),
406
- )
407
- ?.find((b) => b.id === stableId);
408
-
409
- if (!oldBinding) {
410
- if (binding.type === "durable_object_namespace" && binding.sqlite) {
411
- meta.migrations!.new_sqlite_classes!.push(className);
473
+ let prevBinding: WorkerBindingDurableObjectNamespace | undefined;
474
+ if (oldBindings) {
475
+ // try and find the prev binding for this
476
+ for (const oldBinding of oldBindings) {
477
+ if (oldBinding.type === "durable_object_namespace") {
478
+ const stableId = bindingNameToStableId[oldBinding.name];
479
+ if (stableId) {
480
+ // (happy case)
481
+ // great, this Worker was created with Alchemy and we can map stable ids
482
+ if (stableId === newBinding.id) {
483
+ prevBinding = oldBinding;
484
+ break;
485
+ }
486
+ } else {
487
+ // (heuristic case)
488
+ // we were unable to find the stableId, this Worker must not have been created with Alchemy
489
+ // now, try and resolve by assuming 1:1 binding name correspondence
490
+ // WARNING: this is an imperfect assumption. Users are advised to upgrade alchemy and re-deploy
491
+ if (oldBinding.name === bindingName) {
492
+ prevBinding = oldBinding;
493
+ break;
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ if (!prevBinding) {
501
+ if (newBinding.sqlite) {
502
+ meta.migrations!.new_sqlite_classes!.push(newBinding.className);
412
503
  } else {
413
- meta.migrations!.new_classes!.push(className);
504
+ meta.migrations!.new_classes!.push(newBinding.className);
414
505
  }
415
- } else if (oldBinding.className !== className) {
506
+ } else if (prevBinding.class_name !== newBinding.className) {
416
507
  meta.migrations!.renamed_classes!.push({
417
- from: oldBinding.className,
418
- to: className,
508
+ from: prevBinding.class_name,
509
+ to: newBinding.className,
419
510
  });
420
511
  }
421
512
  }
@@ -1,11 +1,11 @@
1
- import type { Context } from "../context.js";
2
- import { Resource, ResourceKind } from "../resource.js";
3
- import { handleApiError } from "./api-error.js";
1
+ import type { Context } from "../context.ts";
2
+ import { Resource, ResourceKind } from "../resource.ts";
3
+ import { handleApiError } from "./api-error.ts";
4
4
  import {
5
5
  createCloudflareApi,
6
6
  type CloudflareApi,
7
7
  type CloudflareApiOptions,
8
- } from "./api.js";
8
+ } from "./api.ts";
9
9
 
10
10
  /**
11
11
  * Properties for creating a Worker stub
@@ -1,54 +1,60 @@
1
1
  import path from "node:path";
2
- import type { Context } from "../context.js";
3
- import type { BundleProps } from "../esbuild/bundle.js";
4
- import { InnerResourceScope, Resource, ResourceKind } from "../resource.js";
5
- import { getBindKey, tryGetBinding } from "../runtime/bind.js";
6
- import { isRuntime } from "../runtime/global.js";
7
- import { bootstrapPlugin } from "../runtime/plugin.js";
8
- import { Scope } from "../scope.js";
9
- import { Secret, secret } from "../secret.js";
10
- import { serializeScope } from "../serde.js";
11
- import { withExponentialBackoff } from "../util/retry.js";
12
- import { slugify } from "../util/slugify.js";
13
- import { CloudflareApiError, handleApiError } from "./api-error.js";
2
+ import type { Context } from "../context.ts";
3
+ import type { BundleProps } from "../esbuild/bundle.ts";
4
+ import { InnerResourceScope, Resource, ResourceKind } from "../resource.ts";
5
+ import { getBindKey, tryGetBinding } from "../runtime/bind.ts";
6
+ import { isRuntime } from "../runtime/global.ts";
7
+ import { bootstrapPlugin } from "../runtime/plugin.ts";
8
+ import { Scope } from "../scope.ts";
9
+ import { Secret, secret } from "../secret.ts";
10
+ import { serializeScope } from "../serde.ts";
11
+ import type { type } from "../type.ts";
12
+ import { withExponentialBackoff } from "../util/retry.ts";
13
+ import { slugify } from "../util/slugify.ts";
14
+ import { CloudflareApiError, handleApiError } from "./api-error.ts";
14
15
  import {
15
16
  type CloudflareApi,
16
17
  type CloudflareApiOptions,
17
18
  createCloudflareApi,
18
- } from "./api.js";
19
- import type { Assets } from "./assets.js";
20
- import { type Binding, type Bindings, Json } from "./bindings.js";
21
- import type { Bound } from "./bound.js";
22
- import { isBucket } from "./bucket.js";
23
- import { bundleWorkerScript } from "./bundle/bundle-worker.js";
24
- import { isD1Database } from "./d1-database.js";
19
+ } from "./api.ts";
20
+ import type { Assets } from "./assets.ts";
21
+ import {
22
+ type Binding,
23
+ type Bindings,
24
+ Json,
25
+ type WorkerBindingSpec,
26
+ } from "./bindings.ts";
27
+ import type { Bound } from "./bound.ts";
28
+ import { isBucket } from "./bucket.ts";
29
+ import { bundleWorkerScript } from "./bundle/bundle-worker.ts";
30
+ import { isD1Database } from "./d1-database.ts";
25
31
  import {
26
32
  DurableObjectNamespace,
27
33
  isDurableObjectNamespace,
28
- } from "./durable-object-namespace.js";
34
+ } from "./durable-object-namespace.ts";
29
35
  import {
30
36
  type EventSource,
31
37
  type QueueEventSource,
32
38
  isQueueEventSource,
33
- } from "./event-source.js";
34
- import { isKVNamespace } from "./kv-namespace.js";
35
- import { isPipeline } from "./pipeline.js";
39
+ } from "./event-source.ts";
40
+ import { isKVNamespace } from "./kv-namespace.ts";
41
+ import { isPipeline } from "./pipeline.ts";
36
42
  import {
37
43
  QueueConsumer,
38
44
  deleteQueueConsumer,
39
45
  listQueueConsumers,
40
- } from "./queue-consumer.js";
41
- import { type QueueResource, isQueue } from "./queue.js";
42
- import { isVectorizeIndex } from "./vectorize-index.js";
43
- import { type AssetUploadResult, uploadAssets } from "./worker-assets.js";
46
+ } from "./queue-consumer.ts";
47
+ import { type QueueResource, isQueue } from "./queue.ts";
48
+ import { isVectorizeIndex } from "./vectorize-index.ts";
49
+ import { type AssetUploadResult, uploadAssets } from "./worker-assets.ts";
44
50
  import {
45
51
  type WorkerMetadata,
46
52
  type WorkerScriptMetadata,
47
53
  prepareWorkerMetadata,
48
- } from "./worker-metadata.js";
49
- import type { SingleStepMigration } from "./worker-migration.js";
50
- import { WorkerStub, isWorkerStub } from "./worker-stub.js";
51
- import { type Workflow, upsertWorkflow } from "./workflow.js";
54
+ } from "./worker-metadata.ts";
55
+ import type { SingleStepMigration } from "./worker-migration.ts";
56
+ import { WorkerStub, isWorkerStub } from "./worker-stub.ts";
57
+ import { type Workflow, upsertWorkflow } from "./workflow.ts";
52
58
 
53
59
  /**
54
60
  * Configuration options for static assets
@@ -94,8 +100,10 @@ export interface AssetsConfig {
94
100
  serve_directly?: boolean;
95
101
  }
96
102
 
97
- export interface BaseWorkerProps<B extends Bindings | undefined = undefined>
98
- extends CloudflareApiOptions {
103
+ export interface BaseWorkerProps<
104
+ B extends Bindings | undefined = undefined,
105
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
106
+ > extends CloudflareApiOptions {
99
107
  /**
100
108
  * Bundle options when using entryPoint
101
109
  *
@@ -202,17 +210,27 @@ export interface BaseWorkerProps<B extends Bindings | undefined = undefined>
202
210
  * Can include queues, streams, or other event sources.
203
211
  */
204
212
  eventSources?: EventSource[];
213
+
214
+ /**
215
+ * The RPC class to use for the worker.
216
+ *
217
+ * This is only used when using the rpc property.
218
+ */
219
+ rpc?: (new (...args: any[]) => RPC) | type<RPC>;
205
220
  }
206
221
 
207
- export interface InlineWorkerProps<B extends Bindings | undefined = Bindings>
208
- extends BaseWorkerProps<B> {
222
+ export interface InlineWorkerProps<
223
+ B extends Bindings | undefined = Bindings,
224
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
225
+ > extends BaseWorkerProps<B, RPC> {
209
226
  script: string;
210
227
  entrypoint?: undefined;
211
228
  }
212
229
 
213
230
  export interface EntrypointWorkerProps<
214
231
  B extends Bindings | undefined = Bindings,
215
- > extends BaseWorkerProps<B> {
232
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
233
+ > extends BaseWorkerProps<B, RPC> {
216
234
  entrypoint: string;
217
235
  script?: undefined;
218
236
  }
@@ -220,9 +238,10 @@ export interface EntrypointWorkerProps<
220
238
  /**
221
239
  * Properties for creating or updating a Worker
222
240
  */
223
- export type WorkerProps<B extends Bindings | undefined = Bindings> =
224
- | InlineWorkerProps<B>
225
- | EntrypointWorkerProps<B>;
241
+ export type WorkerProps<
242
+ B extends Bindings | undefined = Bindings,
243
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
244
+ > = InlineWorkerProps<B, RPC> | EntrypointWorkerProps<B, RPC>;
226
245
 
227
246
  export type FetchWorkerProps<
228
247
  B extends Bindings = Bindings,
@@ -270,65 +289,70 @@ export function isWorker(resource: Resource): resource is Worker<any> {
270
289
  /**
271
290
  * Output returned after Worker creation/update
272
291
  */
273
- export type Worker<B extends Bindings | undefined = Bindings | undefined> =
274
- Resource<"cloudflare::Worker"> &
275
- Omit<WorkerProps<B>, "url" | "script"> &
276
- globalThis.Service & {
277
- type: "service";
278
-
279
- /**
280
- * The ID of the worker
281
- */
282
- id: string;
283
-
284
- /**
285
- * The name of the worker
286
- */
287
- name: string;
288
-
289
- /**
290
- * Time at which the worker was created
291
- */
292
- createdAt: number;
293
-
294
- /**
295
- * Time at which the worker was last updated
296
- */
297
- updatedAt: number;
298
-
299
- /**
300
- * The worker's URL if enabled
301
- * Format: {name}.{subdomain}.workers.dev
302
- */
303
- url?: string;
304
-
305
- /**
306
- * The bindings that were created
307
- */
308
- bindings: B;
309
-
310
- /**
311
- * Configuration for static assets
312
- */
313
- assets?: AssetsConfig;
314
-
315
- // phantom property (for typeof myWorker.Env)
316
- Env: B extends Bindings
317
- ? {
318
- [bindingName in keyof B]: Bound<B[bindingName]>;
319
- }
320
- : undefined;
292
+ export type Worker<
293
+ B extends Bindings | undefined = Bindings | undefined,
294
+ RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
295
+ > = Resource<"cloudflare::Worker"> &
296
+ Omit<WorkerProps<B>, "url" | "script"> &
297
+ globalThis.Service & {
298
+ /** @internal phantom property */
299
+ __rpc__?: RPC;
321
300
 
322
- /**
323
- * The compatibility date for the worker
324
- */
325
- compatibilityDate: string;
301
+ type: "service";
326
302
 
327
- /**
328
- * The compatibility flags for the worker
329
- */
330
- compatibilityFlags: string[];
331
- };
303
+ /**
304
+ * The ID of the worker
305
+ */
306
+ id: string;
307
+
308
+ /**
309
+ * The name of the worker
310
+ */
311
+ name: string;
312
+
313
+ /**
314
+ * Time at which the worker was created
315
+ */
316
+ createdAt: number;
317
+
318
+ /**
319
+ * Time at which the worker was last updated
320
+ */
321
+ updatedAt: number;
322
+
323
+ /**
324
+ * The worker's URL if enabled
325
+ * Format: {name}.{subdomain}.workers.dev
326
+ */
327
+ url?: string;
328
+
329
+ /**
330
+ * The bindings that were created
331
+ */
332
+ bindings: B;
333
+
334
+ /**
335
+ * Configuration for static assets
336
+ */
337
+ assets?: AssetsConfig;
338
+
339
+ // phantom property (for typeof myWorker.Env)
340
+ Env: B extends Bindings
341
+ ? {
342
+ [bindingName in keyof B]: Bound<B[bindingName]>;
343
+ }
344
+ : undefined;
345
+
346
+ /**
347
+ * The compatibility date for the worker
348
+ */
349
+ compatibilityDate: string;
350
+
351
+ /**
352
+ * The compatibility flags for the worker
353
+ */
354
+ compatibilityFlags: string[];
355
+ };
332
356
 
333
357
  /**
334
358
  * A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
@@ -445,10 +469,10 @@ export type Worker<B extends Bindings | undefined = Bindings | undefined> =
445
469
  * @see
446
470
  * https://developers.cloudflare.com/workers/
447
471
  */
448
- export function Worker<const B extends Bindings>(
449
- id: string,
450
- props: WorkerProps<B>,
451
- ): Promise<Worker<B>>;
472
+ export function Worker<
473
+ const B extends Bindings,
474
+ RPC extends Rpc.WorkerEntrypointBranded,
475
+ >(id: string, props: WorkerProps<B, RPC>): Promise<Worker<B, RPC>>;
452
476
  export function Worker<const B extends Bindings, const E extends EventSource[]>(
453
477
  id: string,
454
478
  meta: ImportMeta,
@@ -461,6 +485,7 @@ export function Worker<const B extends Bindings>(
461
485
  ): Promise<Worker<B>> {
462
486
  const [id, meta, props] =
463
487
  args.length === 2 ? [args[0], undefined, args[1]] : args;
488
+
464
489
  if (("fetch" in props && props.fetch) || ("queue" in props && props.queue)) {
465
490
  const scope = Scope.current;
466
491
 
@@ -658,6 +683,8 @@ export function Worker<const B extends Bindings>(
658
683
  });
659
684
  }
660
685
 
686
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-04-20";
687
+
661
688
  export const _Worker = Resource(
662
689
  "cloudflare::Worker",
663
690
  {
@@ -674,11 +701,16 @@ export const _Worker = Resource(
674
701
  // Use the provided name
675
702
  const workerName = props.name ?? id;
676
703
 
677
- const compatibilityDate = props.compatibilityDate ?? "2025-04-20";
704
+ const compatibilityDate =
705
+ props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
678
706
  const compatibilityFlags = props.compatibilityFlags ?? [];
679
707
 
680
708
  const uploadWorkerScript = async (props: WorkerProps<B>) => {
681
- const oldBindings = await this.get<Bindings>("bindings");
709
+ const [oldBindings, oldMetadata] = await Promise.all([
710
+ getWorkerBindings(api, workerName),
711
+ getWorkerScriptMetadata(api, workerName),
712
+ ]);
713
+ const oldTags = oldMetadata?.default_environment?.script?.tags;
682
714
 
683
715
  // Get the script content - either from props.script, or by bundling
684
716
  const scriptContent =
@@ -725,6 +757,7 @@ export const _Worker = Resource(
725
757
  const scriptMetadata = await prepareWorkerMetadata(
726
758
  this,
727
759
  oldBindings,
760
+ oldTags,
728
761
  {
729
762
  ...props,
730
763
  compatibilityDate,
@@ -736,9 +769,6 @@ export const _Worker = Resource(
736
769
 
737
770
  await putWorker(api, workerName, scriptContent, scriptMetadata);
738
771
 
739
- // TODO: it is less than ideal that this can fail, resulting in state problem
740
- await this.set("bindings", props.bindings);
741
-
742
772
  for (const workflow of workflowsBindings) {
743
773
  await upsertWorkflow(api, {
744
774
  workflowName: workflow.workflowName,
@@ -1106,34 +1136,49 @@ export async function getWorkerScriptMetadata(
1106
1136
  return ((await response.json()) as any).result as WorkerScriptMetadata;
1107
1137
  }
1108
1138
 
1109
- export async function _getWorkerBindings(
1110
- api: CloudflareApi,
1111
- workerName: string,
1112
- environment = "production",
1113
- ) {
1139
+ async function getWorkerBindings(api: CloudflareApi, workerName: string) {
1140
+ // Fetch the bindings for a worker by calling the Cloudflare API endpoint:
1141
+ // GET /accounts/:account_id/workers/scripts/:script_name/bindings
1142
+ // See: https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/script_and_version_settings/methods/get/
1114
1143
  const response = await api.get(
1115
- `/accounts/${api.accountId}/workers/services/${workerName}/environments/${environment}/bindings`,
1116
- {
1117
- headers: {
1118
- Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
1119
- "Content-Type": "application/json",
1120
- },
1121
- },
1144
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
1122
1145
  );
1123
-
1124
1146
  if (response.status === 404) {
1125
1147
  return undefined;
1126
1148
  }
1127
-
1128
1149
  if (!response.ok) {
1129
1150
  throw new Error(
1130
- `Failed to fetch bindings: ${response.status} ${response.statusText}`,
1151
+ `Error getting worker bindings: ${response.status} ${response.statusText}`,
1131
1152
  );
1132
1153
  }
1133
-
1134
- const data: any = await response.json();
1135
-
1136
- return data.result;
1154
+ // The result is an object with a "result" property containing the bindings array
1155
+ const { result, success, errors } = (await response.json()) as {
1156
+ result: {
1157
+ bindings: WorkerBindingSpec[];
1158
+ compatibility_date: string;
1159
+ compatibility_flags: string[];
1160
+ [key: string]: any;
1161
+ };
1162
+ success: boolean;
1163
+ errors: Array<{
1164
+ code: number;
1165
+ message: string;
1166
+ documentation_url: string;
1167
+ [key: string]: any;
1168
+ }>;
1169
+ messages: Array<{
1170
+ code: number;
1171
+ message: string;
1172
+ documentation_url: string;
1173
+ [key: string]: any;
1174
+ }>;
1175
+ };
1176
+ if (!success) {
1177
+ throw new Error(
1178
+ `Error getting worker bindings: ${response.status} ${response.statusText}\nErrors:\n${errors.map((e) => `- [${e.code}] ${e.message} (${e.documentation_url})`).join("\n")}`,
1179
+ );
1180
+ }
1181
+ return result.bindings;
1137
1182
  }
1138
1183
 
1139
1184
  /**
@@ -1,5 +1,6 @@
1
- import { handleApiError } from "./api-error.js";
2
- import type { CloudflareApi } from "./api.js";
1
+ import { handleApiError } from "./api-error.ts";
2
+ import type { CloudflareApi } from "./api.ts";
3
+ import type { Binding } from "./bindings.ts";
3
4
 
4
5
  export interface WorkflowProps {
5
6
  /**
@@ -20,6 +21,10 @@ export interface WorkflowProps {
20
21
  className?: string;
21
22
  }
22
23
 
24
+ export function isWorkflow(binding: Binding): binding is Workflow {
25
+ return typeof binding === "object" && binding.type === "workflow";
26
+ }
27
+
23
28
  export class Workflow<PARAMS = unknown> {
24
29
  public readonly type = "workflow";
25
30
  /**