@veams/status-quo-query 0.11.0 → 0.13.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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/README.md +166 -34
  3. package/dist/index.d.ts +0 -1
  4. package/dist/index.js +0 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/mutation.d.ts +8 -8
  7. package/dist/mutation.js +9 -9
  8. package/dist/mutation.js.map +1 -1
  9. package/dist/provider.d.ts +3 -2
  10. package/dist/provider.js +2 -0
  11. package/dist/provider.js.map +1 -1
  12. package/dist/query.d.ts +26 -15
  13. package/dist/query.js +40 -31
  14. package/dist/query.js.map +1 -1
  15. package/dist/react/hooks/index.d.ts +2 -1
  16. package/dist/react/hooks/index.js +2 -1
  17. package/dist/react/hooks/index.js.map +1 -1
  18. package/dist/react/hooks/use-mutation-handle.d.ts +2 -0
  19. package/dist/react/hooks/use-mutation-handle.js +71 -0
  20. package/dist/react/hooks/use-mutation-handle.js.map +1 -0
  21. package/dist/react/hooks/use-query-handle.d.ts +2 -0
  22. package/dist/react/hooks/use-query-handle.js +71 -0
  23. package/dist/react/hooks/use-query-handle.js.map +1 -0
  24. package/dist/react/hooks/use-query-subscription.d.ts +1 -2
  25. package/dist/react/hooks/use-query-subscription.js +1 -72
  26. package/dist/react/hooks/use-query-subscription.js.map +1 -1
  27. package/dist/react/index.d.ts +1 -1
  28. package/dist/react/index.js +1 -1
  29. package/dist/react/index.js.map +1 -1
  30. package/package.json +1 -8
  31. package/src/__tests__/provider.spec.ts +8 -0
  32. package/src/index.ts +0 -2
  33. package/src/mutation.ts +27 -27
  34. package/src/provider.ts +13 -9
  35. package/src/query.ts +84 -64
  36. package/src/react/__tests__/use-mutation-handle.spec.tsx +107 -0
  37. package/src/react/__tests__/{query-subscription.spec.tsx → use-query-handle.spec.tsx} +7 -7
  38. package/src/react/hooks/index.ts +2 -1
  39. package/src/react/hooks/use-mutation-handle.ts +98 -0
  40. package/src/react/hooks/{use-query-subscription.ts → use-query-handle.ts} +19 -21
  41. package/src/react/index.ts +1 -1
  42. package/dist/query-registry.d.ts +0 -9
  43. package/dist/query-registry.js +0 -28
  44. package/dist/query-registry.js.map +0 -1
  45. package/src/__tests__/query-registry.spec.ts +0 -101
  46. package/src/query-registry.ts +0 -52
