Console 1994 migrate laboratory from localstorage to indexeddb (#7904)

This commit is contained in:
Jonathan Brennan 2026-03-25 10:29:39 -05:00 committed by GitHub
parent 70b3e19fe2
commit 4acd1c0446
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 82 additions and 4 deletions

View file

@ -106,6 +106,7 @@
"graphql": "16.9.0",
"graphql-sse": "2.5.3",
"graphql-ws": "5.16.1",
"idb-keyval": "6.2.1",
"immer": "10.1.3",
"js-cookie": "3.0.5",
"json-schema-typed": "8.0.1",

View file

@ -0,0 +1,53 @@
import { createStore, del, get, set } from 'idb-keyval';
import type { LaboratoryHistory } from '@graphql-hive/laboratory';
const STORE = createStore('hive-laboratory-db', 'history-store');
const HISTORY_KEY = 'history';
const LS_KEY = 'hive:laboratory:history';
export async function loadHistory(): Promise<LaboratoryHistory[]> {
try {
const fromIdb = await get<LaboratoryHistory[]>(HISTORY_KEY, STORE);
if (fromIdb !== undefined) {
return fromIdb;
}
// Migrate from localStorage on first load
const raw = localStorage.getItem(LS_KEY);
if (raw) {
const parsed = JSON.parse(raw) as LaboratoryHistory[];
await set(HISTORY_KEY, parsed, STORE);
localStorage.removeItem(LS_KEY);
return parsed;
}
} catch (err) {
console.error('[Laboratory] Failed to load history from IndexedDB:', err);
// Fall back to localStorage
try {
const raw = localStorage.getItem(LS_KEY);
if (raw) {
return JSON.parse(raw) as LaboratoryHistory[];
}
} catch {
// Corrupt data — start fresh
localStorage.removeItem(LS_KEY);
}
}
return [];
}
export async function saveHistory(history: LaboratoryHistory[]): Promise<void> {
try {
await set(HISTORY_KEY, history, STORE);
} catch (err) {
console.error('[Laboratory] Failed to save history to IndexedDB:', err);
}
}
export async function clearHistory(): Promise<void> {
try {
await del(HISTORY_KEY, STORE);
} catch (err) {
console.error('[Laboratory] Failed to clear history from IndexedDB:', err);
}
}

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import clsx from 'clsx';
import { buildSchema, introspectionFromSchema, Kind, parse, print } from 'graphql';
import { throttle } from 'lodash';
@ -30,6 +30,7 @@ import { useCurrentOperationWithFetchingState } from '@/lib/hooks/laboratory/use
import { TargetLaboratoryPageQuery } from '@/lib/hooks/laboratory/use-operation-collections-plugin';
import { useOperationFromQueryString } from '@/lib/hooks/laboratory/useOperationFromQueryString';
import { useResetState } from '@/lib/hooks/use-reset-state';
import { loadHistory, saveHistory } from '@/lib/laboratory-history-storage';
import { cn } from '@/lib/utils';
import {
Laboratory,
@ -319,6 +320,18 @@ function useLaboratoryState(props: {
},
});
const [historyData, setHistoryData] = useState<LaboratoryHistory[] | null>(null);
useEffect(() => {
let cancelled = false;
void loadHistory().then(history => {
if (!cancelled) setHistoryData(history);
});
return () => {
cancelled = true;
};
}, []);
const preflight = useFragment(LaboratoryPreflightScriptTargetFragment, data?.target ?? null);
const collections = useMemo(
@ -566,18 +579,21 @@ function useLaboratoryState(props: {
const operationIdFromSearch = useOperationFromQueryString();
const fetching = useMemo(() => {
if (historyData === null) {
return true;
}
if (operationIdFromSearch) {
return dataFetching || currentOperationFetching;
}
return dataFetching;
}, [dataFetching, currentOperationFetching, operationIdFromSearch]);
}, [dataFetching, currentOperationFetching, operationIdFromSearch, historyData]);
return {
fetching,
defaultCollections: collections,
defaultOperations,
defaultHistory: getLocalStorageState('history', []),
defaultHistory: historyData ?? [],
defaultTabs,
defaultActiveTabId: getLocalStorageState('activeTabId', null),
defaultSettings: getLocalStorageState('settings', {
@ -603,7 +619,7 @@ function useLaboratoryState(props: {
setLocalStorageState('operations', operations);
},
onHistoryChange: (history: LaboratoryHistory[]) => {
setLocalStorageState('history', history);
void saveHistory(history);
},
onTabsChange: (tabs: LaboratoryTab[]) => {
setLocalStorageState('tabs', tabs);

View file

@ -2319,6 +2319,9 @@ importers:
graphql-ws:
specifier: 5.16.1
version: 5.16.1(graphql@16.9.0)
idb-keyval:
specifier: 6.2.1
version: 6.2.1
immer:
specifier: 10.1.3
version: 10.1.3
@ -13569,6 +13572,9 @@ packages:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
idb-keyval@6.2.1:
resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@ -35186,6 +35192,8 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
idb-keyval@6.2.1: {}
ieee754@1.2.1: {}
ignore-walk@6.0.4: