{"record":{"id":"edc9f9b7d0d68158","repo":"lbjlaq/Antigravity-Manager","slug":"common-tauri-api-not-loaded","errorCode":null,"errorMessage":"common.tauri_api_not_loaded","messagePattern":"common\\.tauri_api_not_loaded","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/accountService.ts","lineNumber":9,"sourceCode":"import i18n from '../i18n';\nimport { Account, DeviceProfile, DeviceProfileVersion, QuotaData } from '../types/account';\nimport { request as invoke } from '../utils/request';\n\n// 检查环境 (可选)\nfunction ensureTauriEnvironment() {\n    // Web 模式下 request 也是一个 function，所以这里不应抛错\n    if (typeof invoke !== 'function') {\n        throw new Error(i18n.t('common.tauri_api_not_loaded'));\n    }\n}\n\nexport async function listAccounts(): Promise<Account[]> {\n    const response = await invoke<any>('list_accounts');\n    // 如果返回的是对象格式 { accounts: [...] }, 则取其 accounts 属性\n    if (response && typeof response === 'object' && Array.isArray(response.accounts)) {\n        return response.accounts;\n    }\n    // 否则直接返回响应内容（假设为数组）\n    return response || [];\n}\n\nexport async function getCurrentAccount(): Promise<Account | null> {\n    return await invoke('get_current_account');\n}\n\nexport async function addAccount(email: string, refreshToken: string): Promise<Account> {","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/lbjlaq/Antigravity-Manager/blob/a2e3c454237d6d6ef423dfe20505b1ac62803c7c/src/services/accountService.ts#L1-L27","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (src/services/accountService.ts)\nfunction ensureTauriEnvironment() {\n    if (typeof invoke !== 'function') {\n        throw new Error(i18n.t('common.tauri_api_not_loaded'));\n    }\n}\n\n// after: guard the real condition — Tauri-only commands need the desktop shell\nconst isTauriShell = typeof window !== 'undefined' &&\n    (!!(window as any).__TAURI_INTERNALS__ || !!(window as any).__TAURI__);\nfunction ensureTauriEnvironment() {\n    if (!isTauriShell) {\n        throw new Error(i18n.t('common.tauri_api_not_loaded'));\n    }\n}","handlingStrategy":"validation","validationCode":"// Before calling OAuth service functions, verify the adapter actually loaded\nimport { request as invoke } from '../utils/request';\n\nexport function canUseAccountService(): boolean {\n  return typeof invoke === 'function';\n}\n\n// caller\nif (!canUseAccountService()) {\n  console.error('request module failed to load (circular import?)');\n  return;\n}","typeGuard":"type InvokeFn = (cmd: string, args?: unknown) => Promise<unknown>;\nconst isInvokeFn = (v: unknown): v is InvokeFn => typeof v === 'function';","tryCatchPattern":"try {\n  await startOAuthLogin();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('tauri_api_not_loaded')) {\n    // module-load problem, not a user mistake: log loudly, don't retry\n    console.error('[accountService] adapter missing — check circular imports / mocks:', e);\n    throw e;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["tauri","module-import","circular-dependency","environment-check","i18n"],"backgroundTag":"circular-dependency-undefined-import","analyzedSha":"a2e3c454237d6d6ef423dfe20505b1ac62803c7c","analyzedAt":"2026-08-16T19:44:46.389Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}