@@ -0,0 +1,107 @@
1
+ import React, { act } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { QueryClient } from '@tanstack/query-core';
4
+
5
+ import { setupMutation } from '../../mutation.js';
6
+ import { useMutationHandle } from '../hooks/use-mutation-handle.js';
7
+
8
+ import type { MutationHandle, MutationHandleSnapshot } from '../../mutation.js';
9
+
10
+ declare global {
11
+ var IS_REACT_ACT_ENVIRONMENT: boolean;
12
+ }
13
+
14
+ describe('useMutationHandle', () => {
15
+ let container: HTMLDivElement;
16
+
17
+ beforeAll(() => {
18
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
19
+ });
20
+
21
+ beforeEach(() => {
22
+ container = document.createElement('div');
23
+ document.body.appendChild(container);
24
+ });
25
+
26
+ afterEach(() => {
27
+ container.remove();
28
+ });
29
+
30
+ it('renders the latest mutation snapshot', async () => {
31
+ const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: 0 } } });
32
+ const createMutation = setupMutation(queryClient);
33
+ const mutationFn = jest.fn((_payload: { name: string }) =>
34
+ Promise.resolve({ ok: true as const })
35
+ );
36
+ const handle = createMutation(mutationFn);
37
+ const statuses: string[] = [];
38
+
39
+ const Consumer = () => {
40
+ const snapshot = useMutationHandle(handle);
41
+ statuses.push(snapshot.status);
42
+
43
+ return <span>{snapshot.status}</span>;
44
+ };
45
+
46
+ const root = createRoot(container);
47
+
48
+ await act(async () => {
49
+ root.render(<Consumer />);
50
+ });
51
+
52
+ expect(container.textContent).toBe('idle');
53
+
54
+ await act(async () => {
55
+ await handle.mutate({ name: 'Ada' });
56
+ });
57
+
58
+ expect(container.textContent).toBe('success');
59
+ expect(statuses).toContain('idle');
60
+ expect(statuses).toContain('pending');
61
+ expect(statuses).toContain('success');
62
+
63
+ await act(async () => {
64
+ root.unmount();
65
+ });
66
+ });
67
+
68
+ it('cleans up the store subscription on unmount', async () => {
69
+ const snapshot: MutationHandleSnapshot<{ ok: true }, Error, void> = {
70
+ data: { ok: true },
71
+ error: null,
72
+ status: 'success',
73
+ variables: undefined,
74
+ isError: false,
75
+ isIdle: false,
76
+ isPending: false,
77
+ isSuccess: true,
78
+ };
79
+ const unsubscribe = jest.fn();
80
+ const handle: MutationHandle<{ ok: true }, Error, void> = {
81
+ getSnapshot: () => snapshot,
82
+ subscribe: jest.fn(() => unsubscribe),
83
+ mutate: jest.fn(async () => ({ ok: true as const })),
84
+ reset: jest.fn(),
85
+ unsafe_getResult: jest.fn(),
86
+ };
87
+
88
+ const root = createRoot(container);
89
+
90
+ const Consumer = () => {
91
+ useMutationHandle(handle);
92
+ return <span>ready</span>;
93
+ };
94
+
95
+ await act(async () => {
96
+ root.render(<Consumer />);
97
+ });
98
+
99
+ expect(handle.subscribe).toHaveBeenCalledTimes(1);
100
+
101
+ await act(async () => {
102
+ root.unmount();
103
+ });
104
+
105
+ expect(unsubscribe).toHaveBeenCalledTimes(1);
106
+ });
107
+ });
@@ -3,15 +3,15 @@ import { createRoot } from 'react-dom/client';
3
3
  import { QueryClient } from '@tanstack/query-core';
4
4
 
5
5
  import { setupQuery } from '../../query.js';
6
- import { useQuerySubscription } from '../hooks/use-query-subscription.js';
6
+ import { useQueryHandle } from '../hooks/use-query-handle.js';
7
7
 
8
- import type { QueryService, QueryServiceSnapshot } from '../../query.js';
8
+ import type { QueryHandle, QueryHandleSnapshot } from '../../query.js';
9
9
 
10
10
  declare global {
11
11
  var IS_REACT_ACT_ENVIRONMENT: boolean;
12
12
  }
13
13
 
