Skip to content

Commit a4263ac

Browse files
refactor(console): address review — browser-safe DEV check, dedup type guard
Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/89d7bf38-7ea0-4ee9-b0e9-1ecb766df6df Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent cab16d2 commit a4263ac

2 files changed

Lines changed: 35 additions & 4 deletions

File tree

apps/console/src/__tests__/MetadataProviderLazy.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ function createMockAdapter() {
3232
}
3333

3434
function flushAll() {
35+
// Two microtask ticks are needed: one for the `ensureType` promise
36+
// chain, another for the React state update that bumps the provider's
37+
// version counter. `waitFor` is used elsewhere for assertion polling;
38+
// `flushAll` just gets us past the initial mount transition.
3539
return act(async () => {
3640
await Promise.resolve();
3741
await Promise.resolve();

apps/console/src/context/MetadataProvider.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,24 @@ const TYPE_BY_STATE_KEY: Record<keyof Omit<MetadataState, 'loading' | 'error'>,
150150
const SESSION_STORAGE_PREFIX = 'objectui:metadata:';
151151

152152
/** Dev-only debug logger — silenced in production builds. */
153-
const DEV = typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production';
153+
// Vite replaces `import.meta.env.MODE` at build time. Fall back to `process.env`
154+
// for non-Vite runtimes (Node-based vitest, SSR) where `import.meta.env` is
155+
// undefined. Wrapped in try/catch so a missing global never breaks rendering.
156+
function isDev(): boolean {
157+
try {
158+
const meta: any = (import.meta as any);
159+
if (meta && meta.env && typeof meta.env.MODE === 'string') {
160+
return meta.env.MODE !== 'production';
161+
}
162+
} catch {
163+
/* import.meta unavailable — fall through */
164+
}
165+
if (typeof process !== 'undefined' && process.env && typeof process.env.NODE_ENV === 'string') {
166+
return process.env.NODE_ENV !== 'production';
167+
}
168+
return false;
169+
}
170+
const DEV = isDev();
154171
function debug(...args: unknown[]) {
155172
if (DEV) {
156173
// eslint-disable-next-line no-console
@@ -179,6 +196,16 @@ function extractItem(res: unknown): any | null {
179196
return res;
180197
}
181198

199+
/** Type guard for metadata items that carry a string `name` identifier. */
200+
function isNamedItem(item: unknown): item is { name: string } {
201+
return (
202+
!!item &&
203+
typeof item === 'object' &&
204+
'name' in item &&
205+
typeof (item as { name: unknown }).name === 'string'
206+
);
207+
}
208+
182209
/** Build an empty cache entry for a type. */
183210
function emptyEntry(): TypeCacheEntry {
184211
return {
@@ -278,8 +305,8 @@ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS }:
278305
// Hydrate per-name cache so subsequent getItem() calls hit memory.
279306
entry.byName.clear();
280307
for (const it of items) {
281-
if (it && typeof it === 'object' && 'name' in it && typeof (it as any).name === 'string') {
282-
entry.byName.set((it as any).name, it);
308+
if (isNamedItem(it)) {
309+
entry.byName.set(it.name, it);
283310
}
284311
}
285312
if (type === 'app') saveToSession(type, items);
@@ -422,7 +449,7 @@ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS }:
422449
entry.status = 'ready';
423450
entry.fetchedAt = 0; // mark as stale so a background refresh fires
424451
for (const it of cached) {
425-
if (it && typeof it === 'object' && typeof it.name === 'string') {
452+
if (isNamedItem(it)) {
426453
entry.byName.set(it.name, it);
427454
}
428455
}

0 commit comments

Comments
 (0)