Skip to content

Commit 7cdfac3

Browse files
committed
move to node
1 parent 905fb2c commit 7cdfac3

File tree

11 files changed

+130
-84
lines changed

11 files changed

+130
-84
lines changed

Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM oven/bun:1.3-alpine as builder
1+
FROM node:22.21-alpine as builder
22

33
WORKDIR /app
44

@@ -15,7 +15,7 @@ RUN bun run build
1515

1616

1717

18-
FROM oven/bun:1.3-alpine
18+
FROM node:22.21-alpine
1919

2020
WORKDIR /app
2121

@@ -31,4 +31,4 @@ COPY --from=builder /app/apps/client/dist ./apps/client/dist
3131
ENV NODE_ENV=production
3232
ENV PUBLIC_DIR=/app/apps/client/dist
3333

34-
CMD cd apps/server && bunx gel migrate && bun run dist/main.js
34+
CMD cd apps/server && bunx gel migrate && node --enable-source-maps dist/main.js

apps/client/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
"@tanstack/react-query": "^5.90.5",
1616
"@tanstack/react-query-devtools": "^5.90.2",
1717
"@tanstack/react-router": "^1.133.32",
18-
"@tanstack/react-router-devtools": "^1.133.32",
19-
"@trpc/client": "^11.7.0",
20-
"@trpc/tanstack-react-query": "^11.7.0",
18+
"@tanstack/react-router-devtools": "^1.133.34",
19+
"@trpc/client": "11.6.0",
20+
"@trpc/tanstack-react-query": "11.6.0",
2121
"@types/qrcode": "^1.5.6",
2222
"color-parse": "^2.0.2",
2323
"daisyui": "^5.3.10",

apps/client/src/utils/trpc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const queryClient = new QueryClient();
1010
const trpcClient = createTRPCClient<AppRouter>({
1111
links: [wsLink({
1212
client: createWSClient({
13-
url: `${secure ? "wss" : "ws"}://${location.host}/trpc/socket`,
13+
url: `${secure ? "wss" : "ws"}://${location.host}/trpc`,
1414
}),
1515
}),],
1616
});

apps/server/gel.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ server-version = "6.4"
55
schema-dir = "src/dbschema"
66

77
[hooks]
8-
schema.update.after = "bunx @gel/generate interfaces && bunx @gel/generate edgeql-js"
8+
schema.update.after = "bunx @gel/generate interfaces && bunx @gel/generate edgeql-js --target esm"

