Skip to content
← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

FollowFork

starter-tanstack/app/router.ts

82 lines · 2.6 KB · TypeScript
import { packageContext } from 'kody:runtime'
import { createRequestId, withRequestId } from './middleware/request-id.ts'
import { relativePath } from './routes.ts'
import { icon192, icon512 } from './controllers/icons.ts'
import { manifest } from './controllers/manifest.ts'
import { version } from './controllers/version.ts'
import { notesAction } from './controllers/notes-action.ts'
import { listNotes } from './data/notes.ts'
import { renderApp } from './ssr.tsx'

// Host strips the mount before forwarding. TanStack routes that use basepath /
// remounted URLs need the hosted pathname, so remount here (Remix parity).
function remountRequest(request: Request) {
	const appBasePath = String(packageContext?.appBasePath ?? '').replace(/\/+$/, '')
	if (!appBasePath) return request
	const url = new URL(request.url)
	url.pathname = url.pathname === '/' ? appBasePath : appBasePath + url.pathname
	return new Request(url, request)
}

function wantsJson(request: Request) {
	const accept = (request.headers.get('accept') || '').toLowerCase()
	return accept.startsWith('application/json')
}

async function dispatch(request: Request): Promise<Response> {
	const appBasePath = String(packageContext?.appBasePath ?? '').replace(/\/+$/, '')
	const url = new URL(request.url)
	const path = relativePath(url.pathname, appBasePath)
	const method = request.method.toUpperCase()

	if (path === '/health') {
		return Response.json({ ok: true })
	}
	if (path === '/api/version') {
		return version()
	}
	if (path === '/manifest.webmanifest') {
		return manifest()
	}
	if (path === '/icons/icon-192.png') {
		return icon192()
	}
	if (path === '/icons/icon-512.png') {
		return icon512()
	}

	if (path === '/notes' && method === 'POST') {
		return notesAction(request, appBasePath)
	}

	if (path === '/notes' && method === 'GET' && wantsJson(request)) {
		const notes = await listNotes()
		return Response.json({ notes })
	}

	if (method === 'GET' || method === 'HEAD') {
		return renderApp(request)
	}

	return new Response('Method Not Allowed', { status: 405 })
}

// Default-export { fetch } — do not export a class/router object (Worker env is not RequestInit).
export default {
	async fetch(request: Request) {
		const requestId = createRequestId()
		try {
			const response = await dispatch(remountRequest(request))
			return withRequestId(response, requestId)
		} catch (error) {
			console.error('package app fetch failed:', error)
			return withRequestId(
				Response.json(
					{ ok: false, error: error instanceof Error ? error.message : String(error) },
					{ status: 500 },
				),
				requestId,
			)
		}
	},
}