';
-
- const currentPlayerColor =
- Object.entries(game.players).find(([_, id]) => id === playerId)?.[0] ||
- null;
- const isPlayersTurn =
- game.status === 'playing' && game.currentPlayerColor === currentPlayerColor;
-
for (let row = 0; row < game.board.length; row++) {
for (let col = 0; col < game.board[row].length; col++) {
const stone = game.board[row][col];
@@ -28,7 +21,7 @@ export function renderGameBoardHtml(
const left = col * 30;
// HTMX attributes for making a move
- const wsAttrs = isPlayersTurn && !stone ? `ws-send="click"` : '';
+ const wsAttrs = isPlayerToPlay && !stone ? `ws-send="click"` : '';
boardHtml += `
`;
return boardHtml;
}
-
-export function renderTitleBoxHtml(gameId: string, playerId: string): string {
- return `
You are: ${playerId}
Game ID: ${gameId}
`;
-}
diff --git a/src/web-socket-handler.ts b/src/web-socket-handler.ts
deleted file mode 100644
index db09bc4..0000000
--- a/src/web-socket-handler.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-import { ElysiaWS } from 'elysia/dist/ws';
-import { GameInstance } from './game/game-instance';
-import { renderGameBoardHtml } from './view/board-renderer';
-
-interface MakeMoveMessage {
- gameId: string;
- playerId: string;
- row: number;
- col: number;
-}
-
-type WS = ElysiaWS<{ query: { playerId: string; gameId: string } }>;
-export class WebSocketHandler {
- private connections: Map
>;
- private games: Map;
-
- constructor() {
- this.connections = new Map();
- this.games = new Map();
- }
-
- public handleConnection(ws: WS): void {
- const { gameId, playerId } = ws.data.query;
-
- if (!this.connections.has(gameId)) {
- this.connections.set(gameId, []);
- }
- this.connections.get(gameId)?.push(ws);
-
- const game = this.getGame(gameId);
- if (game) {
- this.broadcastGameState(game.id);
- } else {
- ws.send('Error: game not found');
- ws.close();
- }
-
- console.log(
- `WebSocket connected, registered for Game ${gameId} as Player ${playerId}`,
- );
- }
-
- public handleMessage(ws: WS, message: any): void {
- const type: string = message.type;
- // Someday we might have other message types
- if (type === 'make_move') {
- this.handleMakeMove(ws, message as MakeMoveMessage);
- }
- }
-
- private handleMakeMove(ws: WS, message: MakeMoveMessage): void {
- const { row, col } = message;
- const { gameId, playerId } = ws.data.query;
- console.log(
- `Handling make_move message in game ${gameId} from player ${playerId}: ${{ message }}`,
- );
-
- if (!gameId || !playerId || row === undefined || col === undefined) {
- this.sendMessage(ws, 'Error: missing gameId, playerId, row, or col');
- return;
- }
-
- const game = this.games.get(gameId);
- if (!game) {
- this.sendMessage(ws, 'Error: game not found');
- return;
- }
-
- const playerColor = Object.entries(game.players).find(
- ([_, id]) => id === playerId,
- )?.[0] as ('black' | 'white') | undefined;
- if (!playerColor) {
- this.sendMessage(ws, 'Error: you are not a player in this game');
- return;
- }
-
- if (game.currentPlayerColor !== playerColor) {
- this.sendMessage(ws, "Error: It's not your turn");
- return;
- }
-
- try {
- const result = game.makeMove(playerId, row, col);
- if (result.success) {
- this.broadcastGameState(game.id);
- console.log(
- `Move made in game ${game.id} by ${playerId}: (${row}, ${col})`,
- );
- } else {
- this.sendMessage(ws, result.error || 'Error: invalid move');
- }
- } catch (e: any) {
- this.sendMessage(ws, 'Error: ' + e.message);
- }
- }
-
- public handleDisconnect(ws: WS): void {
- const { gameId, playerId } = ws.data.query;
-
- const connectionsInGame = this.connections.get(gameId);
- if (!connectionsInGame) {
- console.error(
- `Disconnecting WebSocket for player ${playerId} from game ${gameId}, but that game has no connections!`,
- );
- return;
- }
- this.connections.set(
- gameId,
- connectionsInGame.filter(
- (conn) => conn.data.query.playerId !== ws.data.query.playerId,
- ),
- );
- if (this.connections.get(gameId)?.length === 0) {
- this.connections.delete(gameId);
- }
-
- if (this.connections.has(gameId)) {
- // Notify remaining players about disconnect
- this.sendMessageToGame(gameId, `${playerId} disconnected.`);
- }
-
- this.sendTitleBoxesForGame(gameId);
-
- console.log(`${playerId} disconnected from game ${gameId}`);
- }
-
- public broadcastGameState(gameId: string): void {
- const game = this.games.get(gameId);
- if (!game) {
- console.warn(
- 'Attempted to broadcast state of game ${gameId}, which is not loaded.',
- );
- return;
- }
-
- const connectionsToUpdate = this.connections.get(gameId);
- if (connectionsToUpdate) {
- connectionsToUpdate.forEach((ws) => {
- const { gameId, playerId } = ws.data.query;
-
- const updatedBoardHtml = renderGameBoardHtml(game, playerId);
- ws.send(updatedBoardHtml);
-
- if (game.status === 'finished') {
- if (game.winnerColor === 'draw') {
- this.sendMessageToGame(gameId, 'Game ended in draw.');
- } else if (game.winnerColor) {
- this.sendMessageToGame(
- gameId,
- `${game.winnerColor.toUpperCase()} wins!`,
- );
- }
- } else if (game.status === 'playing') {
- const clientPlayerColor = Object.entries(game.players).find(
- ([_, id]) => id === playerId,
- )?.[0] as ('black' | 'white') | undefined;
- if (
- game.currentPlayerColor &&
- clientPlayerColor === game.currentPlayerColor
- ) {
- this.sendMessage(ws, "It's your turn!");
- } else if (game.currentPlayerColor) {
- this.sendMessage(
- ws,
- `Waiting for ${game.currentPlayerColor}'s move.`,
- );
- }
- } else if (game.status === 'waiting') {
- this.sendMessage(ws, 'Waiting for another player...');
- }
- });
- } else {
- console.log(`No connections to update for game ${gameId}.`);
- }
-
- this.sendTitleBoxesForGame(gameId);
- }
-
- public sendMessageToGame(gameId: string, message: string): void {
- const connections = this.connections.get(gameId);
- if (connections) {
- connections.forEach((ws) => {
- this.sendMessage(ws, message);
- });
- }
- }
-
- public sendMessage(targetWs: WS, message: string): void {
- targetWs.send('' + message + '
');
- }
-
- private sendTitleBox(targetWs: WS, message: string): void {
- targetWs.send('' + message + '
');
- }
-
- private sendTitleBoxesForGame(gameId: string): void {
- const game = this.games.get(gameId);
- if (!game) {
- console.error(
- `Tried to send title boxes for game ${gameId}, but it doesn't exist!`,
- );
- return;
- }
- const connections = this.connections.get(gameId);
- if (!connections) {
- console.log(
- `Attempted to send title boxes for game ${gameId}, but no players are connected.`,
- );
- return;
- }
-
- var message = '';
- switch (game.status) {
- case 'waiting': {
- message = 'Waiting for players...';
- break;
- }
- case 'playing': {
- const blackTag = game.players.black
- ? this.playerTag(gameId, game.players.black)
- : 'Unknown';
- const whiteTag = game.players.white
- ? this.playerTag(gameId, game.players.white)
- : 'Unknown';
- message = `${blackTag} vs ${whiteTag}`;
- break;
- }
- case 'finished': {
- switch (game.winnerColor) {
- case 'draw': {
- message = 'Game ended in draw.';
- break;
- }
- case 'black': {
- message = `${this.playerTag(gameId, game.players.black!)} wins!`;
- break;
- }
- case 'white': {
- message = `${this.playerTag(gameId, game.players.white!)} wins!`;
- break;
- }
- }
- break;
- }
- }
-
- connections.forEach((connection) => {
- this.sendTitleBox(connection, message);
- });
- }
-
- private playerTag(gameId: string, playerId: string) {
- // Determine whether the player is disconnected
- var connectionIcon = `
`;
- const connections = this.connections.get(gameId);
- if (connections) {
- connections.forEach((ws) => {
- if (ws.data.query.playerId === playerId) {
- console.log(`Connection exists for player ${playerId}`);
- connectionIcon = '';
- }
- });
- }
-
- // Set the correct name color for the player
- var colorClass = '';
- var turnClass = '';
- const game = this.games.get(gameId);
- if (game) {
- if (game.players.white === playerId) {
- colorClass = 'player-white';
- } else if (game.players.black === playerId) {
- colorClass = 'player-black';
- }
- if (game.getCurrentPlayerId() === playerId) {
- turnClass = 'player-to-play';
- }
- }
- const classes = `player-name ${colorClass} ${turnClass}`.trim();
-
- return `${playerId}${connectionIcon}`;
- }
-
- public getGame(gameId: string): GameInstance | undefined {
- return this.games.get(gameId);
- }
-
- createGame(gameId?: string): GameInstance {
- const game = new GameInstance(gameId);
- this.games.set(game.id, game);
- return game;
- }
-}
diff --git a/src/web-socket-handler.tsx b/src/web-socket-handler.tsx
new file mode 100644
index 0000000..b0aaaf4
--- /dev/null
+++ b/src/web-socket-handler.tsx
@@ -0,0 +1,267 @@
+import { ElysiaWS } from 'elysia/dist/ws';
+import { Html } from '@elysiajs/html';
+import { GomokuGame as GomokuGame, PlayerColor } from './game/game-instance';
+import { renderGameBoardHtml } from './view/board-renderer';
+import { Message, MakeMoveMessage, ResignationMessage } from './messages';
+import { v4 as uuidv4 } from 'uuid';
+
+type WS = ElysiaWS<{ query: { playerId: string; gameId: string } }>;
+
+class PlayerConnection {
+ id: string;
+ name: string;
+ ws: WS;
+
+ constructor(id: string, ws: WS) {
+ this.id = id;
+ this.name = id;
+ this.ws = ws;
+ }
+
+ public sendMessage(severity: 'info' | 'error', message: string) {
+ // TODO
+ }
+}
+
+class GameServer {
+ id: string;
+ gomoku: GomokuGame;
+ connections: Map;
+ blackPlayerId?: string;
+ whitePlayerId?: string;
+
+ constructor(id: string) {
+ this.id = id;
+ this.gomoku = new GomokuGame();
+ this.connections = new Map();
+ }
+
+ public handleConnection(ws: WS) {
+ const { playerId } = ws.data.query;
+ const conn = new PlayerConnection(playerId, ws);
+ this.connections.set(playerId, conn);
+ console.log(`Created connection with player ${conn.id} in game ${this.id}`);
+
+ if (!this.blackPlayerId) {
+ this.blackPlayerId = conn.id;
+ } else if (!this.whitePlayerId) {
+ this.whitePlayerId = conn.id;
+ }
+ if (this.whitePlayerId && this.blackPlayerId) {
+ this.gomoku.status = 'playing';
+ }
+
+ this.broadcastBoard();
+ this.broadcastButtons();
+ this.broadcastTitle();
+ }
+
+ public handleDisconnect(ws: WS) {
+ const { playerId } = ws.data.query;
+ this.connections.delete(playerId);
+ }
+
+ public broadcastBoard() {
+ this.connections.forEach((conn: PlayerConnection) => this.broadcastBoardToPlayer(conn));
+ }
+
+ public broadcastTitle() {
+ this.connections.forEach((conn: PlayerConnection) => this.broadcastTitleToPlayer(conn));
+ }
+
+ public broadcastButtons() {
+ this.connections.forEach((conn: PlayerConnection) => this.broadcastButtonsToPlayer(conn));
+ }
+
+ public broadcastBoardToPlayer(conn: PlayerConnection) {
+ const isToPlay = this.gomoku.currentPlayerColor == this.getPlayerColor(conn);
+ const updatedBoardHtml = renderGameBoardHtml(this.gomoku, isToPlay);
+ conn.ws.send(updatedBoardHtml);
+ console.log(`Sent board for game ${this.id} to player ${conn.id}`);
+ }
+
+ public broadcastTitleToPlayer(conn: PlayerConnection) {
+ let message = '';
+ switch (this.gomoku.status) {
+ case 'waiting': {
+ message = 'Waiting for players...';
+ break;
+ }
+ case 'playing': {
+ const blackTag = this.playerTag(this.blackPlayerId!, 'black');
+ const whiteTag = this.playerTag(this.whitePlayerId!, 'white');
+ message = `${blackTag} vs ${whiteTag}`;
+ break;
+ }
+ case 'finished': {
+ switch (this.gomoku.winnerColor) {
+ case 'draw': {
+ message = 'Game ended in draw.';
+ break;
+ }
+ case 'black': {
+ const name = this.connections.get(this.blackPlayerId!)?.name;
+ message = `${name} wins!`;
+ break;
+ }
+ case 'white': {
+ const name = this.connections.get(this.whitePlayerId!)?.name;
+ message = `${name} wins!`;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ conn.ws.send({message}
);
+ console.log(`Sent title for game ${this.id} to player ${conn.id}`);
+ }
+
+ public broadcastButtonsToPlayer(conn: PlayerConnection) {
+ // TODO
+ console.log(`Sent buttons for game ${this.id} to player ${conn.id}`);
+ }
+
+ private playerTag(playerId: string, color: PlayerColor) {
+ const connectionIcon = this.connections.has(playerId) ? '' : `
`;
+ var turnClass = (this.gomoku.currentPlayerColor === color) ? 'player-to-play' : ''
+ const classes = `player-name ${'player-' + color} ${turnClass}`.trim();
+ return ({playerId}{connectionIcon});
+ }
+
+ public handleMessage(ws: WS, message: Message): void {
+ const conn = this.connections.get(ws.data.query.playerId);
+ if (!conn) {
+ console.error(`Failed to handle message from player ${ws.data.query.playerId}, because they are not in the game ${this.id}, which it was routed to`);
+ return;
+ }
+
+ switch (message.type) {
+ case 'make_move': {
+ this.handleMakeMove(conn, message as MakeMoveMessage);
+ break;
+ }
+ case 'resign': {
+ this.handleResignation(conn, message as ResignationMessage);
+ break;
+ }
+ }
+ }
+
+ private getPlayerColor(conn: PlayerConnection): PlayerColor | undefined {
+ if (this.blackPlayerId === conn.id) {
+ return 'black';
+ } else if (this.whitePlayerId === conn.id) {
+ return 'white';
+ } else {
+ return undefined;
+ }
+ }
+
+ private handleMakeMove(conn: PlayerConnection, message: MakeMoveMessage): void {
+ console.log(
+ `Handling make_move message in game ${this.id} from player ${conn.id}: ${{ message }}`,
+ );
+ const { row, col } = message;
+
+ var playerColor;
+ if (this.blackPlayerId === conn.id) {
+ playerColor = 'black';
+ } else if (this.whitePlayerId == conn.id) {
+ playerColor = 'white';
+ } else {
+ conn.sendMessage('error', 'You are not a player in this game, you cannot make a move!');
+ return;
+ }
+ if (this.gomoku.currentPlayerColor !== playerColor) {
+ conn.sendMessage('error', "It's not your turn");
+ return;
+ }
+
+ const stateBeforeMove = this.gomoku.status;
+ const result = this.gomoku.makeMove(playerColor, row, col);
+ console.log(result);
+ if (result.success) {
+ this.broadcastBoard();
+ this.broadcastTitle();
+ // We only need to re-send buttons when the game state changes
+ if (stateBeforeMove != this.gomoku.status) {
+ this.broadcastButtons();
+ }
+ console.log(
+ `Move made in game ${this.id} by ${conn.id}: (${row}, ${col})`,
+ );
+ } else {
+ conn.sendMessage('error', result.error!);
+ }
+ }
+
+ private handleResignation(conn: PlayerConnection, _message: ResignationMessage): void {
+ // TODO
+ }
+}
+
+export class WebSocketHandler {
+ private games: Map;
+
+ constructor() {
+ this.games = new Map();
+ }
+
+ public handleConnection(ws: WS): void {
+ const { gameId } = ws.data.query;
+ if (this.games.has(gameId)) {
+ this.games.get(gameId)!.handleConnection(ws);
+ } else {
+ ws.send('Error: game not found');
+ ws.close();
+ }
+ }
+
+ public handleDisconnect(ws: WS): void {
+ const { gameId, playerId } = ws.data.query;
+
+ const game = this.games.get(gameId);
+ if (!game) {
+ console.error(`Attempted to disconnect player ${playerId} from game ${gameId}, which does not exist!`);
+ return;
+ }
+
+ game.handleDisconnect(ws);
+ console.log(`${playerId} disconnected from game ${gameId}`);
+
+ if (game.connections.entries.length == 0) {
+ this.games.delete(gameId);
+ console.log(`Game ${gameId} has been deleted (empty).`);
+ }
+ }
+
+ public handleMessage(ws: WS, messageUnparsed: any) {
+ let message = messageUnparsed as Message;
+ if (!message) {
+ console.log(`Received malformed message ${messageUnparsed} from player ${ws.data.query.playerId}.`);
+ ws.send("Error: malformed message!");
+ return;
+ }
+
+ const { gameId } = ws.data.query;
+ const gameServer = this.games.get(gameId);
+ if (!gameServer) {
+ console.error(`A WebSocket connection was left open for the non-existent game ${gameId}`);
+ return;
+ }
+
+ gameServer.handleMessage(ws, message);
+ }
+
+ public createGame(id?: string): string {
+ const realId = id ? id : uuidv4();
+ this.games.set(realId, new GameServer(realId));
+ return realId;
+ }
+
+ public hasGame(id: string): boolean {
+ return this.games.has(id);
+ }
+}
+
diff --git a/tsconfig.json b/tsconfig.json
index 2ca47bb..05967c3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,105 +1,15 @@
{
"compilerOptions": {
- /* Visit https://aka.ms/tsconfig to read more about this file */
-
- /* Projects */
- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
- // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
-
- /* Language and Environment */
- "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
- // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
- // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
- // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
- // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
-
- /* Modules */
- "module": "ES2022" /* Specify what module code is generated. */,
- // "rootDir": "./", /* Specify the root folder within your source files. */
- "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
- // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
- // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
- "types": [
- "bun-types"
- ] /* Specify type package names to be included without being referenced in a source file. */,
- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
- // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
- // "resolveJsonModule": true, /* Enable importing .json files. */
- // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
-
- /* JavaScript Support */
- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
- // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
-
- /* Emit */
- // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- // "declarationMap": true, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
- // "outDir": "./", /* Specify an output folder for all emitted files. */
- // "removeComments": true, /* Disable emitting comments. */
- // "noEmit": true, /* Disable emitting files from a compilation. */
- // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
- // "newLine": "crlf", /* Set the newline character for emitting files. */
- // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
- // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
- // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
- // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
-
- /* Interop Constraints */
- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
-
- /* Type Checking */
- "strict": true /* Enable all strict type-checking options. */,
- // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
- // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
- // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
- // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
- // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
- // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
- // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
- // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
-
- /* Completeness */
- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ES2022",
+ "moduleResolution": "node",
+ "skipLibCheck": true,
+ "strict": true,
+ "target": "ES2021",
+ "types": ["bun-types"],
+ "jsx": "react",
+ "jsxFactory": "Html.createElement",
+ "jsxFragmentFactory": "Html.Fragment"
}
}