Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions extensions/models-dev/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ compiled_raycast_rust

# misc
.DS_Store

# Local files
.claude/
GITHUB_ISSUE.md
11 changes: 11 additions & 0 deletions extensions/models-dev/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
30 changes: 1 addition & 29 deletions extensions/models-dev/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions extensions/models-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
71 changes: 58 additions & 13 deletions extensions/models-dev/src/hooks/useModelsData.ts
Original file line number Diff line number Diff line change
@@ -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<ModelsData | null>(() => {
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 };
}