@mostajs/wagon-engine 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/llms.txt +5 -3
- package/package.json +2 -2
- package/src/wagon.mjs +25 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
**Auteur** : Dr Hamid MADANI <drmdh@msn.com>
|
|
4
4
|
Format : Keep a Changelog ; versions : SemVer.
|
|
5
5
|
|
|
6
|
+
## [0.3.0] — 2026-10-27
|
|
7
|
+
|
|
8
|
+
### Ajouté
|
|
9
|
+
- Les **dispositions** de `@mostajs/wagon-ui` 0.10 : une page de la charge choisit son cadre par
|
|
10
|
+
`ctx.page({ disposition: 'application' | 'console' | 'nu' | <fournie>, application, console })` ; les pages du wagon
|
|
11
|
+
(tarifs, connexion, compte…) restent en `vitrine`. Le moteur transmet `config.jetons` (rayon, police, ombre, fond),
|
|
12
|
+
le logo de `config.marque` (`logo`, `logoClair`) et `charge.dispositions` (dispositions fournies). Essai T-ENG-8.
|
|
13
|
+
- `charge.accueil` peut être une **fonction** `async (ctx) => ({ titre, contenu, disposition, … })` : l'application
|
|
14
|
+
rend son propre accueil (la maquette de Speaking ouvre sur « Rejoindre mon groupe »). La fiche de données
|
|
15
|
+
`{ fr: { titre, lede, actions, preuves } }` marche toujours. Essai T-ENG-9.
|
|
16
|
+
|
|
17
|
+
### Modifié
|
|
18
|
+
- Dépend de `@mostajs/wagon-ui` `^0.10.0`.
|
|
19
|
+
|
|
20
|
+
## [0.2.0] — 2026-10-26
|
|
21
|
+
|
|
22
|
+
### Ajouté
|
|
23
|
+
- `ctx.palier({ compte })` et `ctx.plafond(cle, actuel, { compte })` lisent le palier d'un compte **désigné**, et non plus seulement de celui de la session. Une requête sans session peut ainsi être bornée par le palier d'un compte : un élève qui rejoint le groupe de son école compte dans le plafond d'apprenants de l'école (Speeking). La charge ne désigne qu'un compte qu'elle a elle-même rattaché à la requête. Essai T-ENG-6.
|
|
24
|
+
- `ctx.courriel(message)` permet à la charge d'envoyer un courriel par le transport du wagon (SMTP du `.env`). La fonction rend `true` si le courriel est parti, `false` sans transport ou en cas d'échec ; elle ne lève jamais d'exception. Essai T-ENG-7.
|
|
25
|
+
|
|
6
26
|
## [0.1.1] — 2026-10-26
|
|
7
27
|
|
|
8
28
|
### Corrigé (suite de conformité)
|
package/llms.txt
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> PayPal), espace client, administration, pages légales, robots/sitemap, sauvegarde, fr/en/ar. Une GREFFE n'apporte que
|
|
5
5
|
> wagon.config.mjs, charge/, legal/ et son .env. N2 (pack). Extrait du gabarit mosta-wagon 0.11.0 le 26/10/2026.
|
|
6
6
|
|
|
7
|
-
- Version: 0.
|
|
7
|
+
- Version: 0.2.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
|
|
8
8
|
- Chemin: mostajs/mosta-platform-stack/mosta-wagon-engine · gabarit d'une greffe : Entreprise/mosta-wagon
|
|
9
9
|
|
|
10
10
|
## EXPORTS
|
|
@@ -22,12 +22,14 @@ PAYPAL_WEBHOOK_ID · PAIEMENT_<STRIPE|CHARGILY|SATIM|PAYPAL>=off (coupe le moyen
|
|
|
22
22
|
Moyen ACTIF = clés présentes ET commutateur ≠ off (ET, pour une banque prépayée, proposée par config.paiement.prepaye).
|
|
23
23
|
|
|
24
24
|
## CONTRAT DE LA CHARGE (charge/index.mjs)
|
|
25
|
-
liens · ressources (quota { cle, limite, periode } | plafond { cle, limite, plafond: true }) · accueil · textes ·
|
|
25
|
+
liens · ressources (quota { cle, limite, periode } | plafond { cle, limite, plafond: true }) · accueil (fiche { fr: { titre, lede, actions, preuves } }, ou 0.3 : async (ctx) => ({ titre, contenu, disposition, application, console })) · dispositions (0.3 : { nom: (p) => corps }) · textes ·
|
|
26
26
|
routes(req, res, ctx) (passent en premier ; true si servie) · conformite (essai de quota T-WAG-3) · amorcer() (base au
|
|
27
27
|
démarrage) · exporterCompte(id), effacerCompte(id) (RGPD) · sitemap() · sauvegarde() · permissions (0.11 :
|
|
28
28
|
{ '/page': { microphone: 'self' }, '/dossier/*': … }, garde-http ≥ 0.2).
|
|
29
29
|
ctx : chemin, methode, lang, t, ui, page({ titre, contenu }), envoyer, json, rediriger, formulaire, corpsJson({ strict }),
|
|
30
|
-
compte, consommer(cle), refuser(v), palier(), plafond(cle, n), admin.
|
|
30
|
+
compte, consommer(cle), refuser(v), palier({ compte? }), plafond(cle, n, { compte? }), courriel({ to, subject, text, html? }) → bool, admin.
|
|
31
|
+
0.3 : ctx.page({ disposition: 'vitrine'|'application'|'console'|'nu'|<fournie>, application: { retour, titre, avatar, onglets, langues }, console: { menu, contexte, utilisateur } }) (wagon-ui 0.10) ; config.jetons { rayon, police, policeTitres, ombre, fond } ; config.marque.logo / logoClair (SVG en ligne ou chemin /…).
|
|
32
|
+
0.2 : { compte } = identifiant d'un compte DÉSIGNÉ (requête sans session bornée par le palier d'un compte rattaché par la charge).
|
|
31
33
|
|
|
32
34
|
## PIÈGES
|
|
33
35
|
- wagon-ui ≥ 0.9 exige config.marque.auteur.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/wagon-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Le wagon d'un SaaS, une fois pour toutes : comptes, connexion, paliers, quotas, paiement (Stripe, Chargily, SATIM), espace client, administration, pages légales, fr/en/ar. Une greffe n'apporte que wagon.config.mjs, charge/, legal/ et son .env ; la suite de conformité publiée juge la greffe.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mostajs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@mostajs/orm": "^2.12.1",
|
|
53
53
|
"@mostajs/payment": "^2.6.0",
|
|
54
54
|
"@mostajs/subscriptions-plan": "^0.9.0",
|
|
55
|
-
"@mostajs/wagon-ui": "^0.
|
|
55
|
+
"@mostajs/wagon-ui": "^0.10.0",
|
|
56
56
|
"better-sqlite3": "^12.11.1",
|
|
57
57
|
"stripe": "^17.7.0"
|
|
58
58
|
},
|
package/src/wagon.mjs
CHANGED
|
@@ -187,7 +187,10 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
|
|
|
187
187
|
// 0.9 (wagon-ui 0.6) — la feuille de style de l'application, de confiance, posée sur toutes les pages.
|
|
188
188
|
css: typeof config.css === 'string' ? config.css : '',
|
|
189
189
|
// 0.5 (wagon-ui 0.5) — en-tête collant, bouton d'appel, langue en menu déroulant : déclarés par wagon.config.mjs.
|
|
190
|
-
collant: config.collant === true, appel: config.appel ?? null, selecteurLangue: config.selecteurLangue ?? 'liens'
|
|
190
|
+
collant: config.collant === true, appel: config.appel ?? null, selecteurLangue: config.selecteurLangue ?? 'liens',
|
|
191
|
+
// 0.3 (wagon-ui 0.10) — les jetons de forme, et les dispositions que la charge fournit (une page choisit son cadre
|
|
192
|
+
// par ctx.page({ disposition }) ; le logo vient de config.marque).
|
|
193
|
+
jetons: config.jetons ?? {}, dispositions: charge.dispositions ?? {} });
|
|
191
194
|
const sessions = magasinSessions({ dialect: d, schema: S.Session, nom: 'wagon_session', jours: 30 });
|
|
192
195
|
// 0.5 — les sessions expirées se purgent au démarrage puis toutes les six heures (auth-plug 0.3) ; les inscriptions
|
|
193
196
|
// sont freinées par adresse (config.freinInscription, défaut : 10 puis 1 par minute).
|
|
@@ -499,10 +502,13 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
|
|
|
499
502
|
const texte = v.plafond && v.limit ? v.reason : v.permanent ? t('tarifs.aucun') : v.reason;
|
|
500
503
|
return envoyer(code, page({ titre: String(code), contenu: `<h1>${code}</h1><p class="msg">${esc(texte)}</p><p><a class="bouton" href="/tarifs">${esc(t('nav.tarifs'))}</a></p>` }), en);
|
|
501
504
|
};
|
|
502
|
-
/** Le palier de la requête : celui du compte, sinon le palier anonyme.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
505
|
+
/** Le palier de la requête : celui du compte, sinon le palier anonyme. 0.2 (wagon-engine) — `{ compte }` désigne
|
|
506
|
+
* un AUTRE compte (son identifiant) : une requête sans session (l'élève qui rejoint son école) lit le palier de
|
|
507
|
+
* l'école. La charge n'en fait usage que pour un compte qu'elle a elle-même rattaché à la requête. */
|
|
508
|
+
const palier = async ({ compte: designe } = {}) => {
|
|
509
|
+
const cible = designe ? { id: String(designe) } : compte;
|
|
510
|
+
if (cible) {
|
|
511
|
+
const a = await facturation.activePlan(String(cible.id));
|
|
506
512
|
const l = a?.limits ?? {};
|
|
507
513
|
return { slug: a?.plan?.slug ?? null, nom: a?.plan?.name ?? null, limites: typeof l === 'string' ? JSON.parse(l) : l };
|
|
508
514
|
}
|
|
@@ -514,10 +520,10 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
|
|
|
514
520
|
* une unité de plus tient dans le palier. Refus toujours définitif (403) — un stock ne se recharge pas
|
|
515
521
|
* avec le temps ; il se libère (suppression, archivage) ou se relève (palier supérieur).
|
|
516
522
|
*/
|
|
517
|
-
const plafond = async (cle, actuel) => {
|
|
523
|
+
const plafond = async (cle, actuel, { compte: designe } = {}) => {
|
|
518
524
|
const r = ressources.find((x) => x.cle === cle && x.plafond);
|
|
519
525
|
if (!r) throw new Error(`ressource « ${cle} » : pas un plafond déclaré par la charge`);
|
|
520
|
-
const limite = Number((await palier()).limites?.[r.limite] ?? 0);
|
|
526
|
+
const limite = Number((await palier({ compte: designe })).limites?.[r.limite] ?? 0);
|
|
521
527
|
const n = Number(actuel) || 0;
|
|
522
528
|
if (limite === -1) return { allowed: true, plafond: true, resource: cle, current: n, limit: -1 };
|
|
523
529
|
if (!limite) return { allowed: false, permanent: true, plafond: true, resource: cle, current: n, limit: 0, reason: 'plan does not include this resource' };
|
|
@@ -540,12 +546,23 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
|
|
|
540
546
|
};
|
|
541
547
|
// 0.3.1 — `admin` : la session est celle d'un administrateur (config.admins + WAGON_ADMINS) — pour qu'une
|
|
542
548
|
// charge réserve ses outils (ex. l'outil de l'agence de travel) sans recalculer la liste.
|
|
543
|
-
|
|
549
|
+
/** 0.2 (wagon-engine) — un courriel de la charge, par le transport du wagon (SMTP du .env). Rend true s'il est parti,
|
|
550
|
+
* false s'il n'y a pas de transport ou s'il a échoué (journalisé) : jamais une exception dans la route. */
|
|
551
|
+
const courriel = async (m) => {
|
|
552
|
+
if (!transport) return false;
|
|
553
|
+
try { await transport.envoyer(m); return true; } catch (e) { journal.warn(`wagon : courriel de la charge non parti : ${e?.message ?? e}`); return false; }
|
|
554
|
+
};
|
|
555
|
+
const ctx = { chemin, methode, lang, t, ui, page, envoyer, json, rediriger, formulaire, corpsJson, compte, consommer, refuser, palier, plafond, courriel, admin: estAdmin && !compteCle };
|
|
544
556
|
if (charge.routes && (await charge.routes(req, res, ctx))) return true;
|
|
545
557
|
if (chemin.startsWith('/api/')) return json(404, { ok: false, error: 'introuvable' });
|
|
546
558
|
|
|
547
559
|
// ── Les pages du wagon ──
|
|
548
560
|
if (chemin === '/' && methode === 'GET') {
|
|
561
|
+
// 0.3 — l'accueil peut être une FONCTION de la charge : elle rend la page entière (titre, contenu, disposition).
|
|
562
|
+
if (typeof charge.accueil === 'function') {
|
|
563
|
+
const p = (await charge.accueil(ctx)) ?? {};
|
|
564
|
+
return envoyer(200, page({ actif: '/', ...p, titre: p.titre ?? config.marque.nom }));
|
|
565
|
+
}
|
|
549
566
|
const a = charge.accueil?.[lang] ?? charge.accueil?.fr ?? { titre: config.marque.nom };
|
|
550
567
|
return envoyer(200, page({ actif: '/', titre: config.marque.nom, contenu: ui.accueil({ lang, ...a }) }));
|
|
551
568
|
}
|