Deployment
Overview
Kontract apps run on Cloudflare Workers. The deployment unit is a single Worker script containing the gateway, middleware chain, and compiled backend routes.
wrangler.toml ← Worker configuration
src/gateway.ts ← fetch() entry point
dist/server/routes.js ← compiled @backend handlerswrangler.toml
Minimal configuration:
name = "my-kontract-app"
main = "src/gateway.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]
# Durable Object for MVCC session coordination
[durable_objects]
bindings = [
{ name = "SESSION_DO", class_name = "KontractSessionDO" },
]
[[migrations]]
tag = "v1"
new_classes = ["KontractSessionDO"]Gateway URL
Configure your production domain via routes:
[env.production]
name = "my-kontract-app"
routes = [
{ pattern = "api.example.com/*", zone_name = "example.com" }
]Clients then call https://api.example.com/rpc/<fn> for RPC and https://api.example.com/stream for SSE.
If you don't own a domain, Workers provides a default URL at https://my-kontract-app.<subdomain>.workers.dev.
Secrets
Set secrets via wrangler CLI (never commit these):
# PostgreSQL connection string
wrangler secret put DATABASE_URL
# 32-byte hex key for raystream E2E encryption
wrangler secret put KONTRACT_SECRET| Secret | Required | Description |
|---|---|---|
DATABASE_URL | Yes | PostgreSQL connection string (postgresql://user:pass@host:5432/db) |
KONTRACT_SECRET | Yes | Hex-encoded 32-byte key for ChaCha20-Poly1305 encryption |
Database Setup
Kontract requires two system tables plus your data tables:
-- System tables (required)
CREATE TABLE storage (
id TEXT PRIMARY KEY,
ptr TEXT NOT NULL,
owner TEXT NOT NULL,
permissions INT NOT NULL DEFAULT 7
);
CREATE TABLE trxs (
sid TEXT PRIMARY KEY,
owner TEXT NOT NULL,
create_txid BIGINT NOT NULL
);
-- Data tables follow the pattern:
CREATE TABLE tbl_<name>_<hash> (
id TEXT PRIMARY KEY,
data JSONB NOT NULL DEFAULT '{}',
_txid BIGINT NOT NULL,
_deleted_txid BIGINT,
_owner TEXT NOT NULL,
_order SERIAL
);Register table pointers:
INSERT INTO storage (id, ptr, owner, permissions)
VALUES ('users', 'tbl_users_abc123', 'tenant-1', 7);Recommended PostgreSQL providers:
- Neon — serverless PostgreSQL, free tier
- Supabase — PostgreSQL with dashboard
- Railway — simple deployment
Deploy Commands
# Local development
wrangler dev
# Deploy to production
wrangler deploy --env production
# View logs
wrangler tail --env productionEnvironment-Specific Config
# Development (local)
[env.development]
name = "my-app-dev"
# Staging
[env.staging]
name = "my-app-staging"
# Production
[env.production]
name = "my-app"
routes = [
{ pattern = "api.example.com/*", zone_name = "example.com" }
]CI/CD
Example GitHub Actions workflow:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
command: deploy --env productionNode.js Deployment (non-Cloudflare)
Kontract can run on any Node.js runtime using the adapter layer. Replace Cloudflare-specific primitives (DO, KV) with TiKV-backed implementations.
Architecture
Node.js Instance A ──┐
Node.js Instance B ──┤── TiKV Cluster ── PostgreSQL
Node.js Instance C ──┘Each node:
- Runs DO logic locally (in-process memory)
- Persists state to TiKV for cross-node visibility
- Uses
SharedStorage(TiKVDOStub, TiKVKVStore)for the two-tier cache - Connects to PostgreSQL directly (no Hyperdrive needed)
Setup
import {
createTiKVAdapter,
createGateway,
SharedStorage,
} from '@rand0mdevel0per/kontract';
// 1. Provide a TiKV client (implement TiKVClient interface)
const tikvClient = createYourTiKVClient('pd-host:2379');
// 2. Create adapter
const { doStub, kv } = createTiKVAdapter({ client: tikvClient });
const shared = new SharedStorage(doStub, kv);
// 3. Register your @backend routes
const routes = new Map();
routes.set('getUser', {
handler: async (ctx, args) => { /* ... */ },
meta: { perm: 0b100 },
});
// 4. Start the gateway
const server = createGateway({
adapter: { doStub, kv, pg: myPgPool, routes },
port: 8787,
});TiKVClient Interface
Implement this interface to connect Kontract to your TiKV cluster:
interface TiKVClient {
get(key: string): Promise<string | null>;
put(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
scan(prefix: string, limit: number): Promise<Array<{ key: string; value: string }>>;
}Use @grpc/grpc-js with TiKV proto definitions, or any TiKV-compatible proxy.
RuntimeAdapter Interface
For deploying to platforms beyond Cloudflare and Node.js/TiKV, implement RuntimeAdapter:
interface RuntimeAdapter {
doStub: DOStub; // In-process state (DO equivalent)
kv: KVStore; // Persistent KV (KV equivalent)
pg: PGClient; // PostgreSQL client
routes: Map<string, RouteHandler>;
}RPC Endpoint
The Node.js gateway serves @backend functions at:
POST /rpc/<functionName>Request body is the arguments array (or a single value wrapped in an array). Response:
{ "result": <return value> }Health check at GET /health returns { "status": "ok", "routes": <count> }.