LCOV - code coverage report
Current view: top level - dashql-app/src/storage/migration - storage_root.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 48 48
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import { exists } from '../../platform/electron_fs.js';
       2              : import { type MigrationFolder, NativeFolder, OPFSFolder } from './migration_folder.js';
       3              : 
       4              : /// The manifest of the storage root, it holds the storage version
       5            1 : export const MANIFEST_FILE = 'dashql-manifest.json';
       6              : 
       7              : /// A manifest as it is stored, of any storage version
       8              : export type ManifestJson = Record<string, unknown>;
       9              : 
      10              : /// A session folder that a manifest registers
      11              : export interface RegisteredSessionFolder {
      12              :     /// The session id, the path of the manifest entry
      13              :     sessionId: string;
      14              :     /// The top-level files of the session
      15              :     folder: MigrationFolder;
      16              : }
      17              : 
      18              : /// Read the manifest, null if the storage has none
      19            1 : export async function readManifestJson(root: FileSystemDirectoryHandle): Promise<ManifestJson | null> {
      20            1 :     let text: string;
      21            1 :     try {
      22            1 :         text = await (await (await root.getFileHandle(MANIFEST_FILE, { create: false })).getFile()).text();
      23            1 :     } catch (error) {
      24            1 :         if ((error as DOMException).name === 'NotFoundError') {
      25            1 :             return null;
      26              :         }
      27            1 :         throw error;
      28            1 :     }
      29            1 :     const manifest: unknown = JSON.parse(text);
      30            1 :     if (manifest == null || typeof manifest !== 'object' || Array.isArray(manifest)) {
      31            1 :         throw new Error(`Invalid ${MANIFEST_FILE}: not a JSON object`);
      32              :     }
      33            1 :     return manifest as ManifestJson;
      34            1 : }
      35              : 
      36              : /// Write the manifest
      37            1 : export async function writeManifestJson(root: FileSystemDirectoryHandle, manifest: ManifestJson): Promise<void> {
      38            1 :     const writable = await (await root.getFileHandle(MANIFEST_FILE, { create: true })).createWritable();
      39            1 :     await writable.write(JSON.stringify(manifest, null, 2));
      40            1 :     await writable.close();
      41            1 : }
      42              : 
      43              : /// The session entries of a manifest of storage version 2 to 6, later versions register the sessions
      44              : /// in the session tree
      45            1 : export function manifestSessionEntries(manifest: ManifestJson): unknown[] {
      46            1 :     return Array.isArray(manifest.sessions) ? manifest.sessions as unknown[] : [];
      47            1 : }
      48              : 
      49              : /// The session folders of session entries that can be reached: OPFS sessions live in `sessions/<uuid>`,
      50              : /// native sessions in their own directory, which may be missing or unavailable outside of the native app.
      51            1 : export async function listSessionFolders(root: FileSystemDirectoryHandle, entries: readonly unknown[]): Promise<RegisteredSessionFolder[]> {
      52            1 :     const folders: RegisteredSessionFolder[] = [];
      53            1 :     for (const entry of entries) {
      54            1 :         const { path, storageType, nativePath } = (entry ?? {}) as { path?: unknown; storageType?: unknown; nativePath?: unknown };
      55            1 :         if (typeof path !== 'string' || path.length === 0) {
      56            1 :             continue;
      57              :         }
      58            1 :         if (storageType === 'native') {
      59            1 :             if (typeof nativePath !== 'string' || globalThis.dashqlElectron === undefined) {
      60            1 :                 continue;
      61              :             }
      62              :             // Folders registered before the app granted folders are claimed once, later starts reject the claim
      63            1 :             await globalThis.dashqlElectron.claimSessionFolder(nativePath);
      64            1 :             if (!(await exists(nativePath))) {
      65            1 :                 continue;
      66              :             }
      67            1 :             folders.push({ sessionId: path, folder: new NativeFolder(nativePath) });
      68            1 :             continue;
      69            1 :         }
      70            1 :         try {
      71            1 :             const dir = await (await root.getDirectoryHandle('sessions', { create: false })).getDirectoryHandle(path, { create: false });
      72            1 :             folders.push({ sessionId: path, folder: new OPFSFolder(dir) });
      73            1 :         } catch (error) {
      74            1 :             if ((error as DOMException).name !== 'NotFoundError') {
      75            1 :                 throw error;
      76              :             }
      77            1 :         }
      78            1 :     }
      79            1 :     return folders;
      80            1 : }
        

Generated by: LCOV version 2.0-1