apps/server/package.json

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,35 @@
22
"name": "server",
33
"version": "1.0.0",
44
"license": "MIT",
5-
"main": "./dist/main.js",
65
"type": "module",
76
"scripts": {
8-
"dev": "bun --watch ./src/main.ts",
9-
"build": "bun build --sourcemap=inline --target bun --outdir ./dist ./src/main.ts",
10-
"start": "bun ./dist/main.js",
7+
"dev": "bun tsdown --watch",
8+
"build": "bun tsdown",
9+
"start": "node ./dist/main.js",
1110
"migrate": "gel migration create && gel migrate && bun generate",
12-
"generate": "bunx @gel/generate interfaces && bunx @gel/generate edgeql-js"
11+
"generate": "bunx @gel/generate interfaces && bunx @gel/generate edgeql-js --target esm"
1312
},
14-
"types": "./dist-ts/main.d.ts",
13+
"types": "./dist/main.d.ts",
14+
"main": "./dist/main.js",
1515
"dependencies": {
16+
"@fastify/cors": "^11.1.0",
17+
"@fastify/static": "^8.3.0",
18+
"@fastify/websocket": "^11.2.0",
1619
"@graphql-yoga/subscription": "^5.0.5",
17-
"@trpc/server": "^11.7.0",
18-
"cors": "^2.8.5",
20+
"@trpc/server": "11.6.0",
1921
"dotenv": "^16.6.1",
2022
"dotenv-cli": "^6.0.0",
21-
"express": "^4.21.2",
23+
"execa": "^9.6.0",
24+
"fastify": "^5.6.1",
2225
"gel": "^2.1.1",
2326
"nanoid": "^3.3.11",
24-
"ws": "^8.18.3",
2527
"zod": "^3.25.76"
2628
},
2729
"devDependencies": {
2830
"@gel/generate": "^0.7.0",
29-
"@types/bun": "^1.3.1",
3031
"@types/cors": "^2.8.19",
31-
"@types/express": "^4.17.24",
3232
"@types/node": "^20.19.23",
3333
"@types/ws": "^8.18.1",
34-
"execa": "^9.6.0",
35-
"prettier": "^3.6.2",
3634
"tsdown": "^0.15.10"
3735
}
3836
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
module default {
2+
scalar type UserLocation extending enum<InPerson, Online, Proxy>;
3+
scalar type WaitingState extending enum<Waiting, Admitted, Declined, Kicked>;
4+
scalar type QuestionFormat extending enum<SingleVote, PreferentialVote>;
5+
26
type Room {
37
required adminKey: str;
48
required name: str;
@@ -14,11 +18,7 @@ module default {
1418

1519
link users := .<room[is RoomUser];
1620
multi questions: Question;
17-
}
18-
19-
scalar type UserLocation extending enum<InPerson, Online, Proxy>;
20-
scalar type WaitingState extending enum<Waiting, Admitted, Declined, Kicked>;
21-
scalar type QuestionFormat extending enum<SingleVote, PreferentialVote>;
21+
}
2222

2323
type RoomUser {
2424
required state: WaitingState;

apps/server/src/main.ts

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import path from "node:path";
2-
import * as trpcExpress from "@trpc/server/adapters/express";
3-
import { applyWSSHandler } from "@trpc/server/adapters/ws";
4-
import cors from "cors";
5-
import express from "express";
6-
import { WebSocketServer } from "ws";
7-
1+
import cors from "@fastify/cors";
2+
import ws from "@fastify/websocket";
3+
import {
4+
type FastifyTRPCPluginOptions,
5+
fastifyTRPCPlugin,
6+
} from "@trpc/server/adapters/fastify";
7+
import type { TRPCReconnectNotification } from "@trpc/server/rpc";
8+
import fastify from "fastify";
89
import { env } from "./env";
910
import { roomRouter } from "./routers/room";
1011
import { roomAdminRouter } from "./routers/room-admin";
@@ -23,64 +24,90 @@ export const appRouter = mergeRouters(mainRouter);
2324

2425
export type AppRouter = typeof appRouter;
2526

26-
const app = express();
27+
const server = fastify({
28+
routerOptions: {
29+
maxParamLength: 5000,
30+
},
31+
});
32+
33+
await server.register(ws, {
34+
preClose(done) {
35+
console.log("Broadcasting reconnect to clients before shutdown");
36+
const response: TRPCReconnectNotification = {
37+
id: null,
38+
method: "reconnect",
39+
};
40+
41+
const data = JSON.stringify(response);
42+
43+
for (const client of server.websocketServer.clients) {
44+
if (client.readyState === 1) {
45+
client.send(data);
46+
}
47+
}
48+
49+
server.websocketServer.close(done);
50+
},
51+
});
52+
53+
server.websocketServer.on("connection", (ws) => {
54+
console.log(`➕➕ Connection (${server.websocketServer.clients.size})`);
55+
ws.once("close", () => {
56+
console.log(`➖➖ Connection (${server.websocketServer.clients.size})`);
57+
});
58+
})
2759

2860
// Allow CORS for dev
2961
if (process.env.NODE_ENV !== "production") {
30-
app.use(
31-
cors({
32-
origin: "*",
33-
credentials: true,
34-
}),
35-
);
62+
await server.register(cors, {
63+
origin: "*",
64+
credentials: true,
65+
});
3666
}
3767

3868
// Create the express server
39-
app.use(
40-
"/trpc",
41-
trpcExpress.createExpressMiddleware({
69+
await server.register(fastifyTRPCPlugin, {
70+
prefix: "/trpc",
71+
useWSS: true,
72+
// Enable heartbeat messages to keep connection open (disabled by default)
73+
keepAlive: {
74+
enabled: true,
75+
// server ping message interval in milliseconds
76+
pingMs: 30000,
77+
// connection is terminated if pong message is not received in this many milliseconds
78+
pongWaitMs: 5000,
79+
},
80+
trpcOptions: {
4281
router: appRouter,
43-
}),
44-
);
82+
onError({ path, error }) {
83+
// report to error monitoring
84+
console.error(`Error in tRPC handler on path '${path}':`, error);
85+
},
86+
} satisfies FastifyTRPCPluginOptions<AppRouter>["trpcOptions"],
87+
});
4588

4689
if (env.publicDir) {
4790
const publicDir = env.publicDir;
48-
// Serve up the single page app
49-
app.use(express.static(publicDir));
50-
app.get("*", (_req, res) => {
51-
res.sendFile(path.resolve(publicDir, "index.html"));
91+
await server.register(import("@fastify/static"), {
92+
root: publicDir,
93+
prefix: "/",
5294
});
53-
}
54-
55-
const server = app.listen(8080, () => {
56-
console.log("Server started on port 8080");
57-
});
5895

59-
// Create the websocket server
96+
server.setNotFoundHandler((_request, reply) => {
97+
reply.sendFile("index.html");
98+
});
99+
}
60100

61-
const websocketServer = new WebSocketServer({
62-
noServer: true,
63-
path: "/trpc/socket",
101+
const PORT = env.port || 8080;
102+
server.listen({ port: Number(PORT), host: "0.0.0.0" }).then(() => {
103+
console.log(`Server listening on port ${PORT}`);
64104
});
65105

66-
const handler = applyWSSHandler({ wss: websocketServer, router: appRouter });
67-
68-
server.on("upgrade", (request, socket, head) => {
69-
websocketServer.handleUpgrade(request, socket, head, (websocket) => {
70-
websocketServer.emit("connection", websocket, request);
106+
process.on("SIGTERM", () => {
107+
server.close().then(() => {
108+
console.log("Server closed");
109+
process.exit(0);
71110
});
72111
});
73112

74-
process.on("SIGTERM", () => {
75-
console.log("SIGTERM");
76-
handler.broadcastReconnectNotification();
77-
websocketServer.close();
78-
console.log("Server killed, broadcasting reconnect notification");
79-
});
80113

81-
websocketServer.on("connection", (ws) => {
82-
console.log(`➕➕ Connection (${websocketServer.clients.size})`);
83-
ws.once("close", () => {
84-
console.log(`➖➖ Connection (${websocketServer.clients.size})`);
85-
});
86-
});

apps/server/src/room/interaction/db/queries.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { dbClient } from "../../../dbschema/client";
2-
import e from "../../../dbschema/edgeql-js";
2+
import e from "../../../dbschema/edgeql-js/index.js";
33
import type { $expr_Literal } from "../../../dbschema/edgeql-js/literal";
44
import type { $QuestionFormat } from "../../../dbschema/edgeql-js/modules/default";
55
import type {
@@ -153,7 +153,7 @@ export async function dbSetUserState(
153153
type DbQuestionElement = (typeof e.Question)["__element__"];
154154
type DbQuestionElementQueryShape = Readonly<
155155
objectTypeToSelectShape<DbQuestionElement> &
156-
SelectModifiers<DbQuestionElement>
156+
SelectModifiers<DbQuestionElement>
157157
>;
158158

159159
const questionQueryFields = {
@@ -197,7 +197,7 @@ export async function dbFetchCurrentQuestionData(roomId: string) {
197197
const questions = await e
198198
.select(e.Question, (question) => ({
199199
...questionQueryFields,
200-
filter: e.op(question["<questions[is Room]"].id, "=", e.uuid(roomId)),
200+
filter: e.any(e.op(question["<questions[is Room]"].id, "=", e.uuid(roomId))),
201201
order_by: { expression: question.createdAt, direction: e.DESC },
202202
limit: 1,
203203
}))
@@ -211,7 +211,7 @@ export async function dbFetchAllQuestionsData(roomId: string) {
211211
const questions = await e
212212
.select(e.Question, (question) => ({
213213
...questionQueryFields,
214-
filter: e.op(question["<questions[is Room]"].id, "=", e.uuid(roomId)),
214+
filter: e.any(e.op(question["<questions[is Room]"].id, "=", e.uuid(roomId))),
215215
}))
216216
.run(dbClient);
217217

apps/server/tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"moduleDetection": "force",
1010
"jsx": "react-jsx",
1111
"allowJs": true,
12+
"allowImportingTsExtensions": true,
13+
"emitDeclarationOnly": true,
1214

1315
// Bundler mode
1416
"moduleResolution": "bundler",

apps/server/tsdown.config.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import { execaNode } from "execa";
22
import { defineConfig } from "tsdown";
33

4+
let isRunning = false;
5+
46
export default defineConfig({
57
entry: ["./src/main.ts"],
6-
onSuccess: async (_conf, signal) => {
7-
await execaNode({
8+
inputOptions: {
9+
resolve: {
10+
extensionAlias: {
11+
".js": [".mjs", ".js"],
12+
}
13+
}
14+
},
15+
sourcemap: true,
16+
onSuccess: async ({ watch }, signal) => {
17+
if (!watch) return;
18+
execaNode({
819
cancelSignal: signal,
9-
gracefulCancel: true,
10-
})`./dist/main.js`
20+
gracefulCancel: false,
21+
stdout: ['inherit'],
22+
stderr: ['inherit'],
23+
})`--enable-source-maps ./dist/main.js`.then(() => {
24+
isRunning = false;
25+
}).catch(() => {
26+
isRunning = false;
27+
});
28+
if (isRunning) return;
29+
isRunning = true;
1130
},
1231
});

0 commit comments

Comments
 (0)