1import * as Cloudflare from "@/Cloudflare";
2import * as Prisma from "@/Prisma";
3import * as Test from "@/Test/Alchemy";
4import * as Alchemy from "@/index.ts";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9import * as Schedule from "effect/Schedule";
10import * as HttpClient from "effect/http/HttpClient";
11import * as HttpClientRequest from "effect/http/HttpClientRequest";
12import type { HttpClientResponse } from "effect/http/HttpClientResponse";
13import PrismaHyperdriveWorker from "./fixtures/hyperdrive-worker.ts";
14
15const wantsLive = process.env.ALCHEMY_RUN_LIVE_PRISMA_TESTS === "true";
16const hasLiveCredentials =
17 process.env.ALCHEMY_RUN_LIVE_PRISMA_WITH_PROFILE === "true";
18const runLive = wantsLive && hasLiveCredentials;
19
20const providers = Layer.merge(Cloudflare.providers(), Prisma.providers());
21
22const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
23 providers,
24});
25
26const Stack = Alchemy.Stack(
27 "PrismaHyperdriveStack",
28 { providers, state: Cloudflare.state() },
29 Effect.gen(function* () {
30 const worker = yield* PrismaHyperdriveWorker;
31 return { url: worker.url.as<string>() };
32 }),
33);
34
35class WorkerNotReady extends Data.TaggedError("WorkerNotReady")<{
36 status: number;
37 body: string;
38}> {}
39
40const fetchReady = (req: Effect.Effect<any, any, any>) =>
41 req.pipe(
42 Effect.flatMap((res: any) =>
43 res.status >= 200 && res.status < 300
44 ? Effect.succeed(res)
45 : res.text.pipe(
46 Effect.flatMap((body: string) =>
47 Effect.fail(new WorkerNotReady({ status: res.status, body })),
48 ),
49 ),
50 ),
51 Effect.retry({
52 while: (e: unknown): e is WorkerNotReady => e instanceof WorkerNotReady,
53 schedule: Schedule.max([
54 Schedule.exponential("500 millis"),
55 Schedule.recurs(20),
56 ]),
57 }),
58 ) as Effect.Effect<HttpClientResponse, never, HttpClient.HttpClient>;
59
60const stack = runLive ? beforeAll(deploy(Stack), { timeout: 600_000 }) : null;
61afterAll.skipIf(!runLive || !!process.env.NO_DESTROY)(destroy(Stack), {
62 timeout: 600_000,
63});
64
65test.skipIf(!runLive)(
66 "queries Prisma Postgres from a Worker through Hyperdrive",
67 Effect.gen(function* () {
68 const { url } = yield* stack!;
69
70 const insert = yield* fetchReady(
71 HttpClient.execute(
72 HttpClientRequest.post(`${url}/widgets`).pipe(
73 HttpClientRequest.bodyJsonUnsafe({ id: 1, name: "anvil" }),
74 ),
75 ),
76 );
77 expect(insert.status).toBe(200);
78
79 const read = yield* fetchReady(HttpClient.get(`${url}/widgets`));
80 const body = (yield* read.json) as {
81 widgets: Array<{ id: number; name: string }>;
82 };
83 expect(body.widgets).toEqual(
84 expect.arrayContaining([{ id: 1, name: "anvil" }]),
85 );
86 }),
87 {
88 tags: [
89 "provider:cloudflare",
90 "provider:cloudflare:hyperdrive",
91 "provider:cloudflare:worker",
92 "provider:prisma",
93 "provider:prisma:connection",
94 "provider:prisma:project",
95 "live",
96 ],
97 timeout: 300_000,
98 },
99);