ARCHITECTURE

Contract-first. Headless. Swappable everywhere.

Openora is a contract spine with a plugin host and adapter seams around it. Contracts compile straight to a typed API and SDK - no UI ships in the framework itself, so your frontend, vendors and infrastructure all plug in through the same swap points.

CURRENT STATE
  • The API is oRPC + Hono - not a REST-and-controllers framework.
  • The framework is headless. No UI ships in the core repo - your frontend lives in your own consumer app and talks to the typed API.
  • Every domain is folded into a single published package, @openora/core, exposed through per-domain subpaths like @openora/core/wallet.
  • AGENTS.md files regenerate automatically from one source of truth, so Claude Code, Copilot and other agents stay in sync without manual editing.

Source of truth: docs/architecture.md and the ADRs under docs/adr/ in the Openora repo.

THE PARTS

From contract to running service.

Solid dependencies flow top to bottom; adapter seams cut across every layer so any vendor or surface can be swapped freely.

1
01 · contracts

Contracts

Cross-cutting Zod schemas plus a composed oRPC contract - each module owns its own slice. The typed client is inferred straight from the contract, so there is no client to regenerate; an OpenAPI spec is emitted alongside it. Every type is inferred from the schema, never hand-written.

2
02 · api runtime

API runtime

One list of enabled plugins feeds the plugin host (definePlugin), which composes a typed-token container - lazy, last-wins, no decorators - into a Hono + oRPC handler. The API layer stays a thin caller of createApp().

3
03 · platform services

Platform services

Shared infrastructure every module can depend on: Drizzle-backed storage, auth with admin guards, a logger, and the typed event bus. Single-tenant today - multi-brand hardening is on the roadmap.

4
04 · business modules

Business modules

One folder per domain. A module can import the engine zones and a sibling's read-only schema - but never a sibling's internals. Cross-domain talk goes through events, command ports or shared contracts only.

5
05 · vendor adapters

Vendor adapters

Concrete implementations of a module's adapter interfaces - payments, KYC, game aggregators, chat. The interface is the seam; the implementation underneath is always swappable.

ADAPTER / BRIDGE SEAMS

The swap points.

This is the reason the framework is headless and extensible - one side declares an interface, the other implements it, and you can swap the implementation freely.

Plugin host
INTERFACE
definePlugin contract
IMPLEMENTATION
a module or overlay folder
SWAP TO
add or remove features without touching core
Vendor adapter
INTERFACE
a token from @openora/core/contracts
IMPLEMENTATION
a mock ships today; bind your own in a plugin's ctx.provide()
SWAP TO
a different PSP, KYC provider, or aggregator
Job queue
INTERFACE
JOB_QUEUE token
IMPLEMENTATION
in-process by default; BullMQ when REDIS_URL is set
SWAP TO
your own durable queue, bound as an overlay
Message broker
INTERFACE
MESSAGE_BROKER token
IMPLEMENTATION
in-process event bus
SWAP TO
a durable driver - Kafka/Redpanda, NATS - as an overlay
Consumer link
INTERFACE
createApp() + @openora/core
IMPLEMENTATION
your downstream app, scaffolded by pnpm create:app
SWAP TO
bump the @openora/core version and reinstall
INTER-MODULE COMMUNICATION

Coupled to topics, not to routes.

The API layer is a backend-for-frontend: it triggers commands and serves reads. Modules never call each other directly.

Events for side effects

A module emits through the typed event bus; any number of other modules can subscribe - fan-out, not point-to-point. Consumers must be idempotent, and a throwing subscriber is logged and isolated instead of taking the caller down with it.

Synchronous and atomic for money

Placing a bet - wallet debit, balance check, RNG result - and pre-action gates like KYC or jurisdiction checks run inside a single database transaction. Events record what already happened; they never move funds themselves.

Broker behind a seam

The event bus is a typed facade over a swappable message broker. What ships today is the in-process default plus the MESSAGE_BROKER port. A durable driver - Kafka/Redpanda for a regulated audit/ledger/replay stream, NATS JetStream for lighter fan-out - is an overlay you bind yourself, with zero module changes.

Client push is separate

Server-sent events, behind the REALTIME_TRANSPORT seam, are for talking to the client only - chat and live feeds - never the transport between modules.

Modular monolith now, microservices later

The no-cross-domain-imports rule plus the broker seam mean a hot module - game aggregator, wallet - can be extracted into its own deployable later without touching its code.

DEPLOYMENT

Modular monolith today, microservices later.

A fresh scaffold runs as one deployable. When a hot module - bonus, game aggregator - needs to scale on its own, you extract the service. Module code never changes, only the deployment topology does.

deploy session
$ pnpm create:app my-gaming-core
$cd my-gaming-core && pnpm install
$ docker compose up -d # postgres
$pnpm db:migrate && pnpm dev
 api on :3001

No opinion about hosting - bare metal, your own Kubernetes cluster, or any major cloud, since the framework only assumes a Postgres-compatible database and a Node runtime.

BUILT IN, NOT BOLTED ON

A hash-chained audit trail for every state change.

Every mutation is append-only and chained to the previous entry - nothing can be silently edited or deleted after the fact. Reverting a mistaken grant creates a new, compensating entry instead of erasing history, which is exactly what a licensing review asks for.

Openora.ai engineers whiteboarding a contract change
An Openora.ai engineer working through an adapter implementation
The Openora.ai team reviewing a proposed architecture change

Designed by the engineers who write the contracts, not a sales deck

Build it. Evolve it.
Own it.

Whether you're building from scratch, extending a legacy platform or planning a gradual migration, the framework gives you the freedom to evolve without vendor lock-in.