alchemy 0.65.1 → 0.67.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 (185) hide show
  1. package/bin/alchemy.js +139 -79
  2. package/bin/commands/init.ts +6 -3
  3. package/bin/services/cdp-manager/server.ts +3 -2
  4. package/bin/services/execute-alchemy.ts +33 -3
  5. package/bin/services/find-workspace-root.ts +48 -0
  6. package/lib/alchemy.d.ts +20 -3
  7. package/lib/alchemy.d.ts.map +1 -1
  8. package/lib/alchemy.js +18 -5
  9. package/lib/alchemy.js.map +1 -1
  10. package/lib/apply.d.ts.map +1 -1
  11. package/lib/apply.js +51 -1
  12. package/lib/apply.js.map +1 -1
  13. package/lib/cloudflare/bindings.d.ts +17 -5
  14. package/lib/cloudflare/bindings.d.ts.map +1 -1
  15. package/lib/cloudflare/bindings.js +3 -1
  16. package/lib/cloudflare/bindings.js.map +1 -1
  17. package/lib/cloudflare/bound.d.ts +4 -3
  18. package/lib/cloudflare/bound.d.ts.map +1 -1
  19. package/lib/cloudflare/bucket.js +1 -1
  20. package/lib/cloudflare/bucket.js.map +1 -1
  21. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +1 -1
  22. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -1
  23. package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -1
  24. package/lib/cloudflare/cloudflare-env-proxy.js +3 -3
  25. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
  26. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  27. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  28. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  29. package/lib/cloudflare/d1-database.js +2 -1
  30. package/lib/cloudflare/d1-database.js.map +1 -1
  31. package/lib/cloudflare/d1-local-migrations.d.ts +1 -0
  32. package/lib/cloudflare/d1-local-migrations.d.ts.map +1 -1
  33. package/lib/cloudflare/d1-local-migrations.js +3 -2
  34. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  35. package/lib/cloudflare/hyperdrive-ref.d.ts +23 -0
  36. package/lib/cloudflare/hyperdrive-ref.d.ts.map +1 -0
  37. package/lib/cloudflare/hyperdrive-ref.js +52 -0
  38. package/lib/cloudflare/hyperdrive-ref.js.map +1 -0
  39. package/lib/cloudflare/index.d.ts +2 -0
  40. package/lib/cloudflare/index.d.ts.map +1 -1
  41. package/lib/cloudflare/index.js +2 -0
  42. package/lib/cloudflare/index.js.map +1 -1
  43. package/lib/cloudflare/kv-namespace.js +1 -1
  44. package/lib/cloudflare/kv-namespace.js.map +1 -1
  45. package/lib/cloudflare/miniflare/build-worker-options.d.ts +2 -1
  46. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  47. package/lib/cloudflare/miniflare/build-worker-options.js +11 -6
  48. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  49. package/lib/cloudflare/miniflare/delete.d.ts +3 -2
  50. package/lib/cloudflare/miniflare/delete.d.ts.map +1 -1
  51. package/lib/cloudflare/miniflare/delete.js +13 -12
  52. package/lib/cloudflare/miniflare/delete.js.map +1 -1
  53. package/lib/cloudflare/miniflare/miniflare-controller.d.ts +3 -1
  54. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  55. package/lib/cloudflare/miniflare/miniflare-controller.js +29 -13
  56. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  57. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +24 -13
  58. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  59. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +95 -79
  60. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  61. package/lib/cloudflare/miniflare/paths.d.ts +4 -4
  62. package/lib/cloudflare/miniflare/paths.d.ts.map +1 -1
  63. package/lib/cloudflare/miniflare/paths.js +10 -4
  64. package/lib/cloudflare/miniflare/paths.js.map +1 -1
  65. package/lib/cloudflare/miniflare/symlink-miniflare-state.d.ts +2 -0
  66. package/lib/cloudflare/miniflare/symlink-miniflare-state.d.ts.map +1 -0
  67. package/lib/cloudflare/miniflare/symlink-miniflare-state.js +19 -0
  68. package/lib/cloudflare/miniflare/symlink-miniflare-state.js.map +1 -0
  69. package/lib/cloudflare/miniflare/tunnel.d.ts +18 -0
  70. package/lib/cloudflare/miniflare/tunnel.d.ts.map +1 -0
  71. package/lib/cloudflare/miniflare/tunnel.js +79 -0
  72. package/lib/cloudflare/miniflare/tunnel.js.map +1 -0
  73. package/lib/cloudflare/next.d.ts.map +1 -1
  74. package/lib/cloudflare/next.js +3 -0
  75. package/lib/cloudflare/next.js.map +1 -1
  76. package/lib/cloudflare/nuxt/plugin.d.ts.map +1 -1
  77. package/lib/cloudflare/nuxt/plugin.js +3 -3
  78. package/lib/cloudflare/nuxt/plugin.js.map +1 -1
  79. package/lib/cloudflare/quick-tunnel.d.ts +1 -1
  80. package/lib/cloudflare/redwood/plugin.js +2 -2
  81. package/lib/cloudflare/redwood/plugin.js.map +1 -1
  82. package/lib/cloudflare/secret-ref.d.ts +38 -0
  83. package/lib/cloudflare/secret-ref.d.ts.map +1 -0
  84. package/lib/cloudflare/secret-ref.js +38 -0
  85. package/lib/cloudflare/secret-ref.js.map +1 -0
  86. package/lib/cloudflare/vite/plugin.d.ts.map +1 -1
  87. package/lib/cloudflare/vite/plugin.js +6 -3
  88. package/lib/cloudflare/vite/plugin.js.map +1 -1
  89. package/lib/cloudflare/website.d.ts.map +1 -1
  90. package/lib/cloudflare/website.js +5 -16
  91. package/lib/cloudflare/website.js.map +1 -1
  92. package/lib/cloudflare/worker-metadata.d.ts +1 -1
  93. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  94. package/lib/cloudflare/worker-metadata.js +7 -9
  95. package/lib/cloudflare/worker-metadata.js.map +1 -1
  96. package/lib/cloudflare/worker.d.ts +16 -1
  97. package/lib/cloudflare/worker.d.ts.map +1 -1
  98. package/lib/cloudflare/worker.js +16 -2
  99. package/lib/cloudflare/worker.js.map +1 -1
  100. package/lib/cloudflare/wrangler.json.d.ts +2 -1
  101. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  102. package/lib/cloudflare/wrangler.json.js +5 -6
  103. package/lib/cloudflare/wrangler.json.js.map +1 -1
  104. package/lib/llms.cloudflare.txt +1 -1
  105. package/lib/scope.d.ts +26 -1
  106. package/lib/scope.d.ts.map +1 -1
  107. package/lib/scope.js +12 -5
  108. package/lib/scope.js.map +1 -1
  109. package/lib/state/file-system-state-store.d.ts.map +1 -1
  110. package/lib/state/file-system-state-store.js +1 -2
  111. package/lib/state/file-system-state-store.js.map +1 -1
  112. package/lib/state/sqlite-state-store.d.ts +11 -0
  113. package/lib/state/sqlite-state-store.d.ts.map +1 -1
  114. package/lib/state/sqlite-state-store.js +22 -14
  115. package/lib/state/sqlite-state-store.js.map +1 -1
  116. package/lib/stripe/customer.d.ts +0 -8
  117. package/lib/stripe/customer.d.ts.map +1 -1
  118. package/lib/stripe/customer.js +0 -6
  119. package/lib/stripe/customer.js.map +1 -1
  120. package/lib/stripe/price.d.ts +23 -23
  121. package/lib/stripe/price.d.ts.map +1 -1
  122. package/lib/stripe/price.js +6 -22
  123. package/lib/stripe/price.js.map +1 -1
  124. package/lib/test/vitest.d.ts.map +1 -1
  125. package/lib/test/vitest.js +1 -1
  126. package/lib/test/vitest.js.map +1 -1
  127. package/lib/util/assert-never.js +1 -1
  128. package/lib/util/assert-never.js.map +1 -1
  129. package/lib/util/cli.d.ts.map +1 -1
  130. package/lib/util/cli.js +12 -13
  131. package/lib/util/cli.js.map +1 -1
  132. package/lib/util/idempotent-spawn.d.ts.map +1 -1
  133. package/lib/util/idempotent-spawn.js +5 -8
  134. package/lib/util/idempotent-spawn.js.map +1 -1
  135. package/lib/util/root-dir.d.ts +2 -0
  136. package/lib/util/root-dir.d.ts.map +1 -0
  137. package/lib/util/root-dir.js +2 -0
  138. package/lib/util/root-dir.js.map +1 -0
  139. package/package.json +4 -4
  140. package/src/alchemy.ts +43 -8
  141. package/src/apply.ts +58 -3
  142. package/src/cloudflare/bindings.ts +21 -4
  143. package/src/cloudflare/bound.ts +50 -52
  144. package/src/cloudflare/bucket.ts +1 -1
  145. package/src/cloudflare/bundle/internal-worker-bundle.ts +2 -1
  146. package/src/cloudflare/cloudflare-env-proxy.ts +5 -5
  147. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  148. package/src/cloudflare/d1-database.ts +2 -1
  149. package/src/cloudflare/d1-local-migrations.ts +10 -6
  150. package/src/cloudflare/hyperdrive-ref.ts +98 -0
  151. package/src/cloudflare/index.ts +2 -0
  152. package/src/cloudflare/kv-namespace.ts +1 -1
  153. package/src/cloudflare/miniflare/build-worker-options.ts +21 -12
  154. package/src/cloudflare/miniflare/delete.ts +19 -11
  155. package/src/cloudflare/miniflare/miniflare-controller.ts +33 -14
  156. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +122 -102
  157. package/src/cloudflare/miniflare/paths.ts +11 -7
  158. package/src/cloudflare/miniflare/symlink-miniflare-state.ts +19 -0
  159. package/src/cloudflare/miniflare/tunnel.ts +116 -0
  160. package/src/cloudflare/next.ts +3 -0
  161. package/src/cloudflare/nuxt/plugin.ts +12 -3
  162. package/src/cloudflare/redwood/plugin.ts +2 -2
  163. package/src/cloudflare/secret-ref.ts +56 -0
  164. package/src/cloudflare/vite/plugin.ts +8 -3
  165. package/src/cloudflare/website.ts +5 -19
  166. package/src/cloudflare/worker-metadata.ts +14 -12
  167. package/src/cloudflare/worker.ts +36 -3
  168. package/src/cloudflare/wrangler.json.ts +14 -11
  169. package/src/llms.cloudflare.txt +1 -1
  170. package/src/scope.ts +38 -6
  171. package/src/state/file-system-state-store.ts +1 -2
  172. package/src/state/sqlite-state-store.ts +41 -14
  173. package/src/stripe/customer.ts +0 -14
  174. package/src/stripe/price.ts +59 -56
  175. package/src/test/vitest.ts +4 -1
  176. package/src/util/assert-never.ts +1 -1
  177. package/src/util/cli.ts +15 -13
  178. package/src/util/idempotent-spawn.ts +5 -6
  179. package/src/util/root-dir.ts +1 -0
  180. package/templates/nextjs/alchemy.run.ts +2 -2
  181. package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +1 -1
  182. package/templates/rwsdk/package.json +3 -3
  183. package/workers/cloudflare-state-store.js +1 -1
  184. package/workers/tunnel-proxy.js +110 -0
  185. package/workers/tunnel-proxy.ts +121 -0
