1import * as Fly from "@/Fly";
2import * as Config from "effect/Config";
3import * as Effect from "effect/Effect";
4import * as Ref from "effect/Ref";
5import { HttpServerRequest } from "effect/http/HttpServerRequest";
6import * as HttpServerResponse from "effect/http/HttpServerResponse";
7
8export const Site = Fly.App("Site");
9export class Api extends Fly.Service<Api>()("Api") {}
10
11export const apiLayer = (version: string) =>
12 Api.make(
13 {
14 app: Site,
15 main: import.meta.url,
16 env: { VERSION: version },
17 deploy: { strategy: "bluegreen", healthTimeout: "30 seconds" },
18 shutdown: { timeout: "30 seconds" },
19 services: [
20 {
21 protocol: "tcp",
22 internalPort: 3000,
23 autostop: "off",
24 ports: [{ port: 80, handlers: ["http"] }],
25 checks: [
26 {
27 type: "http",
28 port: 3000,
29 path: "/health",
30 interval: "2s",
31 timeout: "1s",
32 },
33 ],
34 },
35 ],
36 },
37 Effect.gen(function* () {
38 const active = yield* Ref.make(0);
39 const shutdown = { received: false };
40 if (globalThis.__ALCHEMY_RUNTIME__) {
41 yield* Effect.sync(() =>
42 process.once("SIGTERM", () => {
43 shutdown.received = true;
44 }),
45 );
46 }
47 return {
48 fetch: Effect.gen(function* () {
49 const version = yield* Config.String("VERSION").pipe(Effect.orDie);
50 const request = yield* HttpServerRequest;
51 if (request.url.startsWith("/health"))
52 return HttpServerResponse.text("ok");
53 if (request.url.startsWith("/active"))
54 return HttpServerResponse.text(String(yield* Ref.get(active)));
55 if (request.url.startsWith("/slow")) {
56 yield* Ref.update(active, (count) => count + 1);
57 yield* Effect.sleep("60 seconds").pipe(
58 Effect.ensuring(Ref.update(active, (count) => count - 1)),
59 );
60 return HttpServerResponse.text(
61 `${version}:${shutdown.received ? "drained" : "not-signaled"}`,
62 );
63 }
64 return HttpServerResponse.text(version);
65 }),
66 };
67 }),
68 );
69
70export default apiLayer("default");