@gears-frontx/react 0.1.0 → 0.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import { EndpointDescriptor, FrontXApp } from '@gears-frontx/framework';
3
+ import '@gears-frontx/framework/testing';
4
+
5
+ /**
6
+ * QueryCache — restricted interface for interacting with the shared QueryClient.
7
+ *
8
+ * MFEs and screen-set components access the cache only through this sanctioned
9
+ * public interface. It is injected as { queryCache } into useApiMutation
10
+ * callbacks and also returned by useQueryCache() for controlled imperative
11
+ * cache access.
12
+ */
13
+
14
+ type QueryCacheKey = readonly unknown[];
15
+ type QueryCacheRefetchMode = 'active' | 'inactive' | 'all' | 'none';
16
+ type QueryCacheInvalidateTarget = EndpointDescriptor<unknown> | QueryCacheKey | null | undefined;
17
+ interface QueryCacheState<TData = unknown, TError = Error> {
18
+ data: TData | undefined;
19
+ dataUpdatedAt: number;
20
+ error: TError | null;
21
+ errorUpdatedAt: number;
22
+ fetchFailureCount: number;
23
+ fetchFailureReason: TError | null;
24
+ fetchStatus: 'fetching' | 'paused' | 'idle';
25
+ isInvalidated: boolean;
26
+ status: 'pending' | 'error' | 'success';
27
+ }
28
+ type QueryCacheInvalidateFilters = {
29
+ queryKey?: QueryCacheInvalidateTarget;
30
+ exact?: boolean;
31
+ refetchType?: QueryCacheRefetchMode;
32
+ };
33
+ interface QueryCache {
34
+ get<T>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): T | undefined;
35
+ getState<TData = unknown, TError = Error>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): QueryCacheState<TData, TError> | undefined;
36
+ set<T>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey, dataOrUpdater: T | ((old: T | undefined) => T | undefined)): void;
37
+ cancel(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;
38
+ invalidate(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;
39
+ invalidateMany(filters?: QueryCacheInvalidateFilters | null): Promise<void>;
40
+ remove(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): void;
41
+ }
42
+ interface MutationCallbackContext {
43
+ queryCache: QueryCache;
44
+ }
45
+
46
+ declare function resolveFrontXQueryClient(app: FrontXApp): QueryClient | undefined;
47
+ /**
48
+ * Imperatively invalidates cache entries for `target` on the QueryClient bound to `app`.
49
+ * Use from non-React code (e.g. MFE actions); in components prefer `useQueryCache().invalidate`.
50
+ */
51
+ declare function invalidateQueryCacheForApp(app: FrontXApp, target: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;
52
+ /** Resolves the app-bound QueryClient without triggering plugin activation during render. */
53
+ declare function bootstrapFrontXQueryClient(app: FrontXApp): QueryClient | undefined;
54
+ declare function useOptionalFrontXQueryClient(): QueryClient | undefined;
55
+
56
+ export { type MutationCallbackContext as M, type QueryCache as Q, type QueryCacheInvalidateFilters as a, type QueryCacheState as b, bootstrapFrontXQueryClient as c, invalidateQueryCacheForApp as i, resolveFrontXQueryClient as r, useOptionalFrontXQueryClient as u };
@@ -0,0 +1,56 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import { EndpointDescriptor, FrontXApp } from '@gears-frontx/framework';
3
+ import '@gears-frontx/framework/testing';
4
+
5
+ /**
6
+ * QueryCache — restricted interface for interacting with the shared QueryClient.
7
+ *
8
+ * MFEs and screen-set components access the cache only through this sanctioned
9
+ * public interface. It is injected as { queryCache } into useApiMutation
10
+ * callbacks and also returned by useQueryCache() for controlled imperative
11
+ * cache access.
12
+ */
13
+
14
+ type QueryCacheKey = readonly unknown[];
15
+ type QueryCacheRefetchMode = 'active' | 'inactive' | 'all' | 'none';
16
+ type QueryCacheInvalidateTarget = EndpointDescriptor<unknown> | QueryCacheKey | null | undefined;
17
+ interface QueryCacheState<TData = unknown, TError = Error> {
18
+ data: TData | undefined;
19
+ dataUpdatedAt: number;
20
+ error: TError | null;
21
+ errorUpdatedAt: number;
22
+ fetchFailureCount: number;
23
+ fetchFailureReason: TError | null;
24
+ fetchStatus: 'fetching' | 'paused' | 'idle';
25
+ isInvalidated: boolean;
26
+ status: 'pending' | 'error' | 'success';
27
+ }
28
+ type QueryCacheInvalidateFilters = {
29
+ queryKey?: QueryCacheInvalidateTarget;
30
+ exact?: boolean;
31
+ refetchType?: QueryCacheRefetchMode;
32
+ };
33
+ interface QueryCache {
34
+ get<T>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): T | undefined;
35
+ getState<TData = unknown, TError = Error>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): QueryCacheState<TData, TError> | undefined;
36
+ set<T>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey, dataOrUpdater: T | ((old: T | undefined) => T | undefined)): void;
37
+ cancel(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;
38
+ invalidate(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;
39
+ invalidateMany(filters?: QueryCacheInvalidateFilters | null): Promise<void>;
40
+ remove(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): void;
41
+ }
42
+ interface MutationCallbackContext {
43
+ queryCache: QueryCache;
44
+ }
45
+
46
+ declare function resolveFrontXQueryClient(app: FrontXApp): QueryClient | undefined;
47
+ /**
48
+ * Imperatively invalidates cache entries for `target` on the QueryClient bound to `app`.
49
+ * Use from non-React code (e.g. MFE actions); in components prefer `useQueryCache().invalidate`.
50
+ */
51
+ declare function invalidateQueryCacheForApp(app: FrontXApp, target: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;
52
+ /** Resolves the app-bound QueryClient without triggering plugin activation during render. */
53
+ declare function bootstrapFrontXQueryClient(app: FrontXApp): QueryClient | undefined;
54
+ declare function useOptionalFrontXQueryClient(): QueryClient | undefined;
55
+
56
+ export { type MutationCallbackContext as M, type QueryCache as Q, type QueryCacheInvalidateFilters as a, type QueryCacheState as b, bootstrapFrontXQueryClient as c, invalidateQueryCacheForApp as i, resolveFrontXQueryClient as r, useOptionalFrontXQueryClient as u };
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/testing.ts
31
+ var testing_exports = {};
32
+ __export(testing_exports, {
33
+ bootstrapFrontXQueryClient: () => bootstrapFrontXQueryClient,
34
+ describeBootstrapMfeContract: () => import_testing.describeBootstrapMfeContract,
35
+ resolveFrontXQueryClient: () => resolveFrontXQueryClient,
36
+ useOptionalFrontXQueryClient: () => useOptionalFrontXQueryClient
37
+ });
38
+ module.exports = __toCommonJS(testing_exports);
39
+
40
+ // src/queryClient.tsx
41
+ var import_react = __toESM(require("react"), 1);
42
+ var import_react_query = require("@tanstack/react-query");
43
+ var import_framework2 = require("@gears-frontx/framework");
44
+
45
+ // src/hooks/QueryCache.ts
46
+ var import_framework = require("@gears-frontx/framework");
47
+
48
+ // src/queryClient.tsx
49
+ var import_jsx_runtime = require("react/jsx-runtime");
50
+ var APP_QUERY_CLIENT_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client");
51
+ var APP_QUERY_CLIENT_RESOLVER_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client-resolver");
52
+ var FrontXQueryClientContext = (0, import_react.createContext)(void 0);
53
+ var useCommitEffect = typeof globalThis.window === "undefined" ? import_react.useEffect : import_react.useLayoutEffect;
54
+ function resolveFrontXQueryClient(app) {
55
+ const clientApp = app;
56
+ return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_RESOLVER_SYMBOL]?.();
57
+ }
58
+ function bootstrapFrontXQueryClient(app) {
59
+ return resolveFrontXQueryClient(app);
60
+ }
61
+ function useOptionalFrontXQueryClient() {
62
+ return (0, import_react.useContext)(FrontXQueryClientContext);
63
+ }
64
+
65
+ // src/testing.ts
66
+ var import_testing = require("@gears-frontx/framework/testing");
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ bootstrapFrontXQueryClient,
70
+ describeBootstrapMfeContract,
71
+ resolveFrontXQueryClient,
72
+ useOptionalFrontXQueryClient
73
+ });
74
+ //# sourceMappingURL=testing.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing.ts","../src/queryClient.tsx","../src/hooks/QueryCache.ts"],"sourcesContent":["/**\n * Test-only entry for @gears-frontx/react.\n *\n * Utilities here are intended for Vitest (or similar) suites that need access\n * to query-client wiring without importing package internals directly.\n */\n\n// @cpt-dod:cpt-frontx-dod-react-bindings-provider:p1\n\n// @cpt-begin:cpt-frontx-dod-react-bindings-provider:p1:inst-react-testing-reexports\nexport {\n bootstrapFrontXQueryClient,\n resolveFrontXQueryClient,\n useOptionalFrontXQueryClient,\n} from './queryClient';\n\n/** App-layer tests must import this from `@gears-frontx/react/testing`, not `@gears-frontx/framework/testing`. */\nexport { describeBootstrapMfeContract } from '@gears-frontx/framework/testing';\nexport type {\n BootstrapMfeResolveArgs,\n BootstrapMfeTestSpecOptions,\n} from '@gears-frontx/framework/testing';\n// @cpt-end:cpt-frontx-dod-react-bindings-provider:p1:inst-react-testing-reexports\n","import React, {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useState,\n} from 'react';\nimport {\n QueryClientContext,\n QueryClientProvider,\n type QueryClient,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseInfiniteQuery,\n useSuspenseQuery,\n type MutationFunctionContext,\n} from '@tanstack/react-query';\nimport {\n subscribeQueryCacheRuntimeChanged,\n type EndpointDescriptor,\n type FrontXApp,\n} from '@gears-frontx/framework';\nimport type { ApiQueryOverrides } from './hooks/useApiQuery';\nimport type {\n ApiInfiniteQueryOptions,\n ApiInfiniteQueryPageContext,\n} from './hooks/useApiInfiniteQuery';\nimport type { UseApiMutationOptions } from './hooks/useApiMutation';\nimport { createQueryCache, type MutationCallbackContext, type QueryCacheKey } from './hooks/QueryCache';\n\n// @cpt-FEATURE:implement-endpoint-descriptors:p3\n// @cpt-dod:cpt-frontx-dod-request-lifecycle-query-provider:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-use-api-query:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-react-query-client-symbols\nconst APP_QUERY_CLIENT_SYMBOL = Symbol.for('frontx:query-cache:app-client');\nconst APP_QUERY_CLIENT_RESOLVER_SYMBOL = Symbol.for('frontx:query-cache:app-client-resolver');\nconst APP_QUERY_CLIENT_ACTIVATOR_SYMBOL = Symbol.for('frontx:query-cache:app-client-activator');\n\ntype QueryClientApp = FrontXApp & {\n [APP_QUERY_CLIENT_SYMBOL]?: QueryClient;\n [APP_QUERY_CLIENT_RESOLVER_SYMBOL]?: () => QueryClient | undefined;\n [APP_QUERY_CLIENT_ACTIVATOR_SYMBOL]?: () => QueryClient | undefined;\n};\n\nconst FrontXQueryClientContext = createContext<QueryClient | undefined>(undefined);\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-react-query-client-symbols\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-resolve-app-query-client\ntype RuntimeAwareEndpointDescriptor<TData> = EndpointDescriptor<TData> & {\n fetch(options?: { signal?: AbortSignal; staleTime?: number }): Promise<TData>;\n};\n\ntype AppResolvedQueryClient = {\n app: FrontXApp;\n queryClient: QueryClient;\n};\n\nconst useCommitEffect = typeof globalThis.window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport function resolveFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n const clientApp = app as QueryClientApp;\n return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_RESOLVER_SYMBOL]?.();\n}\n\nexport function hasFrontXQueryClientActivator(app: FrontXApp): boolean {\n return typeof (app as QueryClientApp)[APP_QUERY_CLIENT_ACTIVATOR_SYMBOL] === 'function';\n}\n\nexport function activateFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n const clientApp = app as QueryClientApp;\n return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_ACTIVATOR_SYMBOL]?.();\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-resolve-app-query-client\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-imperative-cache-bootstrap\n/**\n * Imperatively invalidates cache entries for `target` on the QueryClient bound to `app`.\n * Use from non-React code (e.g. MFE actions); in components prefer `useQueryCache().invalidate`.\n */\nexport async function invalidateQueryCacheForApp(\n app: FrontXApp,\n target: EndpointDescriptor<unknown> | QueryCacheKey\n): Promise<void> {\n const queryClient = resolveFrontXQueryClient(app) ?? activateFrontXQueryClient(app);\n if (!queryClient) {\n throw new Error(\n '[invalidateQueryCacheForApp] No QueryClient on app. Ensure queryCache() or queryCacheShared() is in the plugin chain.'\n );\n }\n await createQueryCache(queryClient).invalidate(target);\n}\n\n/** Resolves the app-bound QueryClient without triggering plugin activation during render. */\nexport function bootstrapFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n return resolveFrontXQueryClient(app);\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-imperative-cache-bootstrap\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-use-bootstrapped-query-client\nexport function useBootstrappedFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n const fromApp = useMemo(() => bootstrapFrontXQueryClient(app), [app]);\n const [eventResolvedClient, setEventResolvedClient] = useState<AppResolvedQueryClient | undefined>(\n undefined\n );\n const queryClient =\n fromApp ?? (eventResolvedClient?.app === app ? eventResolvedClient.queryClient : undefined);\n\n useCommitEffect(() => {\n if (fromApp || !hasFrontXQueryClientActivator(app)) {\n return;\n }\n\n const tryResolveQueryClient = () => {\n const nextQueryClient = resolveFrontXQueryClient(app) ?? activateFrontXQueryClient(app);\n if (nextQueryClient) {\n setEventResolvedClient((current) => {\n if (current?.app === app && current.queryClient === nextQueryClient) {\n return current;\n }\n\n return {\n app,\n queryClient: nextQueryClient,\n };\n });\n }\n };\n\n // Only activate after commit so abandoned renders cannot leak retainers/listeners.\n tryResolveQueryClient();\n\n const subscription = subscribeQueryCacheRuntimeChanged(() => {\n tryResolveQueryClient();\n });\n\n return () => {\n subscription.unsubscribe();\n };\n }, [app, fromApp]);\n\n return queryClient;\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-use-bootstrapped-query-client\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-frontx-query-client-provider\nexport function FrontXQueryClientProvider({\n queryClient,\n children,\n}: Readonly<{\n queryClient: QueryClient | undefined;\n children: React.ReactNode;\n}>) {\n if (!queryClient) {\n return (\n <FrontXQueryClientContext.Provider value={undefined}>\n <QueryClientContext.Provider value={undefined}>{children}</QueryClientContext.Provider>\n </FrontXQueryClientContext.Provider>\n );\n }\n\n return (\n <FrontXQueryClientContext.Provider value={queryClient}>\n <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n </FrontXQueryClientContext.Provider>\n );\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-frontx-query-client-provider\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-query-client-context-hooks\nexport function useOptionalFrontXQueryClient(): QueryClient | undefined {\n return useContext(FrontXQueryClientContext);\n}\n\nexport function useRequiredFrontXQueryClient(): QueryClient {\n const queryClient = useOptionalFrontXQueryClient();\n if (!queryClient) {\n throw new Error(\n '[FrontXProvider] No query cache available. Add queryCache() or queryCacheShared() to your plugin composition.'\n );\n }\n\n return queryClient;\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-query-client-context-hooks\n\n// @cpt-begin:implement-endpoint-descriptors:p3:inst-descriptor-fetch-stale-helpers\nfunction buildPageContext<TPage>(\n page: TPage,\n pages: readonly TPage[],\n descriptor: EndpointDescriptor<TPage>,\n descriptors: readonly EndpointDescriptor<TPage>[]\n): ApiInfiniteQueryPageContext<TPage> {\n return {\n page,\n pages,\n descriptor,\n descriptors,\n };\n}\n\nfunction normalizeFetchStaleTime(staleTime: unknown): number | undefined {\n if (staleTime === 'static') {\n return Number.POSITIVE_INFINITY;\n }\n\n return typeof staleTime === 'number' ? staleTime : undefined;\n}\n\nfunction resolveFetchStaleTime<TData>(\n queryClient: QueryClient,\n descriptor: EndpointDescriptor<TData>,\n overrideStaleTime?: number\n): number | undefined {\n return normalizeFetchStaleTime(\n overrideStaleTime ??\n descriptor.staleTime ??\n queryClient.getDefaultOptions().queries?.staleTime\n );\n}\n\nfunction fetchDescriptor<TData>(\n descriptor: EndpointDescriptor<TData>,\n options?: { signal?: AbortSignal; staleTime?: number }\n): Promise<TData> {\n return (descriptor as RuntimeAwareEndpointDescriptor<TData>).fetch(options);\n}\n// @cpt-end:implement-endpoint-descriptors:p3:inst-descriptor-fetch-stale-helpers\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-query\nexport function useFrontXQuery<TData = unknown, TError = Error>(\n descriptor: EndpointDescriptor<TData>,\n overrides?: ApiQueryOverrides\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const staleTime = resolveFetchStaleTime(queryClient, descriptor, overrides?.staleTime);\n const result = useQuery<TData, TError>({\n queryKey: descriptor.key as unknown[],\n queryFn: ({ signal }) => fetchDescriptor(descriptor, { signal, staleTime }),\n staleTime: overrides?.staleTime ?? descriptor.staleTime,\n gcTime: overrides?.gcTime ?? descriptor.gcTime,\n });\n\n return {\n data: result.data,\n error: result.error,\n isLoading: result.isLoading,\n isFetching: result.isFetching,\n isError: result.isError,\n refetch: result.refetch,\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-query\nexport function useFrontXSuspenseQuery<TData = unknown, TError = Error>(\n descriptor: EndpointDescriptor<TData>,\n overrides?: ApiQueryOverrides\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const staleTime = resolveFetchStaleTime(queryClient, descriptor, overrides?.staleTime);\n const result = useSuspenseQuery<TData, TError>({\n queryKey: descriptor.key as unknown[],\n queryFn: ({ signal }) => fetchDescriptor(descriptor, { signal, staleTime }),\n staleTime: overrides?.staleTime ?? descriptor.staleTime,\n gcTime: overrides?.gcTime ?? descriptor.gcTime,\n });\n\n return {\n data: result.data,\n error: result.error,\n isFetching: result.isFetching,\n refetch: async () => {\n await result.refetch();\n },\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-infinite-query\nexport function useFrontXInfiniteQuery<TPage = unknown, TError = Error>(\n options: ApiInfiniteQueryOptions<TPage>\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const result = useInfiniteQuery<\n TPage,\n TError,\n readonly TPage[],\n readonly unknown[],\n EndpointDescriptor<TPage>\n >({\n queryKey: options.initialPage.key,\n initialPageParam: options.initialPage,\n queryFn: ({ pageParam, signal }) =>\n fetchDescriptor(pageParam, {\n signal,\n staleTime: resolveFetchStaleTime(queryClient, pageParam, options.staleTime),\n }),\n getNextPageParam: (\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n ) =>\n options.getNextPage(\n buildPageContext(\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n )\n ),\n getPreviousPageParam: options.getPreviousPage\n ? (firstPage, allPages, firstPageDescriptor, allPageDescriptors) =>\n options.getPreviousPage?.(\n buildPageContext(\n firstPage,\n allPages,\n firstPageDescriptor,\n allPageDescriptors\n )\n )\n : undefined,\n select: (data) => data.pages,\n staleTime: options.staleTime ?? options.initialPage.staleTime,\n gcTime: options.gcTime ?? options.initialPage.gcTime,\n maxPages: options.maxPages,\n });\n\n return {\n data: result.data,\n error: result.error,\n isLoading: result.isLoading,\n isFetching: result.isFetching,\n isError: result.isError,\n hasNextPage: result.hasNextPage ?? false,\n hasPreviousPage: result.hasPreviousPage ?? false,\n isFetchingNextPage: result.isFetchingNextPage,\n isFetchingPreviousPage: result.isFetchingPreviousPage,\n fetchNextPage: async () => {\n await result.fetchNextPage();\n },\n fetchPreviousPage: async () => {\n await result.fetchPreviousPage();\n },\n refetch: async () => {\n await result.refetch();\n },\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-infinite-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-infinite-query\nexport function useFrontXSuspenseInfiniteQuery<TPage = unknown, TError = Error>(\n options: ApiInfiniteQueryOptions<TPage>\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const result = useSuspenseInfiniteQuery<\n TPage,\n TError,\n readonly TPage[],\n readonly unknown[],\n EndpointDescriptor<TPage>\n >({\n queryKey: options.initialPage.key,\n initialPageParam: options.initialPage,\n queryFn: ({ pageParam, signal }) =>\n fetchDescriptor(pageParam, {\n signal,\n staleTime: resolveFetchStaleTime(queryClient, pageParam, options.staleTime),\n }),\n getNextPageParam: (\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n ) =>\n options.getNextPage(\n buildPageContext(\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n )\n ),\n getPreviousPageParam: options.getPreviousPage\n ? (firstPage, allPages, firstPageDescriptor, allPageDescriptors) =>\n options.getPreviousPage?.(\n buildPageContext(\n firstPage,\n allPages,\n firstPageDescriptor,\n allPageDescriptors\n )\n )\n : undefined,\n select: (data) => data.pages,\n staleTime: options.staleTime ?? options.initialPage.staleTime,\n gcTime: options.gcTime ?? options.initialPage.gcTime,\n maxPages: options.maxPages,\n });\n\n return {\n data: result.data,\n error: result.error,\n isFetching: result.isFetching,\n hasNextPage: result.hasNextPage ?? false,\n hasPreviousPage: result.hasPreviousPage ?? false,\n isFetchingNextPage: result.isFetchingNextPage,\n isFetchingPreviousPage: result.isFetchingPreviousPage,\n fetchNextPage: async () => {\n await result.fetchNextPage();\n },\n fetchPreviousPage: async () => {\n await result.fetchPreviousPage();\n },\n refetch: async () => {\n await result.refetch();\n },\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-infinite-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2:inst-delegate-frontx-use-mutation\nexport function useFrontXMutation<\n TData = unknown,\n TError = Error,\n TVariables = void,\n TContext = unknown,\n>(\n options: UseApiMutationOptions<TData, TError, TVariables, TContext>,\n callbackCtx: MutationCallbackContext\n) {\n useRequiredFrontXQueryClient();\n\n const latestRef = React.useRef({ options, callbackCtx });\n latestRef.current = { options, callbackCtx };\n const fetchAbortControllersRef = React.useRef<Set<AbortController>>(new Set());\n\n React.useEffect(() => {\n const fetchAbortControllers = fetchAbortControllersRef.current;\n return () => {\n if (latestRef.current.options.abortOnUnmount) {\n for (const controller of fetchAbortControllers) {\n controller.abort();\n }\n fetchAbortControllers.clear();\n }\n };\n }, []);\n\n const mutationFn = React.useCallback((variables: TVariables, context: MutationFunctionContext) => {\n const cancelOnSupersede = latestRef.current.options.cancelOnSupersede;\n if (cancelOnSupersede) {\n for (const controller of fetchAbortControllersRef.current) {\n controller.abort();\n }\n fetchAbortControllersRef.current.clear();\n }\n\n const controller = new AbortController();\n fetchAbortControllersRef.current.add(controller);\n\n const librarySignal = (context as MutationFunctionContext & { signal?: AbortSignal }).signal;\n if (cancelOnSupersede && librarySignal) {\n if (librarySignal.aborted) {\n controller.abort();\n } else {\n librarySignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n }\n\n return latestRef.current.options.endpoint\n .fetch(variables, { signal: controller.signal })\n .finally(() => {\n fetchAbortControllersRef.current.delete(controller);\n });\n }, []);\n\n const onMutate = React.useCallback((variables: TVariables, _context: MutationFunctionContext) => {\n const current = latestRef.current;\n if (!current.options.onMutate) {\n return undefined as TContext;\n }\n\n return current.options.onMutate(variables, current.callbackCtx);\n }, []);\n\n const onSuccess = React.useCallback((data: TData, variables: TVariables, context: TContext | undefined) => {\n const current = latestRef.current;\n return current.options.onSuccess?.(data, variables, context, current.callbackCtx);\n }, []);\n\n const onError = React.useCallback((error: TError, variables: TVariables, context: TContext | undefined) => {\n const current = latestRef.current;\n return current.options.onError?.(error, variables, context, current.callbackCtx);\n }, []);\n\n const onSettled = React.useCallback((\n data: TData | undefined,\n error: TError | null,\n variables: TVariables,\n context: TContext | undefined\n ) => {\n const current = latestRef.current;\n return current.options.onSettled?.(\n data,\n error,\n variables,\n context,\n current.callbackCtx\n );\n }, []);\n\n const mutation = useMutation<TData, TError, TVariables, TContext>({\n mutationFn,\n onMutate: options.onMutate ? onMutate : undefined,\n onSuccess: options.onSuccess ? onSuccess : undefined,\n onError: options.onError ? onError : undefined,\n onSettled: options.onSettled ? onSettled : undefined,\n });\n\n return {\n mutate: mutation.mutate as (variables: TVariables) => void,\n mutateAsync: mutation.mutateAsync as (variables: TVariables) => Promise<TData>,\n isPending: mutation.isPending,\n error: mutation.error,\n data: mutation.data,\n reset: mutation.reset,\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2:inst-delegate-frontx-use-mutation\n","/**\n * QueryCache — restricted interface for interacting with the shared QueryClient.\n *\n * MFEs and screen-set components access the cache only through this sanctioned\n * public interface. It is injected as { queryCache } into useApiMutation\n * callbacks and also returned by useQueryCache() for controlled imperative\n * cache access.\n */\n// @cpt-dod:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2:inst-create-query-cache\n// @cpt-FEATURE:implement-endpoint-descriptors:p3\n\nimport type { EndpointDescriptor } from '@gears-frontx/framework';\nimport { eventBus } from '@gears-frontx/framework';\nimport { type QueryClient, type QueryState } from '@tanstack/react-query';\n\nexport type QueryCacheKey = readonly unknown[];\nexport type QueryCacheRefetchMode = 'active' | 'inactive' | 'all' | 'none';\nexport type QueryCacheInvalidateTarget =\n | EndpointDescriptor<unknown>\n | QueryCacheKey\n | null\n | undefined;\n\nexport interface QueryCacheState<TData = unknown, TError = Error> {\n data: TData | undefined;\n dataUpdatedAt: number;\n error: TError | null;\n errorUpdatedAt: number;\n fetchFailureCount: number;\n fetchFailureReason: TError | null;\n fetchStatus: 'fetching' | 'paused' | 'idle';\n isInvalidated: boolean;\n status: 'pending' | 'error' | 'success';\n}\n\nexport type QueryCacheInvalidateFilters = {\n queryKey?: QueryCacheInvalidateTarget;\n exact?: boolean;\n refetchType?: QueryCacheRefetchMode;\n};\n\ntype QueryCacheUpdater<T> = T | ((old: T | undefined) => T | undefined);\n\ntype CacheInvalidateEvent = {\n queryKey: QueryCacheKey;\n exact?: boolean;\n refetchType?: QueryCacheRefetchMode;\n source?: string;\n};\n\ntype CacheSetEvent = {\n queryKey: QueryCacheKey;\n dataOrUpdater: QueryCacheUpdater<unknown>;\n source?: string;\n};\n\ntype CacheRemoveEvent = {\n queryKey: QueryCacheKey;\n source?: string;\n};\n\nconst QUERY_CLIENT_BROADCAST_TARGET = Symbol.for('frontx:query-cache:broadcast-target');\nconst QUERY_CLIENT_BROADCAST_COUNTER = Symbol.for('frontx:query-cache:broadcast-counter');\n\ntype QueryClientWithMetadata = QueryClient & {\n [QUERY_CLIENT_BROADCAST_TARGET]?: string;\n};\n\ntype QueryClientCounterHost = typeof globalThis & {\n [QUERY_CLIENT_BROADCAST_COUNTER]?: number;\n};\n\nfunction emitCacheEvent(event: string, payload: unknown): void {\n (eventBus.emit as (eventType: string, payload: unknown) => void)(event, payload);\n}\n\nfunction ensureBroadcastSource(queryClient: QueryClient): string {\n const clientWithMetadata = queryClient as QueryClientWithMetadata;\n const existingSource = clientWithMetadata[QUERY_CLIENT_BROADCAST_TARGET];\n if (typeof existingSource === 'string' && existingSource.length > 0) {\n return existingSource;\n }\n\n const host = globalThis as QueryClientCounterHost;\n const nextCounter = (host[QUERY_CLIENT_BROADCAST_COUNTER] ?? 0) + 1;\n host[QUERY_CLIENT_BROADCAST_COUNTER] = nextCounter;\n\n const source = `query-cache-${nextCounter}`;\n clientWithMetadata[QUERY_CLIENT_BROADCAST_TARGET] = source;\n return source;\n}\n\n// @cpt-begin:implement-endpoint-descriptors:p3:inst-resolve-key\nexport function resolveKey(target: EndpointDescriptor<unknown> | QueryCacheKey): readonly unknown[] {\n if (\n target !== null &&\n typeof target === 'object' &&\n !Array.isArray(target) &&\n 'key' in target &&\n 'fetch' in target &&\n Array.isArray(target.key) &&\n typeof target.fetch === 'function'\n ) {\n return target.key;\n }\n\n return target as readonly unknown[];\n}\n// @cpt-end:implement-endpoint-descriptors:p3:inst-resolve-key\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-query-cache-interface\nexport interface QueryCache {\n get<T>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): T | undefined;\n getState<TData = unknown, TError = Error>(\n queryKey: EndpointDescriptor<unknown> | QueryCacheKey\n ): QueryCacheState<TData, TError> | undefined;\n set<T>(\n queryKey: EndpointDescriptor<unknown> | QueryCacheKey,\n dataOrUpdater: T | ((old: T | undefined) => T | undefined)\n ): void;\n cancel(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;\n invalidate(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;\n invalidateMany(filters?: QueryCacheInvalidateFilters | null): Promise<void>;\n remove(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): void;\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-query-cache-interface\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-mutation-callback-context\nexport interface MutationCallbackContext {\n queryCache: QueryCache;\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-mutation-callback-context\n\nfunction toQueryUpdater<T>(\n dataOrUpdater: QueryCacheUpdater<T>\n): (old: T | undefined) => T | undefined {\n if (typeof dataOrUpdater === 'function') {\n return dataOrUpdater as (old: T | undefined) => T | undefined;\n }\n\n return () => dataOrUpdater;\n}\n\nfunction mapQueryState<TData, TError = Error>(\n state: QueryState<TData, TError>\n): QueryCacheState<TData, TError> {\n return {\n data: state.data,\n dataUpdatedAt: state.dataUpdatedAt,\n error: state.error,\n errorUpdatedAt: state.errorUpdatedAt,\n fetchFailureCount: state.fetchFailureCount,\n fetchFailureReason: state.fetchFailureReason,\n fetchStatus: state.fetchStatus,\n isInvalidated: state.isInvalidated,\n status: state.status,\n };\n}\n\n// @cpt-begin:implement-endpoint-descriptors:p3:inst-create-query-cache\nexport function createQueryCache(queryClient: QueryClient): QueryCache {\n return {\n get: <T,>(key: EndpointDescriptor<unknown> | QueryCacheKey): T | undefined => {\n return queryClient.getQueryData<T>(resolveKey(key));\n },\n getState: <TData = unknown, TError = Error>(key: EndpointDescriptor<unknown> | QueryCacheKey) => {\n const state = queryClient.getQueryState<TData, TError>(resolveKey(key));\n return state ? mapQueryState(state) : undefined;\n },\n set: <T,>(key: EndpointDescriptor<unknown> | QueryCacheKey, dataOrUpdater: QueryCacheUpdater<T>): void => {\n const resolvedKey = resolveKey(key);\n queryClient.setQueryData<T>(resolvedKey, toQueryUpdater(dataOrUpdater));\n emitCacheEvent('cache/set', {\n queryKey: resolvedKey,\n dataOrUpdater: dataOrUpdater as QueryCacheUpdater<unknown>,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheSetEvent);\n },\n cancel: (key: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void> => {\n return queryClient.cancelQueries({ queryKey: resolveKey(key) });\n },\n invalidate: (key: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void> => {\n const resolvedKey = resolveKey(key);\n const result = queryClient.invalidateQueries({ queryKey: resolvedKey });\n emitCacheEvent('cache/invalidate', {\n queryKey: resolvedKey,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheInvalidateEvent);\n return result;\n },\n invalidateMany: (filters): Promise<void> => {\n if (filters?.queryKey == null) {\n return Promise.resolve();\n }\n\n const resolvedKey = resolveKey(filters.queryKey);\n if (!Array.isArray(resolvedKey) || resolvedKey.length === 0) {\n return Promise.resolve();\n }\n\n const payload = {\n ...filters,\n queryKey: resolvedKey,\n };\n const result = queryClient.invalidateQueries(payload);\n emitCacheEvent('cache/invalidate', {\n ...payload,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheInvalidateEvent);\n return result;\n },\n remove: (key: EndpointDescriptor<unknown> | QueryCacheKey): void => {\n const resolvedKey = resolveKey(key);\n queryClient.removeQueries({ queryKey: resolvedKey });\n emitCacheEvent('cache/remove', {\n queryKey: resolvedKey,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheRemoveEvent);\n },\n };\n}\n// @cpt-end:implement-endpoint-descriptors:p3:inst-create-query-cache\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAOO;AACP,yBAUO;AACP,IAAAA,oBAIO;;;ACVP,uBAAyB;;;ADmJjB;AAzHR,IAAM,0BAA0B,uBAAO,IAAI,+BAA+B;AAC1E,IAAM,mCAAmC,uBAAO,IAAI,wCAAwC;AAS5F,IAAM,+BAA2B,4BAAuC,MAAS;AAajF,IAAM,kBAAkB,OAAO,WAAW,WAAW,cAAc,yBAAY;AAExE,SAAS,yBAAyB,KAAyC;AAChF,QAAM,YAAY;AAClB,SAAO,UAAU,uBAAuB,KAAK,UAAU,gCAAgC,IAAI;AAC7F;AA+BO,SAAS,2BAA2B,KAAyC;AAClF,SAAO,yBAAyB,GAAG;AACrC;AA0EO,SAAS,+BAAwD;AACtE,aAAO,yBAAW,wBAAwB;AAC5C;;;AD/JA,qBAA6C;","names":["import_framework"]}
@@ -0,0 +1,4 @@
1
+ export { c as bootstrapFrontXQueryClient, r as resolveFrontXQueryClient, u as useOptionalFrontXQueryClient } from './testing-Dlmfu6tE.cjs';
2
+ export { BootstrapMfeResolveArgs, BootstrapMfeTestSpecOptions, describeBootstrapMfeContract } from '@gears-frontx/framework/testing';
3
+ import '@tanstack/react-query';
4
+ import '@gears-frontx/framework';
@@ -0,0 +1,4 @@
1
+ export { c as bootstrapFrontXQueryClient, r as resolveFrontXQueryClient, u as useOptionalFrontXQueryClient } from './testing-Dlmfu6tE.js';
2
+ export { BootstrapMfeResolveArgs, BootstrapMfeTestSpecOptions, describeBootstrapMfeContract } from '@gears-frontx/framework/testing';
3
+ import '@tanstack/react-query';
4
+ import '@gears-frontx/framework';
@@ -0,0 +1,51 @@
1
+ // src/queryClient.tsx
2
+ import React, {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useState
9
+ } from "react";
10
+ import {
11
+ QueryClientContext,
12
+ QueryClientProvider,
13
+ useInfiniteQuery,
14
+ useMutation,
15
+ useQuery,
16
+ useSuspenseInfiniteQuery,
17
+ useSuspenseQuery
18
+ } from "@tanstack/react-query";
19
+ import {
20
+ subscribeQueryCacheRuntimeChanged
21
+ } from "@gears-frontx/framework";
22
+
23
+ // src/hooks/QueryCache.ts
24
+ import { eventBus } from "@gears-frontx/framework";
25
+
26
+ // src/queryClient.tsx
27
+ import { jsx } from "react/jsx-runtime";
28
+ var APP_QUERY_CLIENT_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client");
29
+ var APP_QUERY_CLIENT_RESOLVER_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client-resolver");
30
+ var FrontXQueryClientContext = createContext(void 0);
31
+ var useCommitEffect = typeof globalThis.window === "undefined" ? useEffect : useLayoutEffect;
32
+ function resolveFrontXQueryClient(app) {
33
+ const clientApp = app;
34
+ return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_RESOLVER_SYMBOL]?.();
35
+ }
36
+ function bootstrapFrontXQueryClient(app) {
37
+ return resolveFrontXQueryClient(app);
38
+ }
39
+ function useOptionalFrontXQueryClient() {
40
+ return useContext(FrontXQueryClientContext);
41
+ }
42
+
43
+ // src/testing.ts
44
+ import { describeBootstrapMfeContract } from "@gears-frontx/framework/testing";
45
+ export {
46
+ bootstrapFrontXQueryClient,
47
+ describeBootstrapMfeContract,
48
+ resolveFrontXQueryClient,
49
+ useOptionalFrontXQueryClient
50
+ };
51
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queryClient.tsx","../src/hooks/QueryCache.ts","../src/testing.ts"],"sourcesContent":["import React, {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useState,\n} from 'react';\nimport {\n QueryClientContext,\n QueryClientProvider,\n type QueryClient,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseInfiniteQuery,\n useSuspenseQuery,\n type MutationFunctionContext,\n} from '@tanstack/react-query';\nimport {\n subscribeQueryCacheRuntimeChanged,\n type EndpointDescriptor,\n type FrontXApp,\n} from '@gears-frontx/framework';\nimport type { ApiQueryOverrides } from './hooks/useApiQuery';\nimport type {\n ApiInfiniteQueryOptions,\n ApiInfiniteQueryPageContext,\n} from './hooks/useApiInfiniteQuery';\nimport type { UseApiMutationOptions } from './hooks/useApiMutation';\nimport { createQueryCache, type MutationCallbackContext, type QueryCacheKey } from './hooks/QueryCache';\n\n// @cpt-FEATURE:implement-endpoint-descriptors:p3\n// @cpt-dod:cpt-frontx-dod-request-lifecycle-query-provider:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-use-api-query:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-react-query-client-symbols\nconst APP_QUERY_CLIENT_SYMBOL = Symbol.for('frontx:query-cache:app-client');\nconst APP_QUERY_CLIENT_RESOLVER_SYMBOL = Symbol.for('frontx:query-cache:app-client-resolver');\nconst APP_QUERY_CLIENT_ACTIVATOR_SYMBOL = Symbol.for('frontx:query-cache:app-client-activator');\n\ntype QueryClientApp = FrontXApp & {\n [APP_QUERY_CLIENT_SYMBOL]?: QueryClient;\n [APP_QUERY_CLIENT_RESOLVER_SYMBOL]?: () => QueryClient | undefined;\n [APP_QUERY_CLIENT_ACTIVATOR_SYMBOL]?: () => QueryClient | undefined;\n};\n\nconst FrontXQueryClientContext = createContext<QueryClient | undefined>(undefined);\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-react-query-client-symbols\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-resolve-app-query-client\ntype RuntimeAwareEndpointDescriptor<TData> = EndpointDescriptor<TData> & {\n fetch(options?: { signal?: AbortSignal; staleTime?: number }): Promise<TData>;\n};\n\ntype AppResolvedQueryClient = {\n app: FrontXApp;\n queryClient: QueryClient;\n};\n\nconst useCommitEffect = typeof globalThis.window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport function resolveFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n const clientApp = app as QueryClientApp;\n return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_RESOLVER_SYMBOL]?.();\n}\n\nexport function hasFrontXQueryClientActivator(app: FrontXApp): boolean {\n return typeof (app as QueryClientApp)[APP_QUERY_CLIENT_ACTIVATOR_SYMBOL] === 'function';\n}\n\nexport function activateFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n const clientApp = app as QueryClientApp;\n return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_ACTIVATOR_SYMBOL]?.();\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-resolve-app-query-client\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-imperative-cache-bootstrap\n/**\n * Imperatively invalidates cache entries for `target` on the QueryClient bound to `app`.\n * Use from non-React code (e.g. MFE actions); in components prefer `useQueryCache().invalidate`.\n */\nexport async function invalidateQueryCacheForApp(\n app: FrontXApp,\n target: EndpointDescriptor<unknown> | QueryCacheKey\n): Promise<void> {\n const queryClient = resolveFrontXQueryClient(app) ?? activateFrontXQueryClient(app);\n if (!queryClient) {\n throw new Error(\n '[invalidateQueryCacheForApp] No QueryClient on app. Ensure queryCache() or queryCacheShared() is in the plugin chain.'\n );\n }\n await createQueryCache(queryClient).invalidate(target);\n}\n\n/** Resolves the app-bound QueryClient without triggering plugin activation during render. */\nexport function bootstrapFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n return resolveFrontXQueryClient(app);\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-imperative-cache-bootstrap\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-use-bootstrapped-query-client\nexport function useBootstrappedFrontXQueryClient(app: FrontXApp): QueryClient | undefined {\n const fromApp = useMemo(() => bootstrapFrontXQueryClient(app), [app]);\n const [eventResolvedClient, setEventResolvedClient] = useState<AppResolvedQueryClient | undefined>(\n undefined\n );\n const queryClient =\n fromApp ?? (eventResolvedClient?.app === app ? eventResolvedClient.queryClient : undefined);\n\n useCommitEffect(() => {\n if (fromApp || !hasFrontXQueryClientActivator(app)) {\n return;\n }\n\n const tryResolveQueryClient = () => {\n const nextQueryClient = resolveFrontXQueryClient(app) ?? activateFrontXQueryClient(app);\n if (nextQueryClient) {\n setEventResolvedClient((current) => {\n if (current?.app === app && current.queryClient === nextQueryClient) {\n return current;\n }\n\n return {\n app,\n queryClient: nextQueryClient,\n };\n });\n }\n };\n\n // Only activate after commit so abandoned renders cannot leak retainers/listeners.\n tryResolveQueryClient();\n\n const subscription = subscribeQueryCacheRuntimeChanged(() => {\n tryResolveQueryClient();\n });\n\n return () => {\n subscription.unsubscribe();\n };\n }, [app, fromApp]);\n\n return queryClient;\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-query-client-lifecycle:p2:inst-use-bootstrapped-query-client\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-frontx-query-client-provider\nexport function FrontXQueryClientProvider({\n queryClient,\n children,\n}: Readonly<{\n queryClient: QueryClient | undefined;\n children: React.ReactNode;\n}>) {\n if (!queryClient) {\n return (\n <FrontXQueryClientContext.Provider value={undefined}>\n <QueryClientContext.Provider value={undefined}>{children}</QueryClientContext.Provider>\n </FrontXQueryClientContext.Provider>\n );\n }\n\n return (\n <FrontXQueryClientContext.Provider value={queryClient}>\n <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n </FrontXQueryClientContext.Provider>\n );\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-frontx-query-client-provider\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-query-client-context-hooks\nexport function useOptionalFrontXQueryClient(): QueryClient | undefined {\n return useContext(FrontXQueryClientContext);\n}\n\nexport function useRequiredFrontXQueryClient(): QueryClient {\n const queryClient = useOptionalFrontXQueryClient();\n if (!queryClient) {\n throw new Error(\n '[FrontXProvider] No query cache available. Add queryCache() or queryCacheShared() to your plugin composition.'\n );\n }\n\n return queryClient;\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-query-provider:p2:inst-query-client-context-hooks\n\n// @cpt-begin:implement-endpoint-descriptors:p3:inst-descriptor-fetch-stale-helpers\nfunction buildPageContext<TPage>(\n page: TPage,\n pages: readonly TPage[],\n descriptor: EndpointDescriptor<TPage>,\n descriptors: readonly EndpointDescriptor<TPage>[]\n): ApiInfiniteQueryPageContext<TPage> {\n return {\n page,\n pages,\n descriptor,\n descriptors,\n };\n}\n\nfunction normalizeFetchStaleTime(staleTime: unknown): number | undefined {\n if (staleTime === 'static') {\n return Number.POSITIVE_INFINITY;\n }\n\n return typeof staleTime === 'number' ? staleTime : undefined;\n}\n\nfunction resolveFetchStaleTime<TData>(\n queryClient: QueryClient,\n descriptor: EndpointDescriptor<TData>,\n overrideStaleTime?: number\n): number | undefined {\n return normalizeFetchStaleTime(\n overrideStaleTime ??\n descriptor.staleTime ??\n queryClient.getDefaultOptions().queries?.staleTime\n );\n}\n\nfunction fetchDescriptor<TData>(\n descriptor: EndpointDescriptor<TData>,\n options?: { signal?: AbortSignal; staleTime?: number }\n): Promise<TData> {\n return (descriptor as RuntimeAwareEndpointDescriptor<TData>).fetch(options);\n}\n// @cpt-end:implement-endpoint-descriptors:p3:inst-descriptor-fetch-stale-helpers\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-query\nexport function useFrontXQuery<TData = unknown, TError = Error>(\n descriptor: EndpointDescriptor<TData>,\n overrides?: ApiQueryOverrides\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const staleTime = resolveFetchStaleTime(queryClient, descriptor, overrides?.staleTime);\n const result = useQuery<TData, TError>({\n queryKey: descriptor.key as unknown[],\n queryFn: ({ signal }) => fetchDescriptor(descriptor, { signal, staleTime }),\n staleTime: overrides?.staleTime ?? descriptor.staleTime,\n gcTime: overrides?.gcTime ?? descriptor.gcTime,\n });\n\n return {\n data: result.data,\n error: result.error,\n isLoading: result.isLoading,\n isFetching: result.isFetching,\n isError: result.isError,\n refetch: result.refetch,\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-query\nexport function useFrontXSuspenseQuery<TData = unknown, TError = Error>(\n descriptor: EndpointDescriptor<TData>,\n overrides?: ApiQueryOverrides\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const staleTime = resolveFetchStaleTime(queryClient, descriptor, overrides?.staleTime);\n const result = useSuspenseQuery<TData, TError>({\n queryKey: descriptor.key as unknown[],\n queryFn: ({ signal }) => fetchDescriptor(descriptor, { signal, staleTime }),\n staleTime: overrides?.staleTime ?? descriptor.staleTime,\n gcTime: overrides?.gcTime ?? descriptor.gcTime,\n });\n\n return {\n data: result.data,\n error: result.error,\n isFetching: result.isFetching,\n refetch: async () => {\n await result.refetch();\n },\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-infinite-query\nexport function useFrontXInfiniteQuery<TPage = unknown, TError = Error>(\n options: ApiInfiniteQueryOptions<TPage>\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const result = useInfiniteQuery<\n TPage,\n TError,\n readonly TPage[],\n readonly unknown[],\n EndpointDescriptor<TPage>\n >({\n queryKey: options.initialPage.key,\n initialPageParam: options.initialPage,\n queryFn: ({ pageParam, signal }) =>\n fetchDescriptor(pageParam, {\n signal,\n staleTime: resolveFetchStaleTime(queryClient, pageParam, options.staleTime),\n }),\n getNextPageParam: (\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n ) =>\n options.getNextPage(\n buildPageContext(\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n )\n ),\n getPreviousPageParam: options.getPreviousPage\n ? (firstPage, allPages, firstPageDescriptor, allPageDescriptors) =>\n options.getPreviousPage?.(\n buildPageContext(\n firstPage,\n allPages,\n firstPageDescriptor,\n allPageDescriptors\n )\n )\n : undefined,\n select: (data) => data.pages,\n staleTime: options.staleTime ?? options.initialPage.staleTime,\n gcTime: options.gcTime ?? options.initialPage.gcTime,\n maxPages: options.maxPages,\n });\n\n return {\n data: result.data,\n error: result.error,\n isLoading: result.isLoading,\n isFetching: result.isFetching,\n isError: result.isError,\n hasNextPage: result.hasNextPage ?? false,\n hasPreviousPage: result.hasPreviousPage ?? false,\n isFetchingNextPage: result.isFetchingNextPage,\n isFetchingPreviousPage: result.isFetchingPreviousPage,\n fetchNextPage: async () => {\n await result.fetchNextPage();\n },\n fetchPreviousPage: async () => {\n await result.fetchPreviousPage();\n },\n refetch: async () => {\n await result.refetch();\n },\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-infinite-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-infinite-query\nexport function useFrontXSuspenseInfiniteQuery<TPage = unknown, TError = Error>(\n options: ApiInfiniteQueryOptions<TPage>\n) {\n const queryClient = useRequiredFrontXQueryClient();\n const result = useSuspenseInfiniteQuery<\n TPage,\n TError,\n readonly TPage[],\n readonly unknown[],\n EndpointDescriptor<TPage>\n >({\n queryKey: options.initialPage.key,\n initialPageParam: options.initialPage,\n queryFn: ({ pageParam, signal }) =>\n fetchDescriptor(pageParam, {\n signal,\n staleTime: resolveFetchStaleTime(queryClient, pageParam, options.staleTime),\n }),\n getNextPageParam: (\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n ) =>\n options.getNextPage(\n buildPageContext(\n lastPage,\n allPages,\n lastPageDescriptor,\n allPageDescriptors\n )\n ),\n getPreviousPageParam: options.getPreviousPage\n ? (firstPage, allPages, firstPageDescriptor, allPageDescriptors) =>\n options.getPreviousPage?.(\n buildPageContext(\n firstPage,\n allPages,\n firstPageDescriptor,\n allPageDescriptors\n )\n )\n : undefined,\n select: (data) => data.pages,\n staleTime: options.staleTime ?? options.initialPage.staleTime,\n gcTime: options.gcTime ?? options.initialPage.gcTime,\n maxPages: options.maxPages,\n });\n\n return {\n data: result.data,\n error: result.error,\n isFetching: result.isFetching,\n hasNextPage: result.hasNextPage ?? false,\n hasPreviousPage: result.hasPreviousPage ?? false,\n isFetchingNextPage: result.isFetchingNextPage,\n isFetchingPreviousPage: result.isFetchingPreviousPage,\n fetchNextPage: async () => {\n await result.fetchNextPage();\n },\n fetchPreviousPage: async () => {\n await result.fetchPreviousPage();\n },\n refetch: async () => {\n await result.refetch();\n },\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-query:p2:inst-delegate-frontx-use-suspense-infinite-query\n\n// @cpt-begin:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2:inst-delegate-frontx-use-mutation\nexport function useFrontXMutation<\n TData = unknown,\n TError = Error,\n TVariables = void,\n TContext = unknown,\n>(\n options: UseApiMutationOptions<TData, TError, TVariables, TContext>,\n callbackCtx: MutationCallbackContext\n) {\n useRequiredFrontXQueryClient();\n\n const latestRef = React.useRef({ options, callbackCtx });\n latestRef.current = { options, callbackCtx };\n const fetchAbortControllersRef = React.useRef<Set<AbortController>>(new Set());\n\n React.useEffect(() => {\n const fetchAbortControllers = fetchAbortControllersRef.current;\n return () => {\n if (latestRef.current.options.abortOnUnmount) {\n for (const controller of fetchAbortControllers) {\n controller.abort();\n }\n fetchAbortControllers.clear();\n }\n };\n }, []);\n\n const mutationFn = React.useCallback((variables: TVariables, context: MutationFunctionContext) => {\n const cancelOnSupersede = latestRef.current.options.cancelOnSupersede;\n if (cancelOnSupersede) {\n for (const controller of fetchAbortControllersRef.current) {\n controller.abort();\n }\n fetchAbortControllersRef.current.clear();\n }\n\n const controller = new AbortController();\n fetchAbortControllersRef.current.add(controller);\n\n const librarySignal = (context as MutationFunctionContext & { signal?: AbortSignal }).signal;\n if (cancelOnSupersede && librarySignal) {\n if (librarySignal.aborted) {\n controller.abort();\n } else {\n librarySignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n }\n\n return latestRef.current.options.endpoint\n .fetch(variables, { signal: controller.signal })\n .finally(() => {\n fetchAbortControllersRef.current.delete(controller);\n });\n }, []);\n\n const onMutate = React.useCallback((variables: TVariables, _context: MutationFunctionContext) => {\n const current = latestRef.current;\n if (!current.options.onMutate) {\n return undefined as TContext;\n }\n\n return current.options.onMutate(variables, current.callbackCtx);\n }, []);\n\n const onSuccess = React.useCallback((data: TData, variables: TVariables, context: TContext | undefined) => {\n const current = latestRef.current;\n return current.options.onSuccess?.(data, variables, context, current.callbackCtx);\n }, []);\n\n const onError = React.useCallback((error: TError, variables: TVariables, context: TContext | undefined) => {\n const current = latestRef.current;\n return current.options.onError?.(error, variables, context, current.callbackCtx);\n }, []);\n\n const onSettled = React.useCallback((\n data: TData | undefined,\n error: TError | null,\n variables: TVariables,\n context: TContext | undefined\n ) => {\n const current = latestRef.current;\n return current.options.onSettled?.(\n data,\n error,\n variables,\n context,\n current.callbackCtx\n );\n }, []);\n\n const mutation = useMutation<TData, TError, TVariables, TContext>({\n mutationFn,\n onMutate: options.onMutate ? onMutate : undefined,\n onSuccess: options.onSuccess ? onSuccess : undefined,\n onError: options.onError ? onError : undefined,\n onSettled: options.onSettled ? onSettled : undefined,\n });\n\n return {\n mutate: mutation.mutate as (variables: TVariables) => void,\n mutateAsync: mutation.mutateAsync as (variables: TVariables) => Promise<TData>,\n isPending: mutation.isPending,\n error: mutation.error,\n data: mutation.data,\n reset: mutation.reset,\n };\n}\n// @cpt-end:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2:inst-delegate-frontx-use-mutation\n","/**\n * QueryCache — restricted interface for interacting with the shared QueryClient.\n *\n * MFEs and screen-set components access the cache only through this sanctioned\n * public interface. It is injected as { queryCache } into useApiMutation\n * callbacks and also returned by useQueryCache() for controlled imperative\n * cache access.\n */\n// @cpt-dod:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2\n// @cpt-flow:cpt-frontx-flow-request-lifecycle-use-api-mutation:p2:inst-create-query-cache\n// @cpt-FEATURE:implement-endpoint-descriptors:p3\n\nimport type { EndpointDescriptor } from '@gears-frontx/framework';\nimport { eventBus } from '@gears-frontx/framework';\nimport { type QueryClient, type QueryState } from '@tanstack/react-query';\n\nexport type QueryCacheKey = readonly unknown[];\nexport type QueryCacheRefetchMode = 'active' | 'inactive' | 'all' | 'none';\nexport type QueryCacheInvalidateTarget =\n | EndpointDescriptor<unknown>\n | QueryCacheKey\n | null\n | undefined;\n\nexport interface QueryCacheState<TData = unknown, TError = Error> {\n data: TData | undefined;\n dataUpdatedAt: number;\n error: TError | null;\n errorUpdatedAt: number;\n fetchFailureCount: number;\n fetchFailureReason: TError | null;\n fetchStatus: 'fetching' | 'paused' | 'idle';\n isInvalidated: boolean;\n status: 'pending' | 'error' | 'success';\n}\n\nexport type QueryCacheInvalidateFilters = {\n queryKey?: QueryCacheInvalidateTarget;\n exact?: boolean;\n refetchType?: QueryCacheRefetchMode;\n};\n\ntype QueryCacheUpdater<T> = T | ((old: T | undefined) => T | undefined);\n\ntype CacheInvalidateEvent = {\n queryKey: QueryCacheKey;\n exact?: boolean;\n refetchType?: QueryCacheRefetchMode;\n source?: string;\n};\n\ntype CacheSetEvent = {\n queryKey: QueryCacheKey;\n dataOrUpdater: QueryCacheUpdater<unknown>;\n source?: string;\n};\n\ntype CacheRemoveEvent = {\n queryKey: QueryCacheKey;\n source?: string;\n};\n\nconst QUERY_CLIENT_BROADCAST_TARGET = Symbol.for('frontx:query-cache:broadcast-target');\nconst QUERY_CLIENT_BROADCAST_COUNTER = Symbol.for('frontx:query-cache:broadcast-counter');\n\ntype QueryClientWithMetadata = QueryClient & {\n [QUERY_CLIENT_BROADCAST_TARGET]?: string;\n};\n\ntype QueryClientCounterHost = typeof globalThis & {\n [QUERY_CLIENT_BROADCAST_COUNTER]?: number;\n};\n\nfunction emitCacheEvent(event: string, payload: unknown): void {\n (eventBus.emit as (eventType: string, payload: unknown) => void)(event, payload);\n}\n\nfunction ensureBroadcastSource(queryClient: QueryClient): string {\n const clientWithMetadata = queryClient as QueryClientWithMetadata;\n const existingSource = clientWithMetadata[QUERY_CLIENT_BROADCAST_TARGET];\n if (typeof existingSource === 'string' && existingSource.length > 0) {\n return existingSource;\n }\n\n const host = globalThis as QueryClientCounterHost;\n const nextCounter = (host[QUERY_CLIENT_BROADCAST_COUNTER] ?? 0) + 1;\n host[QUERY_CLIENT_BROADCAST_COUNTER] = nextCounter;\n\n const source = `query-cache-${nextCounter}`;\n clientWithMetadata[QUERY_CLIENT_BROADCAST_TARGET] = source;\n return source;\n}\n\n// @cpt-begin:implement-endpoint-descriptors:p3:inst-resolve-key\nexport function resolveKey(target: EndpointDescriptor<unknown> | QueryCacheKey): readonly unknown[] {\n if (\n target !== null &&\n typeof target === 'object' &&\n !Array.isArray(target) &&\n 'key' in target &&\n 'fetch' in target &&\n Array.isArray(target.key) &&\n typeof target.fetch === 'function'\n ) {\n return target.key;\n }\n\n return target as readonly unknown[];\n}\n// @cpt-end:implement-endpoint-descriptors:p3:inst-resolve-key\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-query-cache-interface\nexport interface QueryCache {\n get<T>(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): T | undefined;\n getState<TData = unknown, TError = Error>(\n queryKey: EndpointDescriptor<unknown> | QueryCacheKey\n ): QueryCacheState<TData, TError> | undefined;\n set<T>(\n queryKey: EndpointDescriptor<unknown> | QueryCacheKey,\n dataOrUpdater: T | ((old: T | undefined) => T | undefined)\n ): void;\n cancel(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;\n invalidate(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void>;\n invalidateMany(filters?: QueryCacheInvalidateFilters | null): Promise<void>;\n remove(queryKey: EndpointDescriptor<unknown> | QueryCacheKey): void;\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-query-cache-interface\n\n// @cpt-begin:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-mutation-callback-context\nexport interface MutationCallbackContext {\n queryCache: QueryCache;\n}\n// @cpt-end:cpt-frontx-dod-request-lifecycle-use-api-mutation:p2:inst-mutation-callback-context\n\nfunction toQueryUpdater<T>(\n dataOrUpdater: QueryCacheUpdater<T>\n): (old: T | undefined) => T | undefined {\n if (typeof dataOrUpdater === 'function') {\n return dataOrUpdater as (old: T | undefined) => T | undefined;\n }\n\n return () => dataOrUpdater;\n}\n\nfunction mapQueryState<TData, TError = Error>(\n state: QueryState<TData, TError>\n): QueryCacheState<TData, TError> {\n return {\n data: state.data,\n dataUpdatedAt: state.dataUpdatedAt,\n error: state.error,\n errorUpdatedAt: state.errorUpdatedAt,\n fetchFailureCount: state.fetchFailureCount,\n fetchFailureReason: state.fetchFailureReason,\n fetchStatus: state.fetchStatus,\n isInvalidated: state.isInvalidated,\n status: state.status,\n };\n}\n\n// @cpt-begin:implement-endpoint-descriptors:p3:inst-create-query-cache\nexport function createQueryCache(queryClient: QueryClient): QueryCache {\n return {\n get: <T,>(key: EndpointDescriptor<unknown> | QueryCacheKey): T | undefined => {\n return queryClient.getQueryData<T>(resolveKey(key));\n },\n getState: <TData = unknown, TError = Error>(key: EndpointDescriptor<unknown> | QueryCacheKey) => {\n const state = queryClient.getQueryState<TData, TError>(resolveKey(key));\n return state ? mapQueryState(state) : undefined;\n },\n set: <T,>(key: EndpointDescriptor<unknown> | QueryCacheKey, dataOrUpdater: QueryCacheUpdater<T>): void => {\n const resolvedKey = resolveKey(key);\n queryClient.setQueryData<T>(resolvedKey, toQueryUpdater(dataOrUpdater));\n emitCacheEvent('cache/set', {\n queryKey: resolvedKey,\n dataOrUpdater: dataOrUpdater as QueryCacheUpdater<unknown>,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheSetEvent);\n },\n cancel: (key: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void> => {\n return queryClient.cancelQueries({ queryKey: resolveKey(key) });\n },\n invalidate: (key: EndpointDescriptor<unknown> | QueryCacheKey): Promise<void> => {\n const resolvedKey = resolveKey(key);\n const result = queryClient.invalidateQueries({ queryKey: resolvedKey });\n emitCacheEvent('cache/invalidate', {\n queryKey: resolvedKey,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheInvalidateEvent);\n return result;\n },\n invalidateMany: (filters): Promise<void> => {\n if (filters?.queryKey == null) {\n return Promise.resolve();\n }\n\n const resolvedKey = resolveKey(filters.queryKey);\n if (!Array.isArray(resolvedKey) || resolvedKey.length === 0) {\n return Promise.resolve();\n }\n\n const payload = {\n ...filters,\n queryKey: resolvedKey,\n };\n const result = queryClient.invalidateQueries(payload);\n emitCacheEvent('cache/invalidate', {\n ...payload,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheInvalidateEvent);\n return result;\n },\n remove: (key: EndpointDescriptor<unknown> | QueryCacheKey): void => {\n const resolvedKey = resolveKey(key);\n queryClient.removeQueries({ queryKey: resolvedKey });\n emitCacheEvent('cache/remove', {\n queryKey: resolvedKey,\n source: ensureBroadcastSource(queryClient),\n } satisfies CacheRemoveEvent);\n },\n };\n}\n// @cpt-end:implement-endpoint-descriptors:p3:inst-create-query-cache\n","/**\n * Test-only entry for @gears-frontx/react.\n *\n * Utilities here are intended for Vitest (or similar) suites that need access\n * to query-client wiring without importing package internals directly.\n */\n\n// @cpt-dod:cpt-frontx-dod-react-bindings-provider:p1\n\n// @cpt-begin:cpt-frontx-dod-react-bindings-provider:p1:inst-react-testing-reexports\nexport {\n bootstrapFrontXQueryClient,\n resolveFrontXQueryClient,\n useOptionalFrontXQueryClient,\n} from './queryClient';\n\n/** App-layer tests must import this from `@gears-frontx/react/testing`, not `@gears-frontx/framework/testing`. */\nexport { describeBootstrapMfeContract } from '@gears-frontx/framework/testing';\nexport type {\n BootstrapMfeResolveArgs,\n BootstrapMfeTestSpecOptions,\n} from '@gears-frontx/framework/testing';\n// @cpt-end:cpt-frontx-dod-react-bindings-provider:p1:inst-react-testing-reexports\n"],"mappings":";AAAA,OAAO;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAGK;;;ACVP,SAAS,gBAAgB;;;ADmJjB;AAzHR,IAAM,0BAA0B,uBAAO,IAAI,+BAA+B;AAC1E,IAAM,mCAAmC,uBAAO,IAAI,wCAAwC;AAS5F,IAAM,2BAA2B,cAAuC,MAAS;AAajF,IAAM,kBAAkB,OAAO,WAAW,WAAW,cAAc,YAAY;AAExE,SAAS,yBAAyB,KAAyC;AAChF,QAAM,YAAY;AAClB,SAAO,UAAU,uBAAuB,KAAK,UAAU,gCAAgC,IAAI;AAC7F;AA+BO,SAAS,2BAA2B,KAAyC;AAClF,SAAO,yBAAyB,GAAG;AACrC;AA0EO,SAAS,+BAAwD;AACtE,SAAO,WAAW,wBAAwB;AAC5C;;;AE/JA,SAAS,oCAAoC;","names":[]}
@@ -0,0 +1,231 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { ChildMfeBridge, AccessRecord, AccessQuery, FrontXConfig, FrontXApp, RootState, Formatters, Language } from '@gears-frontx/framework';
4
+
5
+ /**
6
+ * MFE Context Value
7
+ * Contains bridge and metadata about the MFE instance.
8
+ */
9
+ interface MfeContextValue {
10
+ /** Child bridge for communication with host */
11
+ bridge: ChildMfeBridge;
12
+ /** Extension ID */
13
+ extensionId: string;
14
+ /** Domain ID where MFE is mounted */
15
+ domainId: string;
16
+ }
17
+ /**
18
+ * MFE Context
19
+ * Holds the MFE bridge and metadata for child components.
20
+ */
21
+ declare const MfeContext: React.Context<MfeContextValue | null>;
22
+ /**
23
+ * Use the MFE context.
24
+ * Throws if used outside of MFE context.
25
+ *
26
+ * @returns The MFE context value
27
+ */
28
+ declare function useMfeContext(): MfeContextValue;
29
+
30
+ /**
31
+ * @gears-frontx/react - Type Definitions
32
+ *
33
+ * Core types for FrontX React bindings.
34
+ * Provides type-safe hooks and components.
35
+ *
36
+ * Now using real imports from @gears-frontx/framework since packages are built together.
37
+ */
38
+
39
+ /**
40
+ * Gears FrontX-owned query result type.
41
+ * Returned by useApiQuery — callers depend on this contract, not on TanStack internals.
42
+ */
43
+ interface ApiQueryResult<TData, TError = Error> {
44
+ data: TData | undefined;
45
+ error: TError | null;
46
+ isLoading: boolean;
47
+ isFetching: boolean;
48
+ isError: boolean;
49
+ refetch: () => Promise<unknown>;
50
+ }
51
+ /**
52
+ * Gears FrontX-owned suspense query result type.
53
+ * Returned by useApiSuspenseQuery — callers depend on this contract, not on TanStack internals.
54
+ */
55
+ interface ApiSuspenseQueryResult<TData, TError = Error> {
56
+ data: TData;
57
+ error: TError | null;
58
+ isFetching: boolean;
59
+ refetch: () => Promise<void>;
60
+ }
61
+ /**
62
+ * Gears FrontX-owned infinite query result type.
63
+ * Returned by useApiInfiniteQuery for descriptor-driven pagination.
64
+ */
65
+ interface ApiInfiniteQueryResult<TPage, TError = Error> {
66
+ data: readonly TPage[] | undefined;
67
+ error: TError | null;
68
+ isLoading: boolean;
69
+ isFetching: boolean;
70
+ isError: boolean;
71
+ hasNextPage: boolean;
72
+ hasPreviousPage: boolean;
73
+ isFetchingNextPage: boolean;
74
+ isFetchingPreviousPage: boolean;
75
+ fetchNextPage: () => Promise<void>;
76
+ fetchPreviousPage: () => Promise<void>;
77
+ refetch: () => Promise<void>;
78
+ }
79
+ /**
80
+ * Gears FrontX-owned suspense infinite query result type.
81
+ * Returned by useApiSuspenseInfiniteQuery for descriptor-driven pagination.
82
+ */
83
+ interface ApiSuspenseInfiniteQueryResult<TPage, TError = Error> {
84
+ data: readonly TPage[];
85
+ error: TError | null;
86
+ isFetching: boolean;
87
+ hasNextPage: boolean;
88
+ hasPreviousPage: boolean;
89
+ isFetchingNextPage: boolean;
90
+ isFetchingPreviousPage: boolean;
91
+ fetchNextPage: () => Promise<void>;
92
+ fetchPreviousPage: () => Promise<void>;
93
+ refetch: () => Promise<void>;
94
+ }
95
+ /**
96
+ * Gears FrontX-owned mutation result type.
97
+ * Returned by useApiMutation — callers depend on this contract, not on TanStack internals.
98
+ */
99
+ interface ApiMutationResult<TData, TError = Error, TVariables = void> {
100
+ mutate: (variables: TVariables) => void;
101
+ mutateAsync: (variables: TVariables) => Promise<TData>;
102
+ isPending: boolean;
103
+ error: TError | null;
104
+ data: TData | undefined;
105
+ reset: () => void;
106
+ }
107
+ type Selector<TResult, TState = RootState> = (state: TState) => TResult;
108
+ type TranslationParams = Record<string, string | number | boolean>;
109
+ /**
110
+ * FrontX Provider Props
111
+ * Props for the main FrontXProvider component.
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * <FrontXProvider config={{ devMode: true }}>
116
+ * <App />
117
+ * </FrontXProvider>
118
+ *
119
+ * // With pre-built app
120
+ * const app = createFrontX().use(microfrontends()).build();
121
+ * <FrontXProvider app={app}>
122
+ * <App />
123
+ * </FrontXProvider>
124
+ *
125
+ * // With MFE bridge (for MFE components)
126
+ * <FrontXProvider mfeBridge={{ bridge, extensionId, domainId }}>
127
+ * <MyMfeApp />
128
+ * </FrontXProvider>
129
+ *
130
+ * // QueryCache is resolved from the app's plugin composition
131
+ * <FrontXProvider app={app}>
132
+ * <MyMfeApp />
133
+ * </FrontXProvider>
134
+ * ```
135
+ */
136
+ interface FrontXProviderProps {
137
+ /** Child components */
138
+ children: ReactNode;
139
+ /** FrontX configuration */
140
+ config?: FrontXConfig;
141
+ /** Pre-built FrontX app instance (optional) */
142
+ app?: FrontXApp;
143
+ /** MFE bridge context (for MFE components) */
144
+ mfeBridge?: MfeContextValue;
145
+ }
146
+ /**
147
+ * useFrontX Hook Return Type
148
+ * Returns the FrontX app instance from context.
149
+ */
150
+ type UseFrontXReturn = FrontXApp;
151
+ /**
152
+ * useAppSelector Hook
153
+ * Type-safe selector hook for Redux state.
154
+ *
155
+ * @template TResult - The result type of the selector
156
+ */
157
+ type UseAppSelector = <TResult>(selector: Selector<TResult>) => TResult;
158
+ /**
159
+ * useAppDispatch Hook Return Type
160
+ * Returns the typed dispatch function.
161
+ */
162
+ type UseAppDispatchReturn = (action: unknown) => unknown;
163
+ /**
164
+ * useTranslation Hook Return Type
165
+ * Translation utilities.
166
+ */
167
+ interface UseTranslationReturn {
168
+ /** Translate a key */
169
+ t: (key: string, params?: TranslationParams) => string;
170
+ /** Current language */
171
+ language: Language | null;
172
+ /** Change language */
173
+ setLanguage: (language: Language) => void;
174
+ /** Check if current language is RTL */
175
+ isRTL: boolean;
176
+ }
177
+ /**
178
+ * useScreenTranslations Hook Return Type
179
+ * Screen-level translation loading state.
180
+ */
181
+ interface UseScreenTranslationsReturn {
182
+ /** Whether translations are loaded */
183
+ isLoaded: boolean;
184
+ /** Loading error (if any) */
185
+ error: Error | null;
186
+ }
187
+ /**
188
+ * useTheme Hook Return Type
189
+ * Theme utilities.
190
+ */
191
+ interface UseThemeReturn {
192
+ /** Current theme ID */
193
+ currentTheme: string | undefined;
194
+ /** All available themes */
195
+ themes: Array<{
196
+ id: string;
197
+ name: string;
198
+ }>;
199
+ /** Change theme */
200
+ setTheme: (themeId: string) => void;
201
+ }
202
+ /**
203
+ * useFormatters Hook Return Type
204
+ * Locale-aware formatters (locale from i18nRegistry.getLanguage()).
205
+ * References @gears-frontx/i18n Formatters so signatures stay in sync.
206
+ */
207
+ type UseFormattersReturn = Formatters;
208
+ /**
209
+ * useCanAccess Hook Return Type
210
+ * Result of an RBAC access check.
211
+ * allow is false until an explicit 'allow' decision arrives (pessimistic default).
212
+ */
213
+ interface UseCanAccessResult {
214
+ allow: boolean;
215
+ isResolving: boolean;
216
+ }
217
+ /**
218
+ * CanAccess Component Props.
219
+ * Pessimistic: renders `denied` (or `loading`) until an authoritative 'allow' arrives.
220
+ */
221
+ interface CanAccessProps<TRecord extends AccessRecord = AccessRecord> {
222
+ query: AccessQuery<TRecord>;
223
+ /** Content rendered when access is explicitly allowed. */
224
+ allowed: ReactNode;
225
+ /** Content rendered on deny or error. Defaults to null. */
226
+ denied?: ReactNode;
227
+ /** Optional slot shown while the decision is resolving. Falls back to `denied`. */
228
+ loading?: ReactNode;
229
+ }
230
+
231
+ export { type ApiQueryResult as A, type CanAccessProps as C, type FrontXProviderProps as F, type MfeContextValue as M, type UseTranslationReturn as U, type ApiInfiniteQueryResult as a, type ApiMutationResult as b, type UseScreenTranslationsReturn as c, type UseFormattersReturn as d, type UseThemeReturn as e, type ApiSuspenseQueryResult as f, type ApiSuspenseInfiniteQueryResult as g, type UseCanAccessResult as h, MfeContext as i, type UseAppDispatchReturn as j, type UseAppSelector as k, type UseFrontXReturn as l, useMfeContext as u };