perf(studio): cache nitro scan, stream progress, virtualize cleanup list
CI / check (push) Successful in 4m14s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m10s

This commit is contained in:
openhands committed 2026-09-19 13:01:59 +02:00
1 parent c916e42572
commit ba81d16f00
5 files changed
+727 -257

No files matched your search

+58 -1
View File
@@ -16,7 +16,11 @@ import { existsSync } from "node:fs";
import path from "node:path";
import { sql } from "drizzle-orm";
import { db, ItemsBase, queryRows } from "@/lib/db";
import { normalizeClassname } from "@/lib/furni/classname";
import {
getBaseClassname,
getSafeClassnameStem,
normalizeClassname,
} from "@/lib/furni/classname";
import { indexLocalFurniture } from "@/lib/furni/local-presence";
import { readFurniData } from "./furni-data";
@@ -107,6 +111,58 @@ export async function getCatalogIdSet(): Promise<Set<number>> {
const nitroPresenceCache = new Map<string, boolean>();
/** Every classname stem a `.nitro`/`.swf`/icon file may legitimately use. */
function addClassnameStem(set: Set<string>, classname: string): void {
const normalized = classname.trim().toLowerCase();
if (!normalized) return;
const base = getBaseClassname(normalized).toLowerCase();
if (base) set.add(base);
if (normalized.includes("*")) {
const safe = getSafeClassnameStem(normalized).toLowerCase();
if (safe) set.add(safe);
}
}
let cleanupStemsSnapshot: { set: Set<string>; timestamp: number } | null = null;
/**
* Authoritative stems for the nitro-cleanup scan: every `item_name` plus the
* `public_name` fallback (mirroring repair-nitros.ts resolution). Cached the
* same way as the Studio listings so repeated scans (auto-clean, panel) don't
* re-query `items_base` on every call.
*/
export async function getCleanupValidStems(): Promise<Set<string>> {
const now = Date.now();
if (
cleanupStemsSnapshot &&
now - cleanupStemsSnapshot.timestamp < CACHE_TTL
) {
return cleanupStemsSnapshot.set;
}
const set = new Set<string>();
try {
const rows = await queryRows<{
item_name: string;
public_name: string;
}>(sql`SELECT item_name, public_name FROM items_base`);
for (const row of rows) {
addClassnameStem(set, row.item_name);
if (
row.public_name &&
row.public_name !== row.item_name &&
!row.public_name.includes(" ") &&
row.public_name !== "0"
) {
addClassnameStem(set, row.public_name);
}
}
} catch {
/* empty set → every asset is treated as orphaned */
}
cleanupStemsSnapshot = { set, timestamp: now };
return set;
}
/** `existsSync` memoized per (dir + base) — nitro files change rarely. */
export function nitroExistsCached(dir: string, base: string): boolean {
const key = `${dir}/${base}`;
@@ -210,5 +266,6 @@ export function clearFurniImportCache(): void {
furniDataSetSnapshot = null;
catalogIdsSnapshot = null;
nitroPresenceCache.clear();
cleanupStemsSnapshot = null;
statsSnapshot = null;
}