lbjlaq/Antigravity-Manager · error · Error
common.tauri_api_not_loaded
Error message
common.tauri_api_not_loaded
What it means
Thrown by ensureTauriEnvironment() (src/services/accountService.ts:9) when the imported `request` from src/utils/request.ts is not a function. The comment in the source admits this should never fire in normal builds, because request.ts statically exports `async function request<T>()` for both Tauri and Web modes. In practice it means the module evaluated to undefined at call time: a circular import (request.ts pulling in a module that imports accountService), a test mock returning `{}` instead of a function, or broken tree-shaking/bundling. Only the Tauri-only OAuth functions in this service call the guard (e.g. startOAuthLogin), so the blast radius is OAuth flows.
Source
Thrown at src/services/accountService.ts:9
import i18n from '../i18n';
import { Account, DeviceProfile, DeviceProfileVersion, QuotaData } from '../types/account';
import { request as invoke } from '../utils/request';
// 检查环境 (可选)
function ensureTauriEnvironment() {
// Web 模式下 request 也是一个 function,所以这里不应抛错
if (typeof invoke !== 'function') {
throw new Error(i18n.t('common.tauri_api_not_loaded'));
}
}
export async function listAccounts(): Promise<Account[]> {
const response = await invoke<any>('list_accounts');
// 如果返回的是对象格式 { accounts: [...] }, 则取其 accounts 属性
if (response && typeof response === 'object' && Array.isArray(response.accounts)) {
return response.accounts;
}
// 否则直接返回响应内容(假设为数组)
return response || [];
}
export async function getCurrentAccount(): Promise<Account | null> {
return await invoke('get_current_account');
}
export async function addAccount(email: string, refreshToken: string): Promise<Account> {View on GitHub (pinned to a2e3c45423)
Solutions
- Run `npx madge --circular src` (or similar) and break any cycle between src/utils/request.ts and src/services/accountService.ts by moving shared imports into a third module.
- If this fires in tests, fix the mock: jest.mock('../utils/request', () => ({ request: jest.fn() })) so the named export is a function.
- Replace the dead `typeof invoke !== 'function'` check with a real environment check on window.__TAURI_INTERNALS__ / window.__TAURI__ (the flag request.ts:2 already uses), since in Web mode request is intentionally a function and the current guard checks the wrong thing.
- Verify the bundler config (vite/webpack aliases, optimizeDeps) is not duplicating or transforming src/utils/request.ts so the named export survives.
Example fix
// before (src/services/accountService.ts)
function ensureTauriEnvironment() {
if (typeof invoke !== 'function') {
throw new Error(i18n.t('common.tauri_api_not_loaded'));
}
}
// after: guard the real condition — Tauri-only commands need the desktop shell
const isTauriShell = typeof window !== 'undefined' &&
(!!(window as any).__TAURI_INTERNALS__ || !!(window as any).__TAURI__);
function ensureTauriEnvironment() {
if (!isTauriShell) {
throw new Error(i18n.t('common.tauri_api_not_loaded'));
}
} Defensive patterns
Strategy: validation
Validate before calling
// Before calling OAuth service functions, verify the adapter actually loaded
import { request as invoke } from '../utils/request';
export function canUseAccountService(): boolean {
return typeof invoke === 'function';
}
// caller
if (!canUseAccountService()) {
console.error('request module failed to load (circular import?)');
return;
} Type guard
type InvokeFn = (cmd: string, args?: unknown) => Promise<unknown>; const isInvokeFn = (v: unknown): v is InvokeFn => typeof v === 'function';
Try / catch
try {
await startOAuthLogin();
} catch (e) {
if (e instanceof Error && e.message.includes('tauri_api_not_loaded')) {
// module-load problem, not a user mistake: log loudly, don't retry
console.error('[accountService] adapter missing — check circular imports / mocks:', e);
throw e;
}
throw e;
} Prevention
- Keep src/utils/request.ts dependency-free (no imports of services) so it can never enter a circular-import cycle.
- In unit tests, always mock '../utils/request' with a function export: jest.mock('../utils/request', () => ({ request: jest.fn() })).
- Add a smoke test asserting `typeof request === 'function'` after import in every environment (web, Tauri, test).
- Prefer runtime environment detection (window.__TAURI_INTERNALS__) over checking the shape of your own import.
When it happens
Trigger: Calling startOAuthLogin()/other OAuth functions in accountService when the `import { request as invoke }` binding resolved to undefined — typically after introducing a circular import chain through ../utils/request, or when a unit test mocks '../utils/request' with jest.mock without a function export, or after an aggressive bundle transform drops the re-exported binding.
Common situations: Adding an import to request.ts (e.g. a logger or i18n helper) that transitively imports accountService.ts, creating a cycle; Vitest/Jest mocks that forget `mockReturnValue`/export shape; mixing CJS/ESM interop where the named export is attached under .default; HMR leaving a stale module during development.
Related errors
AI-assisted analysis of lbjlaq/Antigravity-Manager@a2e3c45423 (2026-08-16).
Data as JSON: /api/errors/edc9f9b7d0d68158.
Report an issue: GitHub.