@@ -4,41 +4,60 @@ import { Resource } from "../resource.ts";
4
4
  import type { Secret } from "../secret.ts";
5
5
  import { logger } from "../util/logger.ts";
6
6
  import { createStripeClient, handleStripeDeleteError } from "./client.ts";
7
+ import type { Meter } from "./meter.ts";
7
8
 
8
9
  /**
9
10
  * Properties for price recurring configuration
10
11
  */
11
- export interface PriceRecurring {
12
- /**
13
- * Specifies billing frequency. Either 'day', 'week', 'month' or 'year'.
14
- */
15
- interval: Stripe.PriceCreateParams.Recurring.Interval;
16
-
17
- /**
18
- * The number of intervals between subscription billings. For example, interval=month and interval_count=3 bills every 3 months.
19
- */
20
- intervalCount?: number;
21
-
22
- /**
23
- * Configures how the quantity per period should be determined, can be either 'metered' or 'licensed'.
24
- * 'licensed' will automatically bill the quantity set for a plan when adding it to a subscription,
25
- * 'metered' will aggregate the total usage based on usage records.
26
- */
27
- usageType?: Stripe.PriceCreateParams.Recurring.UsageType;
28
-
29
- /**
30
- * Specifies a usage aggregation strategy for prices of `usage_type=metered`. Allowed values are `sum` for summing up all usage during a period,
31
- * `last_during_period` for picking the last usage record reported within a period,
32
- * `last_ever` for picking the last usage record ever (across period bounds) or `max` which picks the usage record with the maximum reported usage during a period.
33
- */
34
- aggregateUsage?: Stripe.PriceCreateParams.Recurring.AggregateUsage;
35
-
36
- /**
37
- * The ID of the billing meter this price is associated with.
38
- * Only applicable when usageType = 'metered'.
39
- */
40
- meter?: string;
41
- }
12
+ export type PriceRecurring =
13
+ // If usageType is 'metered', meter is required
14
+ | {
15
+ /**
16
+ * Specifies billing frequency. Either 'day', 'week', 'month' or 'year'.
17
+ */
18
+ interval: Stripe.PriceCreateParams.Recurring.Interval;
19
+
20
+ /**
21
+ * The number of intervals between subscription billings. For example, interval=month and interval_count=3 bills every 3 months.
22
+ */
23
+ intervalCount?: number;
24
+
25
+ /**
26
+ * Configures how the quantity per period should be determined, can be either 'metered' or 'licensed'.
27
+ * 'metered' will aggregate the total usage based on usage records.
28
+ */
29
+ usageType: "metered";
30
+
31
+ /**
32
+ * The ID of the billing meter this price is associated with.
33
+ * Required when usageType = 'metered'.
34
+ */
35
+ meter: string | Meter;
36
+ }
37
+ // If usageType is 'licensed' or not provided, meter is not allowed
38
+ | {
39
+ /**
40
+ * Specifies billing frequency. Either 'day', 'week', 'month' or 'year'.
41
+ */
42
+ interval: Stripe.PriceCreateParams.Recurring.Interval;
43
+
44
+ /**
45
+ * The number of intervals between subscription billings. For example, interval=month and interval_count=3 bills every 3 months.
46
+ */
47
+ intervalCount?: number;
48
+
49
+ /**
50
+ * Configures how the quantity per period should be determined, can be either 'metered' or 'licensed'.
51
+ * 'licensed' will automatically bill the quantity set for a plan when adding it to a subscription.
52
+ */
53
+ usageType?: "licensed" | undefined;
54
+
55
+ /**
56
+ * The ID of the billing meter this price is associated with.
57
+ * Not applicable when usageType is not 'metered'.
58
+ */
59
+ meter?: never;
60
+ };
42
61
 
43
62
  type TaxBehavior = Stripe.PriceCreateParams.TaxBehavior;
44
63
  type BillingScheme = Stripe.PriceCreateParams.BillingScheme;
@@ -262,19 +281,6 @@ export interface Price extends Resource<"stripe::Price">, PriceProps {
262
281
  * });
263
282
  *
264
283
  * @example
265
- * // Create a metered price for usage-based billing
266
- * const meteredPrice = await Price("storage", {
267
- * currency: "usd",
268
- * unitAmount: 25, // $0.25 per GB
269
- * product: "prod_xyz",
270
- * recurring: {
271
- * interval: "month",
272
- * usageType: "metered",
273
- * aggregateUsage: "sum"
274
- * }
275
- * });
276
- *
277
- * @example
278
284
  * // Create a graduated tiered price with usage-based billing
279
285
  * const tieredPrice = await Price("api-usage", {
280
286
  * currency: "usd",
@@ -428,22 +434,21 @@ export const Price = Resource(
428
434
  interval: props.recurring.interval,
429
435
  interval_count: props.recurring.intervalCount,
430
436
  usage_type: props.recurring.usageType,
431
- aggregate_usage: props.recurring.aggregateUsage,
432
437
  };
433
438
 
434
439
  // Add meter to recurring if present (only for metered usage type)
435
- if (props.recurring.meter) {
436
- if (props.recurring.usageType !== "metered") {
437
- throw new Error(
438
- "Meter can only be set for prices with recurring.usageType = 'metered'",
439
- );
440
- }
441
- // Extend the recurring params with meter
440
+ if (
441
+ props.recurring.usageType === "metered" &&
442
+ props.recurring.meter
443
+ ) {
442
444
  (
443
445
  createParams.recurring as Stripe.PriceCreateParams.Recurring & {
444
446
  meter: string;
445
447
  }
446
- ).meter = props.recurring.meter;
448
+ ).meter =
449
+ typeof props.recurring.meter === "string"
450
+ ? props.recurring.meter
451
+ : props.recurring.meter.id;
447
452
  }
448
453
  }
449
454
 
@@ -514,8 +519,6 @@ export const Price = Resource(
514
519
  intervalCount: price.recurring.interval_count,
515
520
  usageType: price.recurring
516
521
  .usage_type as Stripe.PriceCreateParams.Recurring.UsageType,
517
- aggregateUsage: price.recurring
518
- .aggregate_usage as Stripe.PriceCreateParams.Recurring.AggregateUsage,
519
522
  meter: (
520
523
  price.recurring as Stripe.Price.Recurring & { meter?: string }
521
524
  ).meter,
@@ -552,7 +555,7 @@ export const Price = Resource(
552
555
  active: price.active,
553
556
  billingScheme: price.billing_scheme as BillingScheme,
554
557
  nickname: price.nickname || undefined,
555
- recurring: recurring,
558
+ recurring: recurring as PriceRecurring,
556
559
  metadata: price.metadata || undefined,
557
560
  taxBehavior: price.tax_behavior as TaxBehavior,
558
561
  createdAt: price.created,
@@ -112,7 +112,10 @@ export function test(
112
112
  return new D1StateStore(scope);
113
113
  default:
114
114
  return new SQLiteStateStore(scope, {
115
- filename: `.alchemy/${path.relative(process.cwd(), meta.filename)}.sqlite`,
115
+ filename: path.join(
116
+ scope.dotAlchemy,
117
+ `${path.relative(process.cwd(), meta.filename)}.sqlite`,
118
+ ),
116
119
  });
117
120
  }
118
121
  };
@@ -1,3 +1,3 @@
1
1
  export function assertNever(value: never, message?: string): never {
2
- throw new Error(message ?? `Unexpected value: ${value}`);
2
+ throw new Error(message ?? `Unexpected value: ${JSON.stringify(value)}`);
3
3
  }
package/src/util/cli.ts CHANGED
@@ -55,16 +55,16 @@ interface AlchemyInfo {
55
55
  appName: string;
56
56
  }
57
57
 
58
- let loggerApi: LoggerApi | null = null;
58
+ let loggers: {
59
+ [appName: string]: LoggerApi;
60
+ } = {};
61
+
59
62
  export const createLoggerInstance = (
60
63
  alchemyInfo: AlchemyInfo,
61
64
  customLogger?: LoggerApi,
62
65
  ) => {
63
- if (loggerApi) return loggerApi;
64
-
65
- // Use custom logger if provided, otherwise use the basic fallback logger
66
- loggerApi = customLogger ?? createFallbackLogger(alchemyInfo);
67
- return loggerApi;
66
+ return (loggers[alchemyInfo.appName] ??=
67
+ customLogger ?? createFallbackLogger(alchemyInfo));
68
68
  };
69
69
 
70
70
  export const createDummyLogger = (): LoggerApi => {
@@ -79,13 +79,15 @@ export const createDummyLogger = (): LoggerApi => {
79
79
  };
80
80
 
81
81
  export const createFallbackLogger = (alchemyInfo: AlchemyInfo): LoggerApi => {
82
- console.log(dedent`
83
- ${colorize("Alchemy", "cyanBright")} (v${packageJson.version})
84
- App: ${alchemyInfo.appName}
85
- Phase: ${alchemyInfo.phase}
86
- Stage: ${alchemyInfo.stage}
87
-
88
- `);
82
+ if (alchemyInfo.phase !== "read") {
83
+ console.log(dedent`
84
+ ${colorize("Alchemy", "cyanBright")} (v${packageJson.version})
85
+ App: ${alchemyInfo.appName}
86
+ Phase: ${alchemyInfo.phase}
87
+ Stage: ${alchemyInfo.stage}
88
+
89
+ `);
90
+ }
89
91
 
90
92
  return {
91
93
  log: console.log,
@@ -161,7 +161,7 @@ export async function idempotentSpawn({
161
161
  {
162
162
  write,
163
163
  chunkSize = 64 * 1024,
164
- // tickMs = 100,
164
+ tickMs = 100,
165
165
  }: {
166
166
  stateKey: string;
167
167
  write: (buf: Buffer) => boolean;
@@ -229,15 +229,14 @@ export async function idempotentSpawn({
229
229
  await drain();
230
230
  });
231
231
 
232
- // TODO(sam): do we need this?
233
- // const tick = setInterval(() => {
234
- // drain().catch(() => {});
235
- // }, tickMs);
232
+ const tick = setInterval(() => {
233
+ drain().catch(() => {});
234
+ }, tickMs);
236
235
 
237
236
  return async function stop() {
238
237
  closed = true;
239
238
  watcher.close();
240
- // clearInterval(tick);
239
+ clearInterval(tick);
241
240
  await fh.close().catch(() => {});
242
241
  await persist();
243
242
  };
@@ -0,0 +1 @@
1
+ export const ALCHEMY_ROOT = process.env.ALCHEMY_ROOT ?? process.cwd();
@@ -5,12 +5,12 @@ import { Nextjs } from "alchemy/cloudflare";
5
5
 
6
6
  const app = await alchemy("{projectName}");
7
7
 
8
- export const website = await Nextjs("website", {
8
+ export const worker = await Nextjs("website", {
9
9
  name: `${app.name}-${app.stage}-website`,
10
10
  });
11
11
 
12
12
  console.log({
13
- url: website.url,
13
+ url: worker.url,
14
14
  });
15
15
 
16
16
  await app.finalize();
@@ -274,7 +274,7 @@ const worker = await Worker("my-worker", {
274
274
  ### 2. Enable Development Compatibility
275
275
  ```typescript
276
276
  // Generate wrangler.json for local development
277
- await WranglerJson("wrangler.json", {
277
+ await WranglerJson({
278
278
  worker,
279
279
  });
280
280
  ```
@@ -27,8 +27,8 @@
27
27
  "worker:run": "rw-scripts worker-run"
28
28
  },
29
29
  "dependencies": {
30
- "@prisma/adapter-d1": "~6.8.2",
31
- "@prisma/client": "~6.8.2",
30
+ "@prisma/adapter-d1": "~6.16.1",
31
+ "@prisma/client": "~6.16.1",
32
32
  "@simplewebauthn/browser": "^13.1.0",
33
33
  "@simplewebauthn/server": "^13.1.1",
34
34
  "rwsdk": "^0.1.35"
@@ -39,7 +39,7 @@
39
39
  "@types/node": "^22.14.0",
40
40
  "@types/react": "^19.1.2",
41
41
  "@types/react-dom": "^19.1.2",
42
- "prisma": "~6.8.2",
42
+ "prisma": "~6.16.1",
43
43
  "typescript": "^5.8.3",
44
44
  "vibe-rules": "^0.2.31",
45
45
  "alchemy": "workspace:*",
@@ -5397,7 +5397,7 @@ var migrations_default = {
5397
5397
 
5398
5398
  // src/util/assert-never.ts
5399
5399
  function assertNever(value, message) {
5400
- throw new Error(message ?? `Unexpected value: ${value}`);
5400
+ throw new Error(message ?? `Unexpected value: ${JSON.stringify(value)}`);
5401
5401
  }
5402
5402
 
5403
5403
  // src/state/schema.ts
@@ -0,0 +1,110 @@
1
+ // workers/tunnel-proxy.ts
2
+ var tunnel_proxy_default = {
3
+ async fetch(request, env) {
4
+ const url = new URL(request.url);
5
+ url.host = env.TUNNEL_HOST;
6
+ const headers = new Headers(request.headers);
7
+ headers.set("alchemy-worker-name", env.WORKER_NAME);
8
+ const response = await fetch(url, {
9
+ method: request.method,
10
+ headers,
11
+ body: request.body,
12
+ redirect: "manual"
13
+ });
14
+ if (response.status === 530) {
15
+ const error = await extractResponseError(response);
16
+ const headers2 = new Headers(response.headers);
17
+ headers2.set("Content-Type", "text/html");
18
+ return new Response(
19
+ renderErrorHtml({
20
+ name: env.WORKER_NAME,
21
+ error,
22
+ rayId: response.headers.get("cf-ray") ?? "Unknown"
23
+ }),
24
+ {
25
+ status: 530,
26
+ headers: headers2
27
+ }
28
+ );
29
+ }
30
+ return response;
31
+ }
32
+ };
33
+ var extractResponseError = async (response) => {
34
+ const error = response.headers.get("Alchemy-Error");
35
+ if (error) {
36
+ return error;
37
+ }
38
+ let code;
39
+ let message;
40
+ const { promise, resolve } = Promise.withResolvers();
41
+ const rewriter = new HTMLRewriter();
42
+ rewriter.on("h1", {
43
+ text(text) {
44
+ if (code) return;
45
+ const match = text.text.match(/(\d+)/);
46
+ if (match) {
47
+ code = match[1];
48
+ }
49
+ }
50
+ });
51
+ rewriter.on("h2", {
52
+ text(text) {
53
+ if (message) return;
54
+ message = text.text;
55
+ }
56
+ });
57
+ rewriter.onDocument({
58
+ end() {
59
+ resolve([code, message].filter(Boolean).join(": ") || "Unknown");
60
+ }
61
+ });
62
+ await rewriter.transform(response).text();
63
+ return promise;
64
+ };
65
+ var renderErrorHtml = (props) => `
66
+ <!DOCTYPE html>
67
+ <html lang="en">
68
+
69
+ <head>
70
+ <title>Alchemy | Tunnel Error</title>
71
+ <meta charset="utf-8">
72
+ <meta name="viewport" content="width=device-width, initial-scale=1">
73
+ <link rel="shortcut icon" href="https://alchemy.run/potion.png" type="image/png" />
74
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"><\/script>
75
+ </head>
76
+
77
+ <body class="font-sans bg-slate-100 text-slate-950">
78
+ <div class="flex flex-col items-center justify-center h-screen">
79
+ <div class="border-t-3 border-red-600 px-5 py-7 bg-white gap-3 flex flex-col w-full max-w-lg">
80
+ <h1 class="text-2xl font-semibold">530: Tunnel Error</h1>
81
+ <p class="text-slate-800">The worker "${props.name}" could not be reached.</p>
82
+ <p class="text-slate-800">Please ensure that <code
83
+ class="bg-slate-100 text-slate-800 p-1 rounded-md text-sm">alchemy dev</code>
84
+ is running and tunnel mode is enabled for the worker.</p>
85
+ <div class="flex flex-col gap-2 border-l-2 border-slate-300 pl-3 my-1.5">
86
+ <div class="text-sm flex flex-col gap-1">
87
+ <div class="text-slate-500">Error:</div>
88
+ <div class="text-slate-800 font-mono">${props.error}</div>
89
+ </div>
90
+ <div class="text-sm flex flex-col gap-1">
91
+ <div class="text-slate-500">Ray ID:</div>
92
+ <div class="text-slate-800 font-mono">${props.rayId}</div>
93
+ </div>
94
+ </div>
95
+ <p class="text-slate-800"><a href="https://alchemy.run/docs/guides/cloudflare-tunnel"
96
+ class="text-slate-800 underline underline-offset-2 hover:no-underline" target="_blank">Learn more about tunnel
97
+ mode in
98
+ Alchemy</a>.</p>
99
+ </div>
100
+ <div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
101
+ <p class="text-sm text-slate-500">Alchemy ${"0.67.0"}</p>
102
+ </div>
103
+ </div>
104
+ </body>
105
+
106
+ </html>
107
+ `;
108
+ export {
109
+ tunnel_proxy_default as default
110
+ };
@@ -0,0 +1,121 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+
3
+ interface Env {
4
+ WORKER_NAME: string;
5
+ TUNNEL_HOST: string;
6
+ }
7
+
8
+ declare const ALCHEMY_VERSION: string; // injected during `bun run build:workers`
9
+
10
+ export default {
11
+ async fetch(request: Request, env: Env) {
12
+ const url = new URL(request.url);
13
+ url.host = env.TUNNEL_HOST;
14
+ const headers = new Headers(request.headers);
15
+ headers.set("alchemy-worker-name", env.WORKER_NAME);
16
+ const response = await fetch(url, {
17
+ method: request.method,
18
+ headers,
19
+ body: request.body,
20
+ redirect: "manual",
21
+ });
22
+ if (response.status === 530) {
23
+ const error = await extractResponseError(response);
24
+ const headers = new Headers(response.headers);
25
+ headers.set("Content-Type", "text/html");
26
+ return new Response(
27
+ renderErrorHtml({
28
+ name: env.WORKER_NAME,
29
+ error,
30
+ rayId: response.headers.get("cf-ray") ?? "Unknown",
31
+ }),
32
+ {
33
+ status: 530,
34
+ headers,
35
+ },
36
+ );
37
+ }
38
+ return response;
39
+ },
40
+ } as ExportedHandler<Env>;
41
+
42
+ const extractResponseError = async (response: Response) => {
43
+ const error = response.headers.get("Alchemy-Error");
44
+ if (error) {
45
+ return error;
46
+ }
47
+ let code: string | undefined;
48
+ let message: string | undefined;
49
+ const { promise, resolve } = Promise.withResolvers<string>();
50
+ const rewriter = new HTMLRewriter();
51
+ rewriter.on("h1", {
52
+ text(text) {
53
+ if (code) return;
54
+ const match = text.text.match(/(\d+)/);
55
+ if (match) {
56
+ code = match[1];
57
+ }
58
+ },
59
+ });
60
+ rewriter.on("h2", {
61
+ text(text) {
62
+ if (message) return;
63
+ message = text.text;
64
+ },
65
+ });
66
+ rewriter.onDocument({
67
+ end() {
68
+ resolve([code, message].filter(Boolean).join(": ") || "Unknown");
69
+ },
70
+ });
71
+ await rewriter.transform(response).text(); // consume response body so rewriter runs
72
+ return promise;
73
+ };
74
+
75
+ const renderErrorHtml = (props: {
76
+ name: string;
77
+ error: string;
78
+ rayId: string;
79
+ }) => `
80
+ <!DOCTYPE html>
81
+ <html lang="en">
82
+
83
+ <head>
84
+ <title>Alchemy | Tunnel Error</title>
85
+ <meta charset="utf-8">
86
+ <meta name="viewport" content="width=device-width, initial-scale=1">
87
+ <link rel="shortcut icon" href="https://alchemy.run/potion.png" type="image/png" />
88
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
89
+ </head>
90
+
91
+ <body class="font-sans bg-slate-100 text-slate-950">
92
+ <div class="flex flex-col items-center justify-center h-screen">
93
+ <div class="border-t-3 border-red-600 px-5 py-7 bg-white gap-3 flex flex-col w-full max-w-lg">
94
+ <h1 class="text-2xl font-semibold">530: Tunnel Error</h1>
95
+ <p class="text-slate-800">The worker "${props.name}" could not be reached.</p>
96
+ <p class="text-slate-800">Please ensure that <code
97
+ class="bg-slate-100 text-slate-800 p-1 rounded-md text-sm">alchemy dev</code>
98
+ is running and tunnel mode is enabled for the worker.</p>
99
+ <div class="flex flex-col gap-2 border-l-2 border-slate-300 pl-3 my-1.5">
100
+ <div class="text-sm flex flex-col gap-1">
101
+ <div class="text-slate-500">Error:</div>
102
+ <div class="text-slate-800 font-mono">${props.error}</div>
103
+ </div>
104
+ <div class="text-sm flex flex-col gap-1">
105
+ <div class="text-slate-500">Ray ID:</div>
106
+ <div class="text-slate-800 font-mono">${props.rayId}</div>
107
+ </div>
108
+ </div>
109
+ <p class="text-slate-800"><a href="https://alchemy.run/docs/guides/cloudflare-tunnel"
110
+ class="text-slate-800 underline underline-offset-2 hover:no-underline" target="_blank">Learn more about tunnel
111
+ mode in
112
+ Alchemy</a>.</p>
113
+ </div>
114
+ <div class="bg-slate-200 px-5 py-3 flex flex-col w-full max-w-lg">
115
+ <p class="text-sm text-slate-500">Alchemy ${ALCHEMY_VERSION}</p>
116
+ </div>
117
+ </div>
118
+ </body>
119
+
120
+ </html>
121
+ `;