import { packageContext } from 'kody:runtime' import { addNote, archiveNote, getNote, listNotesByTag, listRecent, searchNotes, updateNote, } from './lib/notes.ts' function json(data: unknown, status = 200): Response { return new Response(JSON.stringify(data), { status, headers: { 'content-type': 'application/json; charset=utf-8' }, }) } async function readJson(request: Request): Promise> { try { const body = await request.json() return body && typeof body === 'object' ? (body as Record) : {} } catch { return {} } } function requireAppContext() { if (!packageContext?.hostedUrl) { throw new Error('This module must run as a package app.') } return packageContext } function pageHtml(appBasePath: string): string { return ` Stash

Stash

Zero-auth notes in this package's storage. Hashtags become tags.

` } function page(): Response { const ctx = requireAppContext() return new Response(pageHtml(ctx.appBasePath), { headers: { 'content-type': 'text/html; charset=utf-8' }, }) } function stringList(value: unknown): string[] | undefined { if (!Array.isArray(value)) return undefined return value.filter((tag): tag is string => typeof tag === 'string') } async function appFetch(input: Request): Promise { const request = input instanceof Request ? input : new Request('https://stash.invalid/') const url = new URL(request.url) if (request.method === 'GET' && !url.pathname.includes('/api/')) { return page() } try { if (request.method === 'POST' && url.pathname.endsWith('/api/notes')) { const body = await readJson(request) return json( await addNote({ text: String(body.text ?? ''), tags: stringList(body.tags) ?? [], kind: typeof body.kind === 'string' ? body.kind : '', dueAt: typeof body.dueAt === 'string' ? body.dueAt : null, }), ) } if (request.method === 'GET' && url.pathname.endsWith('/api/note')) { return json({ note: await getNote({ id: url.searchParams.get('id') ?? '' }) }) } if (request.method === 'PATCH' && url.pathname.endsWith('/api/note')) { const body = await readJson(request) return json( await updateNote({ id: typeof body.id === 'string' ? body.id : '', text: typeof body.text === 'string' ? body.text : '', tags: stringList(body.tags), kind: typeof body.kind === 'string' ? body.kind : '', dueAt: 'dueAt' in body ? typeof body.dueAt === 'string' ? body.dueAt : (body.dueAt ?? null) : undefined, }), ) } if (request.method === 'GET' && url.pathname.endsWith('/api/search')) { return json( await searchNotes({ query: url.searchParams.get('q') ?? '', limit: 25, }), ) } if (request.method === 'GET' && url.pathname.endsWith('/api/recent')) { return json(await listRecent({ limit: 25 })) } if (request.method === 'GET' && url.pathname.endsWith('/api/tag')) { return json( await listNotesByTag({ tag: url.searchParams.get('tag') ?? '', limit: 100, }), ) } if (request.method === 'POST' && url.pathname.endsWith('/api/archive')) { const body = await readJson(request) return json(await archiveNote({ id: typeof body.id === 'string' ? body.id : '' })) } return json({ error: 'Not found' }, 404) } catch (error) { const message = error instanceof Error ? error.message : String(error) return json({ error: message }, 500) } } /** * Stash package app `fetch(request)` handler for the notes UI and JSON API. */ export default appFetch