discovery-media-player 0.1.57 → 0.1.59
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/docs/HOST-CONTRACT.md +38 -1
- package/package.json +1 -1
- package/server/handler.js +15 -4
- package/server/presentations.js +3 -8
- package/server/schema.js +148 -2
package/docs/HOST-CONTRACT.md
CHANGED
|
@@ -31,7 +31,8 @@ need.
|
|
|
31
31
|
"separateIssuer": true,
|
|
32
32
|
"hostShare": true,
|
|
33
33
|
"hostMail": true,
|
|
34
|
-
"plugins": { "bot": false, "visitors": false, "brandIntro": false, "botBrowser": false, "providerQuotas": false }
|
|
34
|
+
"plugins": { "bot": false, "visitors": false, "brandIntro": false, "botBrowser": false, "providerQuotas": false },
|
|
35
|
+
"schema": { "attendues": 3, "sondees": 1, "verdict": "partiel", "manquant": [] }
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
@@ -39,6 +40,42 @@ need.
|
|
|
39
40
|
never by order. `plugins` lets you refuse to start when you depend on an optional module this
|
|
40
41
|
instance does not have.
|
|
41
42
|
|
|
43
|
+
⚠️ **`schema` tells you which migrations this instance is still waiting for.** The player never
|
|
44
|
+
applies migrations — it cannot, it only speaks PostgREST — so it *detects* instead, and a missing
|
|
45
|
+
column makes the feature that needs it **degrade silently**, by design, so as not to break a host
|
|
46
|
+
mid-migration. That silence is the point: an operator whose write ordering and message idempotency
|
|
47
|
+
are both switched off sees an instance that looks perfectly healthy. Each entry names the file to
|
|
48
|
+
apply and the feature that is asleep.
|
|
49
|
+
|
|
50
|
+
⚠️ **`manquant: []` has four meanings, so read `verdict`, not the array.** The card **reports**
|
|
51
|
+
what this process has already asked; it never probes on its own, because a diagnostic must answer
|
|
52
|
+
when the database does not — and on a serverless host that means it is usually empty. Ask a
|
|
53
|
+
different question when you want a real answer:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
GET /api/doc?contract=1&schema=1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That parameter **is** the one part of this card that needs the database, and only when you ask for
|
|
60
|
+
it. `verdict` is then one of:
|
|
61
|
+
|
|
62
|
+
| verdict | meaning |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `non-sonde` | nothing asked yet — **not** *nothing missing* |
|
|
65
|
+
| `partiel` | some expectations checked, none of them missing |
|
|
66
|
+
| `complet` | all checked, all present |
|
|
67
|
+
| `incomplet` | at least one is missing — `manquant` names the file and the sleeping feature |
|
|
68
|
+
| `indetermine` | the database did not answer; this measurement did not happen |
|
|
69
|
+
|
|
70
|
+
⚠️ **`incomplet` wins over `partiel`**: a missing column is a positive fact and settles the verdict
|
|
71
|
+
on its own, even when the rest has not been checked.
|
|
72
|
+
|
|
73
|
+
⚠️ **`indetermine` is not a failure of the card.** A control column — the primary key of the oldest
|
|
74
|
+
table — is queried first. If *it* does not answer, nothing is missing: the database is. Without that
|
|
75
|
+
control, an unreachable database would make all three probes fail and the card would announce three
|
|
76
|
+
missing migrations **that exist**, sending you to apply what you already have. In that state nothing
|
|
77
|
+
is cached either, so a passing outage does not switch features off for the life of the process.
|
|
78
|
+
|
|
42
79
|
⚠️ **`frameAncestors` matters more than it looks.** A host that is not listed will never see the
|
|
43
80
|
viewer: the browser blocks the iframe **before any script runs**, so no message can be emitted and
|
|
44
81
|
the host sees a silence indistinguishable from an unreachable instance. Check that your domain is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
4
4
|
"description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pdf-viewer",
|
package/server/handler.js
CHANGED
|
@@ -3476,10 +3476,7 @@ async function handler(req, res) {
|
|
|
3476
3476
|
// capable d'expédier en son nom : le repli silencieux ouvrirait exactement la porte que
|
|
3477
3477
|
// la séparation ferme. On nomme le fichier à appliquer et on s'arrête.
|
|
3478
3478
|
if (atteste) {
|
|
3479
|
-
const pret = await require("./schema").
|
|
3480
|
-
"commercial_doc_shares", "attested_recipient_email",
|
|
3481
|
-
"supabase/migrations/0001-destinataire-atteste.sql",
|
|
3482
|
-
);
|
|
3479
|
+
const pret = await require("./schema").attendue("destinataireAtteste");
|
|
3483
3480
|
if (!pret) {
|
|
3484
3481
|
return jd(409, { ok: false, error: "migration", message: "Destinataire attesté indisponible : appliquez supabase/migrations/0001-destinataire-atteste.sql." });
|
|
3485
3482
|
}
|
|
@@ -3793,6 +3790,10 @@ async function handler(req, res) {
|
|
|
3793
3790
|
// en booléens parce qu'un hôte doit pouvoir refuser de démarrer si le mur d'accès manque
|
|
3794
3791
|
// alors qu'il compte dessus.
|
|
3795
3792
|
if (String(q.contract || "") === "1") {
|
|
3793
|
+
// ⚠️ `&schema=1` : la SEULE partie de cette carte qui demande la base, et seulement quand on
|
|
3794
|
+
// la réclame. Sans le paramètre, la route garde sa propriété de répondre quand plus rien ne
|
|
3795
|
+
// répond ; avec lui, l'appelant choisit d'en avoir besoin. Un échec ici ne fait pas échouer
|
|
3796
|
+
// la carte — il devient le verdict « indetermine ».
|
|
3796
3797
|
res.statusCode = 200;
|
|
3797
3798
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3798
3799
|
res.setHeader("Cache-Control", "no-store, max-age=0");
|
|
@@ -3824,6 +3825,16 @@ async function handler(req, res) {
|
|
|
3824
3825
|
// l'hôte connaît déjà son émetteur — il veut seulement savoir si l'instance le regarde.
|
|
3825
3826
|
// Dire lequel n'aiderait personne et renseignerait qui sonde.
|
|
3826
3827
|
separateIssuer: !!(PLAYER.config && PLAYER.config.separateIssuer),
|
|
3828
|
+
// ⚠️ L'ÉTAT DU SCHÉMA, LÀ OÙ ON REGARDE. Une colonne absente était signalée par un
|
|
3829
|
+
// `console.warn`, une fois par processus : sur une fonction serverless, une ligne perdue
|
|
3830
|
+
// dans une sortie que personne n'ouvre tant que tout a l'air de marcher — et « tout a
|
|
3831
|
+
// l'air de marcher » est exactement l'état d'un hôte dont trois protections dorment. Cette
|
|
3832
|
+
// carte est ce qu'un hôte interroge déjà pour épingler sa version ; c'est donc ici.
|
|
3833
|
+
// Elle RAPPORTE ce qui est connu, elle ne sonde pas : un diagnostic ne doit pas tomber en
|
|
3834
|
+
// même temps que ce qu'il diagnostique.
|
|
3835
|
+
schema: String(q.schema || "") === "1"
|
|
3836
|
+
? await require("./schema").sonderTout()
|
|
3837
|
+
: require("./schema").etatDuSchema(),
|
|
3827
3838
|
// « L'hôte peut-il créer un lien en son nom propre ? » — configuré, pas seulement
|
|
3828
3839
|
// possible. Un hôte qui oublie le secret reçoit un 401 qui ressemble à un droit
|
|
3829
3840
|
// manquant ; ce booléen le lui dit sans qu'il ait à essayer.
|
package/server/presentations.js
CHANGED
|
@@ -54,9 +54,7 @@ async function reclaimPresentation(slug, email) {
|
|
|
54
54
|
// ⚠️ Et j'ai écrit ce champ sans condition en premier jet, ce qui est exactement le piège que
|
|
55
55
|
// docs/MIGRATIONS.md décrit : PostgREST rejette le PATCH ENTIER si la colonne manque. La reprise
|
|
56
56
|
// aurait cessé de fonctionner chez tout hôte non migré — pas la nouvelle garantie, la reprise.
|
|
57
|
-
const rangDispo = await require("./schema").
|
|
58
|
-
"doc_presentations", "write_seq", "supabase/migrations/0002-ordre-des-ecritures.sql",
|
|
59
|
-
);
|
|
57
|
+
const rangDispo = await require("./schema").attendue("rangEcriture");
|
|
60
58
|
await PLAYER.db.request(`doc_presentations?slug=eq.${enc(slug)}`, { method: "PATCH", headers: { Prefer: "return=minimal" }, body: { control_hash: sha(control), active: true, ...(rangDispo ? { write_seq: 0 } : {}), last_seen: new Date().toISOString(), updated_at: new Date().toISOString() } });
|
|
61
59
|
return { ok: true, slug, control, page: row.current_page || 1, fileUrl: row.file_url, fileName: row.file_name, docTitle: row.doc_title, docId: row.doc_id };
|
|
62
60
|
}
|
|
@@ -200,9 +198,7 @@ async function getPresentation(slug) {
|
|
|
200
198
|
async function rangAccepte(row, seq) {
|
|
201
199
|
const rang = Number(seq);
|
|
202
200
|
if (!Number.isFinite(rang) || rang <= 0) return { controle: false }; // client plus ancien : pas de rang
|
|
203
|
-
const dispo = await require("./schema").
|
|
204
|
-
"doc_presentations", "write_seq", "supabase/migrations/0002-ordre-des-ecritures.sql",
|
|
205
|
-
);
|
|
201
|
+
const dispo = await require("./schema").attendue("rangEcriture");
|
|
206
202
|
if (!dispo) return { controle: false };
|
|
207
203
|
if (rang <= Number(row.write_seq || 0)) return { controle: true, perime: true };
|
|
208
204
|
// ⚠️ ON REND LE RANG, PAS UN OBJET TOUT FAIT. La première version rendait « { write_seq: rang } »,
|
|
@@ -428,8 +424,7 @@ async function addMessage(slug, { name, email, avatar, isPresenter, isMember, bo
|
|
|
428
424
|
// colonne inconnue : chez un hôte non migré, ce n'est pas l'idempotence qu'on perdrait, c'est
|
|
429
425
|
// l'envoi de messages. Même piège que le rang d'écriture, même sonde.
|
|
430
426
|
const cle = String(clientKey || "").slice(0, 80);
|
|
431
|
-
if (cle && await require("./schema").
|
|
432
|
-
"doc_presentation_messages", "client_key", "supabase/migrations/0005-envoi-unique.sql")) {
|
|
427
|
+
if (cle && await require("./schema").attendue("envoiUnique")) {
|
|
433
428
|
row.client_key = cle;
|
|
434
429
|
}
|
|
435
430
|
|
package/server/schema.js
CHANGED
|
@@ -16,6 +16,49 @@
|
|
|
16
16
|
// chantiers ont été repoussés pour cette seule raison. Avec cette sonde, l'ordre de déploiement
|
|
17
17
|
// cesse d'être un piège.
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* CE QUE CE CODE ATTEND DE LA BASE — DÉCLARÉ UNE FOIS, ET C'EST LA SOURCE.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ UN INVENTAIRE QUI N'EST PAS LA SOURCE DÉRIVE. Ces couples vivaient recopiés sur quatre
|
|
23
|
+
* appels ; en tirer une simple liste « pour l'affichage » aurait refait, en plus petit, le défaut
|
|
24
|
+
* qui a vidé supabase/init.sql de ses cinq migrations : deux exemplaires du même fait, personne
|
|
25
|
+
* pour les confronter. Les appelants passent donc par `attendue(nom)` et ne nomment plus de
|
|
26
|
+
* colonne — il n'existe plus qu'un endroit où se tromper. Une étape de la forge vérifie en outre
|
|
27
|
+
* que chaque fichier nommé ici existe, et qu'aucun appel ne contourne cette table.
|
|
28
|
+
*/
|
|
29
|
+
const ATTENDUES = {
|
|
30
|
+
destinataireAtteste: {
|
|
31
|
+
table: "commercial_doc_shares", colonne: "attested_recipient_email",
|
|
32
|
+
migration: "supabase/migrations/0001-destinataire-atteste.sql",
|
|
33
|
+
fonction: "attribuer une lecture au destinataire attesté par l'hôte",
|
|
34
|
+
},
|
|
35
|
+
rangEcriture: {
|
|
36
|
+
table: "doc_presentations", colonne: "write_seq",
|
|
37
|
+
migration: "supabase/migrations/0002-ordre-des-ecritures.sql",
|
|
38
|
+
fonction: "refuser une écriture de pilotage doublée en vol",
|
|
39
|
+
},
|
|
40
|
+
envoiUnique: {
|
|
41
|
+
table: "doc_presentation_messages", colonne: "client_key",
|
|
42
|
+
migration: "supabase/migrations/0005-envoi-unique.sql",
|
|
43
|
+
fonction: "empêcher qu'un renvoi crée un second message",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* LE TÉMOIN — une colonne dont l'absence est impossible.
|
|
49
|
+
*
|
|
50
|
+
* ⚠️ IL DISTINGUE « ABSENTE » DE « INJOIGNABLE » SANS LIRE UN MESSAGE D'ERREUR. La sonde ne fait
|
|
51
|
+
* pas cette différence, et c'est juste pour DÉCIDER (les deux mènent à ne pas écrire le champ).
|
|
52
|
+
* Pour RAPPORTER, les confondre serait faux dans les deux sens : une base momentanément muette
|
|
53
|
+
* ferait annoncer trois migrations manquantes qui existent — une fausse alerte qui envoie
|
|
54
|
+
* l'exploitant appliquer ce qu'il a déjà.
|
|
55
|
+
*
|
|
56
|
+
* Le témoin est la clé primaire de la plus ancienne table : si LUI ne répond pas, ce n'est pas une
|
|
57
|
+
* migration qui manque, c'est la base. Mesure différentielle, aucune dépendance au texte d'un
|
|
58
|
+
* service tiers — la raison même pour laquelle la sonde refusait de distinguer.
|
|
59
|
+
*/
|
|
60
|
+
const TEMOIN = { table: "doc_presentations", colonne: "slug" };
|
|
61
|
+
|
|
19
62
|
let PLAYER = null;
|
|
20
63
|
/**
|
|
21
64
|
* Une question posée une fois, retenue pour le processus.
|
|
@@ -28,9 +71,65 @@ let PLAYER = null;
|
|
|
28
71
|
*/
|
|
29
72
|
const connues = new Map();
|
|
30
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Les réponses DÉJÀ OBTENUES, pour qui veut les lire — la carte d'identité, essentiellement.
|
|
76
|
+
*
|
|
77
|
+
* ⚠️ CE N'EST PAS UN DOUBLON DE `connues`. Celle-ci porte des promesses, dont on ne peut rien dire
|
|
78
|
+
* sans les attendre ; celle-là porte des réponses. La distinction compte parce que la carte
|
|
79
|
+
* d'identité ne doit RIEN demander à la base — elle doit répondre quand la base ne répond plus.
|
|
80
|
+
*/
|
|
81
|
+
const reponses = new Map();
|
|
82
|
+
|
|
31
83
|
function init(ctx) {
|
|
32
84
|
PLAYER = ctx;
|
|
33
85
|
connues.clear();
|
|
86
|
+
reponses.clear();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** La sonde d'une attente déclarée. C'est la seule forme d'appel que les appelants utilisent. */
|
|
90
|
+
function attendue(nom) {
|
|
91
|
+
const a = ATTENDUES[nom];
|
|
92
|
+
// Un nom inconnu est une faute de frappe, pas une dégradation : la taire ferait passer la
|
|
93
|
+
// fonction pour « en attente de migration » alors qu'elle est simplement mal câblée.
|
|
94
|
+
if (!a) throw new Error(`attente de schéma inconnue : ${nom}`);
|
|
95
|
+
return aLaColonne(a.table, a.colonne, a.migration);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ⚠️ CE QUE LE JOURNAL NE DIRA JAMAIS À PERSONNE.
|
|
100
|
+
*
|
|
101
|
+
* La sonde signale une colonne absente par un `console.warn`, une fois par processus. Sur une
|
|
102
|
+
* fonction serverless, c'est une ligne perdue dans une sortie que personne n'ouvre quand tout a
|
|
103
|
+
* l'air de marcher — et « tout a l'air de marcher » est précisément l'état d'un hôte dont trois
|
|
104
|
+
* protections dorment. Remarque du second hôte, et elle est juste : la trace existait à l'endroit
|
|
105
|
+
* exact où on ne regarde pas.
|
|
106
|
+
*
|
|
107
|
+
* ⚠️ ON NE SONDE PAS ICI, ON RAPPORTE. La carte d'identité doit répondre quand la base ne répond
|
|
108
|
+
* plus ; sonder depuis elle en ferait un diagnostic qui tombe en même temps que ce qu'il diagnostique.
|
|
109
|
+
*
|
|
110
|
+
* ⚠️ D'OÙ TROIS ÉTATS, ET PAS DEUX. Un processus qui n'a encore rien demandé ne sait rien — et
|
|
111
|
+
* « rien de manquant » se lirait « tout va bien ». Une absence de résultat ressemble à un
|
|
112
|
+
* résultat ; `sondees` est là pour qu'on ne puisse pas les confondre.
|
|
113
|
+
*/
|
|
114
|
+
function etatDuSchema() {
|
|
115
|
+
const manquant = [];
|
|
116
|
+
for (const [, r] of reponses) if (!r.present) manquant.push({ migration: r.migration, fonction: r.fonction });
|
|
117
|
+
const attendues = Object.keys(ATTENDUES).length;
|
|
118
|
+
return {
|
|
119
|
+
attendues,
|
|
120
|
+
sondees: reponses.size,
|
|
121
|
+
// ⚠️ UN MOT, PAS UN TABLEAU VIDE À INTERPRÉTER. `manquant: []` a quatre sens selon ce qu'on
|
|
122
|
+
// sait par ailleurs — rien demandé, tout vérifié, vérifié en partie, base muette — et forcer
|
|
123
|
+
// le lecteur à les reconstituer en croisant deux champs, c'est lui laisser la faute. Le second
|
|
124
|
+
// hôte l'a posé comme condition à ce paramètre, et il avait raison avant même de le voir.
|
|
125
|
+
verdict: verdict(manquant.length, reponses.size, attendues),
|
|
126
|
+
// ⚠️ ON NOMME LE FICHIER, alors que cette route est publique. Même raison que `frameAncestors`
|
|
127
|
+
// juste au-dessus d'elle : l'exploitant n'a AUCUN autre moyen d'apprendre laquelle manque, et
|
|
128
|
+
// un compte nu le laisserait deviner. Ce qu'on révèle en échange — qu'une fonction de
|
|
129
|
+
// fiabilité est en attente, dans un dépôt dont les migrations sont publiques — n'ouvre aucun
|
|
130
|
+
// accès : il faut déjà détenir un jeton de pilotage pour tirer parti d'un rang absent.
|
|
131
|
+
manquant,
|
|
132
|
+
};
|
|
34
133
|
}
|
|
35
134
|
|
|
36
135
|
/**
|
|
@@ -67,6 +166,7 @@ async function sonder(table, colonne, migration, cle) {
|
|
|
67
166
|
// portage a un fichier à réécrire, pas une habitude à retrouver partout.
|
|
68
167
|
const champ = encodeURIComponent(colonne);
|
|
69
168
|
await PLAYER.db.request(`${table}?select=${champ}&limit=0`);
|
|
169
|
+
noter(cle, true, migration);
|
|
70
170
|
return true;
|
|
71
171
|
} catch {
|
|
72
172
|
// ⚠️ ON NE DISTINGUE PAS « COLONNE ABSENTE » DE « BASE INJOIGNABLE », ET C'EST VOULU. Les deux
|
|
@@ -74,11 +174,57 @@ async function sonder(table, colonne, migration, cle) {
|
|
|
74
174
|
// message d'erreur, c'est-à-dire de dépendre du texte d'un service tiers. Ce qui change entre
|
|
75
175
|
// les deux, c'est la durée : une base injoignable le redevient, et le processus suivant reposera
|
|
76
176
|
// la question.
|
|
177
|
+
noter(cle, false, migration);
|
|
77
178
|
signaler(cle, migration);
|
|
78
179
|
return false;
|
|
79
180
|
}
|
|
80
181
|
}
|
|
81
182
|
|
|
183
|
+
function verdict(manque, sondees, attendues) {
|
|
184
|
+
if (manque) return "incomplet"; // un manque est un fait positif : il tranche seul
|
|
185
|
+
if (!sondees) return "non-sonde";
|
|
186
|
+
return sondees < attendues ? "partiel" : "complet";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* SONDER TOUT, À LA DEMANDE — `?contract=1&schema=1`.
|
|
191
|
+
*
|
|
192
|
+
* ⚠️ LE COÛT EST SUR L'APPELANT QUI VEUT LA RÉPONSE. Sonder au démarrage mettrait un aller-retour
|
|
193
|
+
* base sur chaque démarrage à froid, donc sur le chemin critique de la première vraie requête,
|
|
194
|
+
* pour un diagnostic que presque personne ne lit — et ferait dépendre le contenu de la carte de ce
|
|
195
|
+
* que la base a répondu, c'est-à-dire déplacerait le couplage que sa doctrine interdit au lieu de
|
|
196
|
+
* le supprimer. Arbitrage tranché avec le second hôte, sur ses trois raisons.
|
|
197
|
+
*
|
|
198
|
+
* ⚠️ ET UN DIAGNOSTIC NE DOIT PAS ÉTEINDRE CE QU'IL DIAGNOSTIQUE. `aLaColonne` retient sa réponse
|
|
199
|
+
* pour la vie du processus : appelé pendant un hoquet de la base, ce paramètre aurait mis en cache
|
|
200
|
+
* « absente » pour les trois attentes — désactivant l'ordre des écritures et l'idempotence des
|
|
201
|
+
* messages jusqu'au prochain démarrage. Une route de contrôle qui casse la production. Si le
|
|
202
|
+
* témoin ne répond pas, on ne sonde RIEN et on ne retient RIEN.
|
|
203
|
+
*/
|
|
204
|
+
async function sonderTout() {
|
|
205
|
+
// ⚠️ LA PART ENCODÉE EST CALCULÉE À PART, comme dans `sonder()` dix lignes plus haut, et pour la
|
|
206
|
+
// même raison : la garde de portabilité traque une parenthèse après « select= », et un appel de
|
|
207
|
+
// fonction écrit dans le gabarit en produit une. J'ai reproduit ici le défaut dont le correctif
|
|
208
|
+
// était commenté juste au-dessus — la forme exacte de la migration 0004, où dix lignes
|
|
209
|
+
// expliquaient la prudence qu'un revoke six lignes plus haut n'appliquait pas.
|
|
210
|
+
const champTemoin = encodeURIComponent(TEMOIN.colonne);
|
|
211
|
+
try {
|
|
212
|
+
await PLAYER.db.request(`${TEMOIN.table}?select=${champTemoin}&limit=0`);
|
|
213
|
+
} catch {
|
|
214
|
+
// On rend ce qu'on savait déjà — un manque constaté plus tôt reste un fait — mais le verdict
|
|
215
|
+
// dit que cette mesure-ci n'a pas eu lieu. Taire l'un ou l'autre serait mentir d'un côté.
|
|
216
|
+
return { ...etatDuSchema(), verdict: "indetermine" };
|
|
217
|
+
}
|
|
218
|
+
for (const nom of Object.keys(ATTENDUES)) await attendue(nom);
|
|
219
|
+
return etatDuSchema();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** La réponse, retenue pour qui la demandera — sans repasser par la base. */
|
|
223
|
+
function noter(cle, present, migration) {
|
|
224
|
+
const a = Object.values(ATTENDUES).find((x) => `${x.table}.${x.colonne}` === cle);
|
|
225
|
+
reponses.set(cle, { present, migration, fonction: a ? a.fonction : "" });
|
|
226
|
+
}
|
|
227
|
+
|
|
82
228
|
/**
|
|
83
229
|
* ⚠️ ON NOMME LE FICHIER, PAS L'ERREUR. « column does not exist » envoie l'exploitant lire du
|
|
84
230
|
* PostgREST ; « appliquez supabase/migrations/0001-…sql » lui dit quoi faire. La différence entre
|
|
@@ -92,6 +238,6 @@ function signaler(cle, migration) {
|
|
|
92
238
|
}
|
|
93
239
|
|
|
94
240
|
/** Pour les tests et l'exploitation : reposer la question. */
|
|
95
|
-
function oublier() { connues.clear(); }
|
|
241
|
+
function oublier() { connues.clear(); reponses.clear(); }
|
|
96
242
|
|
|
97
|
-
module.exports = { init, aLaColonne, oublier };
|
|
243
|
+
module.exports = { init, aLaColonne, attendue, etatDuSchema, sonderTout, oublier, ATTENDUES, TEMOIN };
|