ConstReads a layer's offline entities back as a GeoJSON FeatureCollection.
🛑 PREMIER LECTEUR DU STORE features (tâche 4.3). Le store existe depuis 3.4 et
n'avait ni producteur ni consommateur : DBFeatures n'était référencé que par
db-modules-registry.ts. Son écrivain est arrivé en 4.1 — voir
IndexedDB.putLayerFeatures juste en dessous.
⚠️ Rend une FeatureCollection et non les enregistrements bruts, parce que
l'appelant est le chargeur de couche du kernel : il attend la même forme que celle
qu'un fetch lui aurait rendue, et la faire diverger obligerait le seam à distinguer
deux formes — la distinction même que cette lecture existe pour supprimer.
⚠️ Rend null — et non une collection VIDE — quand rien n'est stocké. Une collection
vide est indiscernable d'une couche réellement vide, et l'appelant doit pouvoir
retomber sur le réseau plutôt que d'afficher zéro entité en croyant avoir lu.
Identifiant de la couche.
La collection, ou null si la couche n'a aucune entité stockée.
Applique une édition locale — l'entité ET sa mise en file, dans UNE transaction (4.4).
Miroir d'écriture de IndexedDB.getLayerFeatureCollection : la façade délègue,
elle n'arbitre pas. La coalescence et l'annulation vivent dans db/local-edit.ts, seul
endroit où les deux stores tiennent dans une même transaction.
⚠️ Ne vérifie AUCUN droit d'édition. L'invariant S6 — le rapatriement ne confère jamais l'éditabilité — se tient une couche plus haut, là où la déclaration de la couche est lisible. Le mettre ici en ferait une règle de stockage, donc contournable par tout appelant qui parlerait à la base directement.
L'édition à appliquer.
Ce qui a été fait (fusion, annulation, entrée neuve), ou null sans module.
Writes a pulled batch into the features store — the store's FIRST writer (tâche 4.1).
Miroir de IndexedDB.getLayerFeatureCollection : la façade délègue, elle n'arbitre
pas. La règle « ne jamais écraser une saisie non synchronisée » vit dans
db/features.ts, seul endroit où lecture et écriture tiennent dans une transaction.
⚠️ Chaque enregistrement doit porter un feature défini. getLayerFeatureCollection
décide son null sur records.length === 0, avant de filtrer les feature
indéfinis : un lot écrit sans géométrie lui ferait rendre une collection vide et non
nulle, et le chargeur afficherait zéro entité en croyant avoir lu.
Enregistrements complets, feature compris.
Le décompte réel { written, preserved }, ou null si le module est absent.
Counts what the sync report needs, per layer, in one pass (tâche 4.8).
🛑 UNE méthode plutôt que quatre. La composition du rapport a besoin de trois
décomptes par couche ; les exposer un à un (countByLayer, listByState…) élargirait la
façade de quatre membres pour un seul consommateur, et déplacerait la connaissance des
magasins hors de la couche qui les possède. La façade délègue, elle n'arbitre pas — mais
ce qu'elle délègue est le décompte, pas les magasins.
⚠️ pendingCount agrège pending + inFlight + failed. C'est « ce qui est encore
dû au serveur », et failed en fait partie : la tâche 3.10 l'a explicitement remis dans
l'ensemble rejouable, précisément parce qu'une entrée en échec est une saisie de terrain
qui n'a pas d'autre copie. quarantined est compté à part — le contrat le décrit comme
« gardé, visible, mais non rejouable en l'état », donc ce n'est pas la même dette.
Couches à compter. Une couche sans entité rend des zéros, jamais rien.
Les décomptes par identifiant de couche, ou null si les modules sont absents.
Lists the edits still owed to the server, each joined to the entity it edits (4.10).
C'est ce que le panneau « POI locaux » exporte : le TRAVAIL, pas le cache. Une entrée
d'outbox ne porte pas la charge utile — elle référence [layerId, localId] —, donc la
jointure vers features se fait ici, où les deux magasins sont ouverts.
⚠️ Une entrée dont l'entité a disparu du magasin est rendue avec feature: null plutôt
qu'écartée. Une saisie qu'on ne sait plus décrire reste une saisie due au serveur ; la
taire dans un export dont c'est précisément le rôle de tout sortir serait la perdre.
Une entrée par édition en attente, la plus ancienne d'abord ; [] sans module.
Removes the entities that are pure CACHE — synchronised, and re-pullable (4.10).
🛑 CE QUI FAIT QUE LE NOM DU BOUTON DEVIENT VRAI. Depuis 4.1, le magasin features
EST le cache : ses enregistrements synced se re-rapatrient par pullLayer(). L'outbox,
elle, porte du travail de terrain qui n'existe nulle part ailleurs. Une purge qui
annonce « vider le cache » ne doit donc toucher que le premier — vocabulaire de cache,
données re-téléchargeables, et rien d'autre.
⚠️ La garde sur l'outbox est là bien que l'invariant de 4.4 la rende théoriquement
inutile : applyEdit écrit l'entité en pending en même temps que l'entrée, donc un
enregistrement synced ne devrait avoir aucune entrée en attente. « Ne devrait pas » ne
garde rien — et l'enjeu ici est une destruction irréversible de saisie.
{ removed, preserved } — preserved compte les entités épargnées parce
qu'une entrée d'outbox les réclame encore. Les deux sont affichables.
Relaie le rapport de stockage de DB.Preferences.
⚠️ Ce relais déclarait MOINS que ce que le module rend, et il le déclarait faux. Sa
forme était { used, quota, percentage, layersCount, syncQueueCount } : elle nommait un
compteur du magasin v3 — retiré à la tâche 4.11 — et omettait featuresCount et
outboxCount, que preferences.ts renseigne depuis B-121. Un appelant du chemin
dégradé recevait donc un objet dont deux champs manquaient sans que le type le dise.
Le quota, l'usage, et les décomptes des trois magasins qui portent de la donnée.
Closes the database connection
Public name of the StorageDB façade: opens the offline database, wires the
db/modules through DBModulesRegistry, and forwards the layer/sync/image/ backup/preference operations to whichever module owns each object store.This is the handle
offline-engine-entry.tspasses toGeoLeaf.Storage.wireModules().