Kynetra FX

Documentation

Packages

Kynetra FX is published as 21 focused packages in a single monorepo. Every package has a single responsibility, ships its own TypeScript types, and can be installed independently. You only pay for what you use — a minimal API server needs only @kynetra/fx and a runtime adapter.

Monorepo structure#

All packages live under the packages/ directory. They share a common build pipeline, test runner, and TypeScript configuration but are versioned and published separately to npm under the @kynetra scope. Packages reference each other as peer dependencies rather than bundled dependencies so that your application's bundler can tree-shake across the whole graph.

The dependency graph is intentionally shallow. Most packages depend only on @kynetra/fx (the core) and optionally on @kynetra/fx-ports (the port interfaces). Runtime adapters, auth packages, and tooling packages do not depend on each other, which keeps installs fast and avoids accidental coupling.

  • Core@kynetra/fx, @kynetra/fx-schemas
  • Runtime adapters@kynetra/fx-cloudflare, @kynetra/fx-node, @kynetra/fx-browser
  • Auth and access control@kynetra/fx-auth, @kynetra/fx-rbac
  • SaaS and multi-tenancy@kynetra/fx-saas, @kynetra/fx-tenancy
  • Ports and adapters@kynetra/fx-ports
  • Middleware@kynetra/fx-middleware
  • Polyglot@kynetra/fx-wasm
  • AI@kynetra/fx-ai
  • Plugins@kynetra/fx-plugin
  • Developer experience@kynetra/fx-cli, @kynetra/fx-client, @kynetra/fx-openapi, @kynetra/fx-standard-schema

Core packages#

@kynetra/fx

The main framework package. Everything starts here — routing, middleware, the context model, contracts, errors, plugins, and hooks. Install this in every project.

npm install @kynetra/fx
src/index.ts
import { createFX, fx } from '@kynetra/fx'
 
const app = createFX()
 
app.use(async (ctx, next) => {
console.log(ctx.method, ctx.path)
return next()
})
 
app.route({
method: 'POST',
path: '/items',
input: fx.object({ name: fx.string().min(1) }),
handler: (ctx) => ctx.json({ created: ctx.input.name }, { status: 201 }),
})
 
return app

Key exports: createFX, fx, FXContext, FXMiddleware, FXPlugin, FXError, cors, logger.

@kynetra/fx-schemas

The built-in schema builder. Provides the fx.* namespace used in contracts and validation. This package is re-exported from @kynetra/fx for convenience, but can be imported directly when you need only the schema primitives.

src/schema.ts
import { fx, type Infer } from '@kynetra/fx-schemas'
 
const UserSchema = fx.object({
id: fx.string(),
name: fx.string().min(1).max(100),
email: fx.string().email(),
age: fx.optional(fx.number().int().min(0).max(150)),
role: fx.enum(['admin', 'member', 'viewer']),
})
 
type User = Infer<typeof UserSchema>
// { id: string; name: string; email: string; age?: number; role: 'admin' | 'member' | 'viewer' }

Runtime adapters#

Runtime adapters convert a generic FX application into the entry-point shape expected by a specific execution environment. Only one adapter is needed per deployment target.

@kynetra/fx-cloudflare

Bridges FX applications to Cloudflare Workers. Provides cloudflare(app) (also exported as toCloudflareHandler), the d1Sql query helper, d1Store for key-value storage on D1, and the D1_STORE_MIGRATION SQL string to initialise the store table. See Cloudflare Workers and D1 for detailed usage.

src/worker.ts
import { createFX } from '@kynetra/fx'
import { cloudflare, d1Sql } from '@kynetra/fx-cloudflare'
 
const app = createFX<{ Env: { DB: D1Database } }>({ runtime: 'cloudflare' })
 
app.get('/users', async (ctx) => {
const rows = await d1Sql(ctx.env.DB, 'SELECT * FROM users LIMIT 50')
return ctx.json(rows)
})
 
return cloudflare(app)

@kynetra/fx-node

Runs FX applications on Node.js via node:http. The serve(app, options) function starts an HTTP server and returns a handle you can call .close() on. Compatible with Node 18+ and fully supports app.fetch for use in test environments without starting an actual server. See Node.js.

