Framekit is a TypeScript meta-framework inspired by Frappe. It lets you build metadata-driven business applications with DocTypes, modules, permissions, workflows, hooks, generated APIs, customization, audit trails, outbox events, realtime publishing, a Desk UI, and portable Nitro deployment.
Nitro is the default host engine, but the framework core does not depend on Nitro, React, Drizzle, Redis, BullMQ, or Postgres. Those live behind adapter ports so applications can run in Node containers first and later target serverless or edge-style platforms where appropriate.
- Metadata-defined DocTypes and modules.
- Generated CRUD, workflow, metadata, customization, audit, outbox, diagnostics, auth, and OpenAPI endpoints.
- Password auth with signed session tokens.
- Tenant-aware permissions and Bearer-token context resolution.
- In-memory development stores and durable Postgres stores.
- Custom fields, view metadata, and naming series.
- Audit log and durable outbox with worker dispatch helpers.
- Realtime document event publishing.
- React Desk UI generated from metadata.
- Typed SDK, CLI scaffolding, Docker Compose, CI, and deployment docs.
corepack enable
corepack prepare pnpm@11.9.0 --activate
pnpm install
pnpm devThe CRM example API runs at:
http://localhost:3000Run the Desk UI in another terminal:
pnpm dev:deskThe Desk usually runs at http://localhost:5173. If that port is occupied, Vite will choose the next available port.
Desk uses the API's HttpOnly session cookie; it does not persist bearer tokens in browser storage.
Development-only CRM login:
{ "email": "admin@example.com", "password": "admin12345" }pnpm typecheck
pnpm test
pnpm build
pnpm audit:all
pnpm --filter @framekit/example-crm outbox:dispatch
pnpm --filter @framekit/cli framekit create-app alpha-suite
pnpm --filter @framekit/cli framekit new-module sales
pnpm --filter @framekit/cli framekit new-doctype sales-order
pnpm --filter @framekit/cli framekit generate-sdk examples/crm/src/app.ts
pnpm --filter @framekit/cli framekit generate-migration examples/crm/src/app.ts examples/crm/src/app.tsSystem, contracts, migrations, realtime, and operations:
GET /health
GET /health/dependencies
GET /api/meta
GET /api/diagnostics
GET /api/migrations
POST /api/migrations/plan
POST /api/migrations/apply
POST /api/commands/{command}
GET /api/realtime/events
GET /api/realtime/stream
GET /api/openapi.json/health/dependencies runs adapter-provided dependency checks, for example Postgres, Redis, queues, or downstream services.
Migration planning, executable apply/replay, drift rules, upgrade backfill, and rollback limits are documented in Executable migrations. Atomic bulk commands, cross-document sagas, idempotency, compensation, and recovery limits are documented in Mutation consistency.
Auth lifecycle, provider login, audit, and admin APIs:
Production identity-linking, OIDC Authorization Code + PKCE, invitation, recovery, and MFA policy are documented in docs/identity.md.
POST /api/auth/login
GET /api/auth/me
POST /api/auth/refresh
POST /api/auth/logout
POST /api/auth/password/change
POST /api/auth/providers/{id}/login
GET /api/auth/audit
GET /api/auth/users
POST /api/auth/users
PATCH /api/auth/users/{id}
PUT /api/auth/users/{id}
DELETE /api/auth/users/{id}
POST /api/auth/users/{id}/password
GET /api/auth/roles
POST /api/auth/roles
PATCH /api/auth/roles/{id}
PUT /api/auth/roles/{id}
DELETE /api/auth/roles/{id}
GET /api/auth/tokens
POST /api/auth/tokens
DELETE /api/auth/tokens/{id}Documents:
GET /api/doctypes/{doctype}
POST /api/doctypes/{doctype}
GET /api/doctypes/{doctype}/{id}
PATCH /api/doctypes/{doctype}/{id}
DELETE /api/doctypes/{doctype}/{id}
POST /api/doctypes/{doctype}/{id}/transitionFramework records:
GET /api/audit
GET /api/outbox
POST /api/outbox/{id}/dispatch
POST /api/outbox/{id}/failCustomization:
GET /api/custom-fields
POST /api/custom-fields
GET /api/views
POST /api/viewsExample login and authenticated request:
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H 'content-type: application/json' \
-H 'origin: http://localhost:3000' \
-d '{"email":"admin@example.com","password":"admin12345"}' \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(JSON.parse(s).token))')
curl -s http://localhost:3000/api/doctypes/customer \
-H "authorization: Bearer $TOKEN"When an auth service is configured, every API route except health checks, login/provider login, and the OpenAPI document requires a valid bearer token or session cookie. Tenant, user, role, and permission headers never override that authenticated context.
Framework operations use dedicated permissions (or the * superuser permission):
| Permission | Operations |
|---|---|
framekit.diagnostics.read |
Runtime diagnostics |
framekit.migrations.read |
Migration history |
framekit.migrations.manage |
Migration planning and apply |
framekit.realtime.read |
Realtime event history and SSE stream |
framekit.audit.read |
Runtime audit trail |
framekit.outbox.read |
Outbox inspection |
framekit.outbox.manage |
Outbox dispatch/failure mutation |
framekit.customization.read |
Custom-field and view inspection |
framekit.customization.manage |
Custom-field and view mutation |
Apps without an auth service cannot access protected routes by default. Local-only prototypes may explicitly enable development.allowHeaderIdentity; Framekit accepts that escape hatch only when NODE_ENV=development or NODE_ENV=test.
SDK auth lifecycle and admin example:
import { createClient } from "@framekit/sdk";
const client = createClient({ baseUrl: "http://localhost:3000" });
await client.login("admin@example.com", "admin12345");
await client.me();
await client.refresh();
await client.upsertRole({
id: "support",
name: "Support",
permissions: ["crm.customer.read"]
});
const apiToken = await client.createApiToken({
name: "CRM import",
roles: ["support"],
permissions: ["crm.customer.read"]
});
await client.authAudit();
await client.logout();SDK failures are typed and preserve server status, code, details, and request identity. Retries are opt-in and limited to safe/idempotent operations; see SDK errors, retries, and configuration upgrades.
Provider login uses the same session shape after an app registers an auth provider:
await client.loginWithProvider("oidc", "<provider-token>");Migration workflow with SDK and CLI:
import { nextApp } from "./next-app";
const plan = await client.planMigration(nextApp);
await client.applyMigration(plan, { allowDestructive: false });pnpm --filter @framekit/cli framekit generate-sdk examples/crm/src/app.ts --out /tmp/crm-sdk.ts
pnpm --filter @framekit/cli framekit generate-migration examples/crm/src/app.ts examples/crm/src/next-app.ts --out /tmp/crm-migration.tsDocType is the primary metadata unit. It defines fields, permissions, naming, workflows, indexes, and views for a business document.
import { defineDocType } from "@framekit/core";
export const deal = defineDocType({
name: "deal",
label: "Deal",
naming: { prefix: "DEAL", series: true, digits: 5 },
fields: [
{ name: "title", label: "Title", type: "text", required: true, inList: true },
{ name: "amount", label: "Amount", type: "currency", default: 0, inList: true }
],
permissions: [
{ action: "create", permissions: ["crm.deal.write"] },
{ action: "read", permissions: ["crm.deal.read"] }
]
});Module groups DocTypes, permissions, navigation, hooks, jobs, typed settings, and dependencies. Apps can provide stable translation keys and canonical BCP 47 locale fallback; see Localization and Typed Settings.
Runtime executes framework use cases: validation, permissions, hooks, document persistence, audit, outbox, realtime publishing, custom fields, views, and naming series.
Nitro adapter exposes the runtime through generated HTTP routes.
| Package | Purpose | Verification status |
|---|---|---|
@framekit/core |
Pure metadata definitions: DocTypes, modules, apps, permissions, workflows, views. | Unit covered. |
@framekit/runtime |
Application services and ports for documents, audit, outbox, customization, naming, realtime, migration planning, checksums, destructive guards, and migration apply records. | Atomicity and concurrency paths covered; durable long-running saga coordination remains deferred. |
@framekit/auth |
Password hashing, signed sessions, refresh/logout, revocation, lockout, API tokens, auth audit, user/role admin, and provider-independent login ports. | Identity lifecycle and OIDC flows covered; native WebAuthn/TOTP remains deferred. |
@framekit/nitro |
Nitro/H3 adapter for generated framework APIs, cookie transport, auth/admin routes, operations authorization, rate limiting, telemetry hooks, and health dependency checks. | In-process, built, forged-header, cross-tenant, and least-privilege checks covered. |
@framekit/openapi |
OpenAPI 3.1 generator from Framekit metadata and framework routes. | Unit covered. |
@framekit/db |
Postgres adapters for documents, users, roles, API tokens, session revocations, audit, outbox, custom fields, views, naming series, and migration history. | Postgres integration, atomicity, query pushdown, and executable migration paths covered; production-scale load evidence remains deferred. |
@framekit/jobs |
Queue port, BullMQ adapter, outbox dispatcher, scheduled job registry. | Unit and Redis/BullMQ integration covered; sustained load/fault evidence remains deferred. |
@framekit/realtime |
Event bus contract and in-memory publisher/subscriber for document events and SSE routes. | Durable replay and full-stack authorization paths covered; sustained load evidence remains deferred. |
@framekit/sdk |
HTTP client for auth lifecycle, provider login, metadata, documents, audit, outbox, customization, views, migrations, realtime, and admin APIs. | Endpoint parity and standalone-consumer verification covered. |
@framekit/cli |
App/module/DocType scaffolding, generated SDK types, and executable migration workflows. | CLI smoke and standalone consumer proof covered; the Desk template remains intentionally un-packaged. |
@framekit/desk |
React Desk UI generated from metadata, auth/admin/operations/customization surfaces. | Build, mocked browser, and real full-stack Chromium/Firefox journeys covered. |
apps/desk React metadata-driven admin UI
examples/crm Nitro CRM example app
packages/* Framework packages
docs/ Architecture, deployment, and roadmap docsThe intended production target is a Nitro Node server with private or managed Postgres and Redis services. The current release is a beta: production-depth gates are present, while the remaining 1.0 work is operational hardening and explicit production boundaries—durable saga coordination, native MFA, load/soak evidence, deeper schema-drift detection, packaged Desk, and production secret/object-storage adapters. See the maturity roadmap.
docker compose up --builddocker-compose.yml is a local/reference stack, not a production deployment template. Its Postgres and Redis ports bind only to 127.0.0.1. It defaults to the local framekit password. To override it, set both FRAMEKIT_POSTGRES_PASSWORD (the raw password used by Postgres) and FRAMEKIT_POSTGRES_URL (the CRM connection URL with its password percent-encoded); for example, p@:/#ss becomes p%40%3A%2F%23ss in the URL. For production, provision unique credentials through the platform secret manager and point DATABASE_URL and REDIS_URL at private or managed services rather than exposing the bundled stores.
When DATABASE_URL is set, the CRM example uses Postgres for:
- Documents
- Users
- Audit events
- Outbox events
- Custom fields
- View metadata
- Naming series
Nitro can also emit provider-specific outputs through NITRO_PRESET. Keep long-running work behind queue/outbox ports for serverless deployments.
See docs/deployment.md. See docs/security.md before exposing a deployment to untrusted traffic. See docs/observability.md for lifecycle, health, telemetry, and redaction contracts. See docs/compatibility.md for supported runtimes, services, and browsers.
Copy .env.example only for local development. Production deployments start from .env.production.example and provision blank secrets through the deployment platform; do not reuse the Compose defaults:
cp .env.example .envImportant variables:
DATABASE_URL=postgresql://framekit:framekit@localhost:5432/framekit
REDIS_URL=redis://localhost:6379
FRAMEKIT_AUTH_SECRET=<provision-at-least-32-random-characters>
FRAMEKIT_ALLOWED_ORIGINS=https://desk.example.com
FRAMEKIT_ADMIN_EMAIL=ops@your-company.example
FRAMEKIT_ADMIN_PASSWORD=<provision-with-a-secret-manager>
VITE_FRAMEKIT_API_URL=http://localhost:3000pnpm --filter @framekit/cli framekit create-app alpha-suiteScaffold commands refuse to overwrite generated paths by default. Use --dry-run to inspect every planned write and --force to replace only the listed scaffold files.
create-app is intentionally a server starter. It includes:
package.jsonnitro.config.tsroutes/[...].tssrc/app.ts.env.example.env.production.exampleDockerfile- A starter
NoteDocType
It does not scaffold the React Desk. Run the repository Desk separately or build a frontend against @framekit/sdk; a packaged Desk template is deferred until its assets, configuration, and upgrade contract can be shipped as one supported unit.
Runnable, copyable SDK examples are available for React, Vue, Svelte, Solid,
and vanilla TypeScript under examples/frontends. Each
template connects to the CRM example, demonstrates bearer login/logout, checks
health and metadata, lists customers, and creates customers with idempotent SDK
mutations.
Start the API and one frontend in separate terminals:
pnpm dev
pnpm dev:frontend:reactSwap react for vue, svelte, solid, or vanilla. Use
pnpm verify:frontends to typecheck and build all five. These examples are
frontend starters, not a promise that framekit create-app packages the full
Desk application. The local CRM demo accepts admin@example.com /
admin12345; templates keep that development credential and the returned token
in memory only.
Every iteration should pass:
pnpm audit:allCurrent verification status:
- Merged #42 baseline:
pnpm audit:allpassed lint, typecheck, coverage tests, and builds. - Unit/in-process suite: 161 passed and 18 skipped; runtime 37/37 and Nitro 22/22.
- Coverage: 68.15% statements, 62.08% branches, 68.93% functions, and 70.62% lines.
- Coverage gates enforce at least 60% statements/functions/lines and 50% branches across public package source.
- Production build passes for packages, Desk, and CRM example.
- Split CI covers package-local tests, coverage, Node 22/24 exports, Postgres 16/17, Redis 7/8, built smoke, standalone consumption, browsers, CodeQL, dependency audit, and SBOM generation. Live PG16/Redis8, built smoke, standalone, mocked browser 7/7, and Chromium/Firefox full-stack 8/8 evidence passed on that baseline.
- In-process Nitro smoke covers auth lifecycle, provider login, OpenAPI, diagnostics, document CRUD, uniqueness, filters, cursor/projection, auth admin, password reset/change, customization, migrations, outbox, realtime history, and security/operations headers.
Framekit follows a clean architecture boundary:
coreandruntimeare inward modules.- Nitro, React, Postgres, Redis, BullMQ, and Docker are outer adapters.
- Source dependencies point inward.
- Framework details are swappable behind ports.
Current clean architecture score: about 8.5/10. The remaining gaps are mostly release hardening and broader adapter coverage, not core dependency direction.
See docs/architecture.md.
Revision checks, atomic Postgres mutations, durable uniqueness, and retry semantics are documented in docs/consistency.md.
Postgres query pushdown and stable opaque cursor semantics are documented in docs/querying.md.
Framekit is currently assessed as a beta: 88% implemented toward a production-ready 1.0. Exact decimals, computed fields, declarative validators, ordered child records, managed attachments, localization, and typed settings are implemented. All feature issues are closed; #60 is the remaining reviewed reconciliation. See component scores, verification evidence, and deliberate production boundaries in docs/maturity-roadmap.md.
Framekit is licensed under the Apache License 2.0.