1import * as Cloudflare from "@/Cloudflare";
2import type { WorkerBuildOptions } from "@/Cloudflare/Workers/Sources/Rolldown.ts";
3import * as Alchemy from "@/index.ts";
4import * as Test from "@/Test/Alchemy";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Schedule from "effect/Schedule";
8import * as HttpClient from "effect/http/HttpClient";
9import { fileURLToPath } from "node:url";
10
11const fixtureMain = fileURLToPath(
12 new URL("./fixtures/drizzle-schema-chunks/worker.ts", import.meta.url),
13);
14
15const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
16 providers: Cloudflare.providers(),
17});
18
19/**
20 * Forces the *cyclic* chunk layout from
21 * https://github.com/alchemy-run/alchemy/issues/749: the schema group
22 * captures only the schema modules (`includeDependenciesRecursively:
23 * false`), so `drizzle-orm` stays with the entry and the graph becomes
24 * `entry -> auth-*.js -> entry`. ESM evaluation then runs the schema chunk
25 * before drizzle's class bindings initialize. (An acyclic split — e.g.
26 * drizzle in its own chunk imported by the schema chunk — would NOT
27 * regress: plain import order already evaluates it correctly.)
28 */
29const chunking = {
30 preserveEntrySignatures: "allow-extension",
31 output: {
32 cleanDir: true,
33 codeSplitting: {
34 groups: [
35 {
36 name: "auth",
37 test: "drizzle-schema-chunks/(schema|auth)/",
38 includeDependenciesRecursively: false,
39 },
40 ],
41 },
42 },
43} satisfies WorkerBuildOptions;
44
45const Stack = Alchemy.Stack(
46 "DrizzleSchemaChunksTestStack",
47 { providers: Cloudflare.providers(), state: Cloudflare.state() },
48 Effect.gen(function* () {
49 const worker = yield* Cloudflare.Worker("DrizzleSchemaChunks", {
50 main: fixtureMain,
51 compatibility: { date: "2026-06-24", flags: ["nodejs_compat"] },
52 build: chunking,
53 });
54 return { url: worker.url.as<string>() };
55 }),
56);
57
58const stack = beforeAll(deploy(Stack));
59afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack));
60
61test(
62 "worker with drizzle schema modules split into their own chunk deploys and serves (#749)",
63 Effect.gen(function* () {
64 const { url } = yield* stack;
65
66 // The worker evaluated its cross-chunk schema at startup and serves.
67 const client = yield* HttpClient.HttpClient;
68 const body = yield* client.get(url).pipe(
69 Effect.flatMap((res) => res.text),
70 Effect.retry({
71 schedule: Schedule.exponential("500 millis"),
72 times: 5,
73 }),
74 // Fresh workers.dev URLs can serve placeholder pages while propagating.
75 Effect.repeat({
76 schedule: Schedule.exponential("500 millis"),
77 until: (b) => b.includes('"ok":true'),
78 times: 10,
79 }),
80 Effect.orDie,
81 );
82 expect(body).toContain('"ok":true');
83 }),
84 {
85 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
86 timeout: 180_000,
87 },
88);
89
90/**
91 * Negative control for the test above.
92 *
93 * WorkerBundle defaults to `strictExecutionOrder: true`, which wraps
94 * cross-chunk modules so evaluation follows ESM semantics regardless of how
95 * the graph was chunked. Turning it off must make Cloudflare reject the
96 * exact same fixture at upload — that is what proves the deploy above is
97 * still exercising #749 rather than passing because a chunking change
98 * quietly made the layout acyclic.
99 *
100 * Cloudflare is the oracle here, so nothing in this file has to assert on
101 * chunk filenames or emitted code.
102 */
103test.provider(
104 "the same layout fails startup validation without strictExecutionOrder",
105 (stack) =>
106 Effect.gen(function* () {
107 yield* stack.destroy();
108
109 const error = yield* stack
110 .deploy(
111 Effect.gen(function* () {
112 return yield* Cloudflare.Worker("DrizzleSchemaChunksUnsafe", {
113 main: fixtureMain,
114 compatibility: { date: "2026-06-24", flags: ["nodejs_compat"] },
115 build: {
116 ...chunking,
117 output: { ...chunking.output, strictExecutionOrder: false },
118 },
119 });
120 }),
121 )
122 .pipe(Effect.flip);
123
124 // `ScriptStartupError: Uncaught ReferenceError: Cannot access '<minified>'
125 // before initialization at auth-*.js` — the #749 crash itself.
126 expect(error._tag).toEqual("ScriptStartupError");
127
128 yield* stack.destroy();
129 }),
130 {
131 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
132 timeout: 240_000,
133 },
134);