Fiches de révision

Trois fiches recto-verso, à imprimer.

Fiche 1 : l'objet standard

Recto : le squelette

class X {
  instanceName; broker; deps;
  supports; support; verbose = false;
  emits    = [{ name: "a", version: 1 }];
  receives = [{ name: "b", version: 1 }];
  constructor(broker, instanceName, deps = {}) {
    this.broker = broker;
    this.instanceName = instanceName;
    this.deps = deps;
    this.supports = deps.support?.nodeType
      ? { main: deps.support } : (deps.support ?? {});
    this.support = this.supports.main;
    this.render();            // client seulement
    this.broker.register(this, this.emits, this.receives);
  }
  handleMessage(message) {
    if (message.b) { /* … */ this.broker.publish(this, { a: {…} }); }
  }
  destroy() {
    this.broker.unregister(this, this.emits, this.receives);
    for (const n of Object.values(this.supports)) n.replaceChildren();
  }
}

Verso : les règles

  • Signature unique, client et serveur.
  • Ne connaît que le broker. Jamais un autre composant.
  • Tout arrive par deps : support, pool, socket, réglages. Jamais process.env, jamais config.js.
  • Ressource ouverte, pas URL. On ne ferme pas ce qu'on n'a pas ouvert.
  • Abonné : pas de révision. Émetteur : la sienne, ou la courante.
  • destroy() toujours ; = null jamais.
  • publish depuis handleMessage : mis en file, livré après.
  • Composants uniques : corréler par ref, cibler par id.
  • À l'intérieur : programmation normale.

Fiche 2 : anatomie d'un contrat

Recto

nom_du_message: {
  versions: {
    1: {
      revisions: {
        0: { fields: { a: { type: "string", required: true } } },
        1: { fields: {                         // snapshot complet
          a: { type: "string", required: true },
          b: { type: "number", required: true, default: 0 },
        } },
      },
    },
    2: {
      upcastFrom: { rename: { "a": "x.a" }, defaults: { "x.c": 1 }, remove: [] },
      revisions: { 0: { fields: {
        x: { type: "object", required: true, fields: {
          a: { type: "string", required: true },
          c: { type: "number", required: true },
        } },
        b: { type: "number", required: true },
      } } },
    },
  },
  examples:        { 1: { 0: [{ a: "x" }], 1: [{ a: "x", b: 2 }] }, 2: { 0: [{ x: { a: "x", c: 1 }, b: 2 }] } },
  counterexamples: { 1: { 0: [{ reason: "a manquant", payload: {} }] } },
}

Verso

typestring · number · boolean · object · array
requiredprésent dans cette révision
patternregex, string seulement
enumvaleurs admises, primitifs
defaultcomplétion à l'upcast, jamais validation
fieldssous-champs, obligatoire sur object
itemsrègle d'élément, obligatoire sur array
oneOfexactement un parmi N ; membres optionnels sans default
upcastFromrename · defaults · remove ; chemins par .
  • Révision = ajout compatible, snapshot. Courante = la plus haute.
  • Version = cassant, rare, upcaster déclaratif. Jamais de downcast.
  • Un contrat validé ne se modifie jamais.
  • Les abonnés d'abord, l'émetteur en dernier.

Fiche 3 : trajet d'un message

Recto : local

  1. A.publish({ x: payload })
  2. Le broker vérifie l'inscription de A sur x (identité).
  3. Validation contre la révision déclarée par A. Invalide : jeté, violations en console.
  4. Forme canonique (défauts), mise en file.
  5. Drain : pour chaque abonné de x, upcast de version si besoin, copie privée, handleMessage sous try/catch.
  6. Un publish pendant 5 : mis en file, livré après.
  7. Plus de 1000 livraisons en un drain : cycle, file jetée.

Verso : à travers un canal

  1. Côté client, le seul abonné de x est le canal : il envoie la trame JSON.
  2. Côté serveur, le canal pose id et session, upcaste depuis la révision du client, consulte allowMessage.
  3. Il publie : le broker serveur valide à nouveau, livre aux composants serveur.
  4. Une réponse avec id n'atteint que ces sessions ; sans id, tous.
  5. Le canal retire id et session avant d'envoyer.
  6. Le canal client publie ; le broker client valide ; les écrans reçoivent.

Avant tout cela : handshake (compatibilité des catalogues, jeton de session), puis await canal.ready().

connection_event : connected → disconnected → resumed | expired.