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/*"] }
}
}
.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)
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-ignorequand le fichier intégré n'a pas de.d.ts. - Attention au tree-shaking : un tableau
constde premier niveau exporté mais lu uniquement de façon indirecte peut être éliminé. Exportez plutôt une fonction accesseur nommée à la place duconstnu.
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 |