src/server.ts
import { createFX } from '@kynetra/fx'
import { serve } from '@kynetra/fx-node'
 
const app = createFX()
app.get('/', (ctx) => ctx.json({ hello: 'world' }))
 
const server = serve(app, { port: 3000 })
console.log('Listening on http://localhost:3000')

@kynetra/fx-browser

A pre-bundled, tree-shaken ~32 KB build of the core framework targeting browser environments. Used internally by the in-browser playground and useful for client-side route matching or shared validation logic. Not intended for production API servers.

Auth and access control#

@kynetra/fx-auth

JWT signing and verification, a principal model, and three built-in auth strategies: jwtAuth, apiKeyAuth, and sessionAuth. Each strategy is a middleware factory that populates ctx.principal on success and short-circuits with a 401 on failure. See Auth and JWT for configuration details.

src/auth.ts
import { createFX } from '@kynetra/fx'
import { jwtAuth, requireAuth } from '@kynetra/fx-auth'
 
const app = createFX()
 
const auth = jwtAuth({ secret: process.env.JWT_SECRET! })
 
app.get('/me', auth, requireAuth, (ctx) => {
return ctx.json({ user: ctx.principal })
})
 
app.post('/admin/action', auth, requireAuth, (ctx) => {
if (ctx.principal.role !== 'admin') {
return ctx.json({ error: 'forbidden' }, { status: 403 })
}
return ctx.json({ ok: true })
})

@kynetra/fx-rbac

Role-based access control layered on top of @kynetra/fx-auth. Define roles and their permissions with defineRoles, create a checked RBAC instance with createRbac, then protect routes with requirePermission or requireRole middleware. Use the can(principal, action, resource) helper for inline permission checks. See RBAC.

src/rbac.ts
import { defineRoles, createRbac, requirePermission } from '@kynetra/fx-rbac'
 
const roles = defineRoles({
admin: { permissions: ['users:read', 'users:write', 'billing:read', 'billing:write'] },
member: { permissions: ['users:read'] },
viewer: { permissions: [] },
})
 
export const rbac = createRbac(roles)
 
// In a route
app.delete('/users/:id', auth, requireAuth, requirePermission(rbac, 'users:write'), (ctx) => {
// Only admins reach here
return ctx.json({ deleted: ctx.params.id })
})

SaaS and multi-tenancy#

@kynetra/fx-saas

A complete multi-tenant SaaS kernel. Provides domain models and service logic for users, organisations, workspaces, memberships, audit logs, feature flags, and billing seats. Designed to plug in via the ports model so the same kernel code runs on D1, Postgres, or an in-memory fake. See SaaS kernel.

@kynetra/fx-tenancy

Lightweight multi-tenancy middleware. The tenant() middleware resolves the current tenant from a header (X-Tenant-Id by default), a subdomain, or a path segment, and exposes it as ctx.tenantId. Use getTenantId(ctx) anywhere downstream to read the resolved value. See Tenancy.

src/tenancy.ts
import { createFX } from '@kynetra/fx'
import { tenant, getTenantId } from '@kynetra/fx-tenancy'
 
const app = createFX()
 
// Resolve tenant from X-Tenant-Id header
app.use(tenant({ strategy: 'header' }))
 
app.get('/data', async (ctx) => {
const tenantId = getTenantId(ctx)
// tenantId is the value of the X-Tenant-Id request header
return ctx.json({ tenantId, rows: [] })
})

Ports and adapters#

@kynetra/fx-ports

Port interfaces and in-memory fakes for every external dependency your application might have. Using ports keeps your domain logic decoupled from infrastructure so you can swap a Cloudflare KV binding for a Redis adapter or an in-memory fake without changing any application code. See Ports.

src/ports.ts
import { createFX } from '@kynetra/fx'
import {
type KvPort,
type StorePort,
type CachePort,
type QueuePort,
memoryKv,
memoryStore,
} from '@kynetra/fx-ports'
 
