Présentation

Chaque application Inklura — la console, SMS Designer, le SEO, le compositeur d'e-mails et les sites des tenants — est une application bext PRISM. PRISM, c'est deux choses : un framework de rendu côté serveur (@bext-stack/framework) et un SDK de plateforme exposé à chaque application via une interface loopback de confiance.

  • Framework — un moteur JSX→chaîne sans runtime. Il n'y a pas de React, pas de DOM virtuel et aucun runtime client par défaut. Un composant n'est qu'une fonction qui retourne une chaîne.
  • SDK — KV, queue, tasks, cache, secrets, un planificateur et des entités relationnelles, accessibles à http://127.0.0.1/__bext/sdk/* et authentifiés par un identifiant d'application.

Ce qu'est PRISM

Un composant PRISM est une simple fonction :

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

Il retourne une chaîne (ou une promesse / un async-iterable de chaînes, pour le streaming). Le JSX compile directement en concaténation de chaînes — il n'y a pas de réconciliation et rien n'est envoyé au navigateur à moins d'ajouter un island.

Chaque fichier source qui utilise du JSX commence par un commentaire pragma :

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

Les fichiers island interactifs (signaux côté client) utilisent un pragma différent, plus une directive :

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

Structure d'un site

Un site PRISM est un package de workspace :

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 dépend du framework en tant que package de workspace :

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

tsconfig.json relie le JSX au framework :

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

.bext/ est un cache de compilation détenu par root et ignoré par git. Ne le commitez jamais. Consultez la Référence CLI pour le workflow dev/build et la Référence de configuration pour bext.config.toml.

Consultez Routage & Rendu pour savoir ce qui va dans src/app/.

Le modèle SDK en loopback

Le SDK de plateforme est un ensemble d'endpoints HTTP sous http://127.0.0.1/__bext/sdk/*. Les applications n'utilisent pas de JWT admin pour les atteindre. À la place, une application hébergée sur l'hôte envoie son identifiant d'application :

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

Cet en-tête n'est digne de confiance que depuis le loopback (127.0.0.1 / ::1). Les plages d'IP privées et Docker ne sont pas dignes de confiance. Lorsqu'il est présent depuis le loopback, il contourne l'exigence du JWT admin et restreint toutes les données à cet identifiant d'application — une application ne peut ni lire ni écrire les données d'une autre application.

La même origine sert aussi la Platform API (tRPC). Les endpoints du SDK présentés ici constituent une surface distincte, restreinte à l'application, et non l'API publique.

Le client createSdk

Le framework fournit un client JS, de sorte que vous touchez rarement au HTTP brut :

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

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

Le client implémente l'interface BextSdk :

Namespace Méthodes
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)

Chaque namespace a sa propre page : KV, Queue, Cache, Secrets et Entités (données relationnelles). Les traitements de longue durée passent par Tasks & Planificateur.

Intégrer des packages npm (mise en garde)

Attention

Le bundler PRISM ne résout pas les spécificateurs npm nus dans l'isolat de rendu, et l'isolat n'a aucun module natif Node (node:crypto, etc.). Un require("pkg") / import "pkg" nu pour un package non intégré échoue.

Contournement : intégrez la build ESM du package sous forme de fichiers .js importés en chemin relatif dans src/**/vendor/ et importez-les par chemin relatif :

// @ts-ignore — no bundled .d.ts
import { sha256 } from "../vendor/noble-hashes/sha256.js";
  • Privilégiez les bibliothèques purement JS sans modules natifs Node (par exemple @noble/hashes).
  • Ajoutez // @ts-ignore quand le fichier intégré n'a pas de .d.ts.
  • Attention au tree-shaking : un tableau const de premier niveau exporté mais lu uniquement de façon indirecte peut être éliminé. Exportez plutôt une fonction accesseur nommée à la place du const nu.

Et ensuite

Page Ce qu'elle couvre
Routage & Rendu L'arbre de routes src/app/, les métadonnées, ISR/SSR, le streaming
Loaders & Actions Chargement de données, mutations, accès à la requête, readSession
KV Store Store de chaînes durable, TTL, le piège du double encodage
Queue Queue de push at-least-once, workers, dead-letter
Tasks & Planificateur Task-executor chaud pour les traitements longs, planificateur cron
Cache Cache éphémère à TTL
Secrets Store de secrets, également exposé comme variables d'environnement
Entités (Neon) CRUD admin avec defineEntity + accès direct à Neon