@codeleap/query 5.8.3 → 5.8.5
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/package.json +6 -10
- package/package.json.bak +3 -7
- package/src/factors/createQueryManager.ts +38 -0
- package/src/factors/createQueryOperations.ts +37 -0
- package/src/factors/index.ts +2 -0
- package/src/index.ts +2 -8
- package/src/lib/Mutations.ts +280 -0
- package/src/{queryClient.ts → lib/QueryClientEnhanced/index.ts} +24 -72
- package/src/lib/QueryClientEnhanced/types.ts +38 -0
- package/src/lib/QueryKeys.ts +319 -0
- package/src/lib/QueryManager.ts +488 -0
- package/src/lib/QueryOperations/index.ts +351 -0
- package/src/lib/QueryOperations/types.ts +47 -0
- package/src/lib/index.ts +5 -0
- package/src/tests/Mutations.spec.tsx +458 -0
- package/src/tests/QueryManager.spec.tsx +920 -0
- package/src/tests/QueryOperations.spec.tsx +109 -0
- package/src/tests/integration.spec.tsx +551 -0
- package/src/tests/setup.ts +119 -0
- package/src/types/core.ts +33 -0
- package/src/types/create.ts +16 -0
- package/src/types/delete.ts +15 -0
- package/src/types/index.ts +7 -0
- package/src/types/list.ts +24 -0
- package/src/types/retrieve.ts +7 -0
- package/src/types/update.ts +14 -0
- package/src/types/utility.ts +22 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +43 -0
- package/src/QueryManager.ts +0 -954
- package/src/types.ts +0 -199
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { afterEach, beforeEach } from 'bun:test'
|
|
2
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
3
|
+
import { cleanup } from '@testing-library/react'
|
|
4
|
+
|
|
5
|
+
export interface TestUser {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
email: string
|
|
9
|
+
status: 'active' | 'inactive'
|
|
10
|
+
createdAt: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TestUserFilters {
|
|
14
|
+
status?: 'active' | 'inactive'
|
|
15
|
+
search?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const createTestQueryClient = () => {
|
|
19
|
+
return new QueryClient({
|
|
20
|
+
defaultOptions: {
|
|
21
|
+
queries: {
|
|
22
|
+
retry: false,
|
|
23
|
+
gcTime: 0,
|
|
24
|
+
},
|
|
25
|
+
mutations: {
|
|
26
|
+
retry: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const createMockUser = (overrides: Partial<TestUser> = {}): TestUser => ({
|
|
33
|
+
id: `user-${Date.now()}-${Math.random()}`,
|
|
34
|
+
name: 'John Doe',
|
|
35
|
+
email: 'john@example.com',
|
|
36
|
+
status: 'active',
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
...overrides,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const createMockUsers = (count: number): TestUser[] => {
|
|
42
|
+
return Array.from({ length: count }, (_, i) => createMockUser({
|
|
43
|
+
id: `user-${i + 1}`,
|
|
44
|
+
name: `User ${i + 1}`,
|
|
45
|
+
email: `user${i + 1}@example.com`,
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const createMockApiFunctions = () => {
|
|
50
|
+
const users: TestUser[] = []
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
listFn: async (limit: number, offset: number, filters?: TestUserFilters) => {
|
|
54
|
+
let filteredUsers = [...users]
|
|
55
|
+
|
|
56
|
+
if (filters?.status) {
|
|
57
|
+
filteredUsers = filteredUsers.filter(user => user.status === filters.status)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (filters?.search) {
|
|
61
|
+
filteredUsers = filteredUsers.filter(user =>
|
|
62
|
+
user.name.toLowerCase().includes(filters.search!.toLowerCase()) ||
|
|
63
|
+
user.email.toLowerCase().includes(filters.search!.toLowerCase())
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return filteredUsers.slice(offset, offset + limit)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
retrieveFn: async (id: string) => {
|
|
71
|
+
const user = users.find(u => u.id === id)
|
|
72
|
+
if (!user) throw new Error('User not found')
|
|
73
|
+
return user
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
createFn: async (data: Partial<TestUser>) => {
|
|
77
|
+
const newUser: TestUser = {
|
|
78
|
+
id: `user-${users?.length + 1}`,
|
|
79
|
+
name: data.name || 'New User',
|
|
80
|
+
email: data.email || 'new@example.com',
|
|
81
|
+
status: data.status || 'active',
|
|
82
|
+
createdAt: new Date().toISOString(),
|
|
83
|
+
}
|
|
84
|
+
users.push(newUser)
|
|
85
|
+
return newUser
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
updateFn: async (data: Partial<TestUser>) => {
|
|
89
|
+
const userIndex = users.findIndex(u => u.id === data.id)
|
|
90
|
+
if (userIndex === -1) throw new Error('User not found')
|
|
91
|
+
|
|
92
|
+
users[userIndex] = { ...users[userIndex], ...data }
|
|
93
|
+
return users[userIndex]
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
deleteFn: async (id: string) => {
|
|
97
|
+
const userIndex = users.findIndex(u => u.id === id)
|
|
98
|
+
if (userIndex === -1) throw new Error('User not found')
|
|
99
|
+
|
|
100
|
+
users.splice(userIndex, 1)
|
|
101
|
+
return id
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
getUsersArray: () => [...users],
|
|
105
|
+
addUser: (user: TestUser) => users.push(user),
|
|
106
|
+
clearUsers: () => users.splice(0, users.length),
|
|
107
|
+
setUsers: (newUsers: TestUser[]) => {
|
|
108
|
+
users.splice(0, users.length, ...newUsers)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
beforeEach(() => { })
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
cleanup()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
export const waitFor = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { UseInfiniteQueryResult, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
export type QueryItem = {
|
|
4
|
+
id: string | number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type PageParam = number
|
|
8
|
+
|
|
9
|
+
export type ListPaginationResponse<T extends QueryItem> = T[]
|
|
10
|
+
|
|
11
|
+
export type QueryManagerOptions<T extends QueryItem, F> = {
|
|
12
|
+
name: string
|
|
13
|
+
|
|
14
|
+
queryClient: ReturnType<typeof useQueryClient>
|
|
15
|
+
|
|
16
|
+
listFn?: (limit: number, offset: number, filters: F) => Promise<ListPaginationResponse<T>>
|
|
17
|
+
|
|
18
|
+
listLimit?: number
|
|
19
|
+
|
|
20
|
+
useListEffect?: (listQuery: UseInfiniteQueryResult<{
|
|
21
|
+
pageParams: number[]
|
|
22
|
+
pages: ListPaginationResponse<T>[]
|
|
23
|
+
allItems: T[]
|
|
24
|
+
}, Error>) => void
|
|
25
|
+
|
|
26
|
+
retrieveFn?: (id: T['id']) => Promise<T>
|
|
27
|
+
|
|
28
|
+
createFn?: (data: Partial<T>) => Promise<T>
|
|
29
|
+
|
|
30
|
+
updateFn?: (data: Partial<T>) => Promise<T>
|
|
31
|
+
|
|
32
|
+
deleteFn?: (id: T['id']) => Promise<T['id']>
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UseMutationOptions } from '@tanstack/react-query'
|
|
2
|
+
import { QueryItem } from './core'
|
|
3
|
+
import { RemovedItemMap } from './utility'
|
|
4
|
+
|
|
5
|
+
export type CreateMutationCtx = {
|
|
6
|
+
tempId?: QueryItem['id']
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CreateMutationOptions<T extends QueryItem, F> = Omit<
|
|
10
|
+
UseMutationOptions<T, Error, Partial<T>, CreateMutationCtx>,
|
|
11
|
+
'mutationKey' | 'mutationFn'
|
|
12
|
+
> & {
|
|
13
|
+
optimistic?: boolean
|
|
14
|
+
listFilters?: F
|
|
15
|
+
appendTo?: RemovedItemMap | 'start' | 'end'
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { UseMutationOptions } from '@tanstack/react-query'
|
|
2
|
+
import { QueryItem } from './core'
|
|
3
|
+
import { RemovedItemMap } from './utility'
|
|
4
|
+
|
|
5
|
+
export type DeleteMutationCtx<T> = {
|
|
6
|
+
previousItem: T | undefined
|
|
7
|
+
removedAt: RemovedItemMap
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DeleteMutationOptions<T extends QueryItem, F> = Omit<
|
|
11
|
+
UseMutationOptions<unknown, Error, T['id'], DeleteMutationCtx<T>>,
|
|
12
|
+
'mutationKey' | 'mutationFn'
|
|
13
|
+
> & {
|
|
14
|
+
optimistic?: boolean
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { QueryKey, UseInfiniteQueryOptions } from '@tanstack/react-query'
|
|
2
|
+
import { ListPaginationResponse, PageParam, QueryItem } from './core'
|
|
3
|
+
|
|
4
|
+
export type ListSelector<T extends QueryItem> = {
|
|
5
|
+
pageParams: PageParam[]
|
|
6
|
+
pages: ListPaginationResponse<T>[]
|
|
7
|
+
allItems: T[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type InfiniteQueryOptions<T extends QueryItem> = UseInfiniteQueryOptions<
|
|
11
|
+
ListPaginationResponse<T>,
|
|
12
|
+
Error,
|
|
13
|
+
ListSelector<T>,
|
|
14
|
+
QueryKey,
|
|
15
|
+
PageParam
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
export type ListQueryOptions<T extends QueryItem, F> = Omit<
|
|
19
|
+
InfiniteQueryOptions<T>,
|
|
20
|
+
'queryKey' | 'queryFn' | 'initialPageParam' | 'getNextPageParam' | 'getPreviousPageParam' | 'select'
|
|
21
|
+
> & {
|
|
22
|
+
limit?: number
|
|
23
|
+
filters?: F
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { UseMutationOptions } from '@tanstack/react-query'
|
|
2
|
+
import { QueryItem } from './core'
|
|
3
|
+
|
|
4
|
+
export type UpdateMutationCtx<T> = {
|
|
5
|
+
previousItem: T | undefined
|
|
6
|
+
optimisticItem: T | undefined
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type UpdateMutationOptions<T extends QueryItem, F> = Omit<
|
|
10
|
+
UseMutationOptions<T, Error, Partial<T>, UpdateMutationCtx<T>>,
|
|
11
|
+
'mutationKey' | 'mutationFn'
|
|
12
|
+
> & {
|
|
13
|
+
optimistic?: boolean
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { QueryItem } from './core';
|
|
2
|
+
import { QueryKey, useQueryClient } from '@tanstack/react-query'
|
|
3
|
+
|
|
4
|
+
export type QueryClient = ReturnType<typeof useQueryClient>
|
|
5
|
+
|
|
6
|
+
export type WithTempId<T extends QueryItem> = T & {
|
|
7
|
+
tempId?: QueryItem['id']
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ItemPosition = {
|
|
11
|
+
pageIndex: number
|
|
12
|
+
itemIndex: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type RemovedItemMap = [QueryKey, ItemPosition][]
|
|
16
|
+
|
|
17
|
+
export type PaginationResponse<T extends QueryItem> = {
|
|
18
|
+
count: number
|
|
19
|
+
next: string
|
|
20
|
+
previous: string
|
|
21
|
+
results: T[]
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './misc'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function uuidV4() {
|
|
2
|
+
function randomHex() {
|
|
3
|
+
return Math.floor(Math.random() * 16).toString(16);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
let uuid = ''
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < 8; i++) {
|
|
9
|
+
uuid += randomHex()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
uuid += '-'
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < 4; i++) {
|
|
15
|
+
uuid += randomHex()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
uuid += '-'
|
|
19
|
+
|
|
20
|
+
uuid += '4';
|
|
21
|
+
for (let i = 0; i < 3; i++) {
|
|
22
|
+
uuid += randomHex()
|
|
23
|
+
}
|
|
24
|
+
uuid += '-'
|
|
25
|
+
|
|
26
|
+
uuid += ['8', '9', 'a', 'b'][Math.floor(Math.random() * 4)];
|
|
27
|
+
for (let i = 0; i < 3; i++) {
|
|
28
|
+
uuid += randomHex()
|
|
29
|
+
}
|
|
30
|
+
uuid += '-'
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < 12; i++) {
|
|
33
|
+
uuid += randomHex()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return uuid
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const tempIdPrefix = 'temp-id'
|
|
40
|
+
|
|
41
|
+
export const generateTempId = () => {
|
|
42
|
+
return tempIdPrefix + '-' + uuidV4()
|
|
43
|
+
}
|