Skip to main content

@owly/runtime (TypeScript / JavaScript)

A Hono-style router and response helpers. Handlers go from a boilerplate addEventListener('fetch', …) listener to one-line route registrations.

import { createApp } from '@owly/runtime';

const app = createApp();

app.get('/api/leaderboard/:scope', (c) => {
const scope = c.param('scope');
const page = Number(c.query('page') ?? '1');
return c.json({ scope, page });
});

app.post('/api/echo', async (c) => {
const body = await c.json<{ msg: string }>();
return c.json({ echoed: body.msg });
});

app.listen();

createApp()

Returns an App. Register routes, then call listen() once at the end to wire up the runtime's fetch event.

const app = createApp();

Routing

app.get(path, handler);
app.post(path, handler);
app.put(path, handler);
app.delete(path, handler);
app.patch(path, handler);
app.all(path, handler); // any method
  • Patterns use :name for path params: /users/:id/posts/:postId.
  • Matching is a linear scan; first match wins.
  • A path that matches but with the wrong method returns 405; no match returns 404.
  • Handlers see the full request path, including the prefix the function is mounted at in owly.yaml. A function mounted at /api/page registers app.get('/api/page', …).
app.listen();

listen() registers the handler with the runtime. Call it last.

The Context (c)

Every handler receives a Context:

interface Context {
readonly req: Request;
readonly url: URL;
readonly params: Record<string, string>;

param(name: string): string | undefined; // a :path param
query(name: string): string | undefined; // first query value
queries(): URLSearchParams; // all query params
header(name: string): string | undefined; // a request header

// Body parsers (lazy)
json<T = unknown>(): Promise<T>;
text(): Promise<string>;

// Response builders
body(body: BodyInit | null, init?: ResponseInit): Response;
json(data: unknown, init?: ResponseInit): Response;
text(body: string, init?: ResponseInit): Response;
html(body: string, init?: ResponseInit): Response;
notFound(): Response;
}

The json and text names are overloaded by arity: called with no arguments they parse the request body; called with data they build a response.

app.post('/save', async (c) => {
const data = await c.json<{ name: string }>(); // parse
return c.json({ saved: data.name }, { status: 201 }); // build
});

Response builders set a sensible Content-Type without clobbering one you supply explicitly.

Build

owly build type-checks and bundles each handler — including npm dependencies like @owly/runtime — and compiles it for deployment. See Build & deploy.