Compare commits
8 Commits
601e3660d3
...
3347452ec4
| Author | SHA1 | Date | |
|---|---|---|---|
| 3347452ec4 | |||
| a2e8887a0b | |||
| cc53470ddf | |||
| 7d8ac0db76 | |||
| 35a5af154f | |||
| 265aad4522 | |||
| 287c19fc0d | |||
| 3f1635880a |
@@ -1,10 +0,0 @@
|
||||
node_modules
|
||||
Dockerfile
|
||||
Makefile
|
||||
README.md
|
||||
.output
|
||||
.vinxi
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
*.db
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,11 +0,0 @@
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
EXPOSE 3000
|
||||
|
||||
COPY package.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm npm install
|
||||
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/app/.vinxi npm run build
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
11
package.json
11
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "games",
|
||||
"type": "module",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.4",
|
||||
"scripts": {
|
||||
"dev": "pnpm --parallel dev",
|
||||
"build": "pnpm run -F client build",
|
||||
@@ -10,7 +10,14 @@
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"object-hash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"@prisma/client",
|
||||
"@prisma/engines",
|
||||
"esbuild",
|
||||
"prisma"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/object-hash": "^3.0.6"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@games/client",
|
||||
"type": "module",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"scripts": {
|
||||
"dev": "vite --port 3000",
|
||||
"build": "vite build"
|
||||
@@ -11,12 +11,15 @@
|
||||
"@solid-primitives/scheduled": "^1.5.2",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"kefir": "^3.8.8",
|
||||
"kefir-bus": "^2.3.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"solid-js": "^1.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/solar": "^1.2.4",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/kefir": "^3.8.11",
|
||||
"@unocss/preset-icons": "^66.4.2",
|
||||
"@unocss/preset-wind4": "^66.4.2",
|
||||
"unocss": "^66.4.2",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { createResource } from "solid-js";
|
||||
import { type Api } from "../../server/src/api";
|
||||
import { treaty } from "@elysiajs/eden";
|
||||
import { EdenWS } from "@elysiajs/eden/treaty";
|
||||
import { fromEvents } from "kefir";
|
||||
|
||||
const { api } = treaty<Api>(
|
||||
import.meta.env.DEV ? "http://localhost:5001" : window.location.origin,
|
||||
@@ -8,3 +11,8 @@ const { api } = treaty<Api>(
|
||||
}
|
||||
);
|
||||
export default api;
|
||||
|
||||
export const fromWebsocket = <T>(ws: any) =>
|
||||
fromEvents(ws, "message").map(
|
||||
(evt) => (evt as unknown as { data: T }).data
|
||||
);
|
||||
|
||||
@@ -34,7 +34,8 @@ const Profile = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => (
|
||||
const App = () => {
|
||||
return (
|
||||
<Router
|
||||
root={(props) => (
|
||||
<>
|
||||
@@ -50,17 +51,11 @@ const App = () => (
|
||||
>
|
||||
<Route path="/" component={lazy(() => import("./routes/index"))} />
|
||||
<Route
|
||||
path="/:game"
|
||||
component={lazy(() => import("./routes/[game]/index"))}
|
||||
/>
|
||||
<Route
|
||||
path="/:game/:instance"
|
||||
component={lazy(() => import("./routes/[game]/[instance]"))}
|
||||
path="/:tableKey"
|
||||
component={lazy(() => import("./routes/[table]"))}
|
||||
/>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
// todo: fix this
|
||||
(Cookies.get("token") == null ? api.whoami.post() : Promise.resolve()).then(
|
||||
() => render(App, document.getElementById("app")!)
|
||||
);
|
||||
render(App, document.getElementById("app")!);
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
import { Accessor, createContext, createResource, Show } from "solid-js";
|
||||
import { Accessor, createContext, useContext } from "solid-js";
|
||||
import {
|
||||
GameState,
|
||||
Action,
|
||||
vGameState,
|
||||
PlayerView,
|
||||
SimpleAction,
|
||||
SimplePlayerView,
|
||||
vSimpleGameState,
|
||||
} from "../../../server/src/games/simple";
|
||||
import api from "../api";
|
||||
import Hand from "./Hand";
|
||||
import Pile from "./Pile";
|
||||
import { TableContext } from "./Table";
|
||||
import Hand from "./Hand";
|
||||
|
||||
export const GameContext = createContext<{
|
||||
view: Accessor<PlayerView | undefined>;
|
||||
submitAction: (action: Action) => Promise<any>;
|
||||
view: Accessor<SimplePlayerView>;
|
||||
submitAction: (action: SimpleAction) => any;
|
||||
}>();
|
||||
|
||||
export default (props: { instanceId: string }) => {
|
||||
const [view, { mutate }] = createResource(() =>
|
||||
api
|
||||
.simple(props)
|
||||
.get()
|
||||
.then((res) => res.data as PlayerView)
|
||||
);
|
||||
const submitAction = (action: Action) =>
|
||||
api
|
||||
.simple(props)
|
||||
.post({ action })
|
||||
.then((res) => res.status == 200 && mutate(res.data as PlayerView));
|
||||
export default () => {
|
||||
const table = useContext(TableContext)!;
|
||||
const view = table.view as Accessor<SimplePlayerView>;
|
||||
const submitAction = (action: SimpleAction) => table.sendWs({ action });
|
||||
|
||||
return (
|
||||
<GameContext.Provider value={{ view, submitAction }}>
|
||||
<Show when={view.latest != undefined}>
|
||||
<Pile
|
||||
count={view.latest!.deckCount}
|
||||
count={view().deckCount}
|
||||
class="cursor-pointer fixed center"
|
||||
onClick={() => submitAction({ type: "draw" })}
|
||||
/>
|
||||
|
||||
<Hand class="fixed bc" hand={view.latest!.myHand} />
|
||||
</Show>
|
||||
<Hand
|
||||
class="fixed bc"
|
||||
hand={view().myHand}
|
||||
onClickCard={(card) => submitAction({ type: "discard", card })}
|
||||
/>
|
||||
</GameContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { Component, For, useContext } from "solid-js";
|
||||
import Card from "./Card";
|
||||
import { Hand } from "../../../shared/cards";
|
||||
import type { Hand as THand, Card as TCard } from "../../../shared/cards";
|
||||
import { GameContext } from "./Game";
|
||||
import { produce } from "solid-js/store";
|
||||
import { Stylable } from "./toolbox";
|
||||
|
||||
export default ((props) => {
|
||||
const { submitAction, view } = useContext(GameContext)!;
|
||||
|
||||
return (
|
||||
<div class={"hand " + props.class} style={props.style}>
|
||||
<For each={props.hand}>
|
||||
{(card) => (
|
||||
{(card, i) => (
|
||||
<Card
|
||||
card={card}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => submitAction({ type: "discard", card })}
|
||||
onClick={() => props.onClickCard?.(card, i())}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}) satisfies Component<{ hand: Hand } & Stylable>;
|
||||
}) satisfies Component<
|
||||
{ hand: THand; onClickCard?: (card: TCard, i: number) => any } & Stylable
|
||||
>;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { playerColor, profile } from "../profile";
|
||||
import { Stylable } from "./toolbox";
|
||||
|
||||
export default (props: { playerKey: string } & Stylable) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...props.style,
|
||||
"background-color": playerColor(props.playerKey),
|
||||
}}
|
||||
class={`${props.class} w-20 h-20 rounded-full flex justify-center items-center`}
|
||||
>
|
||||
<p style={{ "font-size": "1em" }}>
|
||||
{profile(props.playerKey)()?.name}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
Accessor,
|
||||
createContext,
|
||||
createEffect,
|
||||
createSignal,
|
||||
For,
|
||||
onCleanup,
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import { SimplePlayerView } from "../../../server/src/games/simple";
|
||||
import api, { fromWebsocket } from "../api";
|
||||
import { me, playerColor, profile } from "../profile";
|
||||
import { fromEvents, Stream, stream } from "kefir";
|
||||
import Bus from "kefir-bus";
|
||||
import { createObservable, createObservableWithInit, WSEvent } from "../fn";
|
||||
import { EdenWS } from "@elysiajs/eden/treaty";
|
||||
import { TWsIn, TWsOut } from "../../../server/src/table";
|
||||
import Player from "./Player";
|
||||
import Game from "./Game";
|
||||
|
||||
export const TableContext = createContext<{
|
||||
players: Accessor<string[]>;
|
||||
view: Accessor<any>;
|
||||
sendWs: (msg: TWsIn) => void;
|
||||
}>();
|
||||
|
||||
export default (props: { tableKey: string }) => {
|
||||
const ws = api.ws(props).subscribe();
|
||||
const wsEvents = fromWebsocket<TWsOut>(ws);
|
||||
onCleanup(() => ws.close());
|
||||
|
||||
const presenceEvents = wsEvents.filter((evt) => evt.players != null);
|
||||
|
||||
const gameEvents = wsEvents.filter((evt) => evt.view != null);
|
||||
|
||||
const players = createObservableWithInit<string[]>(
|
||||
presenceEvents.map((evt) => evt.players!),
|
||||
[]
|
||||
);
|
||||
|
||||
const view = createObservable(gameEvents.map((evt) => evt.view));
|
||||
|
||||
return (
|
||||
<TableContext.Provider
|
||||
value={{
|
||||
sendWs: (evt) => ws.send(evt),
|
||||
view,
|
||||
players,
|
||||
}}
|
||||
>
|
||||
<div class="flex justify-around p-t-10">
|
||||
<For each={players().filter((p) => p != me())}>
|
||||
{(player, i) => {
|
||||
const verticalOffset = () => {
|
||||
const N = players().length - 1;
|
||||
const x = Math.abs((2 * i() + 1) / (N * 2) - 0.5);
|
||||
const y = Math.sqrt(1 - x * x);
|
||||
return 1 - y;
|
||||
};
|
||||
return (
|
||||
<Player
|
||||
playerKey={player}
|
||||
style={{
|
||||
transform: `translate(0, ${
|
||||
verticalOffset() * 150
|
||||
}vh)`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
<div
|
||||
id="table"
|
||||
class="fixed bg-radial from-orange-950 to-stone-950 border-neutral-950 border-4 top-40 bottom-20 left-10 right-10 shadow-lg"
|
||||
style={{
|
||||
"border-radius": "50% 50%",
|
||||
}}
|
||||
>
|
||||
<Show when={view() == null}>
|
||||
<div class="absolute center">
|
||||
<button
|
||||
onClick={() => ws.send({ startGame: true })}
|
||||
class="button p-1 "
|
||||
>
|
||||
Start Game!
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={view() != null}>
|
||||
<Game />
|
||||
</Show>
|
||||
</TableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Observable } from "kefir";
|
||||
import { Accessor, createSignal } from "solid-js";
|
||||
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
thru<S>(fn: (arr: T[]) => S): S;
|
||||
@@ -7,3 +10,28 @@ Array.prototype.thru = function <T, S>(this: T[], fn: (arr: T[]) => S) {
|
||||
return fn(this);
|
||||
};
|
||||
export const clone = <T>(o: T): T => JSON.parse(JSON.stringify(o));
|
||||
|
||||
export type ApiType<T extends () => Promise<{ data: any }>> = Awaited<
|
||||
ReturnType<T>
|
||||
>["data"];
|
||||
|
||||
export type WSEvent<
|
||||
T extends { subscribe: (handler: (...args: any[]) => any) => any }
|
||||
> = Parameters<Parameters<T["subscribe"]>[0]>[0];
|
||||
|
||||
export const createObservable = <T>(obs: Observable<T, any>) => {
|
||||
const [signal, setSignal] = createSignal<T>();
|
||||
obs.onValue((val) => setSignal(() => val));
|
||||
|
||||
return signal;
|
||||
};
|
||||
|
||||
export const createObservableWithInit = <T>(
|
||||
obs: Observable<T, any>,
|
||||
init: T
|
||||
) => {
|
||||
const [signal, setSignal] = createSignal<T>(init);
|
||||
obs.onValue((val) => setSignal(() => val));
|
||||
|
||||
return signal;
|
||||
};
|
||||
|
||||
25
packages/client/src/profile.ts
Normal file
25
packages/client/src/profile.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createResource, Resource } from "solid-js";
|
||||
import { ApiType } from "./fn";
|
||||
import api from "./api";
|
||||
import hash from "object-hash";
|
||||
|
||||
export const [me] = createResource(() => api.whoami.post().then((r) => r.data));
|
||||
|
||||
const playerProfiles: {
|
||||
[humanKey: string]: Resource<ApiType<typeof api.profile.get>>;
|
||||
} = {};
|
||||
|
||||
export const profile = (humanKey: string) => {
|
||||
if (!(humanKey in playerProfiles)) {
|
||||
playerProfiles[humanKey] = createResource(() =>
|
||||
api.profile
|
||||
.get({ query: { otherHumanKey: humanKey } })
|
||||
.then((r) => r.data)
|
||||
)[0];
|
||||
}
|
||||
|
||||
return playerProfiles[humanKey];
|
||||
};
|
||||
|
||||
export const playerColor = (humanKey: string) =>
|
||||
"#" + hash(humanKey).substring(0, 6);
|
||||
@@ -15,12 +15,7 @@ export default () => {
|
||||
<Suspense>
|
||||
<div style={{ padding: "20px" }}>
|
||||
<p class="text-[40px]">{param.game}</p>
|
||||
<button
|
||||
class="px-2 py-1.5 m-4 button rounded"
|
||||
onClick={() => api.simple.newGame.post().then(refetch)}
|
||||
>
|
||||
New Game
|
||||
</button>
|
||||
<button class="px-2 py-1.5 m-4 button">New Game</button>
|
||||
<ul>
|
||||
<For each={instances() ?? []}>
|
||||
{(instance) => (
|
||||
|
||||
16
packages/client/src/routes/[table].tsx
Normal file
16
packages/client/src/routes/[table].tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { A, useParams } from "@solidjs/router";
|
||||
|
||||
import Table from "../components/Table";
|
||||
|
||||
export default () => {
|
||||
const { tableKey } = useParams();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table tableKey={tableKey} />
|
||||
<A href={"/"} class="fixed tl m-4 px-2 py-1.5 button">
|
||||
Back
|
||||
</A>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -29,7 +29,6 @@ a:visited {
|
||||
background-color: white;
|
||||
color: black;
|
||||
box-shadow: 0px 5px 10px black;
|
||||
border-radius: 10%;
|
||||
transition: background-color 0.15s, color 0.15s, transform 0.15s;
|
||||
}
|
||||
.button:hover {
|
||||
|
||||
@@ -31,6 +31,16 @@ export default defineConfig({
|
||||
"margin-right": "auto",
|
||||
},
|
||||
],
|
||||
[
|
||||
"tc",
|
||||
{
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
"margin-left": "auto",
|
||||
"margin-right": "auto",
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
"center",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- The required column `token` was added to the `Human` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
|
||||
|
||||
*/
|
||||
-- CreateTable
|
||||
CREATE TABLE "_HumanToInstance" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
CONSTRAINT "_HumanToInstance_A_fkey" FOREIGN KEY ("A") REFERENCES "Human" ("key") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "_HumanToInstance_B_fkey" FOREIGN KEY ("B") REFERENCES "Instance" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Human" (
|
||||
"key" TEXT NOT NULL PRIMARY KEY,
|
||||
"token" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL DEFAULT '__name__',
|
||||
"lastActive" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
INSERT INTO "new_Human" ("key", "name") SELECT "key", "name" FROM "Human";
|
||||
DROP TABLE "Human";
|
||||
ALTER TABLE "new_Human" RENAME TO "Human";
|
||||
CREATE UNIQUE INDEX "Human_token_key" ON "Human"("token");
|
||||
CREATE TABLE "new_Instance" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"createdByKey" TEXT NOT NULL,
|
||||
"gameKey" TEXT NOT NULL,
|
||||
"gameState" JSONB NOT NULL,
|
||||
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;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_HumanToInstance_AB_unique" ON "_HumanToInstance"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_HumanToInstance_B_index" ON "_HumanToInstance"("B");
|
||||
@@ -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;
|
||||
@@ -17,16 +17,21 @@ model Game {
|
||||
}
|
||||
|
||||
model Human {
|
||||
key String @id @default(cuid(2))
|
||||
name String @default("")
|
||||
Instance Instance[]
|
||||
key String @id @default(cuid())
|
||||
token String @unique @default(cuid())
|
||||
name String @default("__name__")
|
||||
lastActive DateTime @default(now())
|
||||
|
||||
playingInstances Instance[]
|
||||
}
|
||||
|
||||
model Instance {
|
||||
id String @id @default(cuid(2))
|
||||
id String @id @default(cuid())
|
||||
createdByKey String
|
||||
createdBy Human @relation(fields: [createdByKey], references: [key])
|
||||
gameKey String
|
||||
|
||||
players Human[]
|
||||
|
||||
game Game @relation(fields: [gameKey], references: [key])
|
||||
gameState Json
|
||||
gameState Json?
|
||||
}
|
||||
|
||||
@@ -16,12 +16,19 @@
|
||||
"@elysiajs/static": "^1.3.0",
|
||||
"@games/shared": "workspace:*",
|
||||
"@prisma/client": "6.13.0",
|
||||
"dayjs": "^1.11.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"
|
||||
"prisma": "6.13.0",
|
||||
"ts-xor": "^1.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
import { prisma } from "./db/db";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { simpleApi } from "./games/simple";
|
||||
import {
|
||||
SimpleAction,
|
||||
SimpleGameState,
|
||||
getKnowledge,
|
||||
getView,
|
||||
} from "./games/simple";
|
||||
import { human } from "./human";
|
||||
import dayjs from "dayjs";
|
||||
import db from "./db";
|
||||
import { liveTable, WsOut, WsIn } from "./table";
|
||||
|
||||
const api = new Elysia({ prefix: "/api" })
|
||||
.post("/whoami", async ({ cookie: { token } }) => {
|
||||
let human;
|
||||
if (token.value == null) {
|
||||
const newHuman = await prisma.human.create({
|
||||
human = await db.human.create({
|
||||
data: {},
|
||||
});
|
||||
token.value = newHuman.key;
|
||||
token.set({
|
||||
value: human.token,
|
||||
expires: dayjs().add(1, "year").toDate(),
|
||||
httpOnly: true,
|
||||
});
|
||||
} else {
|
||||
human = await db.human.findUniqueOrThrow({
|
||||
where: {
|
||||
token: token.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
return token.value;
|
||||
return human.key;
|
||||
})
|
||||
.use(human)
|
||||
.post(
|
||||
"/setName",
|
||||
({ body: { name }, humanKey }) =>
|
||||
prisma.human.update({
|
||||
db.human.update({
|
||||
where: {
|
||||
key: humanKey,
|
||||
},
|
||||
@@ -32,25 +49,78 @@ const api = new Elysia({ prefix: "/api" })
|
||||
}),
|
||||
}
|
||||
)
|
||||
.get("/profile", ({ humanKey }) =>
|
||||
prisma.human.findFirst({ where: { key: humanKey } })
|
||||
)
|
||||
.get("/games", () => prisma.game.findMany())
|
||||
|
||||
.get("/instances", ({ query: { game } }) =>
|
||||
prisma.instance.findMany({
|
||||
where: {
|
||||
game: {
|
||||
name: game,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
.get("/profile", ({ humanKey, query: { otherHumanKey } }) =>
|
||||
db.human
|
||||
.findFirst({ where: { key: otherHumanKey ?? humanKey } })
|
||||
.then((human) => {
|
||||
if (human == null) {
|
||||
return null;
|
||||
}
|
||||
const { token, ...safeProfile } = human;
|
||||
return safeProfile;
|
||||
})
|
||||
)
|
||||
.get("/games", () => [{ key: "simple", name: "simple" }])
|
||||
.ws("/ws/:tableKey", {
|
||||
async open({
|
||||
data: {
|
||||
params: { tableKey },
|
||||
humanKey,
|
||||
},
|
||||
send,
|
||||
}) {
|
||||
const table = liveTable<SimpleGameState, SimpleAction>(tableKey);
|
||||
|
||||
.use(simpleApi);
|
||||
table.outputs.playersPresent.onValue((players) =>
|
||||
send({ players })
|
||||
);
|
||||
table.outputs.gameState.onValue((gameState) =>
|
||||
send({
|
||||
view:
|
||||
gameState &&
|
||||
getView(getKnowledge(gameState, humanKey), humanKey),
|
||||
})
|
||||
);
|
||||
table.inputs.presenceChanges.emit({ humanKey, presence: "joined" });
|
||||
},
|
||||
|
||||
response: WsOut,
|
||||
body: WsIn,
|
||||
|
||||
message(
|
||||
{
|
||||
data: {
|
||||
humanKey,
|
||||
params: { tableKey },
|
||||
},
|
||||
},
|
||||
body
|
||||
) {
|
||||
const {
|
||||
inputs: { gameProposals, gameStarts, gameActions },
|
||||
} = liveTable(tableKey);
|
||||
|
||||
if ("proposeGame" in body) {
|
||||
gameProposals.emit(body);
|
||||
} else if ("startGame" in body) {
|
||||
gameStarts.emit(body);
|
||||
} else if ("action" in body) {
|
||||
gameActions.emit({ humanKey, ...body.action });
|
||||
}
|
||||
},
|
||||
|
||||
async close({
|
||||
data: {
|
||||
params: { tableKey },
|
||||
humanKey,
|
||||
},
|
||||
}) {
|
||||
liveTable(tableKey).inputs.presenceChanges.emit({
|
||||
humanKey,
|
||||
presence: "left",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default api;
|
||||
export type Api = typeof api;
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
export const prisma = new PrismaClient();
|
||||
export default new PrismaClient();
|
||||
@@ -1,4 +0,0 @@
|
||||
"use server";
|
||||
import { prisma } from "./db";
|
||||
|
||||
export const queryAll = async () => await prisma.game.findMany();
|
||||
@@ -1,25 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { GameState, newDeck, shuffle } from "../types/cards";
|
||||
import { prisma } from "./db";
|
||||
|
||||
export const queryInstances = async (gameName: string) =>
|
||||
prisma.instance.findMany({ where: { game: { name: gameName } } });
|
||||
|
||||
export const createInstance = (gameName: string) =>
|
||||
prisma.instance.create({
|
||||
data: {
|
||||
gameState: { deck: shuffle(newDeck()), hand: [] } as GameState,
|
||||
game: { connect: { name: gameName } },
|
||||
},
|
||||
});
|
||||
|
||||
export const getGameState = (instanceId: number) =>
|
||||
prisma.instance
|
||||
.findUnique({ where: { id: instanceId } })
|
||||
.then((i) => i?.gameState as GameState | undefined);
|
||||
|
||||
export const updateGameState = async (
|
||||
instanceId: number,
|
||||
gameState: GameState
|
||||
) => prisma.instance.update({ where: { id: instanceId }, data: { gameState } });
|
||||
@@ -1,20 +1,10 @@
|
||||
import {
|
||||
Card,
|
||||
Hand,
|
||||
newDeck,
|
||||
Pile,
|
||||
shuffle,
|
||||
vCard,
|
||||
} from "../../../shared/cards";
|
||||
import { Card, Hand, newDeck, Pile, shuffle, vCard } from "@games/shared/cards";
|
||||
import { heq } from "@games/shared/utils";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { prisma } from "../db/db";
|
||||
import { human } from "../human";
|
||||
|
||||
// omniscient game state
|
||||
export type GameState = {
|
||||
export type SimpleGameState = {
|
||||
prev?: {
|
||||
action: Action;
|
||||
action: SimpleAction;
|
||||
};
|
||||
|
||||
deck: Pile;
|
||||
@@ -22,7 +12,7 @@ export type GameState = {
|
||||
};
|
||||
|
||||
// a particular player's knowledge of the global game state
|
||||
export type vGameState = {
|
||||
export type vSimpleGameState = {
|
||||
humanId: string;
|
||||
|
||||
deck: Pile<vCard>;
|
||||
@@ -30,7 +20,7 @@ export type vGameState = {
|
||||
};
|
||||
|
||||
// a particular player's point of view in the game
|
||||
export type PlayerView = {
|
||||
export type SimplePlayerView = {
|
||||
humanId: string;
|
||||
|
||||
deckCount: number;
|
||||
@@ -38,20 +28,20 @@ export type PlayerView = {
|
||||
myHand: Hand<Card>;
|
||||
};
|
||||
|
||||
export type Action = { type: "draw" } | { type: "discard"; card: Card };
|
||||
export type SimpleAction = { type: "draw" } | { type: "discard"; card: Card };
|
||||
|
||||
export const newGame = (players: string[]) => {
|
||||
console.log("new game called with", JSON.stringify(players));
|
||||
return {
|
||||
deck: shuffle(newDeck()),
|
||||
players: Object.fromEntries(players.map((humanId) => [humanId, []])),
|
||||
} as GameState;
|
||||
} as SimpleGameState;
|
||||
};
|
||||
|
||||
export const getKnowledge = (
|
||||
state: GameState,
|
||||
state: SimpleGameState,
|
||||
humanId: string
|
||||
): vGameState => ({
|
||||
): vSimpleGameState => ({
|
||||
humanId,
|
||||
deck: state.deck.map((_) => null),
|
||||
players: Object.fromEntries(
|
||||
@@ -62,7 +52,10 @@ export const getKnowledge = (
|
||||
),
|
||||
});
|
||||
|
||||
const getView = (state: vGameState, humanId: string): PlayerView => ({
|
||||
export const getView = (
|
||||
state: vSimpleGameState,
|
||||
humanId: string
|
||||
): SimplePlayerView => ({
|
||||
humanId,
|
||||
deckCount: state.deck.length,
|
||||
myHand: state.players[humanId] as Hand,
|
||||
@@ -74,21 +67,20 @@ const getView = (state: vGameState, humanId: string): PlayerView => ({
|
||||
});
|
||||
|
||||
export const resolveAction = (
|
||||
state: GameState,
|
||||
state: SimpleGameState,
|
||||
humanId: string,
|
||||
action: Action
|
||||
): GameState => {
|
||||
// if (action.prevHash != hash(state)) {
|
||||
// throw new Error(
|
||||
// `action thinks it's applying to ${
|
||||
// action.prevHash
|
||||
// }, but we're checking it against ${hash(state)}`
|
||||
// );
|
||||
// }
|
||||
|
||||
action: SimpleAction
|
||||
): SimpleGameState => {
|
||||
console.log("attempting to resolve action", JSON.stringify(action));
|
||||
if (!(humanId in state.players)) {
|
||||
throw Error(
|
||||
`${humanId} is not a player in this game; they cannot perform actions`
|
||||
);
|
||||
}
|
||||
const playerHand = state.players[humanId];
|
||||
if (action.type == "draw") {
|
||||
const [drawn, ...rest] = state.deck;
|
||||
console.log("drew card", JSON.stringify(drawn));
|
||||
return {
|
||||
deck: rest,
|
||||
players: {
|
||||
@@ -110,67 +102,3 @@ export const resolveAction = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const simpleApi = new Elysia({ prefix: "/simple" })
|
||||
.use(human)
|
||||
.post("/newGame", ({ humanKey }) => {
|
||||
return prisma.instance.create({
|
||||
data: {
|
||||
gameState: newGame([humanKey]),
|
||||
gameKey: "simple",
|
||||
createdByKey: humanKey,
|
||||
},
|
||||
});
|
||||
})
|
||||
.group("/:instanceId", (app) =>
|
||||
app
|
||||
.get("/", ({ params: { instanceId }, humanKey }) =>
|
||||
prisma.instance
|
||||
.findUnique({
|
||||
where: {
|
||||
id: instanceId,
|
||||
},
|
||||
})
|
||||
.then((game) =>
|
||||
getView(
|
||||
getKnowledge(
|
||||
game!.gameState as GameState,
|
||||
humanKey
|
||||
),
|
||||
humanKey
|
||||
)
|
||||
)
|
||||
)
|
||||
.post(
|
||||
"/",
|
||||
({ params: { instanceId }, body: { action }, humanKey }) =>
|
||||
prisma.instance
|
||||
.findUniqueOrThrow({
|
||||
where: {
|
||||
id: instanceId,
|
||||
},
|
||||
})
|
||||
.then(async (game) => {
|
||||
const newState = resolveAction(
|
||||
game.gameState as GameState,
|
||||
humanKey,
|
||||
action
|
||||
);
|
||||
await prisma.instance.update({
|
||||
data: { gameState: newState },
|
||||
where: {
|
||||
id: instanceId,
|
||||
},
|
||||
});
|
||||
return getView(
|
||||
getKnowledge(newState, humanKey),
|
||||
humanKey
|
||||
);
|
||||
}),
|
||||
{
|
||||
body: t.Object({
|
||||
action: t.Any(),
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import Elysia from "elysia";
|
||||
import db from "./db";
|
||||
|
||||
export const human = new Elysia({ name: "human" })
|
||||
.derive(async ({ cookie: { token }, status }) => {
|
||||
const humanKey = token.value;
|
||||
const humanKey = await db.human
|
||||
.findUnique({
|
||||
where: { token: token.value },
|
||||
})
|
||||
.then((human) => human?.key);
|
||||
return humanKey != null ? { humanKey } : status(401);
|
||||
})
|
||||
.as("scoped");
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
29
packages/server/src/kefir-extension.ts
Normal file
29
packages/server/src/kefir-extension.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { merge, Observable } from "kefir";
|
||||
|
||||
export const transform = <
|
||||
T,
|
||||
Mutations extends [Observable<any, any>, (prev: T, evt: any) => T][]
|
||||
>(
|
||||
initValue: T,
|
||||
...mutations: Mutations
|
||||
): Observable<T, any> =>
|
||||
merge(
|
||||
mutations.map(([source, mutation]) =>
|
||||
source.map((event) => ({ event, mutation }))
|
||||
)
|
||||
).scan((prev, { event, mutation }) => mutation(prev, event), initValue);
|
||||
|
||||
export const partition =
|
||||
<C extends readonly [...string[]], T, E>(
|
||||
classes: C,
|
||||
partitionFn: (v: T) => C[number]
|
||||
) =>
|
||||
(obs: Observable<T, E>) => {
|
||||
const assigned = obs.map((obj) => ({ obj, cls: partitionFn(obj) }));
|
||||
return Object.fromEntries(
|
||||
classes.map((C) => [
|
||||
C,
|
||||
assigned.filter(({ cls }) => cls == C).map(({ obj }) => obj),
|
||||
])
|
||||
);
|
||||
};
|
||||
104
packages/server/src/table.ts
Normal file
104
packages/server/src/table.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { t } from "elysia";
|
||||
import { combine, Property } from "kefir";
|
||||
import Bus, { type Bus as TBus } from "kefir-bus";
|
||||
import {
|
||||
newGame,
|
||||
resolveAction,
|
||||
SimpleAction,
|
||||
SimpleGameState,
|
||||
} from "./games/simple";
|
||||
import { transform } from "./kefir-extension";
|
||||
|
||||
export const WsOut = t.Object({
|
||||
players: t.Optional(t.Array(t.String())),
|
||||
view: t.Optional(t.Any()),
|
||||
});
|
||||
export type TWsOut = typeof WsOut.static;
|
||||
export const WsIn = t.Union([
|
||||
t.Object({ proposeGame: t.String() }),
|
||||
t.Object({ startGame: t.Literal(true) }),
|
||||
t.Object({ action: t.Any() }),
|
||||
]);
|
||||
export type TWsIn = typeof WsIn.static;
|
||||
|
||||
type Attributed = { humanKey: string };
|
||||
type TablePayload<GameState, GameAction> = {
|
||||
inputs: {
|
||||
presenceChanges: TBus<
|
||||
Attributed & { presence: "joined" | "left" },
|
||||
never
|
||||
>;
|
||||
gameProposals: TBus<{ proposeGame: string }, never>;
|
||||
gameStarts: TBus<{ startGame: true }, never>;
|
||||
gameActions: TBus<Attributed & GameAction, never>;
|
||||
};
|
||||
outputs: {
|
||||
playersPresent: Property<string[], never>;
|
||||
gameState: Property<GameState | null, never>;
|
||||
};
|
||||
};
|
||||
|
||||
const tables: {
|
||||
[key: string]: TablePayload<unknown, unknown>;
|
||||
} = {};
|
||||
|
||||
export const liveTable = <GameState, GameAction>(key: string) => {
|
||||
if (!(key in tables)) {
|
||||
const inputs: TablePayload<GameState, GameAction>["inputs"] = {
|
||||
presenceChanges: Bus(),
|
||||
gameProposals: Bus(),
|
||||
gameStarts: Bus(),
|
||||
gameActions: Bus(),
|
||||
};
|
||||
const { presenceChanges, gameProposals, gameStarts, gameActions } =
|
||||
inputs;
|
||||
|
||||
// =======
|
||||
const playersPresent = presenceChanges.scan((prev, evt) => {
|
||||
if (evt.presence == "joined") {
|
||||
prev.push(evt.humanKey);
|
||||
} else if (evt.presence == "left") {
|
||||
prev.splice(prev.indexOf(evt.humanKey), 1);
|
||||
}
|
||||
return prev;
|
||||
}, [] as string[]);
|
||||
|
||||
const gameState = transform(
|
||||
null as SimpleGameState | null,
|
||||
[
|
||||
combine([gameStarts], [playersPresent], (evt, players) => ({
|
||||
...evt,
|
||||
players,
|
||||
})),
|
||||
(prev, evt: { players: string[] }) =>
|
||||
prev == null
|
||||
? (newGame(evt.players) as SimpleGameState)
|
||||
: prev,
|
||||
],
|
||||
[
|
||||
gameActions,
|
||||
(prev, evt: Attributed & SimpleAction) =>
|
||||
prev != null
|
||||
? resolveAction(prev, evt.humanKey, evt)
|
||||
: prev,
|
||||
]
|
||||
).toProperty();
|
||||
|
||||
tables[key] = {
|
||||
inputs,
|
||||
outputs: {
|
||||
playersPresent,
|
||||
gameState: gameState as Property<unknown, never>,
|
||||
},
|
||||
};
|
||||
|
||||
// cleanup
|
||||
tables[key].outputs.playersPresent
|
||||
.slidingWindow(2)
|
||||
.filter(([prev, curr]) => prev.length > 0 && curr.length == 0)
|
||||
.onValue((_) => {
|
||||
delete tables[key];
|
||||
});
|
||||
}
|
||||
return tables[key] as TablePayload<GameState, GameAction>;
|
||||
};
|
||||
197
pnpm-lock.yaml
generated
197
pnpm-lock.yaml
generated
@@ -19,22 +19,28 @@ importers:
|
||||
dependencies:
|
||||
'@elysiajs/eden':
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2(elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
version: 1.3.2(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
'@solid-primitives/scheduled':
|
||||
specifier: ^1.5.2
|
||||
version: 1.5.2(solid-js@1.9.8)
|
||||
version: 1.5.2(solid-js@1.9.9)
|
||||
'@solidjs/router':
|
||||
specifier: ^0.15.3
|
||||
version: 0.15.3(solid-js@1.9.8)
|
||||
version: 0.15.3(solid-js@1.9.9)
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
kefir:
|
||||
specifier: ^3.8.8
|
||||
version: 3.8.8
|
||||
kefir-bus:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(kefir@3.8.8)
|
||||
object-hash:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
solid-js:
|
||||
specifier: ^1.9.5
|
||||
version: 1.9.8
|
||||
version: 1.9.9
|
||||
devDependencies:
|
||||
'@iconify-json/solar':
|
||||
specifier: ^1.2.4
|
||||
@@ -42,6 +48,9 @@ importers:
|
||||
'@types/js-cookie':
|
||||
specifier: ^3.0.6
|
||||
version: 3.0.6
|
||||
'@types/kefir':
|
||||
specifier: ^3.8.11
|
||||
version: 3.8.11
|
||||
'@unocss/preset-icons':
|
||||
specifier: ^66.4.2
|
||||
version: 66.4.2
|
||||
@@ -56,25 +65,40 @@ importers:
|
||||
version: 4.5.14(@types/node@24.2.0)
|
||||
vite-plugin-solid:
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8(solid-js@1.9.8)(vite@4.5.14(@types/node@24.2.0))
|
||||
version: 2.11.8(solid-js@1.9.9)(vite@4.5.14(@types/node@24.2.0))
|
||||
|
||||
packages/server:
|
||||
dependencies:
|
||||
'@elysiajs/cors':
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3(elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
version: 1.3.3(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
'@elysiajs/static':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
version: 1.3.0(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
'@games/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
'@prisma/client':
|
||||
specifier: 6.13.0
|
||||
version: 6.13.0(prisma@6.13.0(typescript@5.9.2))(typescript@5.9.2)
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
elysia:
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
version: 1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
elysia-ip:
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
elysia-rate-limit:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))
|
||||
kefir:
|
||||
specifier: ^3.8.8
|
||||
version: 3.8.8
|
||||
kefir-bus:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(kefir@3.8.8)
|
||||
object-hash:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -82,12 +106,18 @@ importers:
|
||||
'@types/bun':
|
||||
specifier: latest
|
||||
version: 1.2.20(@types/react@19.1.9)
|
||||
'@types/kefir':
|
||||
specifier: ^3.8.11
|
||||
version: 3.8.11
|
||||
concurrently:
|
||||
specifier: ^9.2.0
|
||||
version: 9.2.0
|
||||
prisma:
|
||||
specifier: 6.13.0
|
||||
version: 6.13.0(typescript@5.9.2)
|
||||
ts-xor:
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
|
||||
packages/shared:
|
||||
dependencies:
|
||||
@@ -97,6 +127,10 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -188,6 +222,9 @@ packages:
|
||||
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@borewit/text-codec@0.1.1':
|
||||
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
||||
|
||||
'@elysiajs/cors@1.3.3':
|
||||
resolution: {integrity: sha512-mYIU6PyMM6xIJuj7d27Vt0/wuzVKIEnFPjcvlkyd7t/m9xspAG37cwNjFxVOnyvY43oOd2I/oW2DB85utXpA2Q==}
|
||||
peerDependencies:
|
||||
@@ -434,6 +471,9 @@ packages:
|
||||
'@types/js-cookie@3.0.6':
|
||||
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||
|
||||
'@types/kefir@3.8.11':
|
||||
resolution: {integrity: sha512-5TRdFXQYsVUvqIH6nYjslHzBgn4hnptcutXnqAhfbKdWD/799c44hFhQGF3887E2t/Q4jSp3RvNFCaQ+b9w6vQ==}
|
||||
|
||||
'@types/node@24.2.0':
|
||||
resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==}
|
||||
|
||||
@@ -658,6 +698,18 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -690,8 +742,18 @@ packages:
|
||||
electron-to-chromium@1.5.198:
|
||||
resolution: {integrity: sha512-G5COfnp3w+ydVu80yprgWSfmfQaYRh9DOxfhAxstLyetKaLyl55QrNjx8C38Pc/C+RaDmb1M0Lk8wPEMQ+bGgQ==}
|
||||
|
||||
elysia@1.3.8:
|
||||
resolution: {integrity: sha512-kxYFhegJbUEf5otzmisEvGt3R7d/dPBNVERO2nHo0kFqKBHyj5slArc90mSRKLfi1vamMtPcz67rL6Zeg5F2yg==}
|
||||
elysia-ip@1.0.10:
|
||||
resolution: {integrity: sha512-xmCxPOl4266sq6CLk5d82P3BZOatG9z0gMP473cYEnORssuopbEI8GAwpOhiaz69X76AOrkYgvCdLkqMJC49dQ==}
|
||||
peerDependencies:
|
||||
elysia: '>= 1.0.9'
|
||||
|
||||
elysia-rate-limit@4.4.0:
|
||||
resolution: {integrity: sha512-pyQdFEdjgf5ELx5CAEfOZ2IWhPaYv8WIQMrXimzHzslsJ9awDHoK6rcF9K7k/yAOh4qB1UhiasNeMMBGtxAwYQ==}
|
||||
peerDependencies:
|
||||
elysia: '>= 1.0.0'
|
||||
|
||||
elysia@1.3.11:
|
||||
resolution: {integrity: sha512-iTBdfLe+CL8UvnqP+TB4NlUUqxhlKGEIxLMUZqlylUp4yGq2lTdFbxlItZuA7Z4/mlv5wC3GfjTd587Iwo552Q==}
|
||||
peerDependencies:
|
||||
exact-mirror: '>= 0.0.9'
|
||||
file-type: '>= 20.0.0'
|
||||
@@ -846,6 +908,14 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
kefir-bus@2.3.1:
|
||||
resolution: {integrity: sha512-wLCQfEw8PddSNeyjDCH2WNgNg3Rb/c+OaG5WEPfEwod+LQfGX4isHcHRWsYNLmdFEw3/KyA+9qDSy+VC4NsifA==}
|
||||
peerDependencies:
|
||||
kefir: ^3.5.1
|
||||
|
||||
kefir@3.8.8:
|
||||
resolution: {integrity: sha512-xWga7QCZsR2Wjy2vNL3Kq/irT+IwxwItEWycRRlT5yhqHZK2fmEhziP+LzcJBWSTAMranGKtGTQ6lFpyJS3+jA==}
|
||||
|
||||
kolorist@1.8.0:
|
||||
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||
|
||||
@@ -879,6 +949,9 @@ packages:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -1034,8 +1107,8 @@ packages:
|
||||
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
solid-js@1.9.8:
|
||||
resolution: {integrity: sha512-zF9Whfqk+s8wWuyDKnE7ekl+dJburjdZq54O6X1k4XChA57uZ5FOauYAa0s4I44XkBOM3CZmPrZC0DGjH9fKjQ==}
|
||||
solid-js@1.9.9:
|
||||
resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==}
|
||||
|
||||
solid-refresh@0.6.3:
|
||||
resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==}
|
||||
@@ -1089,8 +1162,8 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
token-types@6.0.4:
|
||||
resolution: {integrity: sha512-MD9MjpVNhVyH4fyd5rKphjvt/1qj+PtQUz65aFqAZA6XniWAuSFRjLk3e2VALEFlh9OwBpXUN7rfeqSnT/Fmkw==}
|
||||
token-types@6.1.1:
|
||||
resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
totalist@3.0.1:
|
||||
@@ -1101,6 +1174,9 @@ packages:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
ts-xor@1.3.0:
|
||||
resolution: {integrity: sha512-RLXVjliCzc1gfKQFLRpfeD0rrWmjnSTgj7+RFhoq3KRkUYa8LE/TIidYOzM5h+IdFBDSjjSgk9Lto9sdMfDFEA==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
@@ -1116,8 +1192,8 @@ packages:
|
||||
ufo@1.6.1:
|
||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||
|
||||
uint8array-extras@1.4.0:
|
||||
resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==}
|
||||
uint8array-extras@1.4.1:
|
||||
resolution: {integrity: sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
unconfig@7.3.2:
|
||||
@@ -1228,6 +1304,8 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.12
|
||||
@@ -1351,17 +1429,19 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@elysiajs/cors@1.3.3(elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))':
|
||||
dependencies:
|
||||
elysia: 1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
'@borewit/text-codec@0.1.1': {}
|
||||
|
||||
'@elysiajs/eden@1.3.2(elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))':
|
||||
'@elysiajs/cors@1.3.3(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))':
|
||||
dependencies:
|
||||
elysia: 1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
elysia: 1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
|
||||
'@elysiajs/static@1.3.0(elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))':
|
||||
'@elysiajs/eden@1.3.2(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))':
|
||||
dependencies:
|
||||
elysia: 1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
elysia: 1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
|
||||
'@elysiajs/static@1.3.0(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2))':
|
||||
dependencies:
|
||||
elysia: 1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
node-cache: 5.1.2
|
||||
|
||||
'@esbuild/android-arm64@0.18.20':
|
||||
@@ -1507,13 +1587,13 @@ snapshots:
|
||||
'@sinclair/typebox@0.34.38':
|
||||
optional: true
|
||||
|
||||
'@solid-primitives/scheduled@1.5.2(solid-js@1.9.8)':
|
||||
'@solid-primitives/scheduled@1.5.2(solid-js@1.9.9)':
|
||||
dependencies:
|
||||
solid-js: 1.9.8
|
||||
solid-js: 1.9.9
|
||||
|
||||
'@solidjs/router@0.15.3(solid-js@1.9.8)':
|
||||
'@solidjs/router@0.15.3(solid-js@1.9.9)':
|
||||
dependencies:
|
||||
solid-js: 1.9.8
|
||||
solid-js: 1.9.9
|
||||
|
||||
'@standard-schema/spec@1.0.0': {}
|
||||
|
||||
@@ -1521,7 +1601,7 @@ snapshots:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
fflate: 0.8.2
|
||||
token-types: 6.0.4
|
||||
token-types: 6.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -1556,6 +1636,10 @@ snapshots:
|
||||
|
||||
'@types/js-cookie@3.0.6': {}
|
||||
|
||||
'@types/kefir@3.8.11':
|
||||
dependencies:
|
||||
'@types/node': 24.2.0
|
||||
|
||||
'@types/node@24.2.0':
|
||||
dependencies:
|
||||
undici-types: 7.10.0
|
||||
@@ -1741,12 +1825,12 @@ snapshots:
|
||||
parse5: 7.3.0
|
||||
validate-html-nesting: 1.2.3
|
||||
|
||||
babel-preset-solid@1.9.8(@babel/core@7.28.0)(solid-js@1.9.8):
|
||||
babel-preset-solid@1.9.8(@babel/core@7.28.0)(solid-js@1.9.9):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
babel-plugin-jsx-dom-expressions: 0.40.0(@babel/core@7.28.0)
|
||||
optionalDependencies:
|
||||
solid-js: 1.9.8
|
||||
solid-js: 1.9.9
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
@@ -1853,6 +1937,12 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
debug@4.3.4:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -1874,7 +1964,19 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.198: {}
|
||||
|
||||
elysia@1.3.8(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2):
|
||||
elysia-ip@1.0.10(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)):
|
||||
dependencies:
|
||||
elysia: 1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
|
||||
elysia-rate-limit@4.4.0(elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)):
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
debug: 4.3.4
|
||||
elysia: 1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
elysia@1.3.11(exact-mirror@0.1.5(@sinclair/typebox@0.34.38))(file-type@21.0.0)(typescript@5.9.2):
|
||||
dependencies:
|
||||
cookie: 1.0.2
|
||||
exact-mirror: 0.1.5(@sinclair/typebox@0.34.38)
|
||||
@@ -1938,8 +2040,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@tokenizer/inflate': 0.2.7
|
||||
strtok3: 10.3.4
|
||||
token-types: 6.0.4
|
||||
uint8array-extras: 1.4.0
|
||||
token-types: 6.1.1
|
||||
uint8array-extras: 1.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2013,6 +2115,12 @@ snapshots:
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
kefir-bus@2.3.1(kefir@3.8.8):
|
||||
dependencies:
|
||||
kefir: 3.8.8
|
||||
|
||||
kefir@3.8.8: {}
|
||||
|
||||
kolorist@1.8.0: {}
|
||||
|
||||
local-pkg@1.1.1:
|
||||
@@ -2048,6 +2156,8 @@ snapshots:
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.1.2: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
@@ -2195,18 +2305,18 @@ snapshots:
|
||||
mrmime: 2.0.1
|
||||
totalist: 3.0.1
|
||||
|
||||
solid-js@1.9.8:
|
||||
solid-js@1.9.9:
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
seroval: 1.3.2
|
||||
seroval-plugins: 1.3.2(seroval@1.3.2)
|
||||
|
||||
solid-refresh@0.6.3(solid-js@1.9.8):
|
||||
solid-refresh@0.6.3(solid-js@1.9.9):
|
||||
dependencies:
|
||||
'@babel/generator': 7.28.0
|
||||
'@babel/helper-module-imports': 7.27.1
|
||||
'@babel/types': 7.28.2
|
||||
solid-js: 1.9.8
|
||||
solid-js: 1.9.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2259,8 +2369,9 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
token-types@6.0.4:
|
||||
token-types@6.1.1:
|
||||
dependencies:
|
||||
'@borewit/text-codec': 0.1.1
|
||||
'@tokenizer/token': 0.3.0
|
||||
ieee754: 1.2.1
|
||||
|
||||
@@ -2268,6 +2379,8 @@ snapshots:
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
ts-xor@1.3.0: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
@@ -2276,7 +2389,7 @@ snapshots:
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
uint8array-extras@1.4.0: {}
|
||||
uint8array-extras@1.4.1: {}
|
||||
|
||||
unconfig@7.3.2:
|
||||
dependencies:
|
||||
@@ -2334,14 +2447,14 @@ snapshots:
|
||||
spdx-correct: 3.2.0
|
||||
spdx-expression-parse: 3.0.1
|
||||
|
||||
vite-plugin-solid@2.11.8(solid-js@1.9.8)(vite@4.5.14(@types/node@24.2.0)):
|
||||
vite-plugin-solid@2.11.8(solid-js@1.9.9)(vite@4.5.14(@types/node@24.2.0)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@types/babel__core': 7.20.5
|
||||
babel-preset-solid: 1.9.8(@babel/core@7.28.0)(solid-js@1.9.8)
|
||||
babel-preset-solid: 1.9.8(@babel/core@7.28.0)(solid-js@1.9.9)
|
||||
merge-anything: 5.1.7
|
||||
solid-js: 1.9.8
|
||||
solid-refresh: 0.6.3(solid-js@1.9.8)
|
||||
solid-js: 1.9.9
|
||||
solid-refresh: 0.6.3(solid-js@1.9.9)
|
||||
vite: 4.5.14(@types/node@24.2.0)
|
||||
vitefu: 1.1.1(vite@4.5.14(@types/node@24.2.0))
|
||||
transitivePeerDependencies:
|
||||
|
||||
Reference in New Issue
Block a user