Overview

Every Inklura app — the console, SMS Designer, SEO, the mail composer, and the tenant sites — is a bext PRISM app. PRISM is two things: a server-rendering framework (@bext-stack/framework) and a platform SDK exposed to each app over a trusted loopback interface.

  • Framework — a zero-runtime JSX→string engine. There is no React, no virtual DOM, and no client runtime by default. A component is just a function that returns a string.
  • SDK — KV, queue, tasks, cache, secrets, a scheduler, and relational entities, reached at http://127.0.0.1/__bext/sdk/* and authenticated by an app id.

What PRISM is

A PRISM component is a plain function:

type Component = (props) => string | Promise<string> | AsyncIterable<string>;

It returns a string (or a promise/async-iterable of strings, for streaming). JSX compiles straight to string concatenation — there is no reconciliation and nothing ships to the browser unless you add an island.

Every source file that uses JSX starts with a pragma comment:

/** @jsxImportSource @bext-stack/framework */

Interactive island files (client-side signals) use a different pragma plus a directive:

/** @jsxImportSource @bext-stack/framework/signals */
"use signals";

Site layout

A PRISM site is a workspace package:

my-app/
├─ bext.config.toml        # site + rendering config
├─ tsconfig.json
├─ package.json
├─ src/
│  ├─ app/                 # file-based route tree (page.tsx, layout.tsx, route.ts, …)
│  ├─ lib/
│  └─ components/
├─ public/                 # static assets served as-is
│  └─ islands/*.js         # client-side islands
└─ .bext/                  # root-owned, gitignored compiler cache

package.json depends on the framework as a workspace package:

{
  "dependencies": {
    "@bext-stack/framework": "workspace:*"
  }
}

tsconfig.json wires JSX to the framework:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@bext-stack/framework",
    "moduleResolution": "bundler",
    "paths": { "@/*": ["./src/*"] }
  }
}
Note

.bext/ is a root-owned, gitignored compiler cache. Never commit it. See CLI Reference for the dev/build workflow and Config Reference for bext.config.toml.

See Routing & Rendering for what goes in src/app/.

The SDK loopback model

The platform SDK is a set of HTTP endpoints under http://127.0.0.1/__bext/sdk/*. Apps do not use an admin JWT to reach them. Instead, an on-host app sends its app id:

X-Bext-App-Id: <app-id>

This header is only trusted from loopback (127.0.0.1 / ::1). Private and Docker IP ranges are not trusted. When present from loopback, it skips the admin-JWT requirement and scopes all data to that app id — an app cannot read or write another app's data.

The same origin also serves the Platform API (tRPC). The SDK endpoints here are a separate, app-scoped surface, not the public API.

The createSdk client

The framework ships a JS client so you rarely touch raw HTTP:

import { createSdk } from "@bext-stack/framework";

const sdk = createSdk("<app-id>");
// base http://127.0.0.1/__bext/sdk, 30s timeout

The client implements the BextSdk interface:

Namespace Methods
kv get<T>(key) · set(key, value, ttlSecs?) · delete(key) · list(prefix?, limit?)
db all<T>(sql, params?) · get<T>(sql, params?) · exec(sql, params?)
queue push(queue, payload, delaySecs?)
realtime publish(topic, data)
email send({ to, subject, html?, text? })
secrets get(key)

Each namespace has a dedicated page: KV, Queue, Cache, Secrets, and Entities (relational data). Long-running work goes to Tasks & Scheduler.

Vendoring npm packages (caveat)

Warning

The PRISM bundler does not resolve bare npm specifiers into the render isolate, and the isolate has no Node built-ins (node:crypto, etc.). A bare require("pkg") / import "pkg" for an unvendored package fails.

Workaround: vendor the package's ESM build as relative-imported .js files under src/**/vendor/ and import them by relative path:

// @ts-ignore — no bundled .d.ts
import { sha256 } from "../vendor/noble-hashes/sha256.js";
  • Prefer pure-JS libraries with no Node built-ins (for example @noble/hashes).
  • Add // @ts-ignore when the vendored file has no .d.ts.
  • Beware tree-shaking: an exported top-level const array that is only read indirectly can be shaken away. Export a named accessor function instead of the bare const.

Where to next

Page What it covers
Routing & Rendering The src/app/ route tree, metadata, ISR/SSR, streaming
Loaders & Actions Data loading, mutations, request access, readSession
KV Store Durable string store, TTLs, the double-encoding gotcha
Queue At-least-once push queue, workers, dead-letter
Tasks & Scheduler Warm task-executor for long jobs, cron scheduler
Cache TTL scratch cache
Secrets Secret store, also exposed as env vars
Entities (Neon) defineEntity admin CRUD + direct Neon access