cooking with websockets

This commit is contained in:
2025-08-18 23:31:28 -04:00
parent 3f1635880a
commit 287c19fc0d
12 changed files with 205 additions and 81 deletions

View File

@@ -0,0 +1,15 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Instance" (
"id" TEXT NOT NULL PRIMARY KEY,
"createdByKey" TEXT NOT NULL,
"gameKey" TEXT NOT NULL,
"gameState" JSONB,
CONSTRAINT "Instance_gameKey_fkey" FOREIGN KEY ("gameKey") REFERENCES "Game" ("key") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Instance" ("createdByKey", "gameKey", "gameState", "id") SELECT "createdByKey", "gameKey", "gameState", "id" FROM "Instance";
DROP TABLE "Instance";
ALTER TABLE "new_Instance" RENAME TO "Instance";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -32,6 +32,6 @@ model Instance {
players Human[]
game Game @relation(fields: [gameKey], references: [key])
gameState Json
game Game @relation(fields: [gameKey], references: [key])
gameState Json?
}

View File

@@ -20,10 +20,13 @@
"elysia": "^1.3.8",
"elysia-ip": "^1.0.10",
"elysia-rate-limit": "^4.4.0",
"kefir": "^3.8.8",
"kefir-bus": "^2.3.1",
"object-hash": "^3.0.0"
},
"devDependencies": {
"@types/bun": "latest",
"@types/kefir": "^3.8.11",
"concurrently": "^9.2.0",
"prisma": "6.13.0"
}

View File

@@ -3,6 +3,9 @@ import { heq } from "@games/shared/utils";
import { Elysia, t } from "elysia";
import db from "../db";
import { human } from "../human";
import { ElysiaWS } from "elysia/dist/ws";
import K, { Stream } from "kefir";
import Bus, { type Bus as TBus } from "kefir-bus";
// omniscient game state
export type GameState = {
@@ -104,13 +107,18 @@ export const resolveAction = (
};
};
const instanceEvents: {
[instanceId: string]: TBus<
{ view?: PlayerView; players?: string[] },
never
>;
} = {};
export const simpleApi = new Elysia({ prefix: "/simple" })
.use(human)
.post("/newGame", ({ humanKey }) => {
console.log("KEY", humanKey);
return db.instance.create({
data: {
gameState: newGame([humanKey]),
gameKey: "simple",
createdByKey: humanKey,
players: {
@@ -119,25 +127,96 @@ export const simpleApi = new Elysia({ prefix: "/simple" })
},
});
})
.group("/:instanceId", (app) =>
app
.get("/", ({ params: { instanceId }, humanKey }) =>
db.instance
.findUnique({
.ws("/", {
async open(ws) {
console.log("Got ws connection");
ws.send("Hello!");
// send initial state
const instanceId = ws.data.params.instanceId;
const humanKey = ws.data.humanKey;
const instance = await db.instance.update({
data: {
players: {
connect: { key: humanKey },
},
},
where: {
id: instanceId,
},
})
.then((game) =>
getView(
getKnowledge(
game!.gameState as GameState,
select: {
createdByKey: true,
gameState: true,
players: {
select: {
key: true,
},
},
},
});
if (instance == null) {
ws.close(1011, "no such instance");
return;
}
// register this socket as a listener for events of this instance
if (!instanceEvents[instanceId]) {
instanceEvents[instanceId] = Bus();
}
// @ts-ignore
ws.data.cb = instanceEvents[instanceId].onValue((evt) =>
ws.send(evt)
);
ws.send({ creator: instance.createdByKey });
if (instance.gameState != null) {
ws.send(
getView(
getKnowledge(
instance.gameState as GameState,
humanKey
),
humanKey
),
humanKey
)
)
)
)
);
}
instanceEvents[instanceId]?.emit({
players: instance.players.map((p) => p.key),
});
},
close(ws) {
console.log("Got ws close");
const instanceId = ws.data.params.instanceId;
// @ts-ignore
instanceEvents[instanceId]?.offValue(ws.data.cb);
db.instance
.update({
where: {
id: instanceId,
},
data: {
players: {
disconnect: { key: ws.data.humanKey },
},
},
select: {
players: {
select: {
key: true,
},
},
},
})
.then((instance) => {
instanceEvents[instanceId]?.emit({
players: instance.players.map((p) => p.key),
});
});
},
})
.post(
"/",
({ params: { instanceId }, body: { action }, humanKey }) =>

View File

@@ -9,7 +9,7 @@ const port = env.PORT || 5001;
const app = new Elysia()
.use(
cors({
origin: ["localhost:3000", "games.drm.dev"],
origin: ["http://localhost:3000", "https://games.drm.dev"],
})
)
.onRequest(({ request }) => {