diff --git a/extensions/models-dev/.gitignore b/extensions/models-dev/.gitignore index 84fc7188f51..8df5b042ec0 100644 --- a/extensions/models-dev/.gitignore +++ b/extensions/models-dev/.gitignore @@ -12,3 +12,7 @@ compiled_raycast_rust # misc .DS_Store + +# Local files +.claude/ +GITHUB_ISSUE.md diff --git a/extensions/models-dev/CHANGELOG.md b/extensions/models-dev/CHANGELOG.md index f4055f4e065..d7f0dab52ec 100644 --- a/extensions/models-dev/CHANGELOG.md +++ b/extensions/models-dev/CHANGELOG.md @@ -1,5 +1,16 @@ # models.dev Changelog +## [Bug Fix] - 2026-03-31 + +### Fixed + +- Fixed JS heap out of memory crash on first launch caused by `useCachedPromise` memory overhead +- Replaced `useCachedPromise` with direct fetch + Cache API to reduce peak memory usage + +### Changed + +- Removed `@raycast/utils` dependency (no longer needed) + ## [Bug Fixes] - 2026-03-16 ### Changed diff --git a/extensions/models-dev/package-lock.json b/extensions/models-dev/package-lock.json index a0d3e0dfbcd..07f2586bc5f 100644 --- a/extensions/models-dev/package-lock.json +++ b/extensions/models-dev/package-lock.json @@ -7,8 +7,7 @@ "name": "models-dev", "license": "MIT", "dependencies": { - "@raycast/api": "^1.104.3", - "@raycast/utils": "^2.2.2" + "@raycast/api": "^1.104.3" }, "devDependencies": { "@raycast/eslint-config": "^2.1.1", @@ -1209,24 +1208,6 @@ "eslint": ">=8.23.0" } }, - "node_modules/@raycast/utils": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-2.2.3.tgz", - "integrity": "sha512-YwqleVl0Wk/FOq+672gtvswuwMqjNqIr+c63FhouTEHc1LJ3zaohLSkW4+UcfBjPU8HySKQm+kJqZW1iOM9fnA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - }, - "peerDependencies": { - "@raycast/api": ">=1.99.4", - "react": ">=19.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1788,15 +1769,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", diff --git a/extensions/models-dev/package.json b/extensions/models-dev/package.json index be29d046059..50736d8fafe 100644 --- a/extensions/models-dev/package.json +++ b/extensions/models-dev/package.json @@ -120,8 +120,7 @@ } ], "dependencies": { - "@raycast/api": "^1.104.3", - "@raycast/utils": "^2.2.2" + "@raycast/api": "^1.104.3" }, "devDependencies": { "@raycast/eslint-config": "^2.1.1", diff --git a/extensions/models-dev/src/hooks/useModelsData.ts b/extensions/models-dev/src/hooks/useModelsData.ts index 57aa7dbf956..01326d73cea 100644 --- a/extensions/models-dev/src/hooks/useModelsData.ts +++ b/extensions/models-dev/src/hooks/useModelsData.ts @@ -1,21 +1,66 @@ -import { showToast, Toast } from "@raycast/api"; -import { useCachedPromise } from "@raycast/utils"; +import { Cache, showToast, Toast } from "@raycast/api"; +import { useState, useEffect, useRef } from "react"; import { fetchModelsData } from "../lib/api"; +import type { ModelsData } from "../lib/types"; + +const cache = new Cache(); +const CACHE_KEY = "models-data"; /** * Hook to fetch models data from models.dev - * useCachedPromise persists the last successful result to disk automatically, - * serving stale-while-revalidate on subsequent opens with no extra heap allocation. + * + * Uses direct fetch + Cache API instead of useCachedPromise to avoid + * Uses direct fetch + Cache API instead of useCachedPromise to avoid + * memory spikes during fresh cache population. See: + * https://github.com/raycast/utils/issues/65 */ export function useModelsData() { - return useCachedPromise(fetchModelsData, [], { - keepPreviousData: true, - onError: (error) => { - showToast({ - style: Toast.Style.Failure, - title: "Failed to load models", - message: error instanceof Error ? error.message : "Unknown error", - }); - }, + const [data, setData] = useState(() => { + const cached = cache.get(CACHE_KEY); + if (cached) { + try { + return JSON.parse(cached) as ModelsData; + } catch { + cache.remove(CACHE_KEY); + } + } + return null; }); + + const [isLoading, setIsLoading] = useState(!data); + const fetchedRef = useRef(false); + + useEffect(() => { + // Already have cached data or already fetching + if (fetchedRef.current) return; + fetchedRef.current = true; + + // If we have cached data, still revalidate in background + const shouldRevalidate = !!data; + + if (!shouldRevalidate) { + setIsLoading(true); + } + + fetchModelsData() + .then((result) => { + setData(result); + setIsLoading(false); + // Cache write happens after state update to reduce peak memory + cache.set(CACHE_KEY, JSON.stringify(result)); + }) + .catch((error) => { + setIsLoading(false); + // Only show error if we don't have cached data to fall back on + if (!data) { + showToast({ + style: Toast.Style.Failure, + title: "Failed to load models", + message: error instanceof Error ? error.message : "Unknown error", + }); + } + }); + }, []); + + return { data, isLoading }; }