Skip to main content

Data and realtime

Runku exposes a logical document model instead of direct SQL. The same contract is implemented by SQLite for local development and PostgreSQL for production-oriented execution.

Schema and values

The TypeScript schema defines tables, document fields, validators, and logical indexes. Generated types associate document IDs with their table and reject unknown Function names or invalid arguments before a request is sent.

Canonical values include null, booleans, signed 64-bit integers, floating-point values, UTF-8 strings, bytes, timestamps, arrays, objects, and typed document IDs. Persisted and wire encodings are versioned and bounded.

Transactions

Queries read one snapshot. Mutations use optimistic concurrency control, an idempotent operation ID, and an atomic commit containing documents, index changes, outbox events, and scheduled invocations. No realtime notification is visible before commit.

Management Data Admin

The authenticated Management API exposes a logical Data Admin boundary for an operator console; it never accepts SQL, physical table names, index bytes, or caller-supplied index mutations. Every request names environment:default or an explicit release:, channel:, or workspace: code target. Runku resolves that target once, verifies the effective artifact, and uses its exact schema and logical indexes for the whole operation.

data:read authorizes exact document reads and bounded logical-index queries. data:write authorizes insert, replace, and delete. Neither capability is implied by environments:manage, and data:read never authorizes a write. Function db:read/db:write capabilities are a separate application-execution boundary and never authorize Management requests.

The v1 routes are scoped by the URL Project and Environment:

Method and suffixCapabilityContract
POST /data/querydata:readtable/index/prefix query, limit in 1..=200, one snapshot
GET /data/documents/{table}/{document}data:readexact logical document and OCC revision
POST /data/documents/{table}data:writeschema-validated insert; ID derives from Idempotency-Key
PUT /data/documents/{table}/{document}data:writefull replace with expectedRevision and previousValue
DELETE /data/documents/{table}/{document}data:writedelete with expectedRevision and previousValue

Every write requires one valid Idempotency-Key: opn_*. Exact replay returns the committed result with replayed: true; reuse for another target, schema, document, value, or precondition returns PRODUCT_OPERATION_ID_REUSED. Replace/delete require the complete value observed at the expected revision so Runku can derive removals from trusted schema metadata and reconstruct the exact atomic batch after an uncertain response. A stale revision or mismatched observed value returns a conflict. Validation failures return PRODUCT_DATA_VALIDATION_FAILED without echoing stored data.

Catalog reads use GET /functions and GET /schema/tables with target, after, and limit (1..=200) under releases:read. Responses include both the requested target and immutable resolved pin, Release identity, serving revision, and schema digest. This lets a console retain an exact provenance record while paginating stable Function-name or Table-ID cursors.

Indexes

Logical indexes encode ordered compound keys consistently across storage adapters. Mutations derive old and new index entries from the trusted schema rather than accepting index keys from application code.

Realtime

A subscription executes a Query and registers its dependency set. Committed outbox events are matched against active dependencies, and affected Queries are rerun. WebSocket reconnect and resync preserve the selected Environment and code target.

Realtime is authorization-aware: the application key, user identity, origin policy, and Function policy are evaluated at subscription time and again when required by reconnect or credential changes.

Canonical limits and document concurrency

Canonical objects are ordered by UTF-8 key bytes. Integer, float, timestamp, bytes, depth, item count, string, and envelope limits are enforced before storage/wire processing. Unknown encodings fail closed. Document IDs remain statically associated with a logical table.

Mutation reads establish an optimistic read-set. Replace/delete require exact revision. Conflict re-runs business logic from a fresh snapshot within bounded attempts. An operation ID identifies one Mutation intent across retry/replay and cannot be reused for different arguments.

Index scans use explicit bounds and a bounded limit. Schema evolution must make an index ready before code assumes it and retire it only after live Releases/subscriptions/schedules no longer reference it.

Realtime delivery model

A delivered value is an authoritative Query result, not a domain-event log. Reconnect or resync_required reruns the Query; intermediate WebSocket frames are not replay-guaranteed. Clients replace local state with each result.

Outbox records commit atomically with data/index/schedule changes. Dispatch may repeat after crash and must be idempotent. Lag delays Realtime but cannot expose uncommitted state.

Storage and recovery

SQLite is the local single-process adapter. PostgreSQL is the production-oriented adapter for concurrency/distributed claims. Both pass the same logical conformance contract; Function code does not access physical SQL.

An attached runku-server Environment can select that adapter with the optional RUNKU_PLATFORM_DATABASE_URL or _FILE secret. The database is atomically bound to one exact Project/Environment and readiness checks it without falling back to SQLite. This selection covers the logical documents/indexes/Mutation operations/outbox/schedule contract, not every repository in the Product root. See Environment-scoped Function platform PostgreSQL for the exact storage, isolation, and recovery boundary.

On corruption or cross-store inconsistency, stop writers and preserve state. Never repair documents, indexes, outbox, or schedules independently. Follow Backup and recovery.

For an uncertain Data Admin write, retry the identical request with the identical operation ID. After an OCC conflict, fetch the document again and submit a new intent with a new operation ID; never rewrite previousValue merely to force a stale operation through.

Application checklist

  • bound every collection/string/bytes contract;
  • enforce resource ownership inside every Function;
  • handle not-found and OCC conflict explicitly;
  • retain one operation ID for one Mutation intent;
  • make scheduled/external effects idempotent;
  • handle Realtime reconnect/resync as authoritative refresh;
  • evolve schema/indexes compatibly across every live code pin.