alchemy 0.48.3 → 0.49.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 (90) hide show
  1. package/bin/alchemy.mjs +13 -13
  2. package/bin/commands/create.ts +0 -15
  3. package/bin/commands/deploy.ts +2 -0
  4. package/bin/commands/dev.ts +10 -1
  5. package/bin/services/execute-alchemy.ts +9 -0
  6. package/lib/alchemy.d.ts +6 -0
  7. package/lib/alchemy.d.ts.map +1 -1
  8. package/lib/alchemy.js +1 -0
  9. package/lib/alchemy.js.map +1 -1
  10. package/lib/apply.js +13 -12
  11. package/lib/apply.js.map +1 -1
  12. package/lib/cloudflare/bindings.d.ts +21 -2
  13. package/lib/cloudflare/bindings.d.ts.map +1 -1
  14. package/lib/cloudflare/bindings.js.map +1 -1
  15. package/lib/cloudflare/bound.d.ts +8 -1
  16. package/lib/cloudflare/bound.d.ts.map +1 -1
  17. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  18. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  19. package/lib/cloudflare/index.d.ts +1 -0
  20. package/lib/cloudflare/index.d.ts.map +1 -1
  21. package/lib/cloudflare/index.js +1 -0
  22. package/lib/cloudflare/index.js.map +1 -1
  23. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
  24. package/lib/cloudflare/miniflare/miniflare-worker-options.js +4 -0
  25. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
  26. package/lib/cloudflare/rate-limit.d.ts +63 -0
  27. package/lib/cloudflare/rate-limit.d.ts.map +1 -0
  28. package/lib/cloudflare/rate-limit.js +41 -0
  29. package/lib/cloudflare/rate-limit.js.map +1 -0
  30. package/lib/cloudflare/tanstack-start.d.ts.map +1 -1
  31. package/lib/cloudflare/tanstack-start.js +10 -1
  32. package/lib/cloudflare/tanstack-start.js.map +1 -1
  33. package/lib/cloudflare/website.d.ts +4 -0
  34. package/lib/cloudflare/website.d.ts.map +1 -1
  35. package/lib/cloudflare/website.js +14 -1
  36. package/lib/cloudflare/website.js.map +1 -1
  37. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  38. package/lib/cloudflare/worker-metadata.js +8 -0
  39. package/lib/cloudflare/worker-metadata.js.map +1 -1
  40. package/lib/cloudflare/worker.js +1 -1
  41. package/lib/cloudflare/worker.js.map +1 -1
  42. package/lib/cloudflare/wrangler.json.d.ts +11 -0
  43. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  44. package/lib/cloudflare/wrangler.json.js +15 -1
  45. package/lib/cloudflare/wrangler.json.js.map +1 -1
  46. package/lib/context.d.ts +2 -2
  47. package/lib/context.d.ts.map +1 -1
  48. package/lib/planetscale/password.js +1 -1
  49. package/lib/planetscale/password.js.map +1 -1
  50. package/lib/scope.d.ts +7 -0
  51. package/lib/scope.d.ts.map +1 -1
  52. package/lib/scope.js +3 -1
  53. package/lib/scope.js.map +1 -1
  54. package/lib/test/bun.d.ts +1 -24
  55. package/lib/test/bun.d.ts.map +1 -1
  56. package/lib/test/bun.js.map +1 -1
  57. package/lib/test/options.d.ts +30 -0
  58. package/lib/test/options.d.ts.map +1 -0
  59. package/lib/test/options.js +1 -0
  60. package/lib/test/options.js.map +1 -0
  61. package/lib/test/vitest.d.ts +1 -24
  62. package/lib/test/vitest.d.ts.map +1 -1
  63. package/lib/test/vitest.js.map +1 -1
  64. package/lib/util/ignore-matcher.d.ts.map +1 -1
  65. package/lib/util/ignore-matcher.js +1 -3
  66. package/lib/util/ignore-matcher.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/alchemy.ts +7 -0
  69. package/src/apply.ts +14 -13
  70. package/src/cloudflare/bindings.ts +22 -0
  71. package/src/cloudflare/bound.ts +29 -22
  72. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  73. package/src/cloudflare/index.ts +1 -0
  74. package/src/cloudflare/miniflare/miniflare-worker-options.ts +4 -0
  75. package/src/cloudflare/rate-limit.ts +70 -0
  76. package/src/cloudflare/tanstack-start.ts +10 -1
  77. package/src/cloudflare/website.ts +20 -1
  78. package/src/cloudflare/worker-metadata.ts +7 -0
  79. package/src/cloudflare/worker.ts +1 -1
  80. package/src/cloudflare/wrangler.json.ts +30 -1
  81. package/src/context.ts +2 -2
  82. package/src/planetscale/password.ts +1 -1
  83. package/src/scope.ts +9 -1
  84. package/src/test/bun.ts +1 -28
  85. package/src/test/options.ts +34 -0
  86. package/src/test/vitest.ts +1 -28
  87. package/src/util/ignore-matcher.ts +0 -1
  88. package/templates/tanstack-start/alchemy.run.ts +1 -3
  89. package/templates/tanstack-start/package.json +3 -3
  90. package/templates/tanstack-start/vite.config.ts +26 -25