14
- describe('useQuerySubscription', () => {
14
+ describe('useQueryHandle', () => {
15
15
  let container: HTMLDivElement;
16
16
 
17
17
  beforeAll(() => {
@@ -36,7 +36,7 @@ describe('useQuerySubscription', () => {
36
36
  const renderStates: Array<string | undefined> = [];
37
37
 
38
38
  const Consumer = () => {
39
- const snapshot = useQuerySubscription(query);
39
+ const snapshot = useQueryHandle(query);
40
40
  renderStates.push(snapshot.data?.name);
41
41
 
42
42
  return <span>{snapshot.data?.name ?? 'pending'}</span>;
@@ -63,7 +63,7 @@ describe('useQuerySubscription', () => {
63
63
  });
64
64
  });
65
65
  it('cleans up the store subscription on unmount', async () => {
66
- const snapshot: QueryServiceSnapshot<{ name: string }, Error> = {
66
+ const snapshot: QueryHandleSnapshot<{ name: string }, Error> = {
67
67
  data: { name: 'Ada' },
68
68
  error: null,
69
69
  fetchStatus: 'idle',
@@ -74,7 +74,7 @@ describe('useQuerySubscription', () => {
74
74
  isSuccess: true,
75
75
  };
76
76
  const unsubscribe = jest.fn();
77
- const query: QueryService<{ name: string }, Error> = {
77
+ const query: QueryHandle<{ name: string }, Error> = {
78
78
  getSnapshot: () => snapshot,
79
79
  subscribe: jest.fn(() => unsubscribe),
80
80
  refetch: jest.fn(async () => snapshot),
@@ -85,7 +85,7 @@ describe('useQuerySubscription', () => {
85
85
  const root = createRoot(container);
86
86
 
87
87
  const Consumer = () => {
88
- useQuerySubscription(query);
88
+ useQueryHandle(query);
89
89
  return <span>ready</span>;
90
90
  };
91
91
 
@@ -1 +1,2 @@
1
- export { useQuerySubscription } from './use-query-subscription.js';
1
+ export { useQueryHandle } from './use-query-handle.js';
2
+ export { useMutationHandle } from './use-mutation-handle.js';
@@ -0,0 +1,98 @@
1
+ // Import the React hooks needed to memoize callbacks, hold mutable cache state,
2
+ // and connect an external store to React rendering.
3
+ import { useCallback, useRef, useSyncExternalStore } from 'react';
4
+
5
+ // Import the mutation-handle contract and the stable snapshot shape the hook returns.
6
+ import type { MutationHandle, MutationHandleSnapshot } from '../../mutation.js';
7
+ // Describe the no-argument listener shape expected by useSyncExternalStore.
8
+ type Listener = () => void;
9
+
10
+ // Store one cached snapshot together with the store version it belongs to.
11
+ type SnapshotCacheEntry<TData, TError, TVariables> = {
12
+ // Hold the snapshot returned by the mutation handle for this version.
13
+ snapshot: MutationHandleSnapshot<TData, TError, TVariables>;
14
+ // Track which subscription version produced the cached snapshot.
15
+ version: number;
16
+ };
17
+
18
+ // Subscribe a React component to a MutationHandle and return its latest snapshot.
19
+ export function useMutationHandle<TData, TError = Error, TVariables = void>(
20
+ // Receive the external mutation handle instance to subscribe to.
21
+ mutationHandle: MutationHandle<TData, TError, TVariables>
22
+ ) {
23
+ // Count store notifications so we can tell when our cached snapshot is stale.
24
+ const snapshotVersionRef = useRef(0);
25
+ // Cache one client-side snapshot per observed version to keep getSnapshot stable.
26
+ const snapshotCacheRef = useRef<SnapshotCacheEntry<TData, TError, TVariables> | null>(null);
27
+ // Cache the server snapshot separately for the useSyncExternalStore SSR fallback.
28
+ const serverSnapshotCacheRef = useRef<MutationHandleSnapshot<TData, TError, TVariables> | null>(
29
+ null
30
+ );
31
+
32
+ // Create the subscribe function expected by useSyncExternalStore.
33
+ const subscribe = useCallback(
34
+ // React passes a listener that must run whenever the external store changes.
35
+ (listener: Listener) =>
36
+ // Forward the subscription to the mutation handle.
37
+ mutationHandle.subscribe(() => {
38
+ // Bump the version so later reads know the previous cache is outdated.
39
+ snapshotVersionRef.current += 1;
40
+ // Drop the cached client snapshot because the store just changed.
41
+ snapshotCacheRef.current = null;
42
+ // Drop the cached server snapshot for the same reason.
43
+ serverSnapshotCacheRef.current = null;
44
+ // Notify React that it should read a fresh snapshot.
45
+ listener();
46
+ }),
47
+ // Recreate the subscription function only when the handle instance changes.
48
+ [mutationHandle]
49
+ );
50
+
51
+ // Read the current client snapshot in a referentially stable way for React.
52
+ const getSnapshot = useCallback(() => {
53
+ // Read the latest store version number.
54
+ const version = snapshotVersionRef.current;
55
+ // Read the last cached client snapshot, if there is one.
56
+ const cachedSnapshot = snapshotCacheRef.current;
57
+
58
+ // Reuse the cached snapshot when it was produced for the current version.
59
+ if (cachedSnapshot && cachedSnapshot.version === version) {
60
+ // Return the cached snapshot so repeated reads in the same render stay stable.
61
+ return cachedSnapshot.snapshot;
62
+ }
63
+
64
+ // Ask the mutation handle for the latest snapshot because the cache is empty or stale.
65
+ const snapshot = mutationHandle.getSnapshot();
66
+ // Store the new snapshot together with the version it belongs to.
67
+ snapshotCacheRef.current = {
68
+ snapshot,
69
+ version,
70
+ };
71
+
72
+ // Return the freshly read snapshot to React.
73
+ return snapshot;
74
+ }, [mutationHandle]);
75
+
76
+ // Read the server snapshot used by React during SSR or hydration fallback paths.
77
+ const getServerSnapshot = useCallback(() => {
78
+ // Read the cached server snapshot, if one was stored earlier.
79
+ const cachedSnapshot = serverSnapshotCacheRef.current;
80
+
81
+ // Reuse the cached server snapshot to keep server reads stable.
82
+ if (cachedSnapshot) {
83
+ // Return the cached server snapshot directly.
84
+ return cachedSnapshot;
85
+ }
86
+
87
+ // Ask the mutation handle for a snapshot because no server cache exists yet.
88
+ const snapshot = mutationHandle.getSnapshot();
89
+ // Cache that snapshot for the next server read.
90
+ serverSnapshotCacheRef.current = snapshot;
91
+
92
+ // Return the freshly read server snapshot.
93
+ return snapshot;
94
+ }, [mutationHandle]);
95
+
96
+ // Let React subscribe to the external store and read snapshots through the callbacks above.
97
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
98
+ }
@@ -2,37 +2,37 @@
2
2
  // and connect an external store to React rendering.
3
3
  import { useCallback, useRef, useSyncExternalStore } from 'react';
4
4
 
5
- // Import the query service contract and the stable snapshot shape the hook returns.
6
- import type { QueryService, QueryServiceSnapshot } from '../../query.js';
5
+ // Import the query-handle contract and the stable snapshot shape the hook returns.
6
+ import type { QueryHandle, QueryHandleSnapshot } from '../../query.js';
7
7
  // Describe the no-argument listener shape expected by useSyncExternalStore.
8
8
  type Listener = () => void;
9
9
 
10
10
  // Store one cached snapshot together with the store version it belongs to.
11
11
  type SnapshotCacheEntry<TData, TError> = {
12
- // Hold the snapshot returned by the query service for this version.
13
- snapshot: QueryServiceSnapshot<TData, TError>;
12
+ // Hold the snapshot returned by the query handle for this version.
13
+ snapshot: QueryHandleSnapshot<TData, TError>;
14
14
  // Track which subscription version produced the cached snapshot.
15
15
  version: number;
16
16
  };
17
17
 
18
- // Subscribe a React component to a QueryService and return its latest snapshot.
19
- export function useQuerySubscription<TData, TError>(
20
- // Receive the external query service instance to subscribe to.
21
- queryService: QueryService<TData, TError>
18
+ // Subscribe a React component to a QueryHandle and return its latest snapshot.
19
+ export function useQueryHandle<TData, TError>(
20
+ // Receive the external query handle instance to subscribe to.
21
+ queryHandle: QueryHandle<TData, TError>
22
22
  ) {
23
23
  // Count store notifications so we can tell when our cached snapshot is stale.
24
24
  const snapshotVersionRef = useRef(0);
25
25
  // Cache one client-side snapshot per observed version to keep getSnapshot stable.
26
26
  const snapshotCacheRef = useRef<SnapshotCacheEntry<TData, TError> | null>(null);
27
27
  // Cache the server snapshot separately for the useSyncExternalStore SSR fallback.
28
- const serverSnapshotCacheRef = useRef<QueryServiceSnapshot<TData, TError> | null>(null);
28
+ const serverSnapshotCacheRef = useRef<QueryHandleSnapshot<TData, TError> | null>(null);
29
29
 
30
30
  // Create the subscribe function expected by useSyncExternalStore.
31
31
  const subscribe = useCallback(
32
32
  // React passes a listener that must run whenever the external store changes.
33
33
  (listener: Listener) =>
34
- // Forward the subscription to the query service.
35
- queryService.subscribe(() => {
34
+ // Forward the subscription to the query handle.
35
+ queryHandle.subscribe(() => {
36
36
  // Bump the version so later reads know the previous cache is outdated.
37
37
  snapshotVersionRef.current += 1;
38
38
  // Drop the cached client snapshot because the store just changed.
@@ -42,8 +42,8 @@ export function useQuerySubscription<TData, TError>(
42
42
  // Notify React that it should read a fresh snapshot.
43
43
  listener();
44
44
  }),
45
- // Recreate the subscription function only when the service instance changes.
46
- [queryService]
45
+ // Recreate the subscription function only when the handle instance changes.
46
+ [queryHandle]
47
47
  );
48
48
 
49
49
  // Read the current client snapshot in a referentially stable way for React.
@@ -59,8 +59,8 @@ export function useQuerySubscription<TData, TError>(
59
59
  return cachedSnapshot.snapshot;
60
60
  }
61
61
 
62
- // Ask the query service for the latest snapshot because the cache is empty or stale.
63
- const snapshot = queryService.getSnapshot();
62
+ // Ask the query handle for the latest snapshot because the cache is empty or stale.
63
+ const snapshot = queryHandle.getSnapshot();
64
64
  // Store the new snapshot together with the version it belongs to.
65
65
  snapshotCacheRef.current = {
66
66
  snapshot,
@@ -69,8 +69,7 @@ export function useQuerySubscription<TData, TError>(
69
69
 
70
70
  // Return the freshly read snapshot to React.
71
71
  return snapshot;
72
- // Recreate this getter only when the service instance changes.
73
- }, [queryService]);
72
+ }, [queryHandle]);
74
73
 
75
74
  // Read the server snapshot used by React during SSR or hydration fallback paths.
76
75
  const getServerSnapshot = useCallback(() => {
@@ -83,15 +82,14 @@ export function useQuerySubscription<TData, TError>(
83
82
  return cachedSnapshot;
84
83
  }
85
84
 
86
- // Ask the query service for a snapshot because no server cache exists yet.
87
- const snapshot = queryService.getSnapshot();
85
+ // Ask the query handle for a snapshot because no server cache exists yet.
86
+ const snapshot = queryHandle.getSnapshot();
88
87
  // Cache that snapshot for the next server read.
89
88
  serverSnapshotCacheRef.current = snapshot;
90
89
 
91
90
  // Return the freshly read server snapshot.
92
91
  return snapshot;
93
- // Recreate this getter only when the service instance changes.
94
- }, [queryService]);
92
+ }, [queryHandle]);
95
93
 
96
94
  // Let React subscribe to the external store and read snapshots through the callbacks above.
97
95
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
@@ -1 +1 @@
1
- export { useQuerySubscription } from './hooks/index.js';
1
+ export { useQueryHandle, useMutationHandle } from './hooks/index.js';
@@ -1,9 +0,0 @@
1
- import type { QueryService } from './query.js';
2
- export interface QueryRegistry<TParams, TKey extends readonly unknown[]> {
3
- clear: () => void;
4
- getKey: (params: TParams) => TKey;
5
- name: string;
6
- resolve: <TData, TError = Error>(params: TParams, create: (queryKey: TKey) => QueryService<TData, TError>) => QueryService<TData, TError>;
7
- }
8
- export declare function createQueryRegistry<TParams, TKey extends readonly unknown[]>(name: string, createKey: (params: TParams) => TKey): QueryRegistry<TParams, TKey>;
9
- export declare function serializeQueryKey(queryKey: readonly unknown[]): string;
@@ -1,28 +0,0 @@
1
- import { hashKey } from '@tanstack/query-core';
2
- export function createQueryRegistry(name, createKey) {
3
- const entries = new Map();
4
- return {
5
- name,
6
- clear() {
7
- entries.clear();
8
- },
9
- getKey(params) {
10
- return createKey(params);
11
- },
12
- resolve(params, create) {
13
- const queryKey = createKey(params);
14
- const cacheKey = serializeQueryKey(queryKey);
15
- const existingEntry = entries.get(cacheKey);
16
- if (existingEntry) {
17
- return existingEntry;
18
- }
19
- const nextEntry = create(queryKey);
20
- entries.set(cacheKey, nextEntry);
21
- return nextEntry;
22
- },
23
- };
24
- }
25
- export function serializeQueryKey(queryKey) {
26
- return hashKey(queryKey);
27
- }
28
- //# sourceMappingURL=query-registry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"query-registry.js","sourceRoot":"","sources":["../src/query-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAc/C,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,SAAoC;IAEpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0C,CAAC;IAElE,OAAO;QACL,IAAI;QACJ,KAAK;YACH,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QACD,MAAM,CAAC,MAAM;YACX,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,CACL,MAAe,EACf,MAAuD;YAEvD,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YACnC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAA4C,CAAC;YAEvF,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,aAAa,CAAC;YACvB,CAAC;YAED,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;YAEnC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,SAA2C,CAAC,CAAC;YAEnE,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAA4B;IAC5D,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC"}
@@ -1,101 +0,0 @@
1
- import type { QueryService } from '../query';
2
- import { createQueryRegistry, serializeQueryKey } from '../query-registry';
3
-
4
- describe('createQueryRegistry', () => {
5
- it('reuses the same entry for identical params', () => {
6
- const registry = createQueryRegistry('branches', (params: { branchId: string }) => [
7
- 'branch',
8
- {
9
- deps: {
10
- branchId: params.branchId,
11
- },
12
- },
13
- ] as const);
14
- const createEntry = jest.fn((queryKey) => ({ queryKey }));
15
-
16
- const firstEntry = registry.resolve({ branchId: 'branch-1' }, createEntry as never);
17
- const secondEntry = registry.resolve({ branchId: 'branch-1' }, createEntry as never);
18
-
19
- expect(firstEntry).toBe(secondEntry);
20
- expect(createEntry).toHaveBeenCalledTimes(1);
21
- });
22
-
23
- it('creates separate entries for different params', () => {
24
- const registry = createQueryRegistry('branches', (params: { branchId: string }) => [
25
- 'branch',
26
- {
27
- deps: {
28
- branchId: params.branchId,
29
- },
30
- },
31
- ] as const);
32
- const createEntry = jest.fn((queryKey) => ({ queryKey }));
33
-
34
- const firstEntry = registry.resolve({ branchId: 'branch-1' }, createEntry as never);
35
- const secondEntry = registry.resolve({ branchId: 'branch-2' }, createEntry as never);
36
-
37
- expect(firstEntry).not.toBe(secondEntry);
38
- expect(createEntry).toHaveBeenCalledTimes(2);
39
- });
40
-
41
- it('exposes the generated query key', () => {
42
- const registry = createQueryRegistry('branches', (params: { branchId: string }) => [
43
- 'branch',
44
- {
45
- deps: {
46
- branchId: params.branchId,
47
- },
48
- },
49
- ] as const);
50
-
51
- expect(registry.getKey({ branchId: 'branch-1' })).toEqual([
52
- 'branch',
53
- {
54
- deps: {
55
- branchId: 'branch-1',
56
- },
57
- },
58
- ]);
59
- });
60
-
61
- it('uses TanStack-compatible stable hashing for object keys', () => {
62
- const firstKey = ['branch', { deps: { branchId: 'branch-1', companyId: 'company-1' } }] as const;
63
- const secondKey = ['branch', { deps: { companyId: 'company-1', branchId: 'branch-1' } }] as const;
64
-
65
- expect(serializeQueryKey(firstKey)).toBe(serializeQueryKey(secondKey));
66
- });
67
-
68
- it('infers the query service type from the creator callback', () => {
69
- const registry = createQueryRegistry('branches', (params: { branchId: string }) => [
70
- 'branch',
71
- {
72
- deps: {
73
- branchId: params.branchId,
74
- },
75
- },
76
- ] as const);
77
-
78
- const query = registry.resolve({ branchId: 'branch-1' }, () => {
79
- return {
80
- getSnapshot: () => ({
81
- data: { id: 'branch-1' },
82
- error: null,
83
- fetchStatus: 'idle',
84
- isError: false,
85
- isFetching: false,
86
- isPending: false,
87
- isSuccess: true,
88
- status: 'success',
89
- }),
90
- invalidate: jest.fn(),
91
- refetch: jest.fn(),
92
- subscribe: jest.fn(),
93
- unsafe_getResult: jest.fn(),
94
- } as unknown as QueryService<{ id: string }, Error>;
95
- });
96
-
97
- const typedQuery: QueryService<{ id: string }, Error> = query;
98
-
99
- expect(typedQuery).toBe(query);
100
- });
101
- });
@@ -1,52 +0,0 @@
1
- import { hashKey } from '@tanstack/query-core';
2
-
3
- import type { QueryService } from './query.js';
4
-
5
- export interface QueryRegistry<TParams, TKey extends readonly unknown[]> {
6
- clear: () => void;
7
- getKey: (params: TParams) => TKey;
8
- name: string;
9
- resolve: <TData, TError = Error>(
10
- params: TParams,
11
- create: (queryKey: TKey) => QueryService<TData, TError>
12
- ) => QueryService<TData, TError>;
13
- }
14
-
15
- export function createQueryRegistry<TParams, TKey extends readonly unknown[]>(
16
- name: string,
17
- createKey: (params: TParams) => TKey
18
- ): QueryRegistry<TParams, TKey> {
19
- const entries = new Map<string, QueryService<unknown, unknown>>();
20
-
21
- return {
22
- name,
23
- clear() {
24
- entries.clear();
25
- },
26
- getKey(params) {
27
- return createKey(params);
28
- },
29
- resolve<TData, TError = Error>(
30
- params: TParams,
31
- create: (queryKey: TKey) => QueryService<TData, TError>
32
- ) {
33
- const queryKey = createKey(params);
34
- const cacheKey = serializeQueryKey(queryKey);
35
- const existingEntry = entries.get(cacheKey) as QueryService<TData, TError> | undefined;
36
-
37
- if (existingEntry) {
38
- return existingEntry;
39
- }
40
-
41
- const nextEntry = create(queryKey);
42
-
43
- entries.set(cacheKey, nextEntry as QueryService<unknown, unknown>);
44
-
45
- return nextEntry;
46
- },
47
- };
48
- }
49
-
50
- export function serializeQueryKey(queryKey: readonly unknown[]): string {
51
- return hashKey(queryKey);
52
- }