Migrate frontend sources to TypeScript

This commit is contained in:
2026-02-24 21:35:15 +01:00
parent fa3b46c8a9
commit 757f9a259e
13 changed files with 403 additions and 51 deletions

53
RpgRoller/frontend/app.ts Normal file
View File

@@ -0,0 +1,53 @@
import { getHealth, rollDice } from "./generated/api-client.js";
const healthElementRaw = document.getElementById("health");
const resultElementRaw = document.getElementById("result");
const formElementRaw = document.getElementById("roll-form");
const sidesInputRaw = document.getElementById("sides");
if (
!(healthElementRaw instanceof HTMLElement) ||
!(resultElementRaw instanceof HTMLElement) ||
!(formElementRaw instanceof HTMLFormElement) ||
!(sidesInputRaw instanceof HTMLInputElement)
) {
throw new Error("Required UI elements are missing from index.html.");
}
const healthElement = healthElementRaw;
const resultElement = resultElementRaw;
const formElement = formElementRaw;
const sidesInput = sidesInputRaw;
function errorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
async function refreshHealth(): Promise<void> {
try {
const health = await getHealth();
healthElement.textContent = `API status: ${health.status}`;
}
catch (error: unknown) {
healthElement.textContent = `API status check failed: ${errorMessage(error)}`;
}
}
formElement.addEventListener("submit", async (event: SubmitEvent) => {
event.preventDefault();
const sides = Number.parseInt(sidesInput.value, 10);
try {
const roll = await rollDice(sides);
resultElement.textContent = `Rolled d${roll.sides}: ${roll.value}`;
}
catch (error: unknown) {
resultElement.textContent = `Roll failed: ${errorMessage(error)}`;
}
});
await refreshHealth();

View File

@@ -0,0 +1,57 @@
/* This file is generated by scripts/generate-api-client.mjs. */
export interface ApiError {
error: string;
}
export interface HealthResponse {
status: string;
}
export interface RollResponse {
sides: number;
value: number;
}
type ApiOperation = {
method: string;
path: string;
};
export const apiOperations = {
getHealth: { method: "GET", path: "/api/health" },
rollDice: { method: "GET", path: "/api/roll/{sides}" }
} as const satisfies Record<string, ApiOperation>;
async function send<TResult>(operation: ApiOperation, pathParams: Record<string, string | number | boolean> = {}): Promise<TResult> {
let resolvedPath = operation.path;
for (const [key, value] of Object.entries(pathParams)) {
resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(String(value)));
}
const response = await fetch(resolvedPath, {
method: operation.method,
headers: {
"Accept": "application/json"
}
});
if (!response.ok) {
const errorPayload: unknown = await response.json().catch(() => ({ error: "Unknown API error." }));
if (errorPayload && typeof errorPayload === "object" && "error" in errorPayload && typeof errorPayload.error === "string") {
throw new Error(errorPayload.error);
}
throw new Error(`Request failed with status ${response.status}`);
}
return response.json() as Promise<TResult>;
}
export async function getHealth(): Promise<HealthResponse> {
return send<HealthResponse>(apiOperations.getHealth, {});
}
export async function rollDice(sides: number): Promise<RollResponse> {
return send<RollResponse>(apiOperations.rollDice, { sides: sides });
}

View File

@@ -1,31 +1,42 @@
import { getHealth, rollDice } from "./generated/api-client.js";
const healthElement = document.getElementById("health");
const resultElement = document.getElementById("result");
const formElement = document.getElementById("roll-form");
const sidesInput = document.getElementById("sides");
const healthElementRaw = document.getElementById("health");
const resultElementRaw = document.getElementById("result");
const formElementRaw = document.getElementById("roll-form");
const sidesInputRaw = document.getElementById("sides");
if (!(healthElementRaw instanceof HTMLElement) ||
!(resultElementRaw instanceof HTMLElement) ||
!(formElementRaw instanceof HTMLFormElement) ||
!(sidesInputRaw instanceof HTMLInputElement)) {
throw new Error("Required UI elements are missing from index.html.");
}
const healthElement = healthElementRaw;
const resultElement = resultElementRaw;
const formElement = formElementRaw;
const sidesInput = sidesInputRaw;
function errorMessage(error) {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
async function refreshHealth() {
try {
const health = await getHealth();
healthElement.textContent = `API status: ${health.status}`;
}
catch (error) {
healthElement.textContent = `API status check failed: ${error.message}`;
healthElement.textContent = `API status check failed: ${errorMessage(error)}`;
}
}
formElement.addEventListener("submit", async (event) => {
event.preventDefault();
const sides = Number.parseInt(sidesInput.value, 10);
try {
const roll = await rollDice(sides);
resultElement.textContent = `Rolled d${roll.sides}: ${roll.value}`;
}
catch (error) {
resultElement.textContent = `Roll failed: ${error.message}`;
resultElement.textContent = `Roll failed: ${errorMessage(error)}`;
}
});
await refreshHealth();

View File

@@ -1,37 +1,31 @@
/* This file is generated by scripts/generate-api-client.mjs. */
export { apiOperations };
const apiOperations = {
export const apiOperations = {
getHealth: { method: "GET", path: "/api/health" },
rollDice: { method: "GET", path: "/api/roll/{sides}" }
};
async function send(operation, pathParams = {}) {
let resolvedPath = operation.path;
for (const [key, value] of Object.entries(pathParams)) {
resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(String(value)));
}
const response = await fetch(resolvedPath, {
method: operation.method,
headers: {
"Accept": "application/json"
}
});
if (!response.ok) {
const errorPayload = await response.json().catch(() => ({ error: "Unknown API error." }));
throw new Error(errorPayload.error ?? `Request failed with status ${response.status}`);
if (errorPayload && typeof errorPayload === "object" && "error" in errorPayload && typeof errorPayload.error === "string") {
throw new Error(errorPayload.error);
}
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
}
export async function getHealth() {
return send(apiOperations.getHealth, {});
}
export async function rollDice(sides) {
return send(apiOperations.rollDice, { sides: sides });
}