// Wire up in-memory fakes for local development
const kv: KvPort = memoryKv()
const store: StorePort = memoryStore()
 
// In production, swap for Cloudflare KV / D1 adapters
// const kv = cloudflareKv(ctx.env.KV)
// const store = d1Store(ctx.env.DB)

Available port interfaces:

NameTypeDescription
KvPortinterfaceKey-value store: get, set, delete, list.
StorePortinterfaceDocument store: find, findOne, insert, update, remove.
CachePortinterfaceTime-bounded cache: get, set with TTL, invalidate.
QueuePortinterfaceMessage queue: enqueue, dequeue, ack.
BlobPortinterfaceBinary object storage: put, get, delete, list.
VectorPortinterfaceVector store: upsert, query by embedding, delete.
SqlPortinterfaceRelational query runner: execute, transaction.
ClockPortinterfaceMockable clock: now(), sleep().
IdPortinterfaceID generator: generate() returning a unique string.

Middleware#

@kynetra/fx-middleware

Production-ready middleware for common API concerns. All middleware is composable and individually tree-shakeable. See Middleware.

NameTypeDescription
requestId()middlewareGenerates a UUID request ID and sets it on X-Request-Id.
logger()middlewareLogs method, path, status code, and duration for every request.
cors(options)middlewareSets CORS headers; supports origin allowlist, credentials, and preflight caching.
secureHeaders()middlewareSets X-Content-Type-Options, X-Frame-Options, and other security headers.
src/middleware.ts
import { createFX } from '@kynetra/fx'
import { requestId, logger, cors, secureHeaders } from '@kynetra/fx-middleware'
 
const app = createFX()
 
app.use(requestId())
app.use(logger())
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
maxAge: 86400,
}))
app.use(secureHeaders())
 
app.get('/', (ctx) => ctx.json({ ok: true }))

Polyglot and AI#

@kynetra/fx-wasm

Runs compiled WASM guests alongside your FX application. The wasmHandler factory creates a route handler that invokes a WASM module over a typed ABI; wasmMiddleware allows a WASM module to intercept and transform requests mid-pipeline. The WasmRequest / WasmResponse types define the stable binary interface. See WASM and WASM languages.

@kynetra/fx-ai

An AI port and provider adapters so your application can call LLMs through a stable interface without being coupled to a specific SDK. Ships adapters for OpenAI, Anthropic, and Cloudflare Workers AI, plus a mockAi fake for tests. The createRag helper wires a vector store (via VectorPort) and an AI provider into a basic retrieval-augmented generation pipeline. See AI providers.

src/ai.ts
import { createFX, fx } from '@kynetra/fx'
import { anthropicAdapter } from '@kynetra/fx-ai'
 
const ai = anthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY! })
 
const app = createFX()
 
app.route({
method: 'POST',
path: '/chat',
input: fx.object({ message: fx.string().min(1) }),
handler: async (ctx) => {
const reply = await ai.complete({
model: 'claude-sonnet-4-5',
messages: [{ role: 'user', content: ctx.input.message }],
})
return ctx.json({ reply })
},
})

Developer experience#

@kynetra/fx-plugin

The plugin definition helper. Use definePlugin to declare a named plugin with its dependencies, setup function, and exports. The framework resolves the dependency graph and calls setup functions in topological order. See Plugins.

@kynetra/fx-cli

The kynetra CLI for scaffolding new projects. kynetra new my-api generates a project with a runtime adapter of your choice, a sensible directory structure, and working example routes. Also exports scaffoldProject, parseArgs, and run for programmatic use. See CLI.

# Scaffold a new Cloudflare Workers project
npx @kynetra/fx-cli new my-api --runtime cloudflare
 
# Scaffold a Node.js project
npx @kynetra/fx-cli new my-api --runtime node

@kynetra/fx-client

A typed HTTP client generated from your FX route definitions. Use createFXClient with a base URL and the inferred type of your app to get fully-typed get, post, put, patch, and delete methods. See Typed client.

src/client.ts
import { createFXClient } from '@kynetra/fx-client'
import type { App } from './server'
 
