@ainias42/tanstack-ids 0.1.2 → 0.1.3
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.
- package/agents/queries.md +12 -0
- package/package.json +9 -3
- package/.ctirc +0 -112
- package/.prettierrc +0 -1
- package/bin/release.sh +0 -3
- package/bin/updatePackages.sh +0 -3
- package/eslint.config.js +0 -3
- package/src/NotFoundError.ts +0 -6
- package/src/QueryContext/QueriesContext.ts +0 -3
- package/src/QueryContext/QueriesContextProvider.tsx +0 -28
- package/src/QueryContext/SubQueriesContext.tsx +0 -38
- package/src/QueryContext/useGenerateQueriesContextId.ts +0 -7
- package/src/QueryContext/useQueriesError.ts +0 -8
- package/src/QueryContext/useQueriesIsFetching.ts +0 -8
- package/src/QueryContext/useQueriesReload.ts +0 -14
- package/src/QueryContext/useQueriesStore.ts +0 -150
- package/src/QueryContext/useQuerySubscribeToReload.ts +0 -24
- package/src/QueryContext/useScopedQueriesIsFetching.ts +0 -9
- package/src/generateIdsQuery.ts +0 -452
- package/src/generateNestedIdQuery.ts +0 -38
- package/src/generateQuery.ts +0 -58
- package/src/hoistReactQueryKitAttributes.ts +0 -16
- package/src/hooks/formatInitialPage.ts +0 -12
- package/src/hooks/useArrayData.ts +0 -20
- package/src/hooks/useIndexedData.ts +0 -24
- package/src/hooks/useInitialData.ts +0 -13
- package/src/index.ts +0 -21
- package/src/noop.ts +0 -2
- package/src/types/DeepPartial.ts +0 -5
- package/stylelint.config.mjs +0 -4
- package/tsconfig.json +0 -11
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# TanStack IDs Query Instructions
|
|
2
|
+
|
|
3
|
+
## Query Helpers
|
|
4
|
+
- For entities with an `id`, use `generateIdsQuery` from `@ainias42/tanstack-ids`.
|
|
5
|
+
- For filtered entity lists, use `generateIdsQuery` with `generateFilter`.
|
|
6
|
+
- Use `generateQuery` only for non-entity data or data without entity ids.
|
|
7
|
+
- For entity loading hooks, expose only the hooks that are actually needed.
|
|
8
|
+
- When creating a query, export the query directly instead of hiding it behind a helper hook or function, so callers can pass filter/options and avoid duplicate loads.
|
|
9
|
+
|
|
10
|
+
## Query Context
|
|
11
|
+
- Use the package query context helpers for shared reload, loading, and error state instead of duplicating that state locally.
|
|
12
|
+
- Keep query context ids generated through the package helpers so nested or scoped queries remain isolated.
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainias42/tanstack-ids",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
+
"files": [
|
|
9
|
+
"agents",
|
|
10
|
+
"dist/**/*.d.ts",
|
|
11
|
+
"dist/**/*.js",
|
|
12
|
+
"dist/**/*.map"
|
|
13
|
+
],
|
|
8
14
|
"main": "dist/index.js",
|
|
9
15
|
"exports": {
|
|
10
16
|
".": "./dist/index.js"
|
|
@@ -27,8 +33,8 @@
|
|
|
27
33
|
"url": "gitea@git.silas.link:Ainias/libraries.git"
|
|
28
34
|
},
|
|
29
35
|
"dependencies": {
|
|
30
|
-
"@ainias42/react-bootstrap-mobile": "1.2.
|
|
31
|
-
"@ainias42/config": "1.2.
|
|
36
|
+
"@ainias42/react-bootstrap-mobile": "1.2.4",
|
|
37
|
+
"@ainias42/config": "1.2.1",
|
|
32
38
|
"@ainias42/js-helper": ">=0.9.0",
|
|
33
39
|
"@tanstack/react-query": "^5.101.0",
|
|
34
40
|
"@yornaath/batshit": "^0.14.0",
|
package/.ctirc
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Stream of cli spinner, you can pass stdout or stderr
|
|
3
|
-
//
|
|
4
|
-
// @mode all
|
|
5
|
-
// @default stdout
|
|
6
|
-
"spinnerStream": "stdout",
|
|
7
|
-
// Stream of cli progress, you can pass stdout or stderr
|
|
8
|
-
//
|
|
9
|
-
// @mode all
|
|
10
|
-
// @default stdout
|
|
11
|
-
"progressStream": "stdout",
|
|
12
|
-
// Stream of cli reasoner. Reasoner show name conflict error and already exist index.ts file error.
|
|
13
|
-
// You can pass stdout or stderr
|
|
14
|
-
//
|
|
15
|
-
// @mode all
|
|
16
|
-
// @default stderr
|
|
17
|
-
"reasonerStream": "stderr",
|
|
18
|
-
"options": [
|
|
19
|
-
{
|
|
20
|
-
// build mode
|
|
21
|
-
// - create: create an `index.ts` file in each directory
|
|
22
|
-
// - bundle: bundle all export information in one `index.ts` file
|
|
23
|
-
"mode": "bundle",
|
|
24
|
-
// tsconfig.json path: you must pass path with filename, like this "./tsconfig.json"
|
|
25
|
-
// only work root directory or cli parameter
|
|
26
|
-
//
|
|
27
|
-
// @mode all
|
|
28
|
-
"project": "tsconfig.json",
|
|
29
|
-
// Export filename, if you not pass this field that use "index.ts" or "index.d.ts"
|
|
30
|
-
//
|
|
31
|
-
// @mode create, bundle, remove
|
|
32
|
-
// @default index.ts
|
|
33
|
-
"exportFilename": "index.ts",
|
|
34
|
-
// add ctix comment at first line of creted index.ts file, that remark created from ctix
|
|
35
|
-
//
|
|
36
|
-
// @mode create, bundle
|
|
37
|
-
// @default false
|
|
38
|
-
"useSemicolon": true,
|
|
39
|
-
// add ctix comment at first line of creted index.ts file, that remark created from ctix
|
|
40
|
-
//
|
|
41
|
-
// @mode create, bundle
|
|
42
|
-
// @default false
|
|
43
|
-
"useBanner": false,
|
|
44
|
-
// If specified as true, adds the created date to the top of the `index.ts` file,
|
|
45
|
-
// this option only works if the `useBanner` option is enabled
|
|
46
|
-
//
|
|
47
|
-
// @mode create, bundle
|
|
48
|
-
// @default false
|
|
49
|
-
"useTimestamp": false,
|
|
50
|
-
// quote mark " or '
|
|
51
|
-
// @mode create, bundle
|
|
52
|
-
//
|
|
53
|
-
// @default '
|
|
54
|
-
"quote": "'",
|
|
55
|
-
// Use to add a literal like `"use strict"` to the top. It will be added before the banner.
|
|
56
|
-
//
|
|
57
|
-
// @mode create, bundle
|
|
58
|
-
"directive": "",
|
|
59
|
-
// keep file extension in export statement path
|
|
60
|
-
//
|
|
61
|
-
// if this option set true that see below
|
|
62
|
-
// export * from './test.ts'
|
|
63
|
-
//
|
|
64
|
-
// @mode create, bundle
|
|
65
|
-
// @default none
|
|
66
|
-
"fileExt": "none",
|
|
67
|
-
// overwrite each index.ts file
|
|
68
|
-
// @mode create, bundle
|
|
69
|
-
// @default false
|
|
70
|
-
"overwrite": true,
|
|
71
|
-
// Create a backup file if the `index.ts` file already exists.
|
|
72
|
-
// This option only works if the `overwrite` option is enabled.
|
|
73
|
-
//
|
|
74
|
-
// @mode create, bundle
|
|
75
|
-
// @default true
|
|
76
|
-
"backup": false,
|
|
77
|
-
// When generating the `index.ts` file, decide how you want to generate it
|
|
78
|
-
//
|
|
79
|
-
// @mode create, bundle
|
|
80
|
-
// @default auto
|
|
81
|
-
"generationStyle": "auto",
|
|
82
|
-
// A list of files to use when generating the index.ts file. If no value is set,
|
|
83
|
-
// the value of the include setting set in the tsconfig.json file will be used
|
|
84
|
-
//
|
|
85
|
-
// @mode create, bundle
|
|
86
|
-
"include": [
|
|
87
|
-
"src/**/*.tsx",
|
|
88
|
-
"src/**/*.ts"
|
|
89
|
-
],
|
|
90
|
-
// A list of files to exclude when generating the index.ts file. If no value is set,
|
|
91
|
-
// the value of the exclude setting set in the tsconfig.json file is used
|
|
92
|
-
//
|
|
93
|
-
// @mode create, bundle
|
|
94
|
-
"exclude": [
|
|
95
|
-
"../../node_modules",
|
|
96
|
-
"dist/**/*",
|
|
97
|
-
"dist/src/**/*"
|
|
98
|
-
],
|
|
99
|
-
// Output directory. Default value is same project directory
|
|
100
|
-
// @mode bundle
|
|
101
|
-
"output": "./src",
|
|
102
|
-
// remove with backup file
|
|
103
|
-
// @mode remove
|
|
104
|
-
// @default false
|
|
105
|
-
"removeBackup": false,
|
|
106
|
-
// answer `yes` to all questions
|
|
107
|
-
// @mode remove
|
|
108
|
-
// @default false
|
|
109
|
-
"forceYes": false
|
|
110
|
-
}
|
|
111
|
-
]
|
|
112
|
-
}
|
package/.prettierrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"@ainias42/config/prettierrc.js"
|
package/bin/release.sh
DELETED
package/bin/updatePackages.sh
DELETED
package/eslint.config.js
DELETED
package/src/NotFoundError.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useGenerateQueriesContextId } from './useGenerateQueriesContextId';
|
|
3
|
-
import { withMemo } from '@ainias42/react-bootstrap-mobile';
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import type { ReactNode } from 'react';
|
|
6
|
-
|
|
7
|
-
export type QueriesContextProviderProps = { children: ReactNode };
|
|
8
|
-
|
|
9
|
-
export const QueriesContextProvider = withMemo(function QueriesContextProviderComponent({
|
|
10
|
-
children,
|
|
11
|
-
}: QueriesContextProviderProps) {
|
|
12
|
-
// Refs
|
|
13
|
-
|
|
14
|
-
// States/Variables/Selectors
|
|
15
|
-
const id = useGenerateQueriesContextId();
|
|
16
|
-
|
|
17
|
-
// Dispatch
|
|
18
|
-
|
|
19
|
-
// Callbacks
|
|
20
|
-
|
|
21
|
-
// Effects
|
|
22
|
-
|
|
23
|
-
// Other
|
|
24
|
-
|
|
25
|
-
// RenderFunctions
|
|
26
|
-
|
|
27
|
-
return <QueriesContext.Provider value={id}>{children}</QueriesContext.Provider>;
|
|
28
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useClientLayoutEffect, withMemo } from '@ainias42/react-bootstrap-mobile';
|
|
3
|
-
import { useGenerateQueriesContextId } from './useGenerateQueriesContextId';
|
|
4
|
-
import { useQueriesStore } from './useQueriesStore';
|
|
5
|
-
import React, { type ReactNode, useContext } from 'react';
|
|
6
|
-
|
|
7
|
-
export type SubQueriesContextProps = { children: ReactNode; subContextId?: number };
|
|
8
|
-
|
|
9
|
-
export const SubQueriesContext = withMemo(function SubQueriesContextComponent({
|
|
10
|
-
children,
|
|
11
|
-
subContextId,
|
|
12
|
-
}: SubQueriesContextProps) {
|
|
13
|
-
// Refs
|
|
14
|
-
|
|
15
|
-
// States/Variables/Selectors
|
|
16
|
-
const parentId = useContext(QueriesContext);
|
|
17
|
-
|
|
18
|
-
const [addSubContext, removeSubContext] = useQueriesStore((s) => [s.addSubContext, s.removeSubContext]);
|
|
19
|
-
|
|
20
|
-
const id = useGenerateQueriesContextId();
|
|
21
|
-
const usedId = subContextId ?? id;
|
|
22
|
-
|
|
23
|
-
// Dispatch
|
|
24
|
-
|
|
25
|
-
// Callbacks
|
|
26
|
-
|
|
27
|
-
// Effects
|
|
28
|
-
useClientLayoutEffect(() => {
|
|
29
|
-
addSubContext(parentId, usedId);
|
|
30
|
-
return () => removeSubContext(parentId, usedId);
|
|
31
|
-
}, [addSubContext, usedId, parentId, removeSubContext]);
|
|
32
|
-
|
|
33
|
-
// Other
|
|
34
|
-
|
|
35
|
-
// RenderFunctions
|
|
36
|
-
|
|
37
|
-
return <QueriesContext.Provider value={usedId}>{children}</QueriesContext.Provider>;
|
|
38
|
-
});
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useContext } from 'react';
|
|
3
|
-
import { useQueriesStore } from './useQueriesStore';
|
|
4
|
-
|
|
5
|
-
export function useQueriesError(contextId?: number) {
|
|
6
|
-
const context = useContext(QueriesContext);
|
|
7
|
-
return useQueriesStore((s) => s.getError(contextId ?? context));
|
|
8
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useContext } from 'react';
|
|
3
|
-
import { useQueriesStore } from './useQueriesStore';
|
|
4
|
-
|
|
5
|
-
export function useQueriesIsFetching(contextId?: number) {
|
|
6
|
-
const context = useContext(QueriesContext);
|
|
7
|
-
return useQueriesStore((s) => s.isFetching(contextId ?? context));
|
|
8
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useCallback, useContext } from 'react';
|
|
3
|
-
import { useQueriesStore } from './useQueriesStore';
|
|
4
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
5
|
-
|
|
6
|
-
export function useQueriesReload(contextId?: number) {
|
|
7
|
-
const context = useContext(QueriesContext);
|
|
8
|
-
const reloadContext = useQueriesStore((s) => s.reload);
|
|
9
|
-
const queryClient = useQueryClient();
|
|
10
|
-
|
|
11
|
-
return useCallback(async () => {
|
|
12
|
-
return reloadContext(queryClient, contextId ?? context);
|
|
13
|
-
}, [context, contextId, queryClient, reloadContext]);
|
|
14
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { ObjectHelper } from '@ainias42/js-helper';
|
|
2
|
-
import { create } from 'zustand';
|
|
3
|
-
import { immer } from 'zustand/middleware/immer';
|
|
4
|
-
import { subscribeWithSelector } from 'zustand/middleware';
|
|
5
|
-
import type { QueryClient } from '@tanstack/react-query';
|
|
6
|
-
import type { URecord } from '@ainias42/js-helper';
|
|
7
|
-
|
|
8
|
-
type QueryItem = {
|
|
9
|
-
keys: string[];
|
|
10
|
-
isFetching: boolean;
|
|
11
|
-
error: any;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function initialState() {
|
|
15
|
-
return {
|
|
16
|
-
lastContextId: 0,
|
|
17
|
-
contexts: {} as URecord<
|
|
18
|
-
number,
|
|
19
|
-
{
|
|
20
|
-
lastId: number;
|
|
21
|
-
items: URecord<number, QueryItem>;
|
|
22
|
-
}
|
|
23
|
-
>,
|
|
24
|
-
subContexts: {} as URecord<number, number[]>,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type QueriesStoreState = ReturnType<typeof actionsGenerator> & ReturnType<typeof initialState>;
|
|
29
|
-
type SetState = (
|
|
30
|
-
newState: Partial<QueriesStoreState> | QueriesStoreState | ((state: QueriesStoreState) => QueriesStoreState | void),
|
|
31
|
-
replace?: boolean,
|
|
32
|
-
) => void;
|
|
33
|
-
type GetState = () => Readonly<QueriesStoreState>;
|
|
34
|
-
const actionsGenerator = (set: SetState, get: GetState) => ({
|
|
35
|
-
get,
|
|
36
|
-
clear() {
|
|
37
|
-
set({ ...initialState(), ...actionsGenerator(set, get) }, true);
|
|
38
|
-
},
|
|
39
|
-
generateContextId() {
|
|
40
|
-
let id = 0;
|
|
41
|
-
set((state) => {
|
|
42
|
-
state.lastContextId++;
|
|
43
|
-
state.contexts[state.lastContextId] = {
|
|
44
|
-
lastId: 0,
|
|
45
|
-
items: {},
|
|
46
|
-
};
|
|
47
|
-
id = state.lastContextId;
|
|
48
|
-
});
|
|
49
|
-
return id;
|
|
50
|
-
},
|
|
51
|
-
setItem(contextId: number, itemId: number | undefined, item: QueryItem) {
|
|
52
|
-
let id = itemId;
|
|
53
|
-
set((state) => {
|
|
54
|
-
const contextData = state.contexts[contextId];
|
|
55
|
-
if (!contextData) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!id) {
|
|
60
|
-
contextData.lastId++;
|
|
61
|
-
id = contextData.lastId;
|
|
62
|
-
}
|
|
63
|
-
contextData.items[id] = item;
|
|
64
|
-
});
|
|
65
|
-
return id;
|
|
66
|
-
},
|
|
67
|
-
removeItem(contextId: number, itemId: number | undefined) {
|
|
68
|
-
if (!itemId) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
set((state) => {
|
|
72
|
-
delete state.contexts[contextId]?.items[itemId];
|
|
73
|
-
});
|
|
74
|
-
},
|
|
75
|
-
isFetching(contextId: number): boolean {
|
|
76
|
-
const state = get();
|
|
77
|
-
return (
|
|
78
|
-
ObjectHelper.values(state.contexts[contextId]?.items ?? {}).some((item) => item.isFetching) ||
|
|
79
|
-
(state.subContexts[contextId]?.some((id) => state.isFetching(id)) ?? false)
|
|
80
|
-
);
|
|
81
|
-
},
|
|
82
|
-
isFetchingByScope(contextId: number, scope: { global: boolean; subContexts: boolean }): boolean {
|
|
83
|
-
const state = get();
|
|
84
|
-
return (
|
|
85
|
-
((scope.global &&
|
|
86
|
-
ObjectHelper.values(state.contexts[contextId]?.items ?? {}).some((item) => item.isFetching)) ||
|
|
87
|
-
(scope.subContexts && state.subContexts[contextId]?.some((id) => state.isFetching(id)))) ??
|
|
88
|
-
false
|
|
89
|
-
);
|
|
90
|
-
},
|
|
91
|
-
getError(contextId: number): any {
|
|
92
|
-
const state = get();
|
|
93
|
-
|
|
94
|
-
const error = ObjectHelper.values(state.contexts[contextId]?.items ?? {}).find((item) => item.error)?.error;
|
|
95
|
-
if (error) {
|
|
96
|
-
return error;
|
|
97
|
-
}
|
|
98
|
-
const subContextWithError = state.subContexts[contextId]?.find((childId) => state.getError(childId));
|
|
99
|
-
if (subContextWithError) {
|
|
100
|
-
return state.getError(subContextWithError);
|
|
101
|
-
}
|
|
102
|
-
return undefined;
|
|
103
|
-
},
|
|
104
|
-
getAllKeys(contextId: number): Set<string> {
|
|
105
|
-
const state = get();
|
|
106
|
-
|
|
107
|
-
const keys = new Set(ObjectHelper.values(state.contexts[contextId]?.items ?? {}).flatMap((item) => item.keys));
|
|
108
|
-
|
|
109
|
-
const subContext = state.subContexts[contextId];
|
|
110
|
-
if (subContext) {
|
|
111
|
-
for (const childId of subContext) {
|
|
112
|
-
const subKeys = state.getAllKeys(childId);
|
|
113
|
-
for (const key of subKeys) {
|
|
114
|
-
keys.add(key);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return keys;
|
|
120
|
-
},
|
|
121
|
-
reload(queryClient: QueryClient, contextId: number) {
|
|
122
|
-
const state = get();
|
|
123
|
-
|
|
124
|
-
const keys = state.getAllKeys(contextId);
|
|
125
|
-
return queryClient.refetchQueries({
|
|
126
|
-
predicate: (query) => keys.has(query.queryHash),
|
|
127
|
-
});
|
|
128
|
-
},
|
|
129
|
-
addSubContext(parent: number, child: number) {
|
|
130
|
-
set((state) => {
|
|
131
|
-
const newChildren = state.subContexts[parent] ?? [];
|
|
132
|
-
newChildren.push(child);
|
|
133
|
-
state.subContexts[parent] = newChildren;
|
|
134
|
-
});
|
|
135
|
-
},
|
|
136
|
-
removeSubContext(parent: number, child: number) {
|
|
137
|
-
set((state) => {
|
|
138
|
-
state.subContexts[parent] = state.subContexts[parent]?.filter((id) => id !== child);
|
|
139
|
-
});
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
export const useQueriesStore = create<QueriesStoreState>()(
|
|
144
|
-
subscribeWithSelector(
|
|
145
|
-
immer((set, get) => ({
|
|
146
|
-
...initialState(),
|
|
147
|
-
...actionsGenerator(set, get),
|
|
148
|
-
})),
|
|
149
|
-
),
|
|
150
|
-
);
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useClientLayoutEffect } from '@ainias42/react-bootstrap-mobile';
|
|
3
|
-
import { useContext, useRef } from 'react';
|
|
4
|
-
import { useQueriesStore } from './useQueriesStore';
|
|
5
|
-
|
|
6
|
-
export function useQueriesSubscribeToReload(keys: string[], isFetching: boolean, error: any) {
|
|
7
|
-
const contextId = useContext(QueriesContext);
|
|
8
|
-
const setItem = useQueriesStore((s) => s.setItem);
|
|
9
|
-
const removeItem = useQueriesStore((s) => s.removeItem);
|
|
10
|
-
const id = useRef<number | undefined>(undefined);
|
|
11
|
-
|
|
12
|
-
useClientLayoutEffect(() => {
|
|
13
|
-
id.current = setItem(contextId, id.current, {
|
|
14
|
-
keys,
|
|
15
|
-
error,
|
|
16
|
-
isFetching,
|
|
17
|
-
});
|
|
18
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
19
|
-
}, [contextId, error, isFetching, setItem, ...keys]);
|
|
20
|
-
|
|
21
|
-
useClientLayoutEffect(() => {
|
|
22
|
-
return () => removeItem(contextId, id.current);
|
|
23
|
-
}, [contextId, removeItem]);
|
|
24
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { QueriesContext } from './QueriesContext';
|
|
2
|
-
import { useContext } from 'react';
|
|
3
|
-
import { useQueriesStore } from './useQueriesStore';
|
|
4
|
-
|
|
5
|
-
export function useScopedQueriesIsFetching(scope: { global: boolean; subContexts: boolean }, contextId?: number) {
|
|
6
|
-
const context = useContext(QueriesContext);
|
|
7
|
-
|
|
8
|
-
return useQueriesStore((s) => s.isFetchingByScope(contextId ?? context, scope));
|
|
9
|
-
}
|
package/src/generateIdsQuery.ts
DELETED
|
@@ -1,452 +0,0 @@
|
|
|
1
|
-
import { ArrayHelper, JsonHelper } from '@ainias42/js-helper';
|
|
2
|
-
import { NotFoundError } from './NotFoundError';
|
|
3
|
-
import { create, windowScheduler } from '@yornaath/batshit';
|
|
4
|
-
import { createInfiniteQuery, createQuery } from 'react-query-kit';
|
|
5
|
-
import { generateQuery } from './generateQuery';
|
|
6
|
-
import { hashKey, useQueries, useQueryClient } from '@tanstack/react-query';
|
|
7
|
-
import { hoistReactQueryKitAttributes } from './hoistReactQueryKitAttributes';
|
|
8
|
-
import { useIndexedData } from './hooks/useIndexedData';
|
|
9
|
-
import { useMemo, useRef } from 'react';
|
|
10
|
-
import { useQueriesSubscribeToReload } from './QueryContext/useQuerySubscribeToReload';
|
|
11
|
-
import type { Awaitable, Override, URecord } from '@ainias42/js-helper';
|
|
12
|
-
import type { CreateInfiniteQueryOptions, CreateQueryOptions, QueryHookOptions } from 'react-query-kit';
|
|
13
|
-
import type { QueryClient, QueryFunctionContext, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
|
14
|
-
|
|
15
|
-
export type Unpacked<T> = T extends (infer U)[] ? U : T;
|
|
16
|
-
|
|
17
|
-
type DeepPartial<T> = T extends object
|
|
18
|
-
? {
|
|
19
|
-
[P in keyof T]?: DeepPartial<T[P]>;
|
|
20
|
-
}
|
|
21
|
-
: T;
|
|
22
|
-
|
|
23
|
-
export function generateIdsQuery<
|
|
24
|
-
IdType extends number | string | symbol,
|
|
25
|
-
Type extends {
|
|
26
|
-
id: IdType;
|
|
27
|
-
},
|
|
28
|
-
>(
|
|
29
|
-
key: string,
|
|
30
|
-
fetcher: (ids: IdType[]) => Promise<Type[]> | Type[],
|
|
31
|
-
additionalQueryOptions?: Omit<CreateQueryOptions<Type, [IdType]>, 'queryKey' | 'fetcher'> & {
|
|
32
|
-
schedulerTime?: number;
|
|
33
|
-
},
|
|
34
|
-
) {
|
|
35
|
-
const batch = create<URecord<IdType, Type>[], IdType, Type>({
|
|
36
|
-
fetcher: async (ids) => {
|
|
37
|
-
return [ArrayHelper.arrayToObject(await fetcher(ids), (value) => value.id)];
|
|
38
|
-
},
|
|
39
|
-
resolver: ([items], id) => {
|
|
40
|
-
const item = items?.[id];
|
|
41
|
-
if (item === undefined) {
|
|
42
|
-
throw new NotFoundError(id);
|
|
43
|
-
}
|
|
44
|
-
return item;
|
|
45
|
-
},
|
|
46
|
-
scheduler: windowScheduler(additionalQueryOptions?.schedulerTime ?? 15),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const useQueryHook = createQuery<Type, [IdType]>({
|
|
50
|
-
queryKey: [key],
|
|
51
|
-
fetcher: (identifier) => batch.fetch(identifier[0]),
|
|
52
|
-
...additionalQueryOptions,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
function useManyHook(
|
|
56
|
-
ids: IdType[],
|
|
57
|
-
otherAdditionalQueryOptions: Omit<
|
|
58
|
-
UseQueryOptions<Type, unknown, Type, [string, [IdType]]>,
|
|
59
|
-
'context' | 'fetcher' | 'queryKey'
|
|
60
|
-
> & { reloadWithContext?: boolean } = {},
|
|
61
|
-
) {
|
|
62
|
-
const queries = ids.map((id) => ({
|
|
63
|
-
...useQueryHook.getOptions([id]),
|
|
64
|
-
...otherAdditionalQueryOptions,
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
const results = useQueries({
|
|
68
|
-
queries: queries as any,
|
|
69
|
-
}) as UseQueryResult<Type, Error>[];
|
|
70
|
-
|
|
71
|
-
const oldData = useRef<typeof results>([]);
|
|
72
|
-
const isSame = JsonHelper.deepEqual(oldData.current, results, 1);
|
|
73
|
-
|
|
74
|
-
const returnData = useRef({} as URecord<IdType, Unpacked<typeof results>>);
|
|
75
|
-
if (!isSame) {
|
|
76
|
-
oldData.current = results;
|
|
77
|
-
returnData.current = {} as URecord<IdType, Unpacked<typeof results>>;
|
|
78
|
-
for (const [index, val] of results.entries()) {
|
|
79
|
-
const id = ids[index] as IdType;
|
|
80
|
-
returnData.current[id] = val;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const shouldReload = otherAdditionalQueryOptions.reloadWithContext ?? true;
|
|
85
|
-
const keys = shouldReload ? ids.map((id) => hashKey(useQueryHook.getKey([id as DeepPartial<IdType>]))) : [];
|
|
86
|
-
|
|
87
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
88
|
-
const isFetching = useMemo(() => oldData.current.some((item) => item.isFetching), [oldData.current]);
|
|
89
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
|
-
const error = useMemo(() => oldData.current.find((item) => item.error)?.error, [oldData.current]);
|
|
91
|
-
|
|
92
|
-
useQueriesSubscribeToReload(keys, isFetching, error);
|
|
93
|
-
|
|
94
|
-
return returnData.current;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function useOneHook(
|
|
98
|
-
identifier: IdType,
|
|
99
|
-
otherAdditionalQueryOptions: Omit<
|
|
100
|
-
QueryHookOptions<Type, unknown, Type, [IdType]>,
|
|
101
|
-
'context' | 'fetcher' | 'queryKey' | 'variables'
|
|
102
|
-
> & { reloadWithContext?: boolean } = {},
|
|
103
|
-
) {
|
|
104
|
-
const result = useQueryHook({ ...otherAdditionalQueryOptions, variables: [identifier] });
|
|
105
|
-
|
|
106
|
-
const shouldReload = otherAdditionalQueryOptions.reloadWithContext ?? true;
|
|
107
|
-
useQueriesSubscribeToReload(
|
|
108
|
-
shouldReload ? [hashKey(useQueryHook.getKey([identifier as DeepPartial<IdType>]))] : [],
|
|
109
|
-
result.isFetching,
|
|
110
|
-
result.error,
|
|
111
|
-
);
|
|
112
|
-
return result;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function generateFilter<
|
|
116
|
-
Func extends (...args: any[]) => Promise<Type[]> | Type[],
|
|
117
|
-
FuncArguments extends Parameters<Func>['length'] extends 0 ? void : Parameters<Func> =
|
|
118
|
-
Parameters<Func>['length'] extends 0 ? void : Parameters<Func>,
|
|
119
|
-
ResultAsArray extends boolean = false,
|
|
120
|
-
>(
|
|
121
|
-
filterFunc: Func,
|
|
122
|
-
additionalOptions?: Omit<CreateQueryOptions<IdType[], [FuncArguments]>, 'queryKey' | 'fetcher'> & {
|
|
123
|
-
resultAsArray?: ResultAsArray;
|
|
124
|
-
subKey?: string;
|
|
125
|
-
},
|
|
126
|
-
) {
|
|
127
|
-
let queryClient: QueryClient | undefined;
|
|
128
|
-
|
|
129
|
-
function getQueryClient() {
|
|
130
|
-
if (!queryClient) {
|
|
131
|
-
throw new Error('QueryClient not set');
|
|
132
|
-
}
|
|
133
|
-
return queryClient;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const queryHook = generateQuery(
|
|
137
|
-
`${key}-filter${additionalOptions?.subKey ? `-${additionalOptions.subKey}` : ''}`,
|
|
138
|
-
async (filter: FuncArguments) => {
|
|
139
|
-
const result = await filterFunc(filter);
|
|
140
|
-
return result.map((item) => {
|
|
141
|
-
getQueryClient().setQueryData(useQueryHook.getKey([item.id as DeepPartial<IdType>]), item);
|
|
142
|
-
return item.id;
|
|
143
|
-
});
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
initialDataUpdatedAt: 0,
|
|
147
|
-
...additionalOptions,
|
|
148
|
-
initialData: (additionalOptions?.initialData as IdType[]) ?? [],
|
|
149
|
-
use: [
|
|
150
|
-
function myTestMiddleware(useQueryNext) {
|
|
151
|
-
return (options2, currentQueryClient) => {
|
|
152
|
-
queryClient = useQueryClient(currentQueryClient);
|
|
153
|
-
return useQueryNext(options2, queryClient);
|
|
154
|
-
};
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
type QueryHookArgument = Parameters<typeof queryHook>[0];
|
|
161
|
-
type HookArgs = FuncArguments extends void ? [] : FuncArguments;
|
|
162
|
-
type FilterKeyArguments = DeepPartial<HookArgs> | DeepPartial<[FuncArguments]>;
|
|
163
|
-
type FilterGetKey = (variables?: FilterKeyArguments) => readonly unknown[];
|
|
164
|
-
|
|
165
|
-
function useFilter<Args extends HookArgs>(
|
|
166
|
-
args: Args,
|
|
167
|
-
filterQueryOptions: Omit<
|
|
168
|
-
CreateQueryOptions<Type[], [FuncArguments]>,
|
|
169
|
-
'queryKey' | 'fetcher' | 'variables'
|
|
170
|
-
> = {},
|
|
171
|
-
): ResultAsArray extends true ? UseQueryResult<Type, Error>[] : URecord<IdType, UseQueryResult<Type, Error>> {
|
|
172
|
-
const { initialData, ...otherParams } = filterQueryOptions;
|
|
173
|
-
|
|
174
|
-
let initialDataIds: IdType[] = [];
|
|
175
|
-
if (initialData) {
|
|
176
|
-
const updatedAt =
|
|
177
|
-
(typeof otherParams.initialDataUpdatedAt === 'function'
|
|
178
|
-
? otherParams.initialDataUpdatedAt()
|
|
179
|
-
: otherParams.initialDataUpdatedAt) ?? 0;
|
|
180
|
-
const initialDataArray = Array.isArray(initialData) ? initialData : initialData();
|
|
181
|
-
initialDataIds =
|
|
182
|
-
initialDataArray?.map((item) => {
|
|
183
|
-
const itemKey = useOne.getKey([item.id as DeepPartial<IdType>]);
|
|
184
|
-
const state = queryClient?.getQueryState(itemKey);
|
|
185
|
-
if (!state || state.dataUpdatedAt < updatedAt) {
|
|
186
|
-
queryClient?.setQueryData(itemKey, item, {
|
|
187
|
-
updatedAt,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
return item.id;
|
|
191
|
-
}) ?? [];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const result = queryHook({
|
|
195
|
-
variables: args,
|
|
196
|
-
...otherParams,
|
|
197
|
-
initialData: initialDataIds,
|
|
198
|
-
} as QueryHookArgument);
|
|
199
|
-
|
|
200
|
-
useQueriesSubscribeToReload(
|
|
201
|
-
[hashKey(queryHook.getKey(args as Parameters<(typeof queryHook)['getKey']>[0]))],
|
|
202
|
-
result.isFetching,
|
|
203
|
-
result.error,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
// When staleTime is same as filterStaleTime, then the queries will run at the same time as the filter
|
|
207
|
-
// Because of this, use staleTime: Inifinity to prevent the queries from running again. The result of the
|
|
208
|
-
// filter will be saved inside the queryClient, so the queries will be updated with the new data.
|
|
209
|
-
// Same goes for reloading with context.
|
|
210
|
-
// Other usages of useManyHook are not affected by this.
|
|
211
|
-
const indexedResult = useManyHook(result.data, { staleTime: Infinity, reloadWithContext: false });
|
|
212
|
-
const filteredIds = result.data as IdType[];
|
|
213
|
-
const arrayResult: UseQueryResult<Type, Error>[] = useMemo(
|
|
214
|
-
() => ArrayHelper.noUndefined(filteredIds?.map((id) => indexedResult[id])),
|
|
215
|
-
[filteredIds, indexedResult],
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
if (additionalOptions?.resultAsArray) {
|
|
219
|
-
return arrayResult as ResultAsArray extends true
|
|
220
|
-
? UseQueryResult<Type, Error>[]
|
|
221
|
-
: URecord<IdType, UseQueryResult<Type, Error>>;
|
|
222
|
-
}
|
|
223
|
-
return indexedResult as ResultAsArray extends true
|
|
224
|
-
? UseQueryResult<Type, Error>[]
|
|
225
|
-
: URecord<IdType, UseQueryResult<Type, Error>>;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const filterHook = hoistReactQueryKitAttributes(queryHook, useFilter);
|
|
229
|
-
const getFilterKey: FilterGetKey = (variables) => {
|
|
230
|
-
return queryHook.getKey(variables as Parameters<(typeof queryHook)['getKey']>[0]);
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
return Object.assign(filterHook, {
|
|
234
|
-
getKey: getFilterKey,
|
|
235
|
-
}) as typeof filterHook & {
|
|
236
|
-
getKey: FilterGetKey;
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function generateInfiniteQuery<PageParams, Meta, FuncArguments extends any[] = []>({
|
|
241
|
-
queryFn,
|
|
242
|
-
...options
|
|
243
|
-
}: Omit<
|
|
244
|
-
CreateInfiniteQueryOptions<{ data: IdType[]; meta: Meta }, FuncArguments, Error, PageParams>,
|
|
245
|
-
'getNextPageParam' | 'getPreviousPageParam' | 'queryKey' | 'fetcher'
|
|
246
|
-
> & {
|
|
247
|
-
queryFn: (
|
|
248
|
-
pageParams: PageParams,
|
|
249
|
-
args: FuncArguments,
|
|
250
|
-
context: QueryFunctionContext<[string, FuncArguments], PageParams>,
|
|
251
|
-
) => Awaitable<{ data: Type[]; meta: Meta }>;
|
|
252
|
-
getNextPageParam: (options: {
|
|
253
|
-
getLastPage: () => Type[];
|
|
254
|
-
getAllPages: () => Type[][];
|
|
255
|
-
lastPageParam: PageParams;
|
|
256
|
-
allPageParams: PageParams[];
|
|
257
|
-
lastPageLength: number;
|
|
258
|
-
lastPageMeta: Meta;
|
|
259
|
-
}) => PageParams | undefined;
|
|
260
|
-
getPreviousPageParam?: (options: {
|
|
261
|
-
getFirstPage: () => Type[];
|
|
262
|
-
getAllPages: () => Type[][];
|
|
263
|
-
firstPageParam: PageParams;
|
|
264
|
-
allPageParams: PageParams[];
|
|
265
|
-
firstPageLength: number;
|
|
266
|
-
firstPageMeta: Meta;
|
|
267
|
-
}) => PageParams | undefined;
|
|
268
|
-
}) {
|
|
269
|
-
let queryClient: QueryClient | undefined;
|
|
270
|
-
|
|
271
|
-
function getQueryClient() {
|
|
272
|
-
if (!queryClient) {
|
|
273
|
-
throw new Error('QueryClient not set');
|
|
274
|
-
}
|
|
275
|
-
return queryClient;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function findEntity(id: IdType) {
|
|
279
|
-
return getQueryClient().getQueryData(useQueryHook.getKey([id as DeepPartial<IdType>])) as Type | undefined;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const { getPreviousPageParam } = options;
|
|
283
|
-
const infiniteQueryHook = createInfiniteQuery<
|
|
284
|
-
{
|
|
285
|
-
data: IdType[];
|
|
286
|
-
meta: Meta;
|
|
287
|
-
},
|
|
288
|
-
FuncArguments,
|
|
289
|
-
Error,
|
|
290
|
-
PageParams
|
|
291
|
-
>({
|
|
292
|
-
...options,
|
|
293
|
-
use: [
|
|
294
|
-
function myTestMiddleware(useQueryNext) {
|
|
295
|
-
return (options2, currentQueryClient) => {
|
|
296
|
-
queryClient = useQueryClient(currentQueryClient);
|
|
297
|
-
return useQueryNext(options2, queryClient);
|
|
298
|
-
};
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
queryKey: [`${key}-infinite-query`],
|
|
302
|
-
fetcher: async (_, ctx) => {
|
|
303
|
-
const context = ctx as typeof ctx & { pageParam: PageParams };
|
|
304
|
-
const result = await queryFn(
|
|
305
|
-
context.pageParam,
|
|
306
|
-
context.queryKey[1] as FuncArguments,
|
|
307
|
-
context as QueryFunctionContext<[string, FuncArguments], PageParams>,
|
|
308
|
-
);
|
|
309
|
-
return {
|
|
310
|
-
meta: result.meta,
|
|
311
|
-
data: result.data.map((item) => {
|
|
312
|
-
getQueryClient().setQueryData(useQueryHook.getKey([item.id as DeepPartial<IdType>]), item);
|
|
313
|
-
return item.id;
|
|
314
|
-
}),
|
|
315
|
-
};
|
|
316
|
-
},
|
|
317
|
-
getNextPageParam: (
|
|
318
|
-
lastPage: { meta: Meta; data: IdType[] },
|
|
319
|
-
allPages: {
|
|
320
|
-
meta: Meta;
|
|
321
|
-
data: IdType[];
|
|
322
|
-
}[],
|
|
323
|
-
lastPageParam,
|
|
324
|
-
allPageParams,
|
|
325
|
-
) => {
|
|
326
|
-
return options.getNextPageParam({
|
|
327
|
-
lastPageMeta: lastPage.meta,
|
|
328
|
-
getLastPage: () => ArrayHelper.noUndefined(lastPage.data.map(findEntity)),
|
|
329
|
-
getAllPages: () => allPages.map((page) => ArrayHelper.noUndefined(page.data.map(findEntity))),
|
|
330
|
-
lastPageParam,
|
|
331
|
-
allPageParams,
|
|
332
|
-
lastPageLength: lastPage.data.length,
|
|
333
|
-
});
|
|
334
|
-
},
|
|
335
|
-
getPreviousPageParam: !getPreviousPageParam
|
|
336
|
-
? undefined
|
|
337
|
-
: (firstPage, allPages, firstPageParam, allPageParams) => {
|
|
338
|
-
return getPreviousPageParam({
|
|
339
|
-
firstPageMeta: firstPage.meta,
|
|
340
|
-
getFirstPage: () => ArrayHelper.noUndefined(firstPage.data.map(findEntity)),
|
|
341
|
-
getAllPages: () => allPages.map((page) => ArrayHelper.noUndefined(page.data.map(findEntity))),
|
|
342
|
-
firstPageParam,
|
|
343
|
-
allPageParams,
|
|
344
|
-
firstPageLength: firstPage.data.length,
|
|
345
|
-
});
|
|
346
|
-
},
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
type HookOptions = Override<
|
|
350
|
-
Parameters<typeof infiniteQueryHook>[0] & {
|
|
351
|
-
select?: undefined;
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
initialData?: {
|
|
355
|
-
pages: { data: Type[]; meta: Meta }[];
|
|
356
|
-
pageParams: PageParams[];
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
>;
|
|
360
|
-
|
|
361
|
-
type InfiniteQueryArgs = FuncArguments extends
|
|
362
|
-
| {
|
|
363
|
-
length: 0;
|
|
364
|
-
}
|
|
365
|
-
| symbol
|
|
366
|
-
| undefined
|
|
367
|
-
? [HookOptions] | []
|
|
368
|
-
: [HookOptions];
|
|
369
|
-
|
|
370
|
-
function useInfiniteQuery(...args: InfiniteQueryArgs) {
|
|
371
|
-
const infiniteQueryClient = useQueryClient();
|
|
372
|
-
|
|
373
|
-
let infiniteOptions: Parameters<typeof infiniteQueryHook>[0] & {
|
|
374
|
-
select?: undefined;
|
|
375
|
-
};
|
|
376
|
-
if (args.length === 1 && 'initialData' in args[0] && args[0].initialData) {
|
|
377
|
-
const { initialData } = args[0];
|
|
378
|
-
infiniteOptions = {
|
|
379
|
-
...args[0],
|
|
380
|
-
initialData: {
|
|
381
|
-
pages: initialData.pages.map((page) => ({
|
|
382
|
-
...page,
|
|
383
|
-
data: page.data.map((item) => {
|
|
384
|
-
infiniteQueryClient.setQueryData(
|
|
385
|
-
useQueryHook.getKey([item.id as DeepPartial<IdType>]),
|
|
386
|
-
item,
|
|
387
|
-
);
|
|
388
|
-
return item.id;
|
|
389
|
-
}),
|
|
390
|
-
})),
|
|
391
|
-
pageParams: initialData.pageParams,
|
|
392
|
-
},
|
|
393
|
-
} as Parameters<typeof infiniteQueryHook>[0] & {
|
|
394
|
-
select?: undefined;
|
|
395
|
-
};
|
|
396
|
-
} else {
|
|
397
|
-
infiniteOptions =
|
|
398
|
-
(args[0] as Parameters<typeof infiniteQueryHook>[0] & {
|
|
399
|
-
select?: undefined;
|
|
400
|
-
}) ?? {};
|
|
401
|
-
}
|
|
402
|
-
const result = infiniteQueryHook(infiniteOptions);
|
|
403
|
-
useQueriesSubscribeToReload(
|
|
404
|
-
[
|
|
405
|
-
hashKey(
|
|
406
|
-
infiniteQueryHook.getKey(
|
|
407
|
-
infiniteOptions.variables as Parameters<(typeof infiniteQueryHook)['getKey']>[0],
|
|
408
|
-
),
|
|
409
|
-
),
|
|
410
|
-
],
|
|
411
|
-
result.isFetching,
|
|
412
|
-
result.error,
|
|
413
|
-
);
|
|
414
|
-
const ids = useMemo(() => result.data?.pages.flatMap((page) => page.data) ?? [], [result.data?.pages]);
|
|
415
|
-
|
|
416
|
-
// When staleTime is same as filterStaleTime, then the queries will run at the same time as the filter
|
|
417
|
-
// Because of this, use staleTime: Inifinity to prevent the queries from running again. The result of the
|
|
418
|
-
// filter will be saved inside the queryClient, so the queries will be updated with the new data.
|
|
419
|
-
// Same goes for reloading with context.
|
|
420
|
-
// Other usages of useManyHook are not affected by this.
|
|
421
|
-
const manyData = useManyHook(ids, { staleTime: Infinity, reloadWithContext: false });
|
|
422
|
-
const indexedData = useIndexedData(manyData);
|
|
423
|
-
|
|
424
|
-
const pages = useMemo(() => {
|
|
425
|
-
return result.data
|
|
426
|
-
? result.data.pages.map((page) => ArrayHelper.noUndefined(page.data.map((id) => indexedData[id])))
|
|
427
|
-
: [];
|
|
428
|
-
}, [indexedData, result.data]);
|
|
429
|
-
|
|
430
|
-
const data = !result.data ? undefined : { ...result.data, pages };
|
|
431
|
-
type TransformedResult = Omit<typeof result, 'data'> & {
|
|
432
|
-
data: typeof data;
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
return new Proxy(result, {
|
|
436
|
-
get(target, property, receiver) {
|
|
437
|
-
if (property === 'data') {
|
|
438
|
-
return data;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return Reflect.get(target, property, receiver);
|
|
442
|
-
},
|
|
443
|
-
}) as TransformedResult;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return hoistReactQueryKitAttributes(infiniteQueryHook, useInfiniteQuery);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const useMany = hoistReactQueryKitAttributes(useQueryHook, useManyHook);
|
|
450
|
-
const useOne = hoistReactQueryKitAttributes(useQueryHook, useOneHook);
|
|
451
|
-
return { useMany, useOne, generateFilter, generateInfiniteQuery };
|
|
452
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { generateIdsQuery } from './generateIdsQuery';
|
|
2
|
-
import { hashKey } from '@tanstack/react-query';
|
|
3
|
-
import { noop } from './noop';
|
|
4
|
-
import { useMemo } from 'react';
|
|
5
|
-
import type { DeepPartial } from './types/DeepPartial';
|
|
6
|
-
|
|
7
|
-
export function generateNestedIdQuery<
|
|
8
|
-
IdType extends number | string | symbol,
|
|
9
|
-
Type extends {
|
|
10
|
-
id: IdType;
|
|
11
|
-
},
|
|
12
|
-
Args extends any[],
|
|
13
|
-
>(key: string, fetcherFactory: (...args: Args) => (ids: IdType[]) => Promise<Type[]> | Type[]) {
|
|
14
|
-
const factory = (...args: Args) => {
|
|
15
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
16
|
-
return useMemo(() => {
|
|
17
|
-
const keyWithArgs = `${key}-${hashKey(args)}`;
|
|
18
|
-
return generateIdsQuery(keyWithArgs, fetcherFactory(...args));
|
|
19
|
-
}, [args]);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
factory.getFilterKey = (args: Args, variables?: any[]): readonly unknown[] => {
|
|
23
|
-
const keyWithArgs = `${key}-${hashKey(args)}`;
|
|
24
|
-
return generateIdsQuery(keyWithArgs, fetcherFactory(...args))
|
|
25
|
-
.generateFilter(noop as (..._: any[]) => Type[])
|
|
26
|
-
.getKey([variables]);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
factory.getKey = <Variables extends [(DeepPartial<IdType> | undefined)?] | undefined = undefined>(
|
|
30
|
-
args: Args,
|
|
31
|
-
variables?: Variables,
|
|
32
|
-
): readonly unknown[] => {
|
|
33
|
-
const keyWithArgs = `${key}-${hashKey(args)}`;
|
|
34
|
-
return generateIdsQuery(keyWithArgs, fetcherFactory(...args)).useOne.getKey(variables);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
return factory;
|
|
38
|
-
}
|
package/src/generateQuery.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { createQuery } from 'react-query-kit';
|
|
2
|
-
import { hashKey } from '@tanstack/react-query';
|
|
3
|
-
import { hoistReactQueryKitAttributes } from './hoistReactQueryKitAttributes';
|
|
4
|
-
import { useQueriesSubscribeToReload } from './QueryContext/useQuerySubscribeToReload';
|
|
5
|
-
import type { CreateQueryOptions, QueryHookResult } from 'react-query-kit';
|
|
6
|
-
|
|
7
|
-
export function generateQuery<
|
|
8
|
-
Func extends (...args: any[]) => Promise<any> | any,
|
|
9
|
-
FuncArguments extends Parameters<Func>['length'] extends 0
|
|
10
|
-
? void
|
|
11
|
-
: Parameters<Func> = Parameters<Func>['length'] extends 0 ? void : Parameters<Func>,
|
|
12
|
-
FuncReturnType extends Awaited<ReturnType<Func>> = Awaited<ReturnType<Func>>,
|
|
13
|
-
AdditionalOptionsType extends Omit<
|
|
14
|
-
CreateQueryOptions<FuncReturnType, FuncArguments>,
|
|
15
|
-
'queryKey' | 'fetcher'
|
|
16
|
-
> = Omit<CreateQueryOptions<FuncReturnType, FuncArguments>, 'queryKey' | 'fetcher'>,
|
|
17
|
-
>(primaryKey: string, queryFn: Func, additionalOptions?: AdditionalOptionsType) {
|
|
18
|
-
const queryHook = createQuery<FuncReturnType, FuncArguments>({
|
|
19
|
-
queryKey: [primaryKey],
|
|
20
|
-
fetcher: (variables) => {
|
|
21
|
-
if (Array.isArray(variables)) {
|
|
22
|
-
return queryFn(...variables);
|
|
23
|
-
}
|
|
24
|
-
return queryFn();
|
|
25
|
-
},
|
|
26
|
-
initialDataUpdatedAt: 0,
|
|
27
|
-
...additionalOptions,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
type QueryHookArgument = Parameters<typeof queryHook>[0];
|
|
31
|
-
type HookArgs = FuncArguments extends void ? [] | [QueryHookArgument] : [QueryHookArgument];
|
|
32
|
-
|
|
33
|
-
function useHook<Arg extends HookArgs>(...options: Arg) {
|
|
34
|
-
type DataReturnType = Arg extends [
|
|
35
|
-
{
|
|
36
|
-
initialData: FuncReturnType | (() => FuncReturnType);
|
|
37
|
-
},
|
|
38
|
-
]
|
|
39
|
-
? FuncReturnType
|
|
40
|
-
: AdditionalOptionsType extends { initialData: FuncReturnType | (() => FuncReturnType) }
|
|
41
|
-
? FuncReturnType
|
|
42
|
-
: FuncReturnType | undefined;
|
|
43
|
-
|
|
44
|
-
const result = queryHook(options[0] as QueryHookArgument);
|
|
45
|
-
useQueriesSubscribeToReload(
|
|
46
|
-
[hashKey(queryHook.getKey(options[0]?.variables as Parameters<(typeof queryHook)['getKey']>[0]))],
|
|
47
|
-
result.isFetching,
|
|
48
|
-
result.error,
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
type Return = Arg extends [{ select: (...arg: any[]) => any }] ? ReturnType<Arg[0]['select']> : DataReturnType;
|
|
52
|
-
return result as QueryHookResult<Return, Error> & {
|
|
53
|
-
data: Return;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return hoistReactQueryKitAttributes(queryHook, useHook);
|
|
58
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ExposeMethods } from 'react-query-kit';
|
|
2
|
-
|
|
3
|
-
export function hoistReactQueryKitAttributes<
|
|
4
|
-
ReturnType,
|
|
5
|
-
HookArgs,
|
|
6
|
-
ToFunc extends (...args: any[]) => any,
|
|
7
|
-
Error,
|
|
8
|
-
PageParam = never,
|
|
9
|
-
>(from: ExposeMethods<ReturnType, HookArgs, Error, PageParam>, to: ToFunc) {
|
|
10
|
-
const partialHook: Partial<ExposeMethods<ReturnType, HookArgs, Error, PageParam>> & ToFunc = Object.assign(
|
|
11
|
-
to,
|
|
12
|
-
from,
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
return partialHook as ExposeMethods<ReturnType, HookArgs, Error, PageParam> & ToFunc;
|
|
16
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { ArrayHelper, ObjectHelper } from '@ainias42/js-helper';
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import type { URecord } from '@ainias42/js-helper';
|
|
4
|
-
|
|
5
|
-
export function useArrayData<DataType>(
|
|
6
|
-
result:
|
|
7
|
-
| {
|
|
8
|
-
data: DataType;
|
|
9
|
-
}[]
|
|
10
|
-
| URecord<any, { data: DataType | undefined }>
|
|
11
|
-
| undefined,
|
|
12
|
-
): Exclude<DataType, undefined>[] {
|
|
13
|
-
return useMemo(
|
|
14
|
-
() =>
|
|
15
|
-
ArrayHelper.noUndefined(
|
|
16
|
-
(Array.isArray(result) ? result : ObjectHelper.values(result ?? {}))?.map((data) => data.data),
|
|
17
|
-
),
|
|
18
|
-
[result],
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { ObjectHelper } from '@ainias42/js-helper';
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import type { URecord } from '@ainias42/js-helper';
|
|
4
|
-
|
|
5
|
-
export function useIndexedData<DataType, IndexType extends number | string | symbol>(
|
|
6
|
-
result: URecord<
|
|
7
|
-
IndexType,
|
|
8
|
-
{
|
|
9
|
-
data: DataType | undefined;
|
|
10
|
-
}
|
|
11
|
-
>,
|
|
12
|
-
) {
|
|
13
|
-
return useMemo(
|
|
14
|
-
() =>
|
|
15
|
-
ObjectHelper.entries(result).reduce(
|
|
16
|
-
(acc, [key, data]) => {
|
|
17
|
-
acc[key] = data.data;
|
|
18
|
-
return acc;
|
|
19
|
-
},
|
|
20
|
-
{} as URecord<IndexType, DataType>,
|
|
21
|
-
),
|
|
22
|
-
[result],
|
|
23
|
-
);
|
|
24
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { DateHelper } from '@ainias42/js-helper';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
|
|
4
|
-
export function useInitialData<Type>(data: Type | undefined) {
|
|
5
|
-
const [now] = useState(() => DateHelper.now());
|
|
6
|
-
if (data) {
|
|
7
|
-
return {
|
|
8
|
-
initialData: data,
|
|
9
|
-
initialDataUpdatedAt: now,
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
return {};
|
|
13
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export * from './generateIdsQuery';
|
|
2
|
-
export * from './generateNestedIdQuery';
|
|
3
|
-
export * from './generateQuery';
|
|
4
|
-
export * from './hoistReactQueryKitAttributes';
|
|
5
|
-
export * from './noop';
|
|
6
|
-
export * from './NotFoundError';
|
|
7
|
-
export * from './hooks/formatInitialPage';
|
|
8
|
-
export * from './hooks/useArrayData';
|
|
9
|
-
export * from './hooks/useIndexedData';
|
|
10
|
-
export * from './hooks/useInitialData';
|
|
11
|
-
export * from './QueryContext/QueriesContext';
|
|
12
|
-
export * from './QueryContext/QueriesContextProvider';
|
|
13
|
-
export * from './QueryContext/SubQueriesContext';
|
|
14
|
-
export * from './QueryContext/useGenerateQueriesContextId';
|
|
15
|
-
export * from './QueryContext/useQueriesError';
|
|
16
|
-
export * from './QueryContext/useQueriesIsFetching';
|
|
17
|
-
export * from './QueryContext/useQueriesReload';
|
|
18
|
-
export * from './QueryContext/useQueriesStore';
|
|
19
|
-
export * from './QueryContext/useQuerySubscribeToReload';
|
|
20
|
-
export * from './QueryContext/useScopedQueriesIsFetching';
|
|
21
|
-
export * from './types/DeepPartial';
|
package/src/noop.ts
DELETED
package/src/types/DeepPartial.ts
DELETED
package/stylelint.config.mjs
DELETED
package/tsconfig.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "@ainias42/config/tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"jsx": "react",
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"declaration": true
|
|
8
|
-
},
|
|
9
|
-
"exclude": ["../../node_modules", "dist/**/*", "dist/src/**/*"],
|
|
10
|
-
"include": ["src/**/*.tsx", "src/**/*.ts"]
|
|
11
|
-
}
|