Vue d'ensemble
Les applications Inklura, de la console aux sites des tenants, sont des applications bext PRISM.
PRISM réunit deux choses : un framework de rendu côté serveur (@bext-stack/framework) et un
SDK de plateforme que chaque application utilise pour stocker et traiter ses données.
- Framework : un moteur JSX vers chaîne, sans runtime. Il n'y a ni React, ni DOM virtuel, ni runtime client par défaut. Un composant est une fonction qui retourne une chaîne.
- SDK : KV, queue, tasks, cache, secrets, un planificateur et des entités relationnelles. Le SDK authentifie votre application automatiquement et limite toutes les données à celle-ci.
Le modèle de composant
Un composant PRISM est une simple fonction :
import type { Component } from "@bext-stack/framework";
// type Component<P = {}> = (props: P) => Renderable;
Renderable couvre les chaînes (ainsi que les nombres, les booléens, null et undefined), les
tableaux de renderables, une promesse de renderable pour les composants asynchrones, et un itérable
asynchrone pour le streaming. Le JSX est compilé directement en construction de chaînes : il n'y a
pas de réconciliation, et rien n'est envoyé au navigateur tant que vous n'ajoutez pas d'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) commencent par la directive
"use signals", suivie d'un autre pragma :
"use signals";
/** @jsxImportSource @bext-stack/framework/signals */
Structure d'un site
Un site PRISM est un 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/ # compiler cache, gitignored
package.json déclare le framework comme dépendance :
{
"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 le cache du compilateur. Il est ignoré par git : ne le commitez pas. Consultez la
Référence CLI pour le cycle 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 client createSdk
Le framework fournit le client du SDK dans son point d'entrée sdk :
import { createSdk } from "@bext-stack/framework/sdk";
const sdk = createSdk("<app-id>", { timeoutMs: 30_000 }); // options are optional
Passez l'identifiant de votre application. timeoutMs borne chaque appel (30 secondes par défaut).
Le client authentifie l'application automatiquement, et chaque lecture ou écriture est limitée à
cette application : une application ne peut ni lire ni écrire les données d'une autre.
Le SDK est distinct de la Platform API (tRPC). La Platform API sert les données métier des tenants, le SDK sert l'état propre à votre application.
Le client implémente l'interface BextSdk :
| Namespace | Méthodes | Retour |
|---|---|---|
kv |
get<T>(key) · set(key, value, ttlSecs?) · delete(key) · list(prefix?, limit?) |
T | null · void · boolean · string[] (clés, 100 au plus par défaut) |
db |
all<T>(sql, params?) · get<T>(sql, params?) · exec(sql, params?) |
T[] · T | null (première ligne) · { changes, lastInsertId } |
queue |
push(queue, payload, delaySecs?) |
identifiant du message (string) |
realtime |
publish(topic, data) |
boolean |
email |
send({ to, subject, html?, text? }), où to est un tableau d'adresses |
boolean |
secrets |
get(key) |
string | null |
Quelques comportements à connaître :
- Une réponse en erreur lève une
Error(message renvoyé par le serveur, sinon le statut HTTP). Deux exceptions :realtime.publishrenvoiefalseetsecrets.getrenvoienullau lieu de lever. - Les paramètres SQL de
dbsont envoyés sous forme de chaînes : un nombre devient"42", etnullouundefineddevient une chaîne vide. Pour écrire un vraiNULL, mettez-le dans le SQL. kv.setsérialise la valeur en JSON etkv.getla désérialise : passez l'objet tel quel, sansJSON.stringifypréalable.
Chaque namespace a sa page : KV, Queue, Cache, Secrets et Entités pour les données relationnelles. Les traitements longs passent par Tasks & Planificateur.
Intégrer des packages npm
Les pages se rendent dans un isolat, pas dans Node : les modules natifs comme node:crypto ou
node:fs sont absents ou seulement imités en partie. Une bibliothèque qui en dépend ne fonctionnera
pas de façon fiable au moment du rendu. L'isolat refuse aussi de transformer une chaîne en
code : eval, new Function et les minuteurs à chaîne lèvent une EvalError.
La méthode fiable consiste à intégrer la build ESM de la bibliothèque sous forme de fichiers .js
dans un répertoire vendor/ de src/, et à les importer par chemin relatif :
// @ts-ignore: no bundled .d.ts
import { sha256 } from "../vendor/noble-hashes/sha256.js";
- Choisissez des bibliothèques en pur JS, sans modules natifs Node ni
eval/new Function(par exemple@noble/hashes). - Ajoutez
// @ts-ignorequand le fichier intégré n'a pas de.d.ts.
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 et à la session |
| KV Store | Store de chaînes durable, TTL, le piège du double encodage |
| Queue | Queue at-least-once, handlers HTTP, dead-letter |
| Tasks & Planificateur | Traitements longs avec statut et progression, planification récurrente |
| Cache | Mise en cache de valeurs recalculables avec TTL |
| Secrets | Secrets et configuration de l'application, lus en variables d'environnement |
| Entités | Écrans d'administration avec defineEntity, requêtes SQL avec sql |