GeoLeaf Core API - v3.0.0
    Preparing search index...

    Variable StorageConst

    Storage: {
        _modules: {
            db?: DBLike;
            cacheManager?: CacheManagerLike;
            cache?: unknown;
            pull?: PullLike;
            report?: ReportLike;
            edit?: EditLike;
        };
        wireModules(
            modules: {
                db?: unknown;
                cacheManager?: unknown;
                cache?: unknown;
                pull?: unknown;
                report?: unknown;
                edit?: unknown;
            },
        ): void;
        get DB(): DBLike | undefined;
        get CacheManager(): CacheManagerLike | undefined;
        get OfflineDetector(): OfflineDetectorLike | undefined;
        get Cache(): unknown;
        get db(): DBLike | undefined;
        get cacheManager(): CacheManagerLike | undefined;
        get cache(): unknown;
        init(options?: StorageInitOptions): Promise<boolean>;
        isAvailable(): boolean;
        isPluginLoaded(): boolean;
        whenReady(): Promise<void>;
        pullLayer(
            layerId: string,
            options?: {
                bbox?: [number, number, number, number];
                signal?: AbortSignal;
            },
        ): Promise<StorageLayerPullReport>;
        applyEdit(
            input: {
                layerId: string;
                kind: "delete" | "create" | "update";
                localId?: string;
                feature?: unknown;
                baseVersion?: { kind: "timestamp" | "etag"; value: string } | null;
            },
        ): Promise<StorageEditReport>;
        mayEdit(layerId: string, kind: "delete" | "create" | "update"): boolean;
        pushOutbox(): Promise<StoragePushReport>;
        requeueQuarantined(id: string): Promise<StorageQuarantineOutcome>;
        discardQuarantined(
            id: string,
            confirmedLocalId: string,
        ): Promise<StorageQuarantineOutcome>;
        getSyncReport(): Promise<readonly LayerSyncReport[]>;
        isOffline(): boolean;
        getStats(): Promise<
            {
                storage: { used: number; quota: number; percentage: number };
                layers: { count: number; byProfile: Record<string, number> };
                features: { count: number };
                outbox: { count: number };
                cache: { profiles: string[] };
                online: boolean;
            },
        >;
        clearAll(): Promise<void>;
        close(): void;
        isProfileAvailableOffline(profileId: string): Promise<boolean>;
        getOfflineProfiles(): Promise<string[]>;
    } = ...

    Type Declaration

    • _modules: {
          db?: DBLike;
          cacheManager?: CacheManagerLike;
          cache?: unknown;
          pull?: PullLike;
          report?: ReportLike;
          edit?: EditLike;
      }

      Implementation modules injected by entry.ts (composition root) at boot.

    • wireModules: function
      • Wire the assembled implementation modules into the facade. Called once by entry.ts after every sub-module has been imported and assembled.

        Parameters

        • modules: {
              db?: unknown;
              cacheManager?: unknown;
              cache?: unknown;
              pull?: unknown;
              report?: unknown;
              edit?: unknown;
          }

        Returns void

    • get DB(): DBLike | undefined

      IndexedDB layer accessor — read by StorageContract.DB.

    • get CacheManager(): CacheManagerLike | undefined

      CacheAPI cache manager accessor — read by StorageContract.CacheManager.

    • get OfflineDetector(): OfflineDetectorLike | undefined

      Network state detector resolved lazily from globalThis.GeoLeaf.

    • get Cache(): unknown

      Cache namespace ({ Storage, LayerSelector }) — read by StorageContract.Cache (download-handler, selection-cache, cache-control-zone).

    • get db(): DBLike | undefined

      Plugin Contract v1 accessor for the IndexedDB layer.

    • get cacheManager(): CacheManagerLike | undefined

      Plugin Contract v1 accessor for the CacheAPI cache manager.

    • get cache(): unknown

      Plugin Contract v1 accessor for the cache namespace ({ Storage, LayerSelector }).

    • init: function
      • Initialise every available storage sub-module (IndexedDB, CacheManager, optionally the offline detector and the Storage plugin's Service Worker).

        Parameters

        • options: StorageInitOptions = {}

          per-module options; enableOfflineDetector and enableServiceWorker are opt-in (default false).

        Returns Promise<boolean>

        true once all available modules are initialised.

        if a sub-module initialisation fails.

        Emits geoleaf:storage:initialized on success.

    • isAvailable: function
    • isPluginLoaded: function
      • 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.

        Returns boolean

    • whenReady: function
      • 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.

        Returns Promise<void>

    • pullLayer: function
      • 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.

        Parameters

        • layerId: string

          Identifiant de la couche à rapatrier.

        • Optionaloptions: { bbox?: [number, number, number, number]; signal?: AbortSignal }

          Emprise bbox et signal d'abandon.

        Returns Promise<StorageLayerPullReport>

        Le rapport de rapatriement ; refused est non nul quand rien n'a été écrit.

        const report = await GeoLeaf?.Storage?.pullLayer?.("sites_rosario");
        console.info(report?.written, report?.preserved, report?.capped);
    • applyEdit: function
      • 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.

        Parameters

        • input: {
              layerId: string;
              kind: "delete" | "create" | "update";
              localId?: string;
              feature?: unknown;
              baseVersion?: { kind: "timestamp" | "etag"; value: string } | null;
          }

          La couche, le type d'opération, l'identité locale et l'entité.

        Returns Promise<StorageEditReport>

        Le rapport : entrée créée, fusionnée, ou annulée ; refused porte le motif.

        const report = await GeoLeaf?.Storage?.applyEdit?.({
        layerId: "sites_rosario", kind: "update", localId: "loc:abc", feature
        });
        console.info(report?.queued, report?.coalescedInto);
    • mayEdit: function
      • 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.

        Parameters

        • layerId: string

          L'identifiant de couche du profil actif.

        • kind: "delete" | "create" | "update"

          L'opération soumise.

        Returns boolean

        true seulement si la couche accorde littéralement cette opération.

        if (GeoLeaf?.Storage?.mayEdit?.("reference-points", "delete")) {
        console.info("la couche accorde la suppression");
        }
    • pushOutbox: function
      • 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.

        Returns Promise<StoragePushReport>

        Le décompte du drain ; refused est non nul si le moteur n'est pas câblé.

        const report = await GeoLeaf?.Storage?.pushOutbox?.();
        console.info(report?.pushed, report?.failed);
    • requeueQuarantined: function
      • 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.

        Parameters

        • id: string

          Identifiant de contrat de l'entrée.

        Returns Promise<StorageQuarantineOutcome>

        {ok} et, en cas de refus, son motif.

        const out = await GeoLeaf?.Storage?.requeueQuarantined?.("create:sites:loc:abc:1");
        if (!out?.ok) console.warn(out?.refused);
    • discardQuarantined: function
      • 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.

        Parameters

        • id: string

          Identifiant de contrat de l'entrée.

        • confirmedLocalId: string

          Le localId de cette entrée, tel que l'appelant l'a lu.

        Returns Promise<StorageQuarantineOutcome>

        {ok} et, en cas de refus, son motif.

        const [e] = (await GeoLeaf?.Storage?.listPendingEdits?.()) ?? [];
        if (e) await GeoLeaf?.Storage?.discardQuarantined?.(e.id, e.localId);
    • getSyncReport: function
      • 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.

        Returns Promise<readonly LayerSyncReport[]>

        Un rapport par couche du profil actif ; vide quand le moteur n'est pas câblé.

        const report = await GeoLeaf?.Storage?.getSyncReport?.();
        const jamais = report?.filter((r) => r.status === "declaredNeverPulled") ?? [];
        if (jamais.length) console.warn("déclarées hors-ligne, jamais rapatriées :", jamais);
    • isOffline: function
      • Returns boolean

        true when offline — uses the OfflineDetector if present, otherwise falls back to navigator.onLine.

    • getStats: function
      • 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.

        Returns Promise<
            {
                storage: { used: number; quota: number; percentage: number };
                layers: { count: number; byProfile: Record<string, number> };
                features: { count: number };
                outbox: { count: number };
                cache: { profiles: string[] };
                online: boolean;
            },
        >

        storage quota/usage, layer counts (total + per profile), entity/outbox counts, cached profile ids and the online flag.

        Never throws — errors are logged and partial stats returned.

    • clearAll: function
      • Wipe every cached profile plus the sync_queue, preferences and metadata IndexedDB stores.

        Returns Promise<void>

        if the IndexedDB transaction fails.

        Emits geoleaf:storage:cleared on success.

    • close: function
    • isProfileAvailableOffline: function
      • Parameters

        • profileId: string

          the profile to check.

        Returns Promise<boolean>

        true if the profile is fully cached for offline use.

    • getOfflineProfiles: function
      • Returns Promise<string[]>

        the ids of every profile currently available offline.