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/*"] }
  }
}
Note

.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.publish renvoie false et secrets.get renvoie null au lieu de lever.
  • Les paramètres SQL de db sont envoyés sous forme de chaînes : un nombre devient "42", et null ou undefined devient une chaîne vide. Pour écrire un vrai NULL, mettez-le dans le SQL.
  • kv.set sérialise la valeur en JSON et kv.get la désérialise : passez l'objet tel quel, sans JSON.stringify pré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

Attention

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-ignore quand 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