const client = createFXClient<App>({ baseUrl: 'https://api.example.com' })
 
// Fully typed — input and response are inferred from the route definition
const user = await client.post('/users', { name: 'Alice', email: 'alice@example.com' })
console.log(user.id)

@kynetra/fx-openapi

Generates OpenAPI 3.1 documents from your FX application's route definitions. Call generateOpenAPI(app, info) and serve the result as JSON or YAML. The schemaToJSON helper converts fx.* schemas into JSON Schema objects for embedding in external documents. See OpenAPI.

@kynetra/fx-standard-schema

Connects third-party schema libraries to FX contracts via the Standard Schema specification. Pass a Zod, Valibot, or ArkType schema as the input, query, or output field of an app.route call and the adapter handles validation automatically. See Standard Schema.

src/zod-route.ts
import { createFX } from '@kynetra/fx'
import { z } from 'zod'
 
const app = createFX()
 
app.route({
method: 'POST',
path: '/signup',
// Zod schema works directly — @kynetra/fx-standard-schema adapts it automatically
input: z.object({
email: z.string().email(),
password: z.string().min(8),
}),
handler: (ctx) => {
// ctx.input is typed as { email: string; password: string }
return ctx.json({ ok: true, email: ctx.input.email })
},
})

Note

@kynetra/fx-standard-schema is auto-detected when you pass a Standard Schema-compatible object as an input or output field. You do not need to call any adapter function explicitly — the package just needs to be installed.

Full package reference#

NameTypeDescription
@kynetra/fxcoreMain framework: createFX, routing, middleware, context, contracts, errors, plugins, hooks.
@kynetra/fx-cloudflareruntimeCloudflare Workers adapter: cloudflare(), d1Sql(), d1Store(), D1_STORE_MIGRATION.
@kynetra/fx-noderuntimeNode.js adapter: serve() over node:http, app.fetch compatibility.
@kynetra/fx-browsertoolingBrowser bundle (~32 KB) powering the in-browser playground.
@kynetra/fx-authauthJWT signing/verification, principal model, jwtAuth, apiKeyAuth, sessionAuth middleware.
@kynetra/fx-rbacauthRole-based access control: defineRoles, createRbac, requirePermission, requireRole, can().
@kynetra/fx-saassaasMulti-tenant SaaS kernel: users, orgs, workspaces, memberships, audit logs, feature flags.
@kynetra/fx-tenancysaasMulti-tenancy middleware: tenant(), getTenantId(), header/subdomain/path resolution.
@kynetra/fx-portsportsPort interfaces and in-memory fakes: Kv, Store, Cache, Queue, Blob, Vector, Sql, Clock, Id.
@kynetra/fx-middlewaremiddlewareBuilt-in middleware: requestId, logger, cors, secureHeaders.
@kynetra/fx-wasmpolyglotPolyglot WASM: wasmHandler, wasmMiddleware, WasmRequest/WasmResponse ABI.
@kynetra/fx-aiaiAI port and adapters: AiPort, openAiAdapter, anthropicAdapter, workersAiAdapter, mockAi, createRag.
@kynetra/fx-pluginpluginsPlugin definition helper: definePlugin, dependency resolution.
@kynetra/fx-clitoolingCLI scaffolding: kynetra new, scaffoldProject, parseArgs, run.
@kynetra/fx-clientclientTyped HTTP client: createFXClient, typed GET/POST/PUT/PATCH/DELETE.
@kynetra/fx-openapiopenapiOpenAPI 3.1 generation: generateOpenAPI, schemaToJSON.
@kynetra/fx-schemasvalidationBuilt-in schema builder (fx.*): string, number, object, array, Infer<T>.
@kynetra/fx-standard-schemavalidationStandard Schema adapter: use Zod, Valibot, or ArkType as input/query/output validators.

Tip

Not sure which packages you need? Start with @kynetra/fx plus the runtime adapter for your target environment (@kynetra/fx-cloudflare or @kynetra/fx-node). Add auth, ports, and SaaS packages as your application grows. The CLI scaffolds a sensible starting set automatically.
Packages · Kynetra FX