Documentation
Glossary
A reference dictionary for the vocabulary used throughout the Kynetra FX documentation. Terms are grouped by theme. Each entry links to the relevant deep-dive page where one exists.
Core concepts#
These are the foundational abstractions that every Kynetra FX application is built from. Understanding them unlocks everything else.
Context (ctx)
The request context object passed as the first argument to every handler and middleware. A fresh ctx is created for each incoming request and is never shared across requests. It carries the raw Request, the parsed URL, route params, query string values, response helper methods (ctx.json, ctx.text, ctx.redirect, …), parsed cookies, and any values attached via decorators.
app.get('/users/:id', async (ctx) => { const id = ctx.params.id // route param const page = ctx.query('page') ?? '1' // query string const token = ctx.cookie('session') return ctx.json({ id, page })})Handler
A function with the signature (ctx: FXContext) => Response | Promise<Response> that produces the HTTP response for a route. Every route has exactly one handler — it is always the last argument passed to app.get(), app.post(), or app.route(). Middleware wraps the handler but is not itself the handler.
Middleware
A function with the signature (ctx: FXContext, next: () => Promise<Response>) => Response | Promise<Response> that wraps the handler pipeline. Middleware calls await next() to pass control to the next layer (or the handler). Code before next() runs on the way in; code after next() runs on the way out — the classic onion model. See Middleware for registration patterns.
async function logTiming(ctx, next) { const start = Date.now() const res = await next() // → into the handler const ms = Date.now() - start // ← back out console.log(ctx.method, ctx.url.pathname, ms + 'ms') return res}Note
next() once per request. Calling it a second time throws FX_NEXT_CALLED_MULTIPLE_TIMES. See Error codes.Onion middleware
The execution model for middleware stacks in Kynetra FX, borrowed from Koa. Layers are entered in registration order and exited in reverse order. If middleware A is registered before middleware B, the execution sequence for a single request is:
→ A (before next) → B (before next) → handler ← B (after next)← A (after next)This means outer middleware can inspect or mutate the response produced by inner middleware and the handler, enabling patterns like response caching, timing, and error enrichment without coupling the layers together.
Route
A combination of an HTTP method, a path pattern, optional per-route middleware, and a handler, bound together as a single unit. Routes are registered with the shorthand methods (app.get, app.post, etc.) or with the typed app.route() contract form. Path patterns support named parameters (:id) and wildcards (*). See Routing.
Group
A logical namespace that applies a shared path prefix and optional shared middleware to a set of routes. Registered with app.group(prefix, fn) where fn receives a sub-app scoped to that prefix. Groups can be nested and are the idiomatic way to version APIs or scope resource routes. See Groups.
app.group('/api/v1', (v1) => { v1.get('/users', listUsersHandler) v1.post('/users', createUserHandler) v1.group('/admin', (admin) => { admin.use(requireAdmin) admin.get('/stats', statsHandler) })})Validation & types#
Kynetra FX is designed to be type-safe end-to-end. These terms describe the validation layer and how it connects to TypeScript.
Contract
A route definition registered with app.route({ method, path, input, output, handler }) that carries schema metadata in addition to the handler. The input schema validates the request body; query validates the query string; output validates (and types) the response. Contracts are the basis for automatic OpenAPI generation and for end-to-end typed clients. See Contracts.
Standard Schema
A community-driven protocol (~standard property) that lets any schema library expose a common validation interface. Zod, Valibot, and ArkType all implement Standard Schema. Kynetra FX accepts any Standard Schema–compliant object as the input, query, or output of a contract — without importing the library directly or writing adapter code. See Standard Schema.
import { z } from 'zod' const CreateUser = z.object({ name: z.string(), email: z.string().email(),}) app.route({ method: 'POST', path: '/users', input: CreateUser, // any Standard Schema works here handler: async (ctx) => { const body = ctx.input // typed as { name: string; email: string } return ctx.json({ ok: true }) }})fx schema
The built-in lightweight schema builder included with Kynetra FX. Provides primitives like fx.string(), fx.number(), fx.boolean(), and fx.object() that all implement Standard Schema. Use it when you do not want an external schema library dependency. It is intentionally minimal — for complex validation logic, Zod or Valibot are richer alternatives.
import { fx } from '@kynetra/fx' const LoginInput = fx.object({ email: fx.string(), password: fx.string(),}) app.route({ method: 'POST', path: '/login', input: LoginInput, handler: loginHandler })Infer
A TypeScript utility type exported by Kynetra FX that extracts the static type from an fx schema or any Standard Schema–compatible schema. Useful when you need the inferred type in application code outside of a route definition.
import { fx, type Infer } from '@kynetra/fx' const UserSchema = fx.object({ id: fx.string(), name: fx.string() })type User = Infer<typeof UserSchema> // { id: string; name: string }Architecture#
Kynetra FX uses a ports-and-adapters architecture for infrastructure concerns, a plugin system for bundling reusable configuration, and decorators for extending the context.
Port
An interface — not an implementation — for an infrastructure capability. Kynetra FX defines standard ports for storage, cache, queue, blob, vector, SQL, clock, and ID generation. Business logic depends on ports; it never imports a concrete driver directly. This lets you swap the underlying technology (e.g. moving from in-memory to D1) without touching application code. See Ports.
Adapter
A concrete implementation of a Port. Adapters are the boundary between Kynetra FX's abstract interfaces and real infrastructure. Examples include inMemoryKv() (useful in tests and local development), d1Store(db) (Cloudflare D1), and any custom adapter you write that satisfies the port interface. Swap adapters at the registration site without touching business logic.
import { createFX } from '@kynetra/fx'import { d1Store } from '@kynetra/fx-cloudflare' const app = createFX()app.decorate('db', d1Store(env.DB)) // swap to inMemoryStore() in testsPlugin
A reusable, self-contained bundle of app configuration registered with app.register(plugin). Defined with definePlugin({ name, dependencies?, register }). A plugin's register function receives the app instance and can add routes, decorators, hooks, or nested plugins. Plugins can declare ordered dependencies on other plugins — Kynetra FX validates that all dependencies are registered before the dependent plugin runs. See Plugins.
import { definePlugin } from '@kynetra/fx' const metricsPlugin = definePlugin({ name: 'metrics', register(app) { app.hook('postHandler', (ctx, res) => { metrics.increment('http.request', { path: ctx.url.pathname }) }) }}) app.register(metricsPlugin)Decorator
A named value attached either to the app instance (app.decorate(key, value)) or to every request context (app.decorateContext(key, value)). App-level decorators are singletons (database pools, config, plugin instances). Context-level decorators are re-initialized per request (request-scoped state, per-request loggers). Values are read with ctx.get(key) or app.getDecorator(key). See Decorators.
Hook
A lifecycle callback registered with app.hook(name, fn) that fires at a specific point in the request or application lifecycle. Available hook names:
app.hook('onBoot', async (app) => { /* called once at startup */ })app.hook('onClose', async (app) => { /* called once at shutdown */ })app.hook('onRegister', (app, spec) => { /* after each plugin registers */ })app.hook('onRequest', (ctx) => { /* very first hook per request */ })app.hook('preHandler', (ctx) => { /* after routing, before middleware chain */ })app.hook('postHandler', (ctx, res) => { /* after handler returns */ })app.hook('onError', (ctx, err) => { /* on any unhandled error */ })See Hooks for full signatures and ordering guarantees.
Auth & multi-tenancy#
Kynetra FX ships first-class primitives for authentication and for multi-tenant SaaS applications.
Principal
The authenticated identity making a request. Resolved by the auth() middleware and accessible via getPrincipal(ctx). A principal has an id, a type ('user', 'apikey', 'session', or 'anonymous'), a roles array, a scopes array, and an optional tenantId. See Authentication.
import { getPrincipal } from '@kynetra/fx-auth' app.get('/me', requireAuth, async (ctx) => { const p = getPrincipal(ctx) // type: Principal return ctx.json({ id: p.id, roles: p.roles })})Strategy
An authentication mechanism wired into the auth() middleware. Built-in strategies are jwtAuth, apiKeyAuth, and sessionAuth. Multiple strategies can be composed: the middleware tries each in order and resolves the first successful one. If all strategies fail and the route requires authentication, FX_UNAUTHENTICATED is thrown. See Authentication.
import { auth, jwtAuth, apiKeyAuth } from '@kynetra/fx-auth' const authenticate = auth([ jwtAuth({ secret: process.env.JWT_SECRET! }), apiKeyAuth({ lookup: (key, ctx) => db.lookupApiKey(key) }),])Tenant
An isolated organizational unit in a multi-tenant SaaS application. Tenants are identified by a tenant ID resolved from the request — typically a header (x-tenant-id), the first subdomain label, or a JWT claim. The tenant() middleware resolves the ID and makes it available via getTenantId(ctx). When required: true is set and no ID can be resolved, the middleware throws FX_TENANT_REQUIRED. See Multi-tenancy.
Polyglot / WASM#
Kynetra FX supports WASM guest modules as first-class route handlers, enabling you to write performance-critical logic in Rust, Go, C, or any language that compiles to WASM.
Guest
A WASM module (or any object) that implements the single-method interface { handle(requestJson: string): string }. Guests speak the host ABI: they receive a WasmRequest JSON string, perform their computation, and return a WasmResponse JSON string. Guests are registered with wasmHandler(guest) and become the handler for a route. See WASM guests.
import { createFX } from '@kynetra/fx'import { wasmHandler } from '@kynetra/fx-wasm'import init, { handle } from './pkg/guest.js' // generated by wasm-pack await init() const app = createFX()app.get('/compute', wasmHandler({ handle }))Host ABI
The JSON-over-string protocol between Kynetra FX and a WASM guest. The host serializes an incoming HTTP request into a WasmRequest JSON string (method, path, headers, body) and calls the guest's handle function. The guest returns a WasmResponse JSON string (status, headers, body) which the host deserializes into a real Response. The protocol is intentionally language-agnostic — any language that can parse and emit JSON strings can implement a compliant guest. See WASM guests.
{ "method": "POST", "path": "/compute", "query": {}, "headers": { "content-type": "application/json" }, "body": "{\"n\": 42}"}{ "status": 200, "headers": { "content-type": "application/json" }, "body": "{\"result\": 1764}"}Component Model
The WASM standard for composable, typed interface definitions using WIT (WebAssembly Interface Types). Kynetra FX guests built with the Component Model use jco to generate JavaScript bindings from .wit interface files. This provides stronger typing and richer interop than raw memory-passing WASM, and is the recommended approach for production WASM guests. See WASM languages for per-language build guides.
Tip
{ handle(requestJson: string): string } interface directly in any language that compiles to a WASM module.