import { Match, Method, Router } from '@atelier/router.ts'; import { OrPromise } from '@atelier/util.ts'; import { Database } from '@db/sqlite'; import { viewShelf, viewShelves } from '@/backend/routes/shelves.ts'; import { postItem, viewCreateItem, viewItem, viewItems } from '@/backend/routes/items.ts'; import { viewHome } from '@/backend/routes/home.ts'; const { GET, POST, PATCH } = Method; export type RequestCtx = { pathParams: Record; db: Database; }; export type Handler = (req: Request, ctx: RequestCtx) => OrPromise; export function makeRoutes(db: Database) { const router = new Router(); const r = (m: Method, p: Match, h: Handler) => router.register(m, p, (req, pathParams) => { return h(req, { pathParams, db }); }); r(GET, '/', viewHome); r(GET, '/shelves', viewShelves); r(GET, '/shelves/:shelfXid', viewShelf); r(GET, '/items', viewItems); r(GET, '/items/create', viewCreateItem); r(GET, '/items/:itemXid', viewItem); r(POST, '/items/create', postItem); r(GET, '/static/js/bundle.js', serveJsBundle); r(GET, '/static/*', serveStatic); return router.handle.bind(router); } const EXT_TO_MIME = { '.css': 'text/css', }; async function serveStatic(req: Request) { const url = new URL(req.url); const path = url.pathname.substring('/static'.length); const staticPath = import.meta.dirname + '/../static' + path; const data = await Deno.readFile(staticPath); let mimetype = 'text/plain'; for (const [ext, mime] of Object.entries(EXT_TO_MIME)) { if (path.endsWith(ext)) { mimetype = mime; } } return new Response(data, { headers: { 'Content-Type': mimetype } }); } const isProduction = false; let cachedBundlePath: string | null = null; let cachedBundleAt = new Date(0); async function serveJsBundle(req: Request) { if (isProduction) { throw new Error('todo'); } if (cachedBundlePath === null) { cachedBundlePath = await Deno.makeTempFile({ prefix: 'bundle', suffix: '.js' }); } const bundlePath = import.meta.dirname + '/../frontend/bundle.ts'; const stat = await Deno.stat(bundlePath); const mtime = stat.mtime ?? new Date(0); if (cachedBundleAt > mtime) { const file = await Deno.open(cachedBundlePath); return new Response(file.readable, { headers: { 'Content-Type': 'text/javascript' }, }); } const esbuild = await import('npm:esbuild'); const { denoPlugins } = await import('jsr:@luca/esbuild-deno-loader'); const result = await esbuild.build({ plugins: [...denoPlugins()], entryPoints: [import.meta.dirname + '/../frontend/bundle.ts'], outfile: cachedBundlePath, bundle: true, format: 'esm', sourcemap: 'inline', }); esbuild.stop(); cachedBundleAt = new Date(); return new Response((await Deno.open(cachedBundlePath)).readable, { headers: { 'Content-Type': 'text/javascript' }, }); }