Files
ComfyUI_frontend/src/platform/remote/queryClient.ts
Glary-Bot d91f5da890 feat(widgets): atomize RichComboWidget + TanStack Query foundation
Implements the master plan from PR #11955 on top of PR #11310:

- Phase 1: Add @tanstack/vue-query, wire VueQueryPlugin in main.ts with
  bounded gcTime + retry policy. Module-level singleton via
  getAppQueryClient() so non-Vue contexts (legacy useRemoteWidget) can
  reuse the same cache.
- Phase 2: Move pure helpers to base/remote/ — itemSchema.ts (getByPath,
  resolveLabel, mapToDropdownItem, extractItems, buildSearchText),
  retry.ts (getBackoff, isRetriableError), diagnostics.ts (summarizeError,
  summarizePayload). Delete fetchRemoteRoute (auth headers now injected
  inline in useRemoteOptions per existing API-client pattern).
- Phase 3: platform/remote/composables/useRemoteOptions.ts wraps
  TanStack Query with a typed RequestDescriptor and a key factory keyed
  by client/route/params/{userId, workspaceId} for defense-in-depth
  partitioning (auth-teardown invariant covers the cache lifecycle).
- Phase 4: RemoteCombo/ atom family (Root/Trigger/Content/Search/List/
  Item/Empty/Loading/Error/Refresh/LayoutSwitcher) over reka-ui's
  Combobox primitives. CVA variants in remoteCombo.variants.ts mirror
  Button.vue conventions (size/variant/border axes). Reka data-attr
  styling for hover/highlighted/checked. Adapter pattern for spec→prop
  extraction (specAdapter.ts + comboAdapter.ts).
- Phase 5: useRemoteCombo (view layer: schema mapping, search index,
  auto_select). useRemoteWidget rewritten on getAppQueryClient() —
  preserves the IWidget mutation contract: first-load defaulting,
  control_after_refresh override, execution_success auto-refresh toggle.
- Phase 6: zComboInputOptionsValidated enforces remote XOR remote_combo
  to match backend XOR validation.
- Phase 7: Tests for new modules (comboAdapter, useRemoteOptions key
  factory, RichComboWidget atom-level flows, fast-check property test
  on mapToDropdownItem, XOR schema validation). Pure-helper tests
  relocated to base/remote/.
- Phase 8: A11y minimums on every atom (aria-label/aria-live/aria-busy/
  aria-disabled/aria-pressed; sr-only error/empty announcements).
- Phase 9: Storybook stories (Default / Loading / Error / Empty /
  WithSelection / KeyboardA11y) for the atom family. Token-aligned to
  the design system per master plan §11.2.b.

Removed:
- src/renderer/extensions/vueNodes/widgets/utils/fetchRemoteRoute.{ts,test.ts}
- src/renderer/extensions/vueNodes/widgets/utils/itemSchemaUtils.{ts,test.ts}
- src/renderer/extensions/vueNodes/widgets/utils/richComboHelpers.{ts,test.ts}
- The auth-scoped Cache API persistence layer in RichComboWidget.vue
- The legacy in-memory cacheEntry map in useRemoteWidget.ts

Quality gates: pnpm lint, typecheck, knip, build, test:unit (754 files,
10053 tests) all pass.
2026-05-06 07:12:10 +00:00

44 lines
1.4 KiB
TypeScript

import { QueryClient } from '@tanstack/vue-query'
import { isRetriableError } from '@/base/remote/retry'
const DEFAULT_GC_TIME_MS = 5 * 60_000
const DEFAULT_RETRY_COUNT = 3
let appQueryClient: QueryClient | undefined
/**
* Create the application-wide TanStack Query client.
*
* Defaults are tuned for remote-option dropdowns and similar widget data:
* - `staleTime: 0` so refresh buttons always re-fetch
* - `gcTime` bounded so a session's footprint stays small (no LRU yet)
* - `retry` driven by {@link isRetriableError} from `base/remote/retry`
* - `refetchOnWindowFocus: false` to avoid surprise re-fetches mid-edit
*
* QueryClient lifetime is bound to the Vue app instance; auth-state changes
* tear down the authenticated layout subtree (see master plan §8), so the
* cache is naturally evicted without manual `queryClient.clear()` calls.
*/
export function createAppQueryClient(): QueryClient {
appQueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 0,
gcTime: DEFAULT_GC_TIME_MS,
retry: (failureCount, error) =>
failureCount < DEFAULT_RETRY_COUNT && isRetriableError(error),
refetchOnWindowFocus: false
}
}
})
return appQueryClient
}
export function getAppQueryClient(): QueryClient {
if (!appQueryClient) {
appQueryClient = createAppQueryClient()
}
return appQueryClient
}