Skip to content

App Contract

The Anyknown Framework defines the boundary an App must satisfy: the shape of the manifest, how handlers are registered, where handler files live, how pages are laid out, and what the route table supports.

The Framework defines the shape of the holes — what the platform requires. The AI fills the holes with App-specific logic, components, and routes. Copy-pasteable src/pages or src/components content is not part of the contract and is never documented here.


Every App has an anyknown.json manifest at the project root. It is generated by the Builder and must not be hand-edited.

{
"schemaVersion": 1,
"sdkVersion": "0.0.0",
"entry": {
"server": "src/server.ts",
"client": "src/client.svelte.ts"
},
"render": "client-only",
"routes": [
{ "method": "GET", "path": "/products", "handler": "src/api/products/get.ts#default" },
{ "method": "POST", "path": "/products", "handler": "src/api/products/post.ts#default" },
{ "method": "GET", "path": "/products/:id", "handler": "src/api/products/[id]/get.ts#default" }
],
"capabilities": {
"db": { "provider": "sqlite" },
"storage": { "provider": "r2" }
}
}
FieldTypeDescription
schemaVersion1Must be 1. Identifies the manifest format version.
sdkVersionstringSDK contract version. Set by the Builder.
entry.serverstringPath to the Hono server entry. Always src/server.ts.
entry.clientstring (optional)Path to the Svelte client entry. Required for client-only and ssr-with-hydration render modes.
render"client-only" | "ssr-with-hydration" | "server-only"Rendering strategy for the App’s frontend.
routesManifestRoute[]The complete route table. Each entry maps a method + path to a handler file and export name.
capabilitiesobjectWhich capabilities the App uses, and their provider selections.

Every API handler is a Hono handler function exported as default from a file under src/api/.

import type { AppSdk } from "@anyknown/app-server"
import type { Context } from "hono"
type AppVariables = { sdk: AppSdk }
export default (c: Context<{ Variables: AppVariables }>) => {
const sdk = c.get("sdk")
return c.json({ ok: true })
}
  • Handlers receive a Hono Context object typed with { Variables: { sdk: AppSdk } }.
  • Retrieve the SDK via c.get("sdk").
  • Return any Response-compatible value: c.json(...), c.text(...), c.html(...), or new Response(...).
  • Route parameters are accessed via c.req.param("name").

The sdk object is initialised once per request by a platform middleware. Handlers must not call initSdk directly.

The generated src/server.ts calls initSdk once per request inside a middleware and makes the result available via c.set("sdk", sdk). This happens before any handler runs. The raw Worker bindings (secrets, storage, etc.) are hidden from handler scope — handlers access capabilities only through sdk.


Handler files live under src/api/ and follow a file-system-to-route mapping:

File pathRoute
src/api/products/get.tsGET /products
src/api/products/post.tsPOST /products
src/api/products/[id]/get.tsGET /products/:id
src/api/products/[id]/delete.tsDELETE /products/:id

Rules:

  • The HTTP method is the file’s base name (lowercase): get.ts, post.ts, put.ts, patch.ts, delete.ts.
  • Dynamic path segments are written as [paramName] directory names and become :paramName in the route.
  • The export must be named default.
  • No catch-all segments (*, **, [...slug]).

The route table supports:

  • Static segments/products, /about, /dashboard
  • Named parameters/products/:id, /users/:userId/posts/:postId

It does not support:

  • Catch-all or wildcard segments
  • Optional parameters
  • Regular-expression segments

Route matching is exact: every segment of the request path must match a literal or a named parameter in the route definition. Routes are matched in declaration order; first match wins.


Every page in an App is laid out using four fixed regions: header, sidebar, main, and footer.

RegionRequiredTypical use
headerNoNavigation bar, site title, auth controls
sidebarNoSecondary navigation, filters, tree views
mainYesPrimary page content
footerNoLinks, copyright, secondary actions

main is always required. The other three regions are included when the App’s design calls for them.

Each region holds a tree of components. The root node of each region is a layout node with a type (e.g. Stack), optional props, and children.


Components are Svelte 5 single-file components (.svelte) that live under src/components/. Each component has an id and belongs to one or more page regions.

A component can be one of two kinds:

  • Layout node — a structural container (Stack, Grid, Flex, etc.) with type, props, style, and children.
  • Custom node — a named component sourced from a specific file (type: "custom", sourceFile, props).

Components use Tailwind utility classes for all styling. Class strings must be complete literal strings — no string interpolation or concatenation. This constraint exists because Tailwind’s build-time scanner can only detect literal class names.

<!-- correct -->
<div class="flex items-center gap-4 p-6 bg-white rounded-lg">
<!-- wrong — interpolated class strings are not detected at build time -->
<div class={`flex ${isActive ? "bg-blue-500" : "bg-gray-200"}`}>

Apps with a frontend (render mode client-only or ssr-with-hydration) have a client entry at src/client.svelte.ts. This file mounts the Svelte 5 component tree. It is platform-owned and generated by the Builder — do not modify it.


SDK call shapes and handler/manifest signatures ARE the documented boundary (always allowed). A copy-pasteable src/pages or src/components file, or a multi-file App skeleton, is content (forbidden — 2026-05-19 freeze). The contract defines the shape of the holes; the AI fills the holes.