diff --git a/README.md b/README.md index 5713a00f4..57524d807 100644 --- a/README.md +++ b/README.md @@ -190,12 +190,6 @@ If either of the Graph Explorer or the proxy-server are served over an HTTPS con Note: To get rid of the “Not Secure” warning, see [Using self-signed certificates on Chrome](./additionaldocs/development.md#using-self-signed-certificates-on-chrome). -### Connection Cache - -Setting up a new connection (or editing an existing connection) allows you to enable a cache for the connector requests. The cache store is configured to use the browser IndexedDB that allows you to make use of data stored between sessions. The time that the data stored in the cache is also configurable, by default it has a lifetime of 10 minutes. - -The purpose of the cache is to avoid making multiple requests to the database with the same criteria. Therefore, a request with particular parameters will be cached at most the time set just with the response obtained. After that time, if the exact same request is made again, the response will be updated and stored again. - ## Authentication Authentication for Amazon Neptune connections is enabled using the [SigV4 signing protocol](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). diff --git a/packages/graph-explorer/src/connector/gremlin/useGremlin.ts b/packages/graph-explorer/src/connector/gremlin/useGremlin.ts index 20da18362..8b4420006 100644 --- a/packages/graph-explorer/src/connector/gremlin/useGremlin.ts +++ b/packages/graph-explorer/src/connector/gremlin/useGremlin.ts @@ -46,14 +46,13 @@ const useGremlin = () => { const fetchSchemaFunc = useCallback( async options => { - const ops = { ...options, disableCache: true }; let summary; try { const response = await useFetch.request( `${url}/pg/statistics/summary?mode=detailed`, { method: "GET", - ...ops, + ...options, } ); summary = (response.payload.graphSummary as GraphSummary) || undefined; @@ -62,7 +61,7 @@ const useGremlin = () => { console.error("[Summary API]", e); } } - return fetchSchema(_gremlinFetch(ops), summary); + return fetchSchema(_gremlinFetch(options), summary); }, [_gremlinFetch, url, useFetch] ); diff --git a/packages/graph-explorer/src/connector/openCypher/useOpenCypher.ts b/packages/graph-explorer/src/connector/openCypher/useOpenCypher.ts index 6256a7923..6c89c74fe 100644 --- a/packages/graph-explorer/src/connector/openCypher/useOpenCypher.ts +++ b/packages/graph-explorer/src/connector/openCypher/useOpenCypher.ts @@ -26,7 +26,6 @@ const useOpenCypher = () => { "Content-Type": "application/json", }, body: JSON.stringify({ query: queryTemplate }), - disableCache: options?.disableCache, ...options, }); }; @@ -36,8 +35,6 @@ const useOpenCypher = () => { const fetchSchemaFunc = useCallback( async (options: any) => { - const ops = { ...options, disableCache: true }; - let summary; try { const endpoint = @@ -46,7 +43,7 @@ const useOpenCypher = () => { : `${url}/summary?mode=detailed`; const response = await useFetch.request(endpoint, { method: "GET", - ...ops, + ...options, }); summary = @@ -58,7 +55,7 @@ const useOpenCypher = () => { console.error("[Summary API]", e); } } - return fetchSchema(_openCypherFetch(ops), summary); + return fetchSchema(_openCypherFetch(options), summary); }, [_openCypherFetch, url, useFetch, serviceType] ); diff --git a/packages/graph-explorer/src/connector/sparql/useSPARQL.ts b/packages/graph-explorer/src/connector/sparql/useSPARQL.ts index 16f19b74e..e4e981960 100644 --- a/packages/graph-explorer/src/connector/sparql/useSPARQL.ts +++ b/packages/graph-explorer/src/connector/sparql/useSPARQL.ts @@ -160,14 +160,13 @@ const useSPARQL = (blankNodes: BlankNodesMap) => { const fetchSchemaFunc = useCallback( async options => { - const ops = { ...options, disableCache: true }; let summary; try { const response = await useFetch.request( `${url}/rdf/statistics/summary?mode=detailed`, { method: "GET", - ...ops, + ...options, } ); summary = (response.payload.graphSummary as GraphSummary) || undefined; @@ -176,7 +175,7 @@ const useSPARQL = (blankNodes: BlankNodesMap) => { console.error("[Summary API]", e); } } - return fetchSchema(_sparqlFetch(ops), summary); + return fetchSchema(_sparqlFetch(options), summary); }, [_sparqlFetch, url, useFetch] ); diff --git a/packages/graph-explorer/src/connector/useGEFetch.ts b/packages/graph-explorer/src/connector/useGEFetch.ts index abcbe3c80..abdde4a62 100644 --- a/packages/graph-explorer/src/connector/useGEFetch.ts +++ b/packages/graph-explorer/src/connector/useGEFetch.ts @@ -1,19 +1,8 @@ import { useCallback } from "react"; -import localforage from "localforage"; -import { CacheItem } from "./useGEFetchTypes"; import { useConfiguration, type ConnectionConfig } from "../core"; import { DEFAULT_SERVICE_TYPE } from "../utils/constants"; import { anySignal } from "./utils/anySignal"; -// 10 minutes -const CACHE_TIME_MS = 10 * 60 * 1000; - -const localforageCache = localforage.createInstance({ - name: "ge", - version: 1.0, - storeName: "connector-cache", -}); - /** * Attempts to decode the error response into a JSON object. * @@ -58,49 +47,8 @@ const useGEFetch = () => { const connection = useConfiguration()?.connection as | ConnectionConfig | undefined; - const _getFromCache = useCallback( - async key => { - if (!connection?.enableCache) { - return; - } - - return localforageCache.getItem(key) as Promise; - }, - [connection?.enableCache] - ); - - const _setToCache = useCallback( - async (key, value) => { - if (connection?.enableCache) { - return; - } - - return localforageCache.setItem(key, value); - }, - [connection?.enableCache] - ); - - const _requestAndCache = useCallback( - async ( - url: URL, - options: (RequestInit & { disableCache: boolean }) | undefined - ) => { - const response = await fetch(url, options); - if (!response.ok) { - const error = await decodeErrorSafely(response); - throw new Error("Network response was not OK", { cause: error }); - } - - // A successful response is assumed to be JSON - const data = await response.json(); - if (options?.disableCache !== true) { - _setToCache(url, { data, updatedAt: new Date().getTime() }); - } - return data as any; - }, - [_setToCache] - ); + // Construct the request headers based on the connection settings const getAuthHeaders = useCallback( typeHeaders => { const headers: HeadersInit = {}; @@ -124,46 +72,40 @@ const useGEFetch = () => { ] ); - const request = useCallback( - async (uri, options) => { - const cachedResponse = await _getFromCache(uri); - if ( - cachedResponse && - cachedResponse.updatedAt + (connection?.cacheTimeMs ?? CACHE_TIME_MS) > - new Date().getTime() - ) { - return cachedResponse.data; - } + // Construct an AbortSignal for the fetch timeout if configured + const getFetchTimeoutSignal = useCallback(() => { + if (!connection?.fetchTimeoutMs) { + return null; + } + if (connection.fetchTimeoutMs <= 0) { + return null; + } + + return AbortSignal.timeout(connection.fetchTimeoutMs); + }, [connection?.fetchTimeoutMs]); + + const request = useCallback( + async (uri: URL | RequestInfo, options: RequestInit) => { + // Apply connection settings to fetch options const fetchOptions: RequestInit = { + ...options, headers: getAuthHeaders(options.headers), + signal: anySignal(getFetchTimeoutSignal(), options.signal), }; - const connectionFetchTimeout = connection?.fetchTimeoutMs; + const response = await fetch(uri, fetchOptions); - if (connectionFetchTimeout && connectionFetchTimeout > 0) { - const timeoutSignal = AbortSignal.timeout(connectionFetchTimeout); - - // Combine timeout with existing signal - if (options.signal) { - fetchOptions.signal = anySignal([timeoutSignal, options.signal]); - } else { - fetchOptions.signal = timeoutSignal; - } + if (!response.ok) { + const error = await decodeErrorSafely(response); + throw new Error("Network response was not OK", { cause: error }); } - return _requestAndCache(uri, { - ...options, - ...fetchOptions, - }) as Promise; + // A successful response is assumed to be JSON + const data = await response.json(); + return data; }, - [ - _getFromCache, - _requestAndCache, - connection?.cacheTimeMs, - connection?.fetchTimeoutMs, - getAuthHeaders, - ] + [getAuthHeaders, getFetchTimeoutSignal] ); return { diff --git a/packages/graph-explorer/src/connector/useGEFetchTypes.ts b/packages/graph-explorer/src/connector/useGEFetchTypes.ts index c3d35a427..70e00d476 100644 --- a/packages/graph-explorer/src/connector/useGEFetchTypes.ts +++ b/packages/graph-explorer/src/connector/useGEFetchTypes.ts @@ -6,9 +6,7 @@ import { } from "../core"; export type QueryOptions = RequestInit & { - disableCache?: boolean; queryId?: string; - successCallback?: (queryId: string) => void; }; export type VertexSchemaResponse = Pick< @@ -190,8 +188,3 @@ export type ConfigurationWithConnection = Omit< "connection" > & Required>; - -export type CacheItem = { - updatedAt: number; - data: any; -}; diff --git a/packages/graph-explorer/src/connector/utils/anySignal.ts b/packages/graph-explorer/src/connector/utils/anySignal.ts index f40170661..8546bb13f 100644 --- a/packages/graph-explorer/src/connector/utils/anySignal.ts +++ b/packages/graph-explorer/src/connector/utils/anySignal.ts @@ -4,13 +4,21 @@ * * Requires at least node.js 18. * - * @param signals An array of AbortSignal values that will be merged. - * @returns A single AbortSignal value. + * @param signals A variable amount of AbortSignal values that will be merged in to one. + * @returns A single AbortSignal value or undefined if none are passed. */ -export function anySignal(signals: AbortSignal[]): AbortSignal { - const controller = new AbortController(); +export function anySignal( + ...signals: (AbortSignal | null | undefined)[] +): AbortSignal | undefined { + // Filter out null or undefined signals + const filteredSignals = signals.flatMap(s => (s ? [s] : [])); + + if (filteredSignals.length === 0) { + return undefined; + } - for (const signal of signals) { + const controller = new AbortController(); + for (const signal of filteredSignals) { if (signal.aborted) { // Exiting early if one of the signals // is already aborted. diff --git a/packages/graph-explorer/src/core/ConfigurationProvider/types.ts b/packages/graph-explorer/src/core/ConfigurationProvider/types.ts index 2cc73e9e9..e759b9505 100644 --- a/packages/graph-explorer/src/core/ConfigurationProvider/types.ts +++ b/packages/graph-explorer/src/core/ConfigurationProvider/types.ts @@ -140,16 +140,6 @@ export type ConnectionConfig = { * It is needed to sign requests. */ awsRegion?: string; - /** - * Enable or disable connector cache. - * By default, it's enabled. - */ - enableCache?: boolean; - /** - * Number of milliseconds before expiring a cached request. - * By default, 10 minutes. - */ - cacheTimeMs?: number; /** * Number of milliseconds before aborting a request. * By default, 60 seconds. diff --git a/packages/graph-explorer/src/core/ConnectorProvider/ConnectorProvider.tsx b/packages/graph-explorer/src/core/ConnectorProvider/ConnectorProvider.tsx index 1fee0c33a..2aa615edf 100644 --- a/packages/graph-explorer/src/core/ConnectorProvider/ConnectorProvider.tsx +++ b/packages/graph-explorer/src/core/ConnectorProvider/ConnectorProvider.tsx @@ -43,8 +43,6 @@ const ConnectorProvider = ({ children }: PropsWithChildren) => { "graphDbUrl", "awsAuthEnabled", "awsRegion", - "enableCache", - "cacheTimeMs", "fetchTimeoutMs", ] as const, [] diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx index 0232e0773..35b04304a 100644 --- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx @@ -31,8 +31,6 @@ type ConnectionForm = { awsAuthEnabled?: boolean; serviceType?: "neptune-db" | "neptune-graph"; awsRegion?: string; - enableCache?: boolean; - cacheTimeMs?: number; fetchTimeMs?: number; }; @@ -78,8 +76,6 @@ const CreateConnection = ({ awsAuthEnabled: data.awsAuthEnabled, serviceType: data.serviceType, awsRegion: data.awsRegion, - enableCache: data.enableCache, - cacheTimeMs: data.cacheTimeMs * 60 * 1000, fetchTimeoutMs: data.fetchTimeMs, }, }; @@ -108,7 +104,6 @@ const CreateConnection = ({ awsAuthEnabled: data.awsAuthEnabled, serviceType: data.serviceType, awsRegion: data.awsRegion, - cacheTimeMs: data.cacheTimeMs * 60 * 1000, fetchTimeoutMs: data.fetchTimeMs, }, }); @@ -150,8 +145,6 @@ const CreateConnection = ({ awsAuthEnabled: initialData?.awsAuthEnabled || false, serviceType: initialData?.serviceType || "neptune-db", awsRegion: initialData?.awsRegion || "", - enableCache: true, - cacheTimeMs: (initialData?.cacheTimeMs ?? 10 * 60 * 1000) / 60000, fetchTimeMs: initialData?.fetchTimeMs, }); @@ -316,48 +309,6 @@ const CreateConnection = ({ )} -
- { - onFormChange("enableCache")(e.target.checked); - }} - styles={{ - label: { - display: "block", - }, - }} - label={ -
- Enable Cache - - Requests made by the Graph Explorer can be temporarily - stored in the browser cache for quick access to the data. -
- } - > -
- -
- -
- } - /> - {form.enableCache && ( -
- -
- )} -