package/src/apply.ts CHANGED
@@ -113,7 +113,8 @@ async function _apply<Out extends Resource>(
113
113
  });
114
114
  if (
115
115
  JSON.stringify(oldProps) === JSON.stringify(newProps) &&
116
- alwaysUpdate !== true
116
+ alwaysUpdate !== true &&
117
+ !scope.force
117
118
  ) {
118
119
  scope.skip();
119
120
  if (!quiet) {
@@ -174,7 +175,7 @@ async function _apply<Out extends Resource>(
174
175
  props: state.oldProps,
175
176
  state,
176
177
  isReplacement: false,
177
- replace: async (force?: boolean) => {
178
+ replace: (force?: boolean) => {
178
179
  if (phase === "create") {
179
180
  throw new Error(
180
181
  `Resource ${resource[ResourceKind]} ${resource[ResourceFQN]} cannot be replaced in create phase.`,
@@ -186,17 +187,6 @@ async function _apply<Out extends Resource>(
186
187
  );
187
188
  }
188
189
 
189
- if (force) {
190
- await destroy(resource, {
191
- quiet: scope.quiet,
192
- strategy: "sequential",
193
- replace: {
194
- props: state.oldProps,
195
- output: resource,
196
- },
197
- });
198
- }
199
-
200
190
  isReplaced = true;
201
191
  throw new ReplacedSignal(force);
202
192
  },
@@ -223,6 +213,17 @@ async function _apply<Out extends Resource>(
223
213
  );
224
214
  }
225
215
 
216
+ if (error.force) {
217
+ await destroy(resource, {
218
+ quiet: scope.quiet,
219
+ strategy: "sequential",
220
+ replace: {
221
+ props: state.oldProps,
222
+ output: resource,
223
+ },
224
+ });
225
+ }
226
+
226
227
  output = await alchemy.run(
227
228
  resource[ResourceID],
228
229
  { isResource: true, parent: scope },
@@ -20,6 +20,7 @@ import type { Images } from "./images.ts";
20
20
  import type { KVNamespaceResource } from "./kv-namespace.ts";
21
21
  import type { PipelineResource } from "./pipeline.ts";
22
22
  import type { QueueResource } from "./queue.ts";
23
+ import type { RateLimit } from "./rate-limit.ts";
23
24
  import type { SecretKey } from "./secret-key.ts";
24
25
  import type { Secret as CloudflareSecret } from "./secret.ts";
25
26
  import type { VectorizeIndexResource } from "./vectorize-index.ts";
@@ -56,6 +57,7 @@ export type Binding =
56
57
  | KVNamespaceResource
57
58
  | PipelineResource
58
59
  | QueueResource
60
+ | RateLimit
59
61
  | R2BucketResource
60
62
  | {
61
63
  type: "kv_namespace";
@@ -108,6 +110,7 @@ export type WorkerBindingSpec =
108
110
  | WorkerBindingPipeline
109
111
  | WorkerBindingPlainText
110
112
  | WorkerBindingQueue
113
+ | WorkerBindingRateLimit
111
114
  | WorkerBindingR2Bucket
112
115
  | WorkerBindingSecretKey
113
116
  | WorkerBindingSecretText
@@ -284,6 +287,25 @@ export interface WorkerBindingQueue {
284
287
  queue_name: string;
285
288
  }
286
289
 
290
+ /**
291
+ * Rate Limit binding type
292
+ */
293
+ export interface WorkerBindingRateLimit {
294
+ /** The name of the binding */
295
+ name: string;
296
+ /** Type identifier for Rate Limit binding */
297
+ type: "ratelimit";
298
+ /** Namespace ID for the rate limit */
299
+ namespace_id: string;
300
+ /** Simple rate limiting configuration */
301
+ simple: {
302
+ /** Maximum number of requests */
303
+ limit: number;
304
+ /** Time period in seconds */
305
+ period: 60 | 10;
306
+ };
307
+ }
308
+
287
309
  /**
288
310
  * R2 Bucket binding type
289
311
  */
@@ -15,6 +15,7 @@ import type { HyperdriveResource as _Hyperdrive } from "./hyperdrive.ts";
15
15
  import type { Images as _Images } from "./images.ts";
16
16
  import type { PipelineResource as _Pipeline } from "./pipeline.ts";
17
17
  import type { QueueResource as _Queue } from "./queue.ts";
18
+ import type { RateLimit } from "./rate-limit.ts";
18
19
  import type { SecretKey } from "./secret-key.ts";
19
20
  import type { Secret as CloudflareSecret } from "./secret.ts";
20
21
  import type { VectorizeIndexResource as _VectorizeIndex } from "./vectorize-index.ts";
@@ -74,25 +75,31 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
74
75
  ? AnalyticsEngineDataset
75
76
  : T extends _Pipeline<infer R>
76
77
  ? Pipeline<R>
77
- : T extends string
78
- ? string
79
- : T extends BrowserRendering
80
- ? Fetcher
81
- : T extends _Ai<infer M>
82
- ? Ai<M>
83
- : T extends _Images
84
- ? ImagesBinding
85
- : T extends _VersionMetadata
86
- ? WorkerVersionMetadata
87
- : T extends Self
88
- ? Service
89
- : T extends Json<infer T>
90
- ? T
91
- : T extends _Container<
92
- infer Obj
93
- >
94
- ? DurableObjectNamespace<
95
- Obj &
96
- Rpc.DurableObjectBranded
97
- >
98
- : Service;
78
+ : T extends RateLimit
79
+ ? {
80
+ limit(options: {
81
+ key: string;
82
+ }): Promise<{ success: boolean }>;
83
+ }
84
+ : T extends string
85
+ ? string
86
+ : T extends BrowserRendering
87
+ ? Fetcher
88
+ : T extends _Ai<infer M>
89
+ ? Ai<M>
90
+ : T extends _Images
91
+ ? ImagesBinding
92
+ : T extends _VersionMetadata
93
+ ? WorkerVersionMetadata
94
+ : T extends Self
95
+ ? Service
96
+ : T extends Json<infer T>
97
+ ? T
98
+ : T extends _Container<
99
+ infer Obj
100
+ >
101
+ ? DurableObjectNamespace<
102
+ Obj &
103
+ Rpc.DurableObjectBranded
104
+ >
105
+ : Service;
@@ -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-07-15";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-07-18";
@@ -36,6 +36,7 @@ export * from "./permission-groups.ts";
36
36
  export * from "./pipeline.ts";
37
37
  export * from "./queue-consumer.ts";
38
38
  export * from "./queue.ts";
39
+ export * from "./rate-limit.ts";
39
40
  export * from "./react-router.ts";
40
41
  export * from "./redirect-rule.ts";
41
42
  export * from "./redwood.ts";
@@ -563,6 +563,10 @@ export async function buildMiniflareWorkerOptions({
563
563
  }
564
564
  break;
565
565
  }
566
+ case "ratelimit": {
567
+ (options.ratelimits ??= {})[name] = binding;
568
+ break;
569
+ }
566
570
  default: {
567
571
  assertNever(binding);
568
572
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Base interface for rate limiting configuration.
3
+ */
4
+ export interface RateLimitProps {
5
+ /**
6
+ * A positive integer that uniquely defines this rate limiting configuration (e.g., namespace_id = 999).
7
+ */
8
+ namespace_id: number;
9
+ /**
10
+ * Simple rate limiting configuration.
11
+ */
12
+ simple: {
13
+ /**
14
+ * The limit (number of requests or API calls) to be applied. This is incremented when you call the limit() function in your Worker.
15
+ */
16
+ limit: number;
17
+ /**
18
+ * The period, in seconds, to measure increments to the limit over. Must be either 10 or 60.
19
+ */
20
+ period: 60 | 10;
21
+ };
22
+ }
23
+
24
+ /**
25
+ * RateLimit binding for Cloudflare Workers.
26
+ */
27
+ export interface RateLimit extends RateLimitProps {
28
+ type: "ratelimit";
29
+ }
30
+
31
+ /**
32
+ * Rate limiting binding for Cloudflare Workers.
33
+ *
34
+ * The RateLimit binding provides access to Cloudflare's rate limiting functionality,
35
+ * allowing you to implement rate limiting directly in your Workers without needing
36
+ * to rely on external services.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { Worker, RateLimit } from "alchemy/cloudflare";
41
+ *
42
+ * const rateLimit = RateLimit({
43
+ * namespace_id: 1001,
44
+ * simple: {
45
+ * limit: 1500,
46
+ * period: 60
47
+ * }
48
+ * });
49
+ *
50
+ * await Worker("my-worker", {
51
+ * name: "my-worker",
52
+ * entrypoint: "./src/worker.ts",
53
+ * bindings: {
54
+ * MY_RATE_LIMIT: rateLimit
55
+ * }
56
+ * });
57
+ * ```
58
+ *
59
+ * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/
60
+ *
61
+ * Creates a rate limiting binding for Cloudflare Workers.
62
+ * @returns A RateLimit binding object.
63
+ */
64
+ export function RateLimit(options: RateLimitProps): RateLimit {
65
+ return {
66
+ type: "ratelimit",
67
+ namespace_id: options.namespace_id,
68
+ simple: options.simple,
69
+ };
70
+ }
@@ -17,7 +17,16 @@ export async function TanStackStart<B extends Bindings>(
17
17
  ): Promise<TanStackStart<B>> {
18
18
  return Website(id, {
19
19
  ...props,
20
- command: props?.command ?? "bun run build",
20
+ command: props?.command ?? "vite build",
21
+ commandEnv: {
22
+ // TODO(sam): should probably be set for all Websites, or at least all Vite Websites
23
+ // needs to be set or else jsxDev is included in the bundle and breaks in production
24
+ NODE_ENV: process.env.NODE_ENV ?? "production",
25
+ },
26
+ noBundle: true,
27
+ dev: props?.dev ?? {
28
+ command: "vite dev",
29
+ },
21
30
  wrangler: props?.wrangler ?? true,
22
31
  main: props?.main ?? ".output/server/index.mjs",
23
32
  compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { alchemy } from "../alchemy.ts";
3
3
  import { Exec } from "../os/exec.ts";
4
4
  import { Scope } from "../scope.ts";
5
+ import { isSecret } from "../secret.ts";
5
6
  import { detectPackageManager } from "../util/detect-package-manager.ts";
6
7
  import { Assets } from "./assets.ts";
7
8
  import type { Bindings } from "./bindings.ts";
@@ -18,6 +19,11 @@ export interface WebsiteProps<B extends Bindings>
18
19
  */
19
20
  command?: string;
20
21
 
22
+ /**
23
+ * Additional environment variables to set when running the command
24
+ */
25
+ commandEnv?: Record<string, string>;
26
+
21
27
  /**
22
28
  * Whether to memoize the command (only re-run if the command changes)
23
29
  *
@@ -224,7 +230,20 @@ export default {
224
230
  await Exec("build", {
225
231
  cwd,
226
232
  command: props.command,
227
- env: props.env,
233
+ env: {
234
+ ...(props.env ?? {}),
235
+ ...(props.commandEnv ?? {}),
236
+ ...Object.fromEntries(
237
+ Object.entries(props.bindings ?? {}).flatMap(([key, value]) => {
238
+ if (isSecret(value)) {
239
+ return [[key, value.unencrypted]];
240
+ } else if (typeof value === "string") {
241
+ return [[key, value]];
242
+ }
243
+ return [];
244
+ }),
245
+ ),
246
+ },
228
247
  memoize: props.memoize,
229
248
  });
230
249
  }
@@ -565,6 +565,13 @@ export async function prepareWorkerMetadata(
565
565
  configureClassMigration(bindingName, binding);
566
566
  }
567
567
  (meta.containers ??= []).push({ class_name: binding.className });
568
+ } else if (binding.type === "ratelimit") {
569
+ meta.bindings.push({
570
+ type: "ratelimit",
571
+ name: bindingName,
572
+ namespace_id: binding.namespace_id.toString(),
573
+ simple: binding.simple,
574
+ });
568
575
  } else {
569
576
  assertNever(
570
577
  binding,
@@ -1166,7 +1166,7 @@ export const _Worker = Resource(
1166
1166
 
1167
1167
  if (oldName !== newName) {
1168
1168
  if (dispatchNamespace) {
1169
- await this.replace(true);
1169
+ this.replace(true);
1170
1170
  } else {
1171
1171
  const renameResponse = await api.patch(
1172
1172
  `/accounts/${api.accountId}/workers/services/${oldName}`,
@@ -4,13 +4,21 @@ import type { Context } from "../context.ts";
4
4
  import { formatJson } from "../fs/static-json-file.ts";
5
5
  import { Resource } from "../resource.ts";
6
6
  import { assertNever } from "../util/assert-never.ts";
7
- import { Self, type Bindings } from "./bindings.ts";
7
+ import {
8
+ Self,
9
+ type Bindings,
10
+ type WorkerBindingRateLimit,
11
+ } from "./bindings.ts";
8
12
  import type { DurableObjectNamespace } from "./durable-object-namespace.ts";
9
13
  import type { EventSource } from "./event-source.ts";
10
14
  import { isQueueEventSource } from "./event-source.ts";
11
15
  import { isQueue } from "./queue.ts";
12
16
  import type { Worker, WorkerProps } from "./worker.ts";
13
17
 
18
+ type WranglerJsonRateLimit = Omit<WorkerBindingRateLimit, "type"> & {
19
+ type: "rate_limit";
20
+ };
21
+
14
22
  /**
15
23
  * Properties for wrangler.json configuration file
16
24
  */
@@ -447,6 +455,13 @@ export interface WranglerJsonSpec {
447
455
  namespace: string;
448
456
  experimental_remote?: boolean;
449
457
  }[];
458
+
459
+ /**
460
+ * Unsafe bindings section for experimental features
461
+ */
462
+ unsafe?: {
463
+ bindings: WranglerJsonRateLimit[];
464
+ };
450
465
  }
451
466
 
452
467
  /**
@@ -534,6 +549,7 @@ function processBindings(
534
549
  namespace: string;
535
550
  experimental_remote?: boolean;
536
551
  }[] = [];
552
+ const unsafeBindings: WranglerJsonRateLimit[] = [];
537
553
  const containers: {
538
554
  class_name: string;
539
555
  }[] = [];
@@ -721,6 +737,13 @@ function processBindings(
721
737
  namespace: binding.namespaceName,
722
738
  experimental_remote: true,
723
739
  });
740
+ } else if (binding.type === "ratelimit") {
741
+ unsafeBindings.push({
742
+ name: bindingName,
743
+ type: "rate_limit",
744
+ namespace_id: binding.namespace_id.toString(),
745
+ simple: binding.simple,
746
+ });
724
747
  } else if (binding.type === "secret_key") {
725
748
  // no-op
726
749
  } else if (binding.type === "container") {
@@ -802,4 +825,10 @@ function processBindings(
802
825
  if (dispatchNamespaces.length > 0) {
803
826
  spec.dispatch_namespaces = dispatchNamespaces;
804
827
  }
828
+
829
+ if (unsafeBindings.length > 0) {
830
+ spec.unsafe = {
831
+ bindings: unsafeBindings,
832
+ };
833
+ }
805
834
  }
package/src/context.ts CHANGED
@@ -57,7 +57,7 @@ export interface BaseContext<Out extends Resource> {
57
57
  * Indicate that this resource is being replaced.
58
58
  * This will cause the resource to be deleted at the end of the stack's CREATE phase.
59
59
  */
60
- replace(force?: boolean): Promise<never>;
60
+ replace(force?: boolean): never;
61
61
  /**
62
62
  * Terminate the resource lifecycle handler and destroy the resource.
63
63
  *
@@ -102,7 +102,7 @@ export function context<
102
102
  seq: number;
103
103
  props: Props;
104
104
  state: State<Kind, Props, Out>;
105
- replace: (force?: boolean) => Promise<never>;
105
+ replace: (force?: boolean) => never;
106
106
  isReplacement?: boolean;
107
107
  }): Context<Out> {
108
108
  type InternalSymbols =
@@ -306,7 +306,7 @@ export const Password = Resource(
306
306
  this.output.cidrs.length === props.cidrs.length &&
307
307
  this.output.cidrs.every((cidr, i) => cidr === props.cidrs![i])))
308
308
  ) {
309
- return await this.replace();
309
+ return this.replace();
310
310
  }
311
311
  const updateResponse = await api.patch(
312
312
  `/organizations/${props.organizationId}/databases/${databaseName}/branches/${branchName}/passwords/${this.output.id}`,
package/src/scope.ts CHANGED
@@ -50,6 +50,12 @@ export interface ScopeOptions {
50
50
  * @default - `true` if ran with `alchemy dev`, `alchemy watch`, `bun --watch ./alchemy.run.ts`
51
51
  */
52
52
  watch?: boolean;
53
+ /**
54
+ * Apply updates to resources even if there are no changes.
55
+ *
56
+ * @default false
57
+ */
58
+ force?: boolean;
53
59
  telemetryClient?: ITelemetryClient;
54
60
  logger?: LoggerApi;
55
61
  }
@@ -120,6 +126,7 @@ export class Scope {
120
126
  public readonly phase: Phase;
121
127
  public readonly local: boolean;
122
128
  public readonly watch: boolean;
129
+ public readonly force: boolean;
123
130
  public readonly logger: LoggerApi;
124
131
  public readonly telemetryClient: ITelemetryClient;
125
132
  public readonly dataMutex: AsyncMutex;
@@ -177,6 +184,7 @@ export class Scope {
177
184
 
178
185
  this.local = options.local ?? this.parent?.local ?? false;
179
186
  this.watch = options.watch ?? this.parent?.watch ?? false;
187
+ this.force = options.force ?? this.parent?.force ?? false;
180
188
 
181
189
  if (this.local) {
182
190
  this.logger.warnOnce(
@@ -432,7 +440,7 @@ export class Scope {
432
440
  event: "app.success",
433
441
  elapsed: performance.now() - this.startedAt,
434
442
  });
435
- } else {
443
+ } else if (this.isErrored) {
436
444
  this.logger.warn("Scope is in error, skipping finalize");
437
445
  this.rootTelemetryClient?.record({
438
446
  event: "app.error",
package/src/test/bun.ts CHANGED
@@ -4,8 +4,8 @@ import { afterAll, beforeAll, it } from "bun:test";
4
4
  import path from "node:path";
5
5
  import { alchemy } from "../alchemy.ts";
6
6
  import { Scope } from "../scope.ts";
7
- import type { StateStoreType } from "../state.ts";
8
7
  import { NoopTelemetryClient } from "../util/telemetry/index.ts";
8
+ import type { TestOptions } from "./options.ts";
9
9
 
10
10
  /**
11
11
  * Extend the Alchemy interface to include test functionality
@@ -21,33 +21,6 @@ declare module "../alchemy.ts" {
21
21
  */
22
22
  alchemy.test = test;
23
23
 
24
- /**
25
- * Options for configuring test behavior
26
- */
27
- export interface TestOptions {
28
- /**
29
- * Whether to suppress logging output.
30
- * @default false.
31
- */
32
- quiet?: boolean;
33
-
34
- /**
35
- * Password to use for test resources.
36
- * @default "test-password".
37
- */
38
- password?: string;
39
-
40
- /**
41
- * Override the default state store for the test.
42
- */
43
- stateStore?: StateStoreType;
44
-
45
- /**
46
- * Prefix to use for the scope to isolate tests and environments.
47
- */
48
- prefix?: string;
49
- }
50
-
51
24
  /**
52
25
  * Test function type definition with overloads
53
26
  */
@@ -0,0 +1,34 @@
1
+ import type { StateStoreType } from "../state.ts";
2
+
3
+ /**
4
+ * Options for configuring test behavior
5
+ */
6
+ export interface TestOptions {
7
+ /**
8
+ * Whether to suppress logging output.
9
+ * @default false.
10
+ */
11
+ quiet?: boolean;
12
+
13
+ /**
14
+ * Apply updates to resources even if there are no changes.
15
+ * @default false
16
+ */
17
+ force?: boolean;
18
+
19
+ /**
20
+ * Password to use for test resources.
21
+ * @default "test-password".
22
+ */
23
+ password?: string;
24
+
25
+ /**
26
+ * Override the default state store for the test.
27
+ */
28
+ stateStore?: StateStoreType;
29
+
30
+ /**
31
+ * Prefix to use for the scope to isolate tests and environments.
32
+ */
33
+ prefix?: string;
34
+ }
@@ -2,7 +2,6 @@ import path from "node:path";
2
2
  import { afterAll, beforeAll, it } from "vitest";
3
3
  import { alchemy } from "../alchemy.ts";
4
4
  import { Scope } from "../scope.ts";
5
- import type { StateStoreType } from "../state.ts";
6
5
  import {
7
6
  CloudflareStateStore,
8
7
  D1StateStore,
@@ -10,6 +9,7 @@ import {
10
9
  SQLiteStateStore,
11
10
  } from "../state/index.ts";
12
11
  import { NoopTelemetryClient } from "../util/telemetry/client.ts";
12
+ import type { TestOptions } from "./options.ts";
13
13
 
14
14
  /**
15
15
  * Extend the Alchemy interface to include test functionality
@@ -25,33 +25,6 @@ declare module "../alchemy.ts" {
25
25
  */
26
26
  alchemy.test = test;
27
27
 
28
- /**
29
- * Options for configuring test behavior
30
- */
31
- export interface TestOptions {
32
- /**
33
- * Whether to suppress logging output.
34
- * @default false.
35
- */
36
- quiet?: boolean;
37
-
38
- /**
39
- * Password to use for test resources.
40
- * @default "test-password".
41
- */
42
- password?: string;
43
-
44
- /**
45
- * Override the default state store for the test.
46
- */
47
- stateStore?: StateStoreType;
48
-
49
- /**
50
- * Prefix to use for the scope to isolate tests and environments.
51
- */
52
- prefix?: string;
53
- }
54
-
55
28
  /**
56
29
  * Test function type definition with overloads
57
30
  */
@@ -835,7 +835,6 @@ const isPathValid = (path: string): boolean =>
835
835
  const setupWindows = (): void => {
836
836
  /* eslint no-control-regex: "off" */
837
837
  const makePosix = (str: string): string =>
838
- // biome-ignore lint/suspicious/noControlCharactersInRegex: adapted from node-ignore
839
838
  /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str)
840
839
  ? str
841
840
  : str.replace(/\\/g, "/");
@@ -5,9 +5,7 @@ import { TanStackStart } from "alchemy/cloudflare";
5
5
 
6
6
  const app = await alchemy("my-alchemy-app");
7
7
 
8
- export const worker = await TanStackStart("website", {
9
- command: "bun run build",
10
- });
8
+ export const worker = await TanStackStart("website");
11
9
 
12
10
  console.log({
13
11
  url: worker.url,
@@ -11,9 +11,9 @@
11
11
  "start": "node .output/server/index.mjs"
12
12
  },
13
13
  "dependencies": {
14
- "@tanstack/react-router": "^1.121.27",
15
- "@tanstack/react-router-devtools": "^1.121.27",
16
- "@tanstack/react-start": "^1.121.32",
14
+ "@tanstack/react-router": "^1.128.3",
15
+ "@tanstack/react-router-devtools": "^1.128.3",
16
+ "@tanstack/react-start": "^1.128.3",
17
17
  "react": "^19.0.0",
18
18
  "react-dom": "^19.0.0",
19
19
  "tailwind-merge": "3",