3import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
4
5function App() {
6 const [property, setProperty] = useState("");
7 const [room, setRoom] = useState("");
524}
525
526function client() {
527 createRoot(document.getElementById("root")).render(<App />);
528}
529if (typeof document !== "undefined") { client(); }
530
531export default async function server(request: Request): Promise<Response> {
532 return new Response(`
533 <html>
3import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
4
5function App() {
6 const [property, setProperty] = useState("");
7 const [room, setRoom] = useState("");
524}
525
526function client() {
527 createRoot(document.getElementById("root")).render(<App />);
528}
529if (typeof document !== "undefined") { client(); }
530
531export default async function server(request: Request): Promise<Response> {
532 return new Response(`
533 <html>
2import type { ReactNode } from "npm:react@18.2.0";
3
4export function Layout({ children }: { children: ReactNode }) {
5 return (
6 <html lang="en">
11import { servePublicFile } from "https://esm.town/v/stevekrouse/utils@179-main/serve-public/index.ts";
12
13export default async function(req: Request): Promise<Response> {
14 const url = new URL(req.url);
15 if (url.pathname === "/") {
13interface TestCase {
14 name: string;
15 function: () => void;
16}
17
18interface TestCaseResult {
19 name: string;
20 function: () => void;
21 passed: boolean;
22 duration: number;
23}
24
25async function runTest(test: TestCase) {
26 let pass, message;
27 let start = performance.now();
28 try {
29 await test.function();
30 pass = true;
31 } catch (e: any) {
40}
41
42function renderBadge({ label, passedCount, testCount }: { label?: string; passedCount: number; testCount: number }) {
43 return makeBadge({
44 label: label ?? "Tests",
48}
49
50function Badge({ label, passedCount, testCount }: { label?: string; passedCount: number; testCount: number }) {
51 const svgMarkup = renderBadge({ label, passedCount, testCount });
52 const srcDoc = `<body style="margin: 0"}>${svgMarkup}</body>`;
62}
63
64function renderTestResults(testGroups: TestGroup[], outputs: { [groupName: string]: TestOutput[] }) {
65 const totalPassed = Object.values(outputs).flat().filter((output: TestOutput) => output.pass).length;
66 const totalTests = testGroups.reduce((sum, group) => sum + group.tests.length, 0);
3import { contentType } from "npm:mime-types@2.1.35";
4
5export async function servePublicFile(path: string, metaImportUrl: string): Promise<Response> {
6 const text = await readFile(path, metaImportUrl);
7 const extname = tsToJSExtension(path).split(".").pop() ?? "";
14}
15
16export async function readFile(path: string, metaImportUrl: string) {
17 const project = parseProject(metaImportUrl);
18 const esmURL = project.links.module.projectPinned + path.slice(1);
42}
43
44export async function getProjectFiles(metaImportUrl: string) {
45 const project = parseProject(metaImportUrl);
46 const { id } = await (await fetch(`https://api.val.town/v1/alias/projects/${project.username}/${project.name}`))
65console.log(await getProjectFiles(import.meta.url));
66
67async function fetchText(url: string) {
68 const res = await fetch(url, {
69 headers: {
79}
80
81function tsToJSExtension(path: string) {
82 if (path.endsWith(".ts")) {
83 return path.slice(0, -3) + ".js";
8 {
9 name: "no-op",
10 function: () => {
11 return true;
12 },
14 {
15 name: "passing test",
16 function: () => {
17 expect(1).toBe(1);
18 },
20 {
21 name: "Failing test",
22 function: () => {
23 expect(1).toBe(2);
24 },
31 {
32 name: "passing test 2",
33 function: () => {
34 expect(1).toBe(1);
35 },
1import { servePublicFile } from "./index.ts";
2
3export default async function(req: Request): Promise<Response> {
4 // serve public files at the root
5 const url = new URL(req.url);
2import { renderToString } from "npm:react-dom/server";
3
4export default async function(req: Request) {
5 return new Response(
6 renderToString(
16In a normal server environment, you would likely use a middleware [like this one](https://hono.dev/docs/getting-started/nodejs#serve-static-files) to serve static files. Some frameworks or deployment platforms automatically make any content inside a `public/` folder public.
17
18However in Val Town you need to handle this yourself, and it can be suprisingly difficult to read and serve files in a Val Town Project. This template uses helper functions from [stevekrouse/utils/serve-public](https://www.val.town/x/stevekrouse/utils/branch/main/code/serve-public/README.md), which handle reading project files in a way that will work across branches and forks, automatically transpiles typescript to javascript, and assigns content-types based on the file's extension.
19
20### `index.html`
26## CRUD API Routes
27
28This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. This routes are called from the React app to refresh and update data.
29
30## Errors