ConstWire the assembled implementation modules into the facade. Called once by
entry.ts after every sub-module has been imported and assembled.
IndexedDB layer accessor — read by StorageContract.DB.
CacheAPI cache manager accessor — read by StorageContract.CacheManager.
Network state detector resolved lazily from globalThis.GeoLeaf.
Cache namespace ({ Storage, LayerSelector }) — read by StorageContract.Cache
(download-handler, selection-cache, cache-control-zone).
Plugin Contract v1 accessor for the IndexedDB layer.
Plugin Contract v1 accessor for the CacheAPI cache manager.
Plugin Contract v1 accessor for the cache namespace ({ Storage, LayerSelector }).
Initialise every available storage sub-module (IndexedDB, CacheManager, optionally the offline detector and the Storage plugin's Service Worker).
per-module options; enableOfflineDetector and
enableServiceWorker are opt-in (default false).
true once all available modules are initialised.
true when the IndexedDB connection is open (plugin ready).
true dès que le moteur hors-ligne s'est enregistré — indépendamment de l'ouverture
d'IndexedDB, que teste isAvailable.
⚠️ API publique S4.4 — délégation ajoutée pour que @geoleaf-plugins/offline-ui cesse
d'importer StorageContract. Le contrat est un SINGLETON dont l'état est un let de
portée module : un plugin chargé en <script type="module"> a son propre graphe et ne
peut pas le partager. La copie qu'il embarquait n'était jamais initialisée — ce membre
lui donne la vraie, par le namespace.
Résout quand le moteur hors-ligne est prêt à piloter.
⚠️ Ne résout JAMAIS tant que modules.offline est désactivé — le moteur ne se charge
pas, et les actions d'UI qui l'attendent défèrent indéfiniment. C'est le comportement du
contrat, repris tel quel. Même motif de délégation que isPluginLoaded.
Pulls a declared layer's entities into the local features store (tâche 4.1).
Bounded by the layer's offline.maxFeatures and, optionally, by a bounding box.
Downloading NEVER grants write access: the records land as synced and no queue entry
is created (invariant S6 of the sync contract).
⏱ Attend le moteur, mais pas indéfiniment. L'implémentation vit dans le chunk
offline, chargé en import() après le boot — l'appeler à la première frame la
trouverait absente. La course est bornée à PULL_ENGINE_WAIT_MS parce que
StorageContract.whenReady() ne résout jamais quand modules.offline est
désactivé : sans borne, l'appel pendrait pour toujours sur une variante sans moteur.
⚠️ Contrairement à la lecture de couche, il n'y a aucun repli réseau ici. Un
rapatriement sans moteur doit se DIRE — refused: "engineUnavailable" — et non rendre
un zéro que rien ne distingue d'une couche vide.
Identifiant de la couche à rapatrier.
Optionaloptions: { bbox?: [number, number, number, number]; signal?: AbortSignal }Emprise bbox et signal d'abandon.
Le rapport de rapatriement ; refused est non nul quand rien n'a été écrit.
Applies an edit locally AND queues it for the server — the optimistic write (tâche 4.4).
L'entité part dans le store features et l'opération dans l'outbox, dans une seule
transaction : une saisie de terrain n'a pas d'autre copie, et une écriture à moitié
faite serait indétectable après coup.
⚠️ Rien de ce qui est écrit ici ne confère l'éditabilité (invariant S6) — au contraire, l'appel est REFUSÉ, avec son motif, quand la couche n'est pas modifiable en ligne. Télécharger une couche n'a jamais rendu une couche modifiable.
Attente bornée du moteur, même motif que Storage.pullLayer : le chunk offline
est différé, et whenReady() ne résout jamais sans modules.offline.
La couche, le type d'opération, l'identité locale et l'entité.
Le rapport : entrée créée, fusionnée, ou annulée ; refused porte le motif.
La couche accorde-t-elle cette opération ? — à consulter AVANT d'écrire.
🛑 C'est la réponse à B-138, et elle est SYNCHRONE ET SANS MOTEUR, délibérément.
La permission se lit dans le profil actif, pas dans IndexedDB : la router par le sac
edit (comme Storage.applyEdit) l'aurait rendue indisponible quand le chunk
hors-ligne n'est pas chargé — c'est-à-dire exactement dans le cas qui portait le
trou, @geoleaf-plugins/editor déclarant requires: [] et tournant en
persistence.mode: "online" sans ce moteur.
⚠️ Ne remplace pas la garde d'applyEdit, elle la précède. applyEdit continue de
refuser pour son propre compte : un appelant qui ne consulterait pas ce prédicat ne
contourne rien. Les deux appliquent grantsEdition, la même fonction.
⚠️ Une couche inconnue rend false — refuser l'inconnu, sans quoi une faute de frappe
dans un identifiant vaudrait autorisation.
L'identifiant de couche du profil actif.
L'opération soumise.
true seulement si la couche accorde littéralement cette opération.
Drains the outbox: pushes every queued edit and reconciles server identities (4.5).
⚠️ Tourne dans la PAGE, jamais dans le Service Worker — point 5 du contrat. Le
patch fetch du connector vit dans la page ; un rejeu depuis le worker ne le voit pas
et partirait sans jeton. C'est le motif qui a fait supprimer le chemin Background Sync.
Ne jette pas : une entrée qui échoue reste en file, failed n'étant pas terminal.
Le décompte du drain ; refused est non nul si le moteur n'est pas câblé.
Remet en file une entrée mise en quarantaine, quand sa cause est LEVÉE.
Réservée aux motifs dont la cause peut être constatée levée — retryBudgetExhausted,
layerNoLongerWritable et notImplementedByServer. Les deux autres nomment un fait du
serveur qu'aucun geste local ne défait : leur sortie est
Storage.discardQuarantined.
Identifiant de contrat de l'entrée.
{ok} et, en cas de refus, son motif.
Détruit une entrée en quarantaine, sur confirmation EXPLICITE.
🛑 confirmedLocalId doit être le localId de l'entrée — une valeur que l'appelant ne
connaît qu'en l'ayant LISTÉE. C'est ce qui rend structurellement vrai le fait que la
saisie a été énumérée avant d'être jetée, ce qu'exige ServerDeletionPolicy depuis son
amendement de la tâche 8.4 : ce que le contrat interdit est la perte que l'opérateur
n'a pas VUE.
Identifiant de contrat de l'entrée.
Le localId de cette entrée, tel que l'appelant l'a lu.
{ok} et, en cas de refus, son motif.
Rapporte, couche par couche, ce que le hors-ligne a réellement sous la main (tâche 4.8).
🛑 Le cas qu'il existe pour rendre visible : une couche déclarée hors-ligne mais
jamais rapatriée. Le contrat le décrit comme « le cas qui n'a aucun observable jusqu'à la
coupure » — tout marche, jusqu'au terrain. Il ressort ici en declaredNeverPulled.
Ne jette jamais. Sans moteur câblé, rend un tableau vide plutôt qu'un rapport rassurant : un rapport qui dirait « tout va bien » sans avoir rien lu serait pire que pas de rapport, puisqu'il se croirait complet.
Un rapport par couche du profil actif ; vide quand le moteur n'est pas câblé.
true when offline — uses the OfflineDetector if present,
otherwise falls back to navigator.onLine.
Aggregate storage usage across IndexedDB and the cache manager.
⚠️ features et outbox sont comptés depuis B-121 (tâche 4.8). Cette méthode ne
lisait que layersCount et syncQueueCount — les deux magasins v3 —, donc après un
rapatriement de 27 entités elle rapportait toujours 0, et offline-ui affichait ce zéro.
Ce n'était pas observable tant que features n'avait aucun écrivain : le zéro était
VRAI. Il est devenu faux le jour où 4.1 a atterri.
🛑 Le bloc sync: { pending, failed } est RETIRÉ (tâche 4.11). Sa seule source
était syncQueueCount, c'est-à-dire le magasin v3 que plus personne n'écrivait depuis
4.4b : il rapportait 0 en toutes circonstances, et failed n'était jamais assigné
du tout. Le décompte réel des écritures dues est outbox.count ci-dessus, et la
ventilation par état est StorageDB.getSyncCounts, qui rend pendingCount et
quarantinedCount par couche.
storage quota/usage, layer counts (total + per profile), entity/outbox counts, cached profile ids and the online flag.
Close the IndexedDB connection and tear down the offline detector.
the profile to check.
true if the profile is fully cached for offline use.
the ids of every profile currently available offline.
Implementation modules injected by
entry.ts(composition root) at boot.