alchemy 0.69.0 → 0.70.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 (314) hide show
  1. package/bin/alchemy.js +3810 -3889
  2. package/bin/commands/init.ts +41 -55
  3. package/bin/trpc.ts +4 -9
  4. package/lib/alchemy.d.ts +3 -3
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +2 -15
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/apply.js +24 -9
  9. package/lib/apply.js.map +1 -1
  10. package/lib/aws/utils.d.ts +2 -1
  11. package/lib/aws/utils.d.ts.map +1 -1
  12. package/lib/aws/utils.js.map +1 -1
  13. package/lib/clickhouse/api/client.gen.d.ts +13 -0
  14. package/lib/clickhouse/api/client.gen.d.ts.map +1 -0
  15. package/lib/clickhouse/api/client.gen.js +7 -0
  16. package/lib/clickhouse/api/client.gen.js.map +1 -0
  17. package/lib/clickhouse/api/sdk.gen.d.ts +322 -0
  18. package/lib/clickhouse/api/sdk.gen.d.ts.map +1 -0
  19. package/lib/clickhouse/api/sdk.gen.js +683 -0
  20. package/lib/clickhouse/api/sdk.gen.js.map +1 -0
  21. package/lib/clickhouse/api/types.gen.d.ts +5030 -0
  22. package/lib/clickhouse/api/types.gen.d.ts.map +1 -0
  23. package/lib/clickhouse/api/types.gen.js +2 -0
  24. package/lib/clickhouse/api/types.gen.js.map +1 -0
  25. package/lib/clickhouse/api-key.d.ts +29 -0
  26. package/lib/clickhouse/api-key.d.ts.map +1 -0
  27. package/lib/clickhouse/api-key.js +83 -0
  28. package/lib/clickhouse/api-key.js.map +1 -0
  29. package/lib/clickhouse/api.d.ts +36 -0
  30. package/lib/clickhouse/api.d.ts.map +1 -0
  31. package/lib/clickhouse/api.js +48 -0
  32. package/lib/clickhouse/api.js.map +1 -0
  33. package/lib/clickhouse/index.d.ts +4 -0
  34. package/lib/clickhouse/index.d.ts.map +1 -0
  35. package/lib/clickhouse/index.js +4 -0
  36. package/lib/clickhouse/index.js.map +1 -0
  37. package/lib/clickhouse/organization.d.ts +16 -0
  38. package/lib/clickhouse/organization.d.ts.map +1 -0
  39. package/lib/clickhouse/organization.js +21 -0
  40. package/lib/clickhouse/organization.js.map +1 -0
  41. package/lib/clickhouse/service.d.ts +275 -0
  42. package/lib/clickhouse/service.d.ts.map +1 -0
  43. package/lib/clickhouse/service.js +274 -0
  44. package/lib/clickhouse/service.js.map +1 -0
  45. package/lib/cloudflare/account-api-token.d.ts.map +1 -1
  46. package/lib/cloudflare/account-api-token.js +1 -1
  47. package/lib/cloudflare/account-api-token.js.map +1 -1
  48. package/lib/cloudflare/bucket-object.d.ts +5 -0
  49. package/lib/cloudflare/bucket-object.d.ts.map +1 -1
  50. package/lib/cloudflare/bucket-object.js +1 -1
  51. package/lib/cloudflare/bucket-object.js.map +1 -1
  52. package/lib/cloudflare/bucket.d.ts +6 -3
  53. package/lib/cloudflare/bucket.d.ts.map +1 -1
  54. package/lib/cloudflare/bucket.js +60 -8
  55. package/lib/cloudflare/bucket.js.map +1 -1
  56. package/lib/cloudflare/bun-spa/bun-spa.d.ts +15 -0
  57. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -0
  58. package/lib/cloudflare/bun-spa/bun-spa.js +95 -0
  59. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -0
  60. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  61. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  62. package/lib/cloudflare/container.d.ts +11 -1
  63. package/lib/cloudflare/container.d.ts.map +1 -1
  64. package/lib/cloudflare/container.js +1 -1
  65. package/lib/cloudflare/container.js.map +1 -1
  66. package/lib/cloudflare/index.d.ts +1 -0
  67. package/lib/cloudflare/index.d.ts.map +1 -1
  68. package/lib/cloudflare/index.js +1 -0
  69. package/lib/cloudflare/index.js.map +1 -1
  70. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  71. package/lib/cloudflare/miniflare/build-worker-options.js +3 -3
  72. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  73. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  74. package/lib/cloudflare/miniflare/miniflare-controller.js +2 -0
  75. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  76. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +1 -1
  77. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  78. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +33 -14
  79. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  80. package/lib/cloudflare/permission-groups.d.ts +2 -3
  81. package/lib/cloudflare/permission-groups.d.ts.map +1 -1
  82. package/lib/cloudflare/permission-groups.js +10 -15
  83. package/lib/cloudflare/permission-groups.js.map +1 -1
  84. package/lib/cloudflare/redwood/redwood.d.ts +1 -1
  85. package/lib/cloudflare/redwood/redwood.js +1 -1
  86. package/lib/cloudflare/tanstack-start/plugin.d.ts +2 -15
  87. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -1
  88. package/lib/cloudflare/tanstack-start/plugin.js +5 -25
  89. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -1
  90. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
  91. package/lib/cloudflare/tanstack-start/tanstack-start.js +15 -4
  92. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
  93. package/lib/cloudflare/util/filter-env-bindings.d.ts +62 -0
  94. package/lib/cloudflare/util/filter-env-bindings.d.ts.map +1 -0
  95. package/lib/cloudflare/util/filter-env-bindings.js +81 -0
  96. package/lib/cloudflare/util/filter-env-bindings.js.map +1 -0
  97. package/lib/cloudflare/website.d.ts +2 -2
  98. package/lib/cloudflare/website.d.ts.map +1 -1
  99. package/lib/cloudflare/website.js +7 -18
  100. package/lib/cloudflare/website.js.map +1 -1
  101. package/lib/cloudflare/worker-metadata.d.ts +5 -1
  102. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  103. package/lib/cloudflare/worker-metadata.js +3 -0
  104. package/lib/cloudflare/worker-metadata.js.map +1 -1
  105. package/lib/cloudflare/worker.d.ts +17 -1
  106. package/lib/cloudflare/worker.d.ts.map +1 -1
  107. package/lib/cloudflare/worker.js +1 -0
  108. package/lib/cloudflare/worker.js.map +1 -1
  109. package/lib/cloudflare/wrangler.json.d.ts +15 -1
  110. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  111. package/lib/cloudflare/wrangler.json.js +12 -4
  112. package/lib/cloudflare/wrangler.json.js.map +1 -1
  113. package/lib/coinbase/client.d.ts +54 -0
  114. package/lib/coinbase/client.d.ts.map +1 -0
  115. package/lib/coinbase/client.js +89 -0
  116. package/lib/coinbase/client.js.map +1 -0
  117. package/lib/coinbase/evm-account.d.ts +118 -0
  118. package/lib/coinbase/evm-account.d.ts.map +1 -0
  119. package/lib/coinbase/evm-account.js +198 -0
  120. package/lib/coinbase/evm-account.js.map +1 -0
  121. package/lib/coinbase/evm-smart-account.d.ts +122 -0
  122. package/lib/coinbase/evm-smart-account.d.ts.map +1 -0
  123. package/lib/coinbase/evm-smart-account.js +213 -0
  124. package/lib/coinbase/evm-smart-account.js.map +1 -0
  125. package/lib/coinbase/faucet.d.ts +11 -0
  126. package/lib/coinbase/faucet.d.ts.map +1 -0
  127. package/lib/coinbase/faucet.js +146 -0
  128. package/lib/coinbase/faucet.js.map +1 -0
  129. package/lib/coinbase/index.d.ts +4 -0
  130. package/lib/coinbase/index.d.ts.map +1 -0
  131. package/lib/coinbase/index.js +4 -0
  132. package/lib/coinbase/index.js.map +1 -0
  133. package/lib/coinbase/types.d.ts +28 -0
  134. package/lib/coinbase/types.d.ts.map +1 -0
  135. package/lib/coinbase/types.js +4 -0
  136. package/lib/{util/telemetry → coinbase}/types.js.map +1 -1
  137. package/lib/coinbase/utils.d.ts +13 -0
  138. package/lib/coinbase/utils.d.ts.map +1 -0
  139. package/lib/coinbase/utils.js +17 -0
  140. package/lib/coinbase/utils.js.map +1 -0
  141. package/lib/docker/api.d.ts +22 -1
  142. package/lib/docker/api.d.ts.map +1 -1
  143. package/lib/docker/api.js +64 -2
  144. package/lib/docker/api.js.map +1 -1
  145. package/lib/docker/container.d.ts +88 -0
  146. package/lib/docker/container.d.ts.map +1 -1
  147. package/lib/docker/container.js +36 -0
  148. package/lib/docker/container.js.map +1 -1
  149. package/lib/neon/api/types.gen.d.ts +7 -2
  150. package/lib/neon/api/types.gen.d.ts.map +1 -1
  151. package/lib/planetscale/api/types.gen.d.ts +20 -4
  152. package/lib/planetscale/api/types.gen.d.ts.map +1 -1
  153. package/lib/planetscale/api.d.ts.map +1 -1
  154. package/lib/planetscale/api.js +1 -0
  155. package/lib/planetscale/api.js.map +1 -1
  156. package/lib/planetscale/password.d.ts.map +1 -1
  157. package/lib/planetscale/password.js +1 -13
  158. package/lib/planetscale/password.js.map +1 -1
  159. package/lib/resource.d.ts.map +1 -1
  160. package/lib/resource.js +7 -3
  161. package/lib/resource.js.map +1 -1
  162. package/lib/scope.d.ts +4 -9
  163. package/lib/scope.d.ts.map +1 -1
  164. package/lib/scope.js +16 -37
  165. package/lib/scope.js.map +1 -1
  166. package/lib/state/instrumented-state-store.d.ts +1 -3
  167. package/lib/state/instrumented-state-store.d.ts.map +1 -1
  168. package/lib/state/instrumented-state-store.js +19 -21
  169. package/lib/state/instrumented-state-store.js.map +1 -1
  170. package/lib/test/bun.d.ts.map +1 -1
  171. package/lib/test/bun.js +1 -2
  172. package/lib/test/bun.js.map +1 -1
  173. package/lib/test/vitest.d.ts.map +1 -1
  174. package/lib/test/vitest.js +1 -2
  175. package/lib/test/vitest.js.map +1 -1
  176. package/lib/util/diff.d.ts +5 -0
  177. package/lib/util/diff.d.ts.map +1 -0
  178. package/lib/util/diff.js +14 -0
  179. package/lib/util/diff.js.map +1 -0
  180. package/lib/util/telemetry.d.ts +52 -0
  181. package/lib/util/telemetry.d.ts.map +1 -0
  182. package/lib/util/telemetry.js +191 -0
  183. package/lib/util/telemetry.js.map +1 -0
  184. package/package.json +19 -1
  185. package/src/alchemy.ts +5 -20
  186. package/src/apply.ts +29 -12
  187. package/src/aws/utils.ts +2 -1
  188. package/src/clickhouse/api/client.gen.ts +28 -0
  189. package/src/clickhouse/api/sdk.gen.ts +1286 -0
  190. package/src/clickhouse/api/types.gen.ts +5898 -0
  191. package/src/clickhouse/api-key.ts +136 -0
  192. package/src/clickhouse/api.ts +84 -0
  193. package/src/clickhouse/index.ts +3 -0
  194. package/src/clickhouse/organization.ts +31 -0
  195. package/src/clickhouse/service.ts +668 -0
  196. package/src/cloudflare/account-api-token.ts +1 -4
  197. package/src/cloudflare/bucket-object.ts +11 -1
  198. package/src/cloudflare/bucket.ts +72 -6
  199. package/src/cloudflare/bun-spa/bun-spa.ts +139 -0
  200. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  201. package/src/cloudflare/container.ts +22 -3
  202. package/src/cloudflare/index.ts +1 -0
  203. package/src/cloudflare/miniflare/build-worker-options.ts +6 -4
  204. package/src/cloudflare/miniflare/miniflare-controller.ts +2 -0
  205. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +42 -15
  206. package/src/cloudflare/permission-groups.ts +21 -49
  207. package/src/cloudflare/redwood/redwood.ts +1 -1
  208. package/src/cloudflare/tanstack-start/plugin.ts +7 -28
  209. package/src/cloudflare/tanstack-start/tanstack-start.ts +15 -4
  210. package/src/cloudflare/util/filter-env-bindings.ts +96 -0
  211. package/src/cloudflare/website.ts +12 -24
  212. package/src/cloudflare/worker-metadata.ts +8 -1
  213. package/src/cloudflare/worker.ts +19 -1
  214. package/src/cloudflare/wrangler.json.ts +27 -9
  215. package/src/coinbase/client.ts +146 -0
  216. package/src/coinbase/evm-account.ts +317 -0
  217. package/src/coinbase/evm-smart-account.ts +339 -0
  218. package/src/coinbase/faucet.ts +173 -0
  219. package/src/coinbase/index.ts +3 -0
  220. package/src/coinbase/types.ts +33 -0
  221. package/src/coinbase/utils.ts +19 -0
  222. package/src/docker/api.ts +86 -2
  223. package/src/docker/container.ts +97 -0
  224. package/src/neon/api/types.gen.ts +13 -1
  225. package/src/planetscale/api/types.gen.ts +22 -2
  226. package/src/planetscale/api.ts +1 -0
  227. package/src/planetscale/password.ts +1 -14
  228. package/src/resource.ts +11 -4
  229. package/src/scope.ts +22 -45
  230. package/src/state/instrumented-state-store.ts +27 -26
  231. package/src/test/bun.ts +1 -2
  232. package/src/test/vitest.ts +1 -2
  233. package/src/util/diff.ts +14 -0
  234. package/src/util/telemetry.ts +297 -0
  235. package/templates/tanstack-start/.cta.json +13 -0
  236. package/templates/tanstack-start/README.md +267 -49
  237. package/templates/tanstack-start/_gitignore +9 -18
  238. package/templates/tanstack-start/alchemy.run.ts +2 -2
  239. package/templates/tanstack-start/package.json +24 -19
  240. package/templates/tanstack-start/public/favicon.ico +0 -0
  241. package/templates/tanstack-start/public/logo192.png +0 -0
  242. package/templates/tanstack-start/public/logo512.png +0 -0
  243. package/templates/tanstack-start/public/manifest.json +25 -0
  244. package/templates/tanstack-start/public/robots.txt +3 -0
  245. package/templates/tanstack-start/src/components/Header.tsx +21 -0
  246. package/templates/tanstack-start/src/logo.svg +12 -0
  247. package/templates/tanstack-start/src/router.tsx +7 -16
  248. package/templates/tanstack-start/src/routes/__root.tsx +13 -114
  249. package/templates/tanstack-start/src/routes/api.demo-names.ts +15 -0
  250. package/templates/tanstack-start/src/routes/demo.start.api-request.tsx +44 -0
  251. package/templates/tanstack-start/src/routes/demo.start.server-funcs.tsx +97 -0
  252. package/templates/tanstack-start/src/routes/index.tsx +31 -4
  253. package/templates/tanstack-start/src/styles.css +15 -0
  254. package/templates/tanstack-start/tsconfig.json +19 -22
  255. package/templates/tanstack-start/types/env.d.ts +2 -2
  256. package/templates/tanstack-start/vite.config.ts +16 -25
  257. package/workers/tunnel-proxy.js +1 -1
  258. package/lib/util/telemetry/client.d.ts +0 -31
  259. package/lib/util/telemetry/client.d.ts.map +0 -1
  260. package/lib/util/telemetry/client.js +0 -121
  261. package/lib/util/telemetry/client.js.map +0 -1
  262. package/lib/util/telemetry/constants.d.ts +0 -5
  263. package/lib/util/telemetry/constants.d.ts.map +0 -1
  264. package/lib/util/telemetry/constants.js +0 -8
  265. package/lib/util/telemetry/constants.js.map +0 -1
  266. package/lib/util/telemetry/context.d.ts +0 -15
  267. package/lib/util/telemetry/context.d.ts.map +0 -1
  268. package/lib/util/telemetry/context.js +0 -128
  269. package/lib/util/telemetry/context.js.map +0 -1
  270. package/lib/util/telemetry/index.d.ts +0 -3
  271. package/lib/util/telemetry/index.d.ts.map +0 -1
  272. package/lib/util/telemetry/index.js +0 -3
  273. package/lib/util/telemetry/index.js.map +0 -1
  274. package/lib/util/telemetry/types.d.ts +0 -68
  275. package/lib/util/telemetry/types.d.ts.map +0 -1
  276. package/lib/util/telemetry/types.js +0 -1
  277. package/src/util/telemetry/client.ts +0 -156
  278. package/src/util/telemetry/constants.ts +0 -13
  279. package/src/util/telemetry/context.ts +0 -154
  280. package/src/util/telemetry/index.ts +0 -2
  281. package/src/util/telemetry/types.ts +0 -105
  282. package/templates/tanstack-start/_prettierignore +0 -4
  283. package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
  284. package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
  285. package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
  286. package/templates/tanstack-start/public/favicon-16x16.png +0 -0
  287. package/templates/tanstack-start/public/favicon-32x32.png +0 -0
  288. package/templates/tanstack-start/public/favicon.png +0 -0
  289. package/templates/tanstack-start/public/site.webmanifest +0 -19
  290. package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +0 -53
  291. package/templates/tanstack-start/src/components/NotFound.tsx +0 -25
  292. package/templates/tanstack-start/src/components/PostError.tsx +0 -5
  293. package/templates/tanstack-start/src/components/UserError.tsx +0 -5
  294. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +0 -10
  295. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +0 -10
  296. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +0 -34
  297. package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +0 -16
  298. package/templates/tanstack-start/src/routes/api/users.$userId.ts +0 -28
  299. package/templates/tanstack-start/src/routes/api/users.ts +0 -59
  300. package/templates/tanstack-start/src/routes/customScript[.]js.ts +0 -10
  301. package/templates/tanstack-start/src/routes/deferred.tsx +0 -62
  302. package/templates/tanstack-start/src/routes/posts.$postId.tsx +0 -34
  303. package/templates/tanstack-start/src/routes/posts.index.tsx +0 -8
  304. package/templates/tanstack-start/src/routes/posts.tsx +0 -38
  305. package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +0 -29
  306. package/templates/tanstack-start/src/routes/redirect.tsx +0 -9
  307. package/templates/tanstack-start/src/routes/users.$userId.tsx +0 -44
  308. package/templates/tanstack-start/src/routes/users.index.tsx +0 -18
  309. package/templates/tanstack-start/src/routes/users.tsx +0 -49
  310. package/templates/tanstack-start/src/styles/app.css +0 -12
  311. package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +0 -41
  312. package/templates/tanstack-start/src/utils/posts.tsx +0 -40
  313. package/templates/tanstack-start/src/utils/seo.ts +0 -33
  314. package/templates/tanstack-start/src/utils/users.tsx +0 -5
