Le contexte du template
Quand vous générez du code, Restorm passe tout votre design au template sous
la forme d’un objet ctx. Vos modèles, vos enums, vos routes et surtout
leurs intentions (searchable, PII, cache, et tout le profil opérationnel du
modèle : volumétrie, profil d’accès, trafic, fraîcheur, sensibilité, rétention)
sont là — un template les lit pour décider quoi générer. Pas besoin d’une
variable pour ça : les informations que vous avez déjà saisies dans le design
sont directement disponibles.

La forme de ctx
Section titled “La forme de ctx”ctx├─ design # name, version, description, basePath, defaultAuth, vars├─ enums[] # name, description, values[]├─ models[] # name, description, inherits, identifier, timestamps, softDelete,│ # volumetry, accessPattern, traffic, freshness, sensitivity,│ # retention, properties[], allProperties[], examples[], vars└─ groups[] # name, basePath, versionPrefix, headers[], routes[] └─ routes[] # method, path, fullPath, params[], body, responses[], # auth, pagination, cacheSeconds, idempotent, deprecated, varsLes références se font par nom (jamais par identifiant interne) :
property.enum est le nom de l’enum, property.references.model le nom du
modèle visé, model.inherits le nom du parent. Sur un modèle, properties[]
liste ses propriétés propres (pour la déclaration du type) et
allProperties[] les propriétés propres + héritées, aplaties (pour une
instance complète : données d’exemple, colonnes SQL, corps de requête).
Lire un flag : la règle d’or
Section titled “Lire un flag : la règle d’or”Les flags booléens ne sont présents dans ctx que lorsqu’ils sont activés.
Le moteur rend en StrictUndefined : il faut donc tester la présence de la
clé, jamais sa valeur.
{# ✅ correct — on teste la présence #}{% if 'searchable' in p %}INDEX({{ p['name'] }}){% endif %}
{# ❌ faux — lève une erreur quand le flag est absent #}{% if p.searchable %}…{% endif %}Seuls property.required (booléen) et route.auth (énumération) sont
toujours présents. Les autres flags booléens sont « présents = vrai » ; les
champs à valeur (volumétrie, profil opérationnel, cache…) portent leur
valeur quand ils sont renseignés, et sont absents sinon — même règle de
présence.
| Sur une propriété | Sur un modèle | Sur une route |
|---|---|---|
readOnly · writeOnly · nullable | timestamps · softDelete (booléens) | idempotent · deprecated (booléens) |
unique · searchable · immutable · pii | Profil opérationnel (valeurs) : volumetry · accessPattern · traffic · freshness · sensitivity · retention | cacheSeconds (nombre) · pagination (objet) |
Les valeurs du profil opérationnel d’un modèle : accessPattern
(readHeavy / writeHeavy / balanced / appendOnly), traffic
(low / medium / high), freshness (strong / shortCache / longCache),
sensitivity (public / internal / confidential / pii), retention
(permanent / archivable / ephemeral), volumetry (hundreds /
tenThousands / millions).
Exemple : mettre en cache les données à forte demande en lecture
Section titled “Exemple : mettre en cache les données à forte demande en lecture”Deux cas, selon que l’information existe déjà dans le design ou pas.
a) Le design porte déjà l’info
Section titled “a) Le design porte déjà l’info”Si vous avez renseigné une durée de cache sur une route, elle arrive dans
route['cacheSeconds'] ; route['idempotent'] vous dit qu’elle est sûre à
mettre en cache (lecture seule) :
{% for group in ctx['groups'] %}{% for route in group['routes'] %}{% if 'cacheSeconds' in route and 'idempotent' in route %}// {{ route['method'] }} {{ route['fullPath'] }}app.use("{{ route['fullPath'] }}", cache({{ route['cacheSeconds'] }}));{% endif %}{% endfor %}{% endfor %}b) « Forte demande en lecture » est un champ natif du modèle
Section titled “b) « Forte demande en lecture » est un champ natif du modèle”Pas besoin de variable : « forte demande en lecture » est directement le profil
d’accès du modèle. Dans les réglages du modèle, groupe Profil opérationnel,
vous choisissez Lecture dominante — le template le lit dans
model['accessPattern']. Combinez-le avec model['freshness'] (tolérance à la
fraîcheur) pour décider s’il faut mettre en cache, et pour combien de temps :
{% for model in ctx['models'] %}{% if model['accessPattern'] == 'readHeavy' and model['freshness'] != 'strong' %}{% set ttl = 3600 if model['freshness'] == 'longCache' else 60 %}registerCache("{{ model['name'] }}", {{ ttl }}); // cache activé ({{ ttl }}s){% endif %}{% endfor %}Notez le test != 'strong' : un modèle en lecture dominante mais en
cohérence forte ne doit pas être mis en cache. C’est tout l’intérêt d’avoir
les deux intentions côte à côte dans le design.
Réservez les variables de génération à ce que le design ne porte pas déjà (un réglage propre au template) : les intentions métier — volumétrie, profil d’accès, trafic, fraîcheur, sensibilité, rétention — sont des champs natifs du modèle.
Le contrat complet
Section titled “Le contrat complet”Ce contexte est versionné (ctx['contextVersion']) et décrit exhaustivement par
un JSON-Schema livré dans le dépôt de l’application
(docs/contributing/design-codegen-context.schema.json) — copiez-le dans votre
template pour que sa CI valide le contexte de référence. Un agent IA obtient le
même contrat à la volée via l’outil MCP get_design_template_contract.