@@ -1,3 +1,4 @@
1
+ import type { R2PutOptions } from "@cloudflare/workers-types/experimental/index.ts";
1
2
  import * as mf from "miniflare";
2
3
  import { isDeepStrictEqual } from "node:util";
3
4
  import type { Context } from "../context.ts";
@@ -253,6 +254,7 @@ export type R2ObjectMetadata = {
253
254
  etag: string;
254
255
  uploaded: Date;
255
256
  size: number;
257
+ httpMetadata?: R2HTTPMetadata;
256
258
  };
257
259
 
258
260
  export type R2ObjectContent = R2ObjectMetadata & {
@@ -272,7 +274,7 @@ export type PutR2ObjectResponse = {
272
274
  };
273
275
 
274
276
  export type R2Objects = {
275
- objects: R2ObjectMetadata[];
277
+ objects: Omit<R2ObjectMetadata, "httpMetadata">[];
276
278
  } & (
277
279
  | {
278
280
  truncated: true;
@@ -296,6 +298,7 @@ export type R2Bucket = _R2Bucket & {
296
298
  | string
297
299
  | null
298
300
  | Blob,
301
+ options?: Pick<R2PutOptions, "httpMetadata">,
299
302
  ): Promise<PutR2ObjectResponse>;
300
303
  delete(key: string): Promise<Response>;
301
304
  list(options?: R2ListOptions): Promise<R2Objects>;
@@ -449,7 +452,17 @@ export async function R2Bucket(
449
452
  ...bucket,
450
453
  head: async (key: string) => {
451
454
  if (isLocal) {
452
- return (await localBucket()).head(key);
455
+ const result = await (await localBucket()).head(key);
456
+ if (result) {
457
+ return {
458
+ key: result.key,
459
+ etag: result.etag,
460
+ uploaded: result.uploaded,
461
+ size: result.size,
462
+ httpMetadata: result.httpMetadata,
463
+ } as R2ObjectMetadata;
464
+ }
465
+ return null;
453
466
  }
454
467
  return headObject(api, {
455
468
  bucketName: bucket.name,
@@ -489,9 +502,9 @@ export async function R2Bucket(
489
502
  put: async (
490
503
  key: string,
491
504
  value: PutObjectObject,
505
+ options?: Pick<R2PutOptions, "httpMetadata">,
492
506
  ): Promise<PutR2ObjectResponse> => {
493
507
  if (isLocal) {
494
- // @ts-expect-error - node built-ins vs cloudflare built-ins
495
508
  return await (await localBucket()).put(
496
509
  key,
497
510
  typeof value === "string"
@@ -505,12 +518,14 @@ export async function R2Bucket(
505
518
  : value instanceof ReadableStream
506
519
  ? new Uint8Array(await streamToBuffer(value))
507
520
  : value,
521
+ options,
508
522
  );
509
523
  }
510
524
  const response = await putObject(api, {
511
525
  bucketName: bucket.name,
512
526
  key: key,
513
527
  object: value,
528
+ options: options,
514
529
  });
515
530
  const body = (await response.json()) as {
516
531
  result: {
@@ -546,6 +561,7 @@ const parseR2Object = (key: string, response: Response): R2ObjectContent => ({
546
561
  uploaded: parseDate(response.headers),
547
562
  key,
548
563
  size: Number(response.headers.get("Content-Length")),
564
+ httpMetadata: mapHeadersToHttpMetadata(response.headers),
549
565
  arrayBuffer: () => response.arrayBuffer(),
550
566
  bytes: () => response.bytes(),
551
567
  text: () => response.text(),
@@ -1155,6 +1171,7 @@ export async function headObject(
1155
1171
  etag: response.headers.get("ETag")?.replace(/"/g, "")!,
1156
1172
  uploaded: parseDate(response.headers),
1157
1173
  size: Number(response.headers.get("Content-Length")),
1174
+ httpMetadata: mapHeadersToHttpMetadata(response.headers),
1158
1175
  };
1159
1176
  }
1160
1177
 
@@ -1192,27 +1209,76 @@ export type PutObjectObject =
1192
1209
  | Buffer
1193
1210
  | Blob;
1194
1211
 
1212
+ function mapHttpMetadataToHeaders(
1213
+ httpMetadata: R2PutOptions["httpMetadata"],
1214
+ ): Record<string, string> {
1215
+ const headers: Record<string, string> = {};
1216
+
1217
+ if (httpMetadata instanceof Headers) {
1218
+ httpMetadata.forEach((value, key) => {
1219
+ headers[key] = value;
1220
+ });
1221
+ } else {
1222
+ const {
1223
+ contentType,
1224
+ contentLanguage,
1225
+ contentDisposition,
1226
+ contentEncoding,
1227
+ cacheControl,
1228
+ cacheExpiry,
1229
+ } = httpMetadata as R2HTTPMetadata;
1230
+ if (contentType) headers["Content-Type"] = contentType;
1231
+ if (contentLanguage) headers["Content-Language"] = contentLanguage;
1232
+ if (contentDisposition) headers["Content-Disposition"] = contentDisposition;
1233
+ if (contentEncoding) headers["Content-Encoding"] = contentEncoding;
1234
+ if (cacheControl) headers["Cache-Control"] = cacheControl;
1235
+ if (cacheExpiry) headers.Expires = cacheExpiry.toUTCString();
1236
+ }
1237
+
1238
+ return headers;
1239
+ }
1240
+
1241
+ function mapHeadersToHttpMetadata(headers: Headers): R2HTTPMetadata {
1242
+ return {
1243
+ contentType: headers.get("Content-Type") ?? undefined,
1244
+ contentLanguage: headers.get("Content-Language") ?? undefined,
1245
+ contentDisposition: headers.get("Content-Disposition") ?? undefined,
1246
+ contentEncoding: headers.get("Content-Encoding") ?? undefined,
1247
+ cacheControl: headers.get("Cache-Control") ?? undefined,
1248
+ cacheExpiry: headers.get("Expires")
1249
+ ? new Date(headers.get("Expires")!)
1250
+ : undefined,
1251
+ };
1252
+ }
1253
+
1195
1254
  export async function putObject(
1196
1255
  api: CloudflareApi,
1197
1256
  {
1198
1257
  bucketName,
1199
1258
  key,
1200
1259
  object,
1260
+ options,
1201
1261
  }: {
1202
1262
  bucketName: string;
1203
1263
  key: string;
1204
1264
  object: PutObjectObject;
1265
+ options?: Pick<R2PutOptions, "httpMetadata">;
1205
1266
  },
1206
1267
  ): Promise<Response> {
1207
1268
  // Using withExponentialBackoff for reliability
1208
1269
  return await withRetries(async () => {
1270
+ const headers: Record<string, string> = {
1271
+ "Content-Type": "application/octet-stream",
1272
+ ...(options?.httpMetadata
1273
+ ? mapHttpMetadataToHeaders(options?.httpMetadata)
1274
+ : {}),
1275
+ };
1276
+
1209
1277
  const response = await api.put(
1210
1278
  `/accounts/${api.accountId}/r2/buckets/${bucketName}/objects/${key}`,
1211
1279
  object,
1212
1280
  {
1213
- headers: {
1214
- "Content-Type": "application/octet-stream",
1215
- },
1281
+ headers,
1216
1282
  },
1217
1283
  );
1218
1284
  if (!response.ok) {
@@ -0,0 +1,139 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { Scope } from "../../scope.ts";
4
+ import { exists } from "../../util/exists.ts";
5
+ import type { Assets } from "../assets.ts";
6
+ import type { Bindings } from "../bindings.ts";
7
+ import {
8
+ extractStringAndSecretBindings,
9
+ unencryptSecrets,
10
+ } from "../util/filter-env-bindings.ts";
11
+ import {
12
+ spreadBuildProps,
13
+ spreadDevProps,
14
+ Website,
15
+ type WebsiteProps,
16
+ } from "../website.ts";
17
+ import type { Worker } from "../worker.ts";
18
+
19
+ export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
20
+ frontend: string;
21
+ outDir?: string;
22
+ }
23
+
24
+ export type BunSPA<B extends Bindings> = B extends { ASSETS: any }
25
+ ? never
26
+ : Worker<B & { ASSETS: Assets }>;
27
+
28
+ export async function BunSPA<B extends Bindings>(
29
+ id: string,
30
+ props: BunSPAProps<B>,
31
+ ): Promise<BunSPA<B>> {
32
+ const frontendPath = path.resolve(props.frontend);
33
+ if (!(await exists(frontendPath))) {
34
+ throw new Error(`Frontend path ${frontendPath} does not exist`);
35
+ }
36
+
37
+ const stats = await fs.stat(frontendPath);
38
+ if (!stats.isFile()) {
39
+ throw new Error(`Frontend path ${frontendPath} is not a file`);
40
+ }
41
+ const outDir = path.resolve(props.outDir ?? "dist/client");
42
+
43
+ if (props.assets) {
44
+ throw new Error("assets are not supported in BunSPA");
45
+ }
46
+
47
+ const scope = Scope.current;
48
+ console.log("creating website", outDir);
49
+ const website = await Website(id, {
50
+ spa: true,
51
+ ...props,
52
+ bindings: {
53
+ ...props.bindings,
54
+ // set NODE_ENV in worker appropriately if not already set
55
+ NODE_ENV:
56
+ (props.bindings?.NODE_ENV ?? scope.local)
57
+ ? "development"
58
+ : "production",
59
+ } as unknown as B,
60
+ assets: {
61
+ directory: path.resolve(outDir),
62
+ },
63
+ build: spreadBuildProps(
64
+ props,
65
+ `bun build '${frontendPath}' --outdir ${outDir}`,
66
+ ),
67
+ });
68
+
69
+ // in dev
70
+ if (scope.local) {
71
+ const cwd = props.cwd ?? process.cwd();
72
+ await validateBunfigToml(cwd);
73
+ const dev = spreadDevProps(
74
+ props,
75
+ `bun '${path.relative(cwd, frontendPath)}'`,
76
+ );
77
+ console.log("backend url", website.url);
78
+ const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
79
+ const env = {
80
+ ...(process.env ?? {}),
81
+ ...(props.env ?? {}),
82
+ ...extractStringAndSecretBindings(props.bindings ?? {}, secrets),
83
+ };
84
+ website.url = await scope.spawn(website.name, {
85
+ cmd: typeof dev === "string" ? dev : dev.command!,
86
+ cwd,
87
+ extract: (line) => {
88
+ const URL_REGEX =
89
+ /http:\/\/(localhost|0\.0\.0\.0|127\.0\.0\.1|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+\/?/;
90
+ const match = line
91
+ .replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")
92
+ .match(URL_REGEX);
93
+ if (match) {
94
+ return match[0];
95
+ }
96
+ },
97
+ env: {
98
+ ...unencryptSecrets(env ?? {}),
99
+ ...(typeof dev === "object" ? dev.env : {}),
100
+ FORCE_COLOR: "1",
101
+ ...process.env,
102
+ NODE_ENV: "development",
103
+ ALCHEMY_ROOT: Scope.current.rootDir,
104
+ PUBLIC_BACKEND_URL: website.url!,
105
+ },
106
+ });
107
+ }
108
+ return website;
109
+ }
110
+
111
+ async function validateBunfigToml(cwd: string): Promise<void> {
112
+ const bunfigPath = path.join(cwd, "bunfig.toml");
113
+
114
+ if (!(await exists(bunfigPath))) {
115
+ throw new Error(
116
+ "bunfig.toml is required for BunSPA to work correctly.\n\n" +
117
+ `Create ${bunfigPath} with the following content:\n\n` +
118
+ "[serve.static]\n" +
119
+ `env='PUBLIC_*'\n\n` +
120
+ "This allows Bun to expose PUBLIC_* environment variables to the frontend during development.",
121
+ );
122
+ }
123
+
124
+ const content = await fs.readFile(bunfigPath, "utf-8");
125
+ const config = Bun.TOML.parse(content) as Record<string, any>;
126
+
127
+ const hasServeStatic = config.serve?.static;
128
+ const hasEnvConfig = hasServeStatic && config.serve.static.env === "PUBLIC_*";
129
+
130
+ if (!hasServeStatic || !hasEnvConfig) {
131
+ throw new Error(
132
+ "bunfig.toml is missing required configuration for BunSPA.\n\n" +
133
+ `Add the following section to ${bunfigPath}:\n\n` +
134
+ "[serve.static]\n" +
135
+ `env='PUBLIC_*'\n\n` +
136
+ "This allows Bun to expose PUBLIC_* environment variables to the frontend during development.",
137
+ );
138
+ }
139
+ }
@@ -4,4 +4,4 @@
4
4
  /**
5
5
  * The default Cloudflare Workers compatibility date, set based on the latest workerd release at the time of build.
6
6
  */
7
- export const DEFAULT_COMPATIBILITY_DATE = "2025-09-27";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-10-01";
@@ -93,9 +93,28 @@ export interface ContainerProps
93
93
  * - `basic`: Basic production instances with standard resources
94
94
  * - `standard`: Standard production instances with enhanced resources
95
95
  *
96
+ * | Instance Type | vCPU | Memory (Min) | Memory (Max) |
97
+ * |---------------|------|--------------|--------------|
98
+ * | lite | 1/16 | 256 MiB | 2 GB |
99
+ * | basic | 1/4 | 1 GiB | 4 GB |
100
+ * | standard-1 | 1/2 | 4 GiB | 8 GB |
101
+ * | standard-2 | 1 | 6 GiB | 12 GB |
102
+ * | standard-3 | 2 | 8 GiB | 16 GB |
103
+ * | standard-4 | 4 | 12 GiB | 20 GB |
104
+ *
96
105
  * @see https://developers.cloudflare.com/containers/pricing/
106
+ * @see https://developers.cloudflare.com/changelog/2025-10-01-new-container-instance-types/
97
107
  */
98
- export type InstanceType = "dev" | "basic" | "standard" | (string & {});
108
+ export type InstanceType =
109
+ | "lite"
110
+ | "dev"
111
+ | "basic"
112
+ | "standard"
113
+ | "standard-1"
114
+ | "standard-2"
115
+ | "standard-3"
116
+ | "standard-4"
117
+ | (string & {});
99
118
 
100
119
  /**
101
120
  * Type guard to check if a binding is a Container binding.
@@ -1044,13 +1063,13 @@ export async function createContainerApplicationRollout(
1044
1063
  );
1045
1064
  const result = (await response.json()) as {
1046
1065
  result: CreateRolloutApplicationResponse;
1047
- errors: { message: string }[];
1066
+ errors: { message: string; code: number }[];
1048
1067
  };
1049
1068
  if (response.ok) {
1050
1069
  return result.result;
1051
1070
  }
1052
1071
  throw Error(
1053
- `Failed to create container application rollout: ${result.errors.map((e: { message: string }) => e.message).join(", ")}`,
1072
+ `Failed to create container application rollout: ${result.errors.map((e) => `[${e.code}] ${e.message}`).join(", ")}`,
1054
1073
  );
1055
1074
  }
1056
1075
 
@@ -15,6 +15,7 @@ export * from "./bound.ts";
15
15
  export * from "./browser-rendering.ts";
16
16
  export * from "./bucket-object.ts";
17
17
  export * from "./bucket.ts";
18
+ export * from "./bun-spa/bun-spa.ts";
18
19
  export * from "./certificate-pack.ts";
19
20
  export * from "./cloudflare-env-proxy.ts";
20
21
  export * from "./compatibility-date.gen.ts";
@@ -52,7 +52,9 @@ type RemoteBinding =
52
52
  | WorkerBindingService;
53
53
 
54
54
  type BaseWorkerOptions = {
55
- [K in keyof miniflare.WorkerOptions]: K extends "compatibilityFlags"
55
+ [K in keyof miniflare.WorkerOptions]: K extends
56
+ | "compatibilityFlags"
57
+ | "routes"
56
58
  ? miniflare.WorkerOptions[K]
57
59
  : Exclude<miniflare.WorkerOptions[K], string[]>;
58
60
  };
@@ -70,11 +72,9 @@ export const buildWorkerOptions = async (
70
72
  compatibilityFlags: input.compatibilityFlags,
71
73
  unsafeDirectSockets: [
72
74
  // This matches the Wrangler configuration by exposing the default handler (e.g. `export default { fetch }`).
73
- // However, unlike Wrangler, we set `proxy: false` to avoid the following error when connecting via a websocket:
74
- // workerd/io/worker.c++:2164: info: uncaught exception; source = Uncaught (in promise); stack = TypeError: Invalid URL string.
75
75
  {
76
76
  entrypoint: "default",
77
- proxy: false,
77
+ proxy: true,
78
78
  },
79
79
  ],
80
80
  containerEngine: {
@@ -85,6 +85,8 @@ export const buildWorkerOptions = async (
85
85
  : "unix:///var/run/docker.sock",
86
86
  },
87
87
  },
88
+ // This exposes the worker as a route that can be accessed by setting the MF-Route-Override header.
89
+ routes: [input.name],
88
90
  };
89
91
  for (const [key, binding] of Object.entries(input.bindings ?? {})) {
90
92
  if (typeof binding === "string") {
@@ -104,6 +104,8 @@ export class MiniflareController {
104
104
  // This is required to allow websites and other separate processes
105
105
  // to detect Alchemy-managed Durable Objects via the Wrangler dev registry.
106
106
  unsafeDevRegistryDurableObjectProxy: true,
107
+ // This exposes other handlers like `scheduled` and `email` via HTTP.
108
+ unsafeTriggerHandlers: true,
107
109
  };
108
110
  for (const worker of this.options.values()) {
109
111
  options.workers.push(worker);
@@ -13,7 +13,7 @@ export interface MiniflareWorkerProxy {
13
13
  export async function createMiniflareWorkerProxy(options: {
14
14
  port: number;
15
15
  transformRequest?: (request: RequestInfo) => void;
16
- getWorkerName: (request: miniflare.Request) => string;
16
+ getWorkerName: (request: RequestInfo) => string;
17
17
  miniflare: miniflare.Miniflare;
18
18
  mode: "local" | "remote";
19
19
  }) {
@@ -31,10 +31,7 @@ export async function createMiniflareWorkerProxy(options: {
31
31
  });
32
32
  server.on("request", async (req, res) => {
33
33
  try {
34
- const request = toMiniflareRequest(req);
35
- const name = options.getWorkerName(request);
36
- const worker = await options.miniflare.getWorker(name);
37
- const response = await worker.fetch(request);
34
+ const response = await handleFetch(req);
38
35
  writeMiniflareResponseToNode(response, res);
39
36
  } catch (error) {
40
37
  console.error(error);
@@ -45,27 +42,53 @@ export async function createMiniflareWorkerProxy(options: {
45
42
  }
46
43
  });
47
44
 
48
- const toMiniflareRequest = (req: http.IncomingMessage): miniflare.Request => {
45
+ const handleFetch = async (
46
+ req: http.IncomingMessage,
47
+ ): Promise<miniflare.Response> => {
49
48
  const info = parseIncomingMessage(req);
50
49
  options.transformRequest?.(info);
51
- return new miniflare.Request(info.url, {
50
+
51
+ const name = options.getWorkerName(info);
52
+ info.headers.set("MF-Route-Override", name);
53
+
54
+ // Handle scheduled events.
55
+ // Wrangler exposes this as /__scheduled, but Miniflare exposes it as /cdn-cgi/handler/scheduled.
56
+ // https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#background
57
+ // https://github.com/cloudflare/workers-sdk/blob/7d53b9ab6b370944b7934ad51ebef43160c3c775/packages/wrangler/templates/middleware/middleware-scheduled.ts#L6
58
+ if (info.url.pathname === "/__scheduled") {
59
+ info.url.pathname = "/cdn-cgi/handler/scheduled";
60
+ }
61
+
62
+ const request = new miniflare.Request(info.url, {
52
63
  method: info.method,
53
64
  headers: info.headers,
54
65
  body: info.body,
55
66
  redirect: info.redirect,
56
67
  duplex: info.duplex,
57
68
  });
69
+ return await options.miniflare.dispatchFetch(request);
58
70
  };
59
71
 
60
72
  const createServerWebSocket = async (req: http.IncomingMessage) => {
61
- const name = options.getWorkerName(toMiniflareRequest(req));
62
- const target = await options.miniflare.unsafeGetDirectURL(name);
73
+ // Rewrite to Miniflare entry URL
74
+ const target = await options.miniflare.ready;
63
75
  const url = new URL(req.url ?? "/", target);
64
76
  url.protocol = url.protocol.replace("http", "ws");
77
+
65
78
  const protocols = req.headers["sec-websocket-protocol"]
66
79
  ?.split(",")
67
80
  .map((p) => p.trim());
68
- const server = new WebSocket(url, protocols);
81
+
82
+ // Get worker name to set MF-Route-Override header
83
+ const info = parseIncomingMessage(req);
84
+ options.transformRequest?.(info);
85
+ const name = options.getWorkerName(info);
86
+
87
+ const server = new WebSocket(url, protocols, {
88
+ headers: {
89
+ "MF-Route-Override": name,
90
+ },
91
+ });
69
92
  await once(server, "open");
70
93
  return server;
71
94
  };
@@ -77,10 +100,8 @@ export async function createMiniflareWorkerProxy(options: {
77
100
  return {
78
101
  url,
79
102
  close: async () => {
80
- await Promise.all([
81
- new Promise((resolve) => server.close(resolve)),
82
- new Promise((resolve) => wss.close(resolve)),
83
- ]);
103
+ // If we await this while the `wss` server is open, the server will hang.
104
+ server.close();
84
105
  },
85
106
  };
86
107
  }
@@ -128,7 +149,13 @@ const writeMiniflareResponseToNode = (
128
149
  ) => {
129
150
  out.statusCode = res.status;
130
151
  res.headers.forEach((value, key) => {
131
- out.setHeader(key, value);
152
+ // The `transfer-encoding` header prevents Cloudflare Tunnels from working because of the following error:
153
+ // Request failed error="Unable to reach the origin service. The service may be down or it may not be
154
+ // responding to traffic from cloudflared: net/http: HTTP/1.x transport connection broken: too many
155
+ // transfer encodings: [\"chunked\" \"chunked\"]"
156
+ if (key !== "transfer-encoding") {
157
+ out.setHeader(key, value);
158
+ }
132
159
  });
133
160
 
134
161
  if (res.body) {
@@ -1,5 +1,5 @@
1
- import type { Context } from "../context.ts";
2
1
  import { Resource } from "../resource.ts";
2
+ import { extractCloudflareResult } from "./api-response.ts";
3
3
  import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
4
4
 
5
5
  /**
@@ -22,16 +22,6 @@ export interface PermissionGroup {
22
22
  scopes: string[];
23
23
  }
24
24
 
25
- /**
26
- * Response from the Cloudflare permission groups API
27
- */
28
- interface PermissionGroupsResponse {
29
- result: PermissionGroup[];
30
- success: boolean;
31
- errors: any[];
32
- messages: any[];
33
- }
34
-
35
25
  export type PermissionGroupName = R2PermissionGroups | (string & {});
36
26
 
37
27
  export type R2PermissionGroups =
@@ -86,7 +76,7 @@ export type PermissionGroups = {
86
76
  *
87
77
  * @example
88
78
  * // Get all permission groups including those for R2
89
- * const permissions = await PermissionGroups("cloudflare-permissions");
79
+ * const permissions = await PermissionGroups();
90
80
  *
91
81
  * // Use with AccountApiToken to create a token with proper permissions
92
82
  * const token = await AccountApiToken("r2-token", {
@@ -106,42 +96,24 @@ export type PermissionGroups = {
106
96
  * ]
107
97
  * });
108
98
  */
109
- export const PermissionGroups = Resource(
110
- "cloudflare::PermissionGroups",
111
- async function (
112
- this: Context<PermissionGroups>,
113
- _id: string,
114
- options: CloudflareApiOptions = {},
115
- ): Promise<PermissionGroups> {
116
- // Only create and update phases are supported
117
- if (this.phase === "delete") {
118
- return this.destroy();
119
- }
120
-
121
- // Initialize API client
122
- const api = await createCloudflareApi(options);
123
-
124
- // Fetch permission groups from Cloudflare API
125
- const response = await api.get(
126
- `/accounts/${api.accountId}/tokens/permission_groups`,
127
- );
128
-
129
- if (!response.ok) {
130
- throw new Error(
131
- `Failed to fetch permission groups: ${response.statusText}`,
132
- );
133
- }
134
-
135
- const data = (await response.json()) as PermissionGroupsResponse;
99
+ export async function PermissionGroups(
100
+ options: CloudflareApiOptions = {},
101
+ ): Promise<PermissionGroups> {
102
+ const api = await createCloudflareApi(options);
103
+ const result = await extractCloudflareResult<PermissionGroup[]>(
104
+ "fetch permission groups",
105
+ api.get(`/accounts/${api.accountId}/tokens/permission_groups`),
106
+ );
107
+ return Object.fromEntries(
108
+ result.map((group) => [group.name, group]),
109
+ ) as PermissionGroups;
110
+ }
136
111
 
137
- if (!data.success || !data.result) {
138
- throw new Error(
139
- `API returned error: ${data.errors?.[0]?.message || "Unknown error"}`,
140
- );
141
- }
112
+ // we are deprecating the PermissionGroups resource (it is now just a function)
113
+ Resource("cloudflare::PermissionGroups", async function (this) {
114
+ if (this.phase === "delete") {
115
+ return this.destroy();
116
+ }
142
117
 
143
- return Object.fromEntries(
144
- data.result.map((group) => [group.name, group]),
145
- ) as PermissionGroups;
146
- },
147
- );
118
+ throw new Error("Not implemented");
119
+ });
@@ -12,7 +12,7 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
12
12
  : Worker<B & { ASSETS: Assets }>;
13
13
 
14
14
  /**
15
- * Deploy a RedwoodJS application to Cloudflare Pages with automatically configured defaults.
15
+ * Deploy a RedwoodJS application to Cloudflare Workers with automatically configured defaults.
16
16
  *
17
17
  * This resource handles the deployment of RedwoodJS applications with optimized settings for
18
18
  * Cloudflare Workers, including proper build commands and compatibility flags.
@@ -1,30 +1,9 @@
1
- import type { GetPlatformProxyOptions } from "wrangler";
2
- import { dedent } from "../../util/dedent.ts";
1
+ import type { PluginConfig } from "@cloudflare/vite-plugin";
2
+ import alchemyVite from "../vite/plugin.ts";
3
3
 
4
- /**
5
- * TanStackStart server functions and middleware run in Node.js intead of Miniflare when using `vite dev`.
6
- *
7
- * This plugin polyfills the cloudflare:workers module & includes `process.env` during the dev server phase.
8
- *
9
- * @see https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack/#using-cloudflare-bindings
10
- */
11
- export default function alchemy(options: GetPlatformProxyOptions = {}) {
12
- return {
13
- name: "cloudflare-workers-dev-shim",
14
- apply: "serve", // dev‑only
15
- enforce: "pre",
16
- resolveId(id: string) {
17
- if (id === "cloudflare:workers") return id; // tell Vite we handled it
18
- },
19
- load(id: string) {
20
- if (id === "cloudflare:workers") {
21
- return dedent`
22
- import { getCloudflareEnvProxy } from "alchemy/cloudflare";
23
- export const env = await getCloudflareEnvProxy(${JSON.stringify(
24
- options,
25
- )});
26
- `;
27
- }
28
- },
29
- } as const;
4
+ export default function alchemy(options: PluginConfig = {}) {
5
+ return alchemyVite({
6
+ viteEnvironment: { name: "ssr" },
7
+ ...options,
8
+ });
30
9
  }