@stack-spot/portal-network 0.2.0 → 0.3.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.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/client/account.ts +79 -14
- package/src/error/StackspotAPIError.ts +1 -1
- package/src/index.ts +1 -0
- package/src/network/AutoInfiniteQuery.ts +7 -0
- package/src/network/AutoOperation.ts +7 -1
- package/src/network/ManualInfiniteQuery.ts +7 -0
- package/src/network/NetworkClient.ts +16 -70
- package/src/network/ReactQueryNetworkClient.ts +18 -38
- package/src/network/types.ts +0 -10
- package/src/utils/use-extended-list.ts +80 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/stack-spot/portal-commons/compare/portal-network@v0.2.0...portal-network@v0.3.0) (2024-08-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* network - member ([#220](https://github.com/stack-spot/portal-commons/issues/220)) ([5bf22c2](https://github.com/stack-spot/portal-commons/commit/5bf22c29e25215052c0ad63dd6266695f437994b))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* rewrite network client methods ([#216](https://github.com/stack-spot/portal-commons/issues/216)) ([f71bcfe](https://github.com/stack-spot/portal-commons/commit/f71bcfe53c95593e59e4cf69f23b176ab96cf02a))
|
|
14
|
+
|
|
3
15
|
## [0.2.0](https://github.com/stack-spot/portal-commons/compare/portal-network-v0.1.0...portal-network@v0.2.0) (2024-07-31)
|
|
4
16
|
|
|
5
17
|
|
package/package.json
CHANGED
package/src/client/account.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { HttpError } from '@oazapfts/runtime'
|
|
2
2
|
import {
|
|
3
|
-
accountDataIsAvailable,
|
|
3
|
+
accountDataIsAvailable,
|
|
4
|
+
bindToGroups, bindToRoles, create, createPartner,
|
|
5
|
+
createUser,
|
|
6
|
+
deactivateFidoCredentials,
|
|
4
7
|
defaults,
|
|
5
|
-
|
|
8
|
+
deleteMember,
|
|
9
|
+
deletePartner, enableFidoCredentials, getAccountMembers1, getAllMemberFidoCredentials, getFeatures,
|
|
10
|
+
getMemberById,
|
|
11
|
+
getMemberGroups,
|
|
6
12
|
getPartnerAccount, getPartnersSharingAllowed,
|
|
7
|
-
getPersonalClientCredentials,
|
|
13
|
+
getPersonalClientCredentials,
|
|
14
|
+
getResources1,
|
|
15
|
+
getRoles1, removeRoleFromMember, resetPassword, updatePartnerAccountAdminData, updatePartnerAccountData, updateUser,
|
|
16
|
+
validateNewPartnerData,
|
|
8
17
|
validatePartnerAssociationLimit,
|
|
9
18
|
} from '../api/account'
|
|
10
19
|
import apis from '../apis.json'
|
|
@@ -23,11 +32,11 @@ class AccountClient extends ReactQueryNetworkClient {
|
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
/**
|
|
26
|
-
*
|
|
35
|
+
* Gets credentials from personal service client (create if not exists one to the logged user).
|
|
27
36
|
*/
|
|
28
37
|
generatePersonalClientCredentials = this.mutation(getPersonalClientCredentials)
|
|
29
38
|
/**
|
|
30
|
-
*
|
|
39
|
+
* Creates a Feature Flag
|
|
31
40
|
*/
|
|
32
41
|
createFeatureFlag = this.mutation(create)
|
|
33
42
|
/**
|
|
@@ -35,35 +44,35 @@ class AccountClient extends ReactQueryNetworkClient {
|
|
|
35
44
|
*/
|
|
36
45
|
isAvailable = this.query(accountDataIsAvailable)
|
|
37
46
|
/**
|
|
38
|
-
*
|
|
47
|
+
* Lists all Feature Flags in an Account
|
|
39
48
|
*/
|
|
40
49
|
featureFlags = this.query(getFeatures)
|
|
41
50
|
/**
|
|
42
|
-
*
|
|
51
|
+
* Gets Partners with whom it is allowed to share content.
|
|
43
52
|
*/
|
|
44
53
|
partners = this.query(getPartnersSharingAllowed)
|
|
45
54
|
/**
|
|
46
|
-
*
|
|
55
|
+
* Gets Partner by account Id
|
|
47
56
|
*/
|
|
48
57
|
partner = this.query(getPartnerAccount)
|
|
49
58
|
/**
|
|
50
|
-
*
|
|
59
|
+
* Creates an Account Order for Partner
|
|
51
60
|
*/
|
|
52
61
|
createPartner = this.mutation(createPartner)
|
|
53
62
|
/**
|
|
54
|
-
*
|
|
63
|
+
* Updates Partner Account data.
|
|
55
64
|
*/
|
|
56
65
|
updatePartner = this.mutation(updatePartnerAccountData)
|
|
57
66
|
/**
|
|
58
|
-
*
|
|
67
|
+
* Updates Partner Account Admin data.
|
|
59
68
|
*/
|
|
60
69
|
updatePartnerAdmin = this.mutation(updatePartnerAccountAdminData)
|
|
61
70
|
/**
|
|
62
|
-
*
|
|
71
|
+
* Deletes Partner
|
|
63
72
|
*/
|
|
64
73
|
deactivatePartner = this.mutation(deletePartner)
|
|
65
74
|
/**
|
|
66
|
-
*
|
|
75
|
+
* Validates new Partner account data
|
|
67
76
|
*/
|
|
68
77
|
validateNewPartnerData = this.mutation(validateNewPartnerData)
|
|
69
78
|
/**
|
|
@@ -86,9 +95,65 @@ class AccountClient extends ReactQueryNetworkClient {
|
|
|
86
95
|
},
|
|
87
96
|
})
|
|
88
97
|
/**
|
|
89
|
-
*
|
|
98
|
+
* Gets member by id.
|
|
99
|
+
*/
|
|
100
|
+
member = this.query(getMemberById)
|
|
101
|
+
/**
|
|
102
|
+
* Gets all members (paginated).
|
|
90
103
|
*/
|
|
91
104
|
allMembers = this.infiniteQuery(getAccountMembers1)
|
|
105
|
+
/**
|
|
106
|
+
* Gets member Groups (paginated).
|
|
107
|
+
*/
|
|
108
|
+
memberGroups = this.infiniteQuery(getMemberGroups)
|
|
109
|
+
/**
|
|
110
|
+
* Gets member roles (paginated).
|
|
111
|
+
*/
|
|
112
|
+
memberRoles = this.infiniteQuery(getRoles1)
|
|
113
|
+
/**
|
|
114
|
+
* Gets member resources.
|
|
115
|
+
*/
|
|
116
|
+
memberResources = this.query(getResources1)
|
|
117
|
+
/**
|
|
118
|
+
* Gets All Fido credentials for the given member (paginated).
|
|
119
|
+
*/
|
|
120
|
+
fidoCredentials = this.infiniteQuery(getAllMemberFidoCredentials, { accumulator: 'items' })
|
|
121
|
+
/**
|
|
122
|
+
* Creates member on current tenant.
|
|
123
|
+
*/
|
|
124
|
+
createMember = this.mutation(createUser)
|
|
125
|
+
/**
|
|
126
|
+
* Updates member on current tenant.
|
|
127
|
+
*/
|
|
128
|
+
updateMember = this.mutation(updateUser)
|
|
129
|
+
/**
|
|
130
|
+
* Adds a member to several groups.
|
|
131
|
+
*/
|
|
132
|
+
addMemberToGroups = this.mutation(bindToGroups)
|
|
133
|
+
/**
|
|
134
|
+
* Attributes several roles to a member.
|
|
135
|
+
*/
|
|
136
|
+
addRolesToMember = this.mutation(bindToRoles)
|
|
137
|
+
/**
|
|
138
|
+
* Removes a role from a member.
|
|
139
|
+
*/
|
|
140
|
+
removeRoleFromMember = this.mutation(removeRoleFromMember)
|
|
141
|
+
/**
|
|
142
|
+
* Sends an e-mail to reset the password of the member with the provided e-mail address.
|
|
143
|
+
*/
|
|
144
|
+
resetMemberPassword = this.mutation(resetPassword)
|
|
145
|
+
/**
|
|
146
|
+
* Enables Fido credentials for the given member.
|
|
147
|
+
*/
|
|
148
|
+
enableFidoCredentials = this.mutation(enableFidoCredentials)
|
|
149
|
+
/**
|
|
150
|
+
* Disables Fido credentials for the given member.
|
|
151
|
+
*/
|
|
152
|
+
disableFidoCredentials = this.mutation(deactivateFidoCredentials)
|
|
153
|
+
/**
|
|
154
|
+
* Removes a member from a group
|
|
155
|
+
*/
|
|
156
|
+
removeMemberFromGroup = this.mutation(deleteMember)
|
|
92
157
|
}
|
|
93
158
|
|
|
94
159
|
export const accountClient = new AccountClient()
|
|
@@ -58,6 +58,6 @@ export class StackspotAPIError extends Error {
|
|
|
58
58
|
*/
|
|
59
59
|
translate(language: Language = getLanguage()) {
|
|
60
60
|
const unknown = language === 'en' ? 'unknown error' : 'erro desconhecido'
|
|
61
|
-
return this.intl?.(language) ?? this.message ?? this.code ?? this.status === 0 ? unknown : `${this.status}`
|
|
61
|
+
return this.intl?.(language) ?? this.message ?? this.code ?? (this.status === 0 ? unknown : `${this.status}`)
|
|
62
62
|
}
|
|
63
63
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { DefaultAPIError } from './error/DefaultAPIError'
|
|
|
3
3
|
export { StackspotAPIError } from './error/StackspotAPIError'
|
|
4
4
|
export { NetworkClient } from './network/NetworkClient'
|
|
5
5
|
export { queryClient } from './network/react-query-client'
|
|
6
|
+
export { useExtendedList } from './utils/use-extended-list'
|
|
@@ -94,4 +94,11 @@ export class AutoInfiniteQuery<Variables, Result, PageParamName extends keyof Va
|
|
|
94
94
|
result,
|
|
95
95
|
]
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
async invalidate(variables?: Partial<Variables> | undefined): Promise<void> {
|
|
99
|
+
await Promise.all([
|
|
100
|
+
super.invalidate(variables),
|
|
101
|
+
queryClient.invalidateQueries({ queryKey: ['infinite', ...this.getKey(variables)] }),
|
|
102
|
+
])
|
|
103
|
+
}
|
|
97
104
|
}
|
|
@@ -61,7 +61,13 @@ export abstract class AutoOperation<Variables> implements OperationObject<Variab
|
|
|
61
61
|
? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
|
|
62
62
|
: [variables?: Partial<Variables>, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
|
|
63
63
|
) {
|
|
64
|
-
|
|
64
|
+
/* `this.fn` is a oazapfts function, i.e. it has arity 1 or 2. If it accepts variables, its arity is 2: the 1st parameter is the
|
|
65
|
+
variables and the 2nd is the RequestOpts. If it doesn't accept variables, its arity is one: it accepts only the RequestOpts.
|
|
66
|
+
We can use this information to determine what the type of `args` actually is at runtime. If variables are accepted, than the 1st
|
|
67
|
+
argument is the variables and the 2nd is the query options, otherwise, it has a single argument, which is the query options. */
|
|
68
|
+
const [variables, options] = this.fn.length > 1
|
|
69
|
+
? args as [Variables, Omit<UseQueryOptions, 'queryFn' | 'queryKey'> | undefined]
|
|
70
|
+
: [undefined, args[0] as Omit<UseQueryOptions, 'queryFn' | 'queryKey'> | undefined]
|
|
65
71
|
const result = useQuery({
|
|
66
72
|
...options,
|
|
67
73
|
queryKey: this.getPermissionKey(variables as Variables),
|
|
@@ -85,4 +85,11 @@ export class ManualInfiniteQuery<
|
|
|
85
85
|
result,
|
|
86
86
|
]
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
async invalidate(variables?: Partial<Variables> | undefined): Promise<void> {
|
|
90
|
+
await Promise.all([
|
|
91
|
+
super.invalidate(variables),
|
|
92
|
+
queryClient.invalidateQueries({ queryKey: ['infinite', ...this.getKey(variables)] }),
|
|
93
|
+
])
|
|
94
|
+
}
|
|
88
95
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { mergeHeaders } from '@oazapfts/runtime/headers'
|
|
2
1
|
import { AuthenticationError } from '@stack-spot/auth'
|
|
3
2
|
import { requestPermission } from '@stack-spot/opa'
|
|
4
|
-
import { Env, HTTPMethod,
|
|
3
|
+
import { Env, HTTPMethod, SessionManager } from './types'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* A set of methods for performing network requests to an API.
|
|
@@ -72,82 +71,29 @@ export abstract class NetworkClient {
|
|
|
72
71
|
return sessionManager
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Makes a request (same signature as `globalThis.fetch`). This request will prepend the base url to the url and, if there's an active
|
|
76
|
+
* session, include authentication headers.
|
|
77
|
+
* @param input the url or request object.
|
|
78
|
+
* @param init the fetch options.
|
|
79
|
+
* @returns a promise with the Response.
|
|
80
|
+
*/
|
|
81
|
+
protected fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {
|
|
76
82
|
const sessionManager = this.getSessionManager()
|
|
77
|
-
|
|
83
|
+
let inputWithBaseUrl: string | URL | Request = ''
|
|
84
|
+
if (typeof input === 'string') inputWithBaseUrl = this.resolveURL(input)
|
|
85
|
+
else if (input instanceof URL) inputWithBaseUrl = this.resolveURL(input.toString())
|
|
86
|
+
else inputWithBaseUrl = { ...input, url: this.resolveURL(input.url).toString() }
|
|
87
|
+
// some APIs throw errors if the method is lowercase, the following line prevents it
|
|
88
|
+
if (init?.method) init.method = init.method.toUpperCase()
|
|
78
89
|
try {
|
|
79
|
-
|
|
80
|
-
Object.keys(request.params).forEach((key) => {
|
|
81
|
-
const value = request?.params?.[key]
|
|
82
|
-
if (value !== undefined) url.searchParams.set(key, value)
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
const isFormData = request?.body instanceof FormData
|
|
86
|
-
const isJsonContent = (typeof request?.body === 'object') && !isFormData
|
|
87
|
-
const body = isJsonContent ? JSON.stringify(request.body) : request?.body as string | FormData
|
|
88
|
-
const defaultHeaders: Record<string, string> = isJsonContent ? { 'Content-Type': 'application/json' } : {}
|
|
89
|
-
const headers = mergeHeaders(defaultHeaders, request?.headers)
|
|
90
|
-
return (
|
|
91
|
-
sessionManager.hasSession()
|
|
92
|
-
? sessionManager.getSession().fetch(url, { method: method.toUpperCase(), headers, body, signal: request?.signal })
|
|
93
|
-
: fetch(url, { method: method.toUpperCase(), headers, body, signal: request?.signal })
|
|
94
|
-
)
|
|
90
|
+
return sessionManager.hasSession() ? sessionManager.getSession().fetch(inputWithBaseUrl, init) : fetch(inputWithBaseUrl, init)
|
|
95
91
|
} catch (error) {
|
|
96
92
|
if (error instanceof AuthenticationError) sessionManager.endSession()
|
|
97
93
|
throw error
|
|
98
94
|
}
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
/**
|
|
102
|
-
* Performs a GET request with the parameters provided.
|
|
103
|
-
* @param path the path to the resource.
|
|
104
|
-
* @param request the request options.
|
|
105
|
-
* @returns a promise that resolves to response's data.
|
|
106
|
-
*/
|
|
107
|
-
protected get(path: string, request?: RequestOptions) {
|
|
108
|
-
return this.sendRequest(path, 'get', request)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Performs a POST request with the parameters provided.
|
|
113
|
-
* @param path the path to the resource.
|
|
114
|
-
* @param request the request options.
|
|
115
|
-
* @returns a promise that resolves to response's data.
|
|
116
|
-
*/
|
|
117
|
-
protected post(path: string, request?: RequestWithBody) {
|
|
118
|
-
return this.sendRequest(path, 'post', request)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Performs a PUT request with the parameters provided.
|
|
123
|
-
* @param path the path to the resource.
|
|
124
|
-
* @param request the request options.
|
|
125
|
-
* @returns a promise that resolves to response's data.
|
|
126
|
-
*/
|
|
127
|
-
protected put(path: string, request?: RequestWithBody) {
|
|
128
|
-
return this.sendRequest(path, 'put', request)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Performs a PATCH request with the parameters provided.
|
|
133
|
-
* @param path the path to the resource.
|
|
134
|
-
* @param request the request options.
|
|
135
|
-
* @returns a promise that resolves to response's data.
|
|
136
|
-
*/
|
|
137
|
-
protected patch(path: string, request?: RequestWithBody) {
|
|
138
|
-
return this.sendRequest(path, 'patch', request)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Performs a DELETE request with the parameters provided.
|
|
143
|
-
* @param path the path to the resource.
|
|
144
|
-
* @param request the request options.
|
|
145
|
-
* @returns a promise that resolves to response's data.
|
|
146
|
-
*/
|
|
147
|
-
protected delete(path: string, request?: RequestWithBody) {
|
|
148
|
-
return this.sendRequest(path, 'delete', request)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
97
|
/**
|
|
152
98
|
* Checks whether or not the current account is freemium.
|
|
153
99
|
* @returns true if it's a freemium account, false otherwise.
|
|
@@ -26,22 +26,7 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
|
|
|
26
26
|
constructor(baseURL: Record<Env, string>, defaults: Defaults<any>) {
|
|
27
27
|
super(baseURL)
|
|
28
28
|
defaults.baseUrl = ''
|
|
29
|
-
defaults.fetch = (...args) => this
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
#getMappedHeaders(headers: HeadersInit | undefined) {
|
|
33
|
-
if (!headers) return undefined
|
|
34
|
-
if (headers instanceof Headers) {
|
|
35
|
-
const headersMap: Record<string, string> = {}
|
|
36
|
-
headers.forEach((value, key) => headersMap[key] = value)
|
|
37
|
-
return headersMap
|
|
38
|
-
}
|
|
39
|
-
if (Array.isArray(headers)) {
|
|
40
|
-
const headersMap: Record<string, string> = {}
|
|
41
|
-
headers.forEach(([key, value]) => headersMap[key] = value)
|
|
42
|
-
return headersMap
|
|
43
|
-
}
|
|
44
|
-
return headers
|
|
29
|
+
defaults.fetch = (...args) => this.fetch(...args)
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
#parseErrorData(error: HttpError) {
|
|
@@ -80,13 +65,6 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
|
|
|
80
65
|
}
|
|
81
66
|
)
|
|
82
67
|
|
|
83
|
-
#onFetch(input: string | URL | Request, init?: RequestInit | undefined): Promise<Response> {
|
|
84
|
-
const [path, method, body, headers] = input instanceof Request
|
|
85
|
-
? [input.url, input.method as HTTPMethod, input.body, input.headers]
|
|
86
|
-
: [`${input}`, (init?.method?.toLowerCase() || 'get') as HTTPMethod, init?.body, init?.headers]
|
|
87
|
-
return this[method](path, { body: body as any, headers: this.#getMappedHeaders(headers), signal: init?.signal })
|
|
88
|
-
}
|
|
89
|
-
|
|
90
68
|
/**
|
|
91
69
|
* Receives an HttpError and returns a StackspotAPIError.
|
|
92
70
|
* @param error the original HttpError created by oazapfts.
|
|
@@ -169,6 +147,20 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
|
|
|
169
147
|
>(
|
|
170
148
|
config: Omit<InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>, 'permission'>,
|
|
171
149
|
): Omit<InfiniteQueryObject<Variables, Result, Accumulator>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
|
|
150
|
+
/**
|
|
151
|
+
* Builds a query automatically by using a function generated by oazapfts.
|
|
152
|
+
* @param fn the oazapfts function.
|
|
153
|
+
* @param options the configuration for the infinite query.
|
|
154
|
+
*/
|
|
155
|
+
protected infiniteQuery<
|
|
156
|
+
Variables extends Record<string, any>,
|
|
157
|
+
Result,
|
|
158
|
+
PageParamName extends keyof Variables,
|
|
159
|
+
Accumulator extends keyof Result,
|
|
160
|
+
>(
|
|
161
|
+
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
162
|
+
options: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
163
|
+
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
172
164
|
/**
|
|
173
165
|
* Builds a query automatically by using a function generated by oazapfts with the variables `page` and `size`.
|
|
174
166
|
* @param fn the oazapfts function that returns an array.
|
|
@@ -184,21 +176,9 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
|
|
|
184
176
|
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
185
177
|
options?: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
186
178
|
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* @param options the configuration for the infinite query.
|
|
191
|
-
*/
|
|
192
|
-
protected infiniteQuery<
|
|
193
|
-
Variables extends Record<string, any>,
|
|
194
|
-
Result,
|
|
195
|
-
PageParamName extends keyof Variables,
|
|
196
|
-
Accumulator extends keyof Result,
|
|
197
|
-
>(
|
|
198
|
-
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
199
|
-
options: InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>,
|
|
200
|
-
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
201
|
-
protected infiniteQuery(fnOrConfig: any, options?: InfiniteQueryOptions<any, any, any, any>): InfiniteQueryObject<any, any, any> {
|
|
179
|
+
protected infiniteQuery(
|
|
180
|
+
fnOrConfig: any, options?: Partial<InfiniteQueryOptions<any, any, any, any>>,
|
|
181
|
+
): InfiniteQueryObject<any, any, any> {
|
|
202
182
|
return typeof fnOrConfig === 'function'
|
|
203
183
|
? new AutoInfiniteQuery(
|
|
204
184
|
{
|
package/src/network/types.ts
CHANGED
|
@@ -9,16 +9,6 @@ export interface SessionManager {
|
|
|
9
9
|
getSession(): Session,
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export interface RequestOptions {
|
|
13
|
-
params?: Record<string, string | undefined>,
|
|
14
|
-
headers?: Record<string, string>,
|
|
15
|
-
signal?: AbortSignal | null,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface RequestWithBody extends RequestOptions {
|
|
19
|
-
body?: string | object | FormData,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
export type Env = 'dev' | 'stg' | 'prd'
|
|
23
13
|
|
|
24
14
|
export type HTTPMethod = 'post' | 'patch' | 'delete' | 'put' | 'get'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
type ExtensionMap<T> = Record<string, (item: T) => any>
|
|
4
|
+
|
|
5
|
+
type Unpromisify<T> = T extends Promise<infer R> ? R : T
|
|
6
|
+
|
|
7
|
+
type Extension<E extends ExtensionMap<any>> = { [K in keyof E]?: Unpromisify<ReturnType<E[K]>> }
|
|
8
|
+
|
|
9
|
+
type ItemWithExtensions<T, E extends ExtensionMap<T>> = T & Extension<E>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Computes the given extensions for the items in the list passed as parameter. An extension will, most of the times, return a promise,
|
|
13
|
+
* but it can return whatever you'd like.
|
|
14
|
+
*
|
|
15
|
+
* If the extensions are promises and are still loading, the items won't have the corresponding keys.
|
|
16
|
+
*
|
|
17
|
+
* Attention: once an extension is calculated for an item, it's never calculated again. An item is identified by the parameter "idProp"
|
|
18
|
+
* (3rd), which is "id" by default.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```
|
|
22
|
+
* const [groups] = accountClient.memberGroups.useInfiniteQuery({ memberId: 'someId' })
|
|
23
|
+
* const extendedGroups = useExtendedList(groups, {
|
|
24
|
+
* canRemoveMemberFromGroup: group => accountClient.removeMemberFromGroup.isAllowed({ groupId: group.id, memberId: 'someId' }),
|
|
25
|
+
* title: group => `${group.name} (${group.totalUsers})`,
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
* Above, each item in `extendedGroups` will have the properties `canRemoveMemberFromGroup` and `title`, once all permission have been
|
|
29
|
+
* fetched.
|
|
30
|
+
*
|
|
31
|
+
* @param list the items to compute extensions for.
|
|
32
|
+
* @param extensions an object where the keys are the extension names and the values are functions that return the value of the extension.
|
|
33
|
+
* @param idProp the name of the property that can be used as an id. 'id' by default.
|
|
34
|
+
* @returns an array with 2 items:
|
|
35
|
+
* - [0]: the list with the permission properties.
|
|
36
|
+
* - [1]: true if waiting for a promise, false otherwise.
|
|
37
|
+
*/
|
|
38
|
+
export function useExtendedList<T, E extends ExtensionMap<T>>(
|
|
39
|
+
list: T[], extensions: E, idProp: keyof T,
|
|
40
|
+
): [ItemWithExtensions<T, E>[], boolean]
|
|
41
|
+
export function useExtendedList<T extends { id: string }, E extends ExtensionMap<T>>(
|
|
42
|
+
list: T[], extensions: E, idProp?: keyof T,
|
|
43
|
+
): [ItemWithExtensions<T, E>[], boolean]
|
|
44
|
+
export function useExtendedList<T, E extends ExtensionMap<T>>(
|
|
45
|
+
list: T[], extensions: E, idProp = 'id' as keyof T,
|
|
46
|
+
): [ItemWithExtensions<T, E>[], boolean] {
|
|
47
|
+
const [listWithExtensions, setListWithExtensions] = useState(list as ItemWithExtensions<T, E>[])
|
|
48
|
+
const [isLoading, setLoading] = useState(true)
|
|
49
|
+
const extensionMap = useRef(new Map<any, Extension<E>>())
|
|
50
|
+
const listId = list.map(i => i[idProp]).join(';')
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
async function update() {
|
|
54
|
+
setLoading(true)
|
|
55
|
+
const newList = await Promise.all(list.map(async (item) => {
|
|
56
|
+
const itemExtensions: Extension<E> = extensionMap.current.get(item[idProp]) ?? {}
|
|
57
|
+
if (Object.keys(itemExtensions).length === 0) {
|
|
58
|
+
await Promise.all(Object.keys(extensions).map(async (key: keyof E) => {
|
|
59
|
+
try {
|
|
60
|
+
// "await" because it might be a promise
|
|
61
|
+
itemExtensions[key] = await extensions[key](item)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.error(`Failed to compute extension "${String(key)}" for item with id "${item[idProp]}". It will have undefined as its value, which may cause errors ahead. Caused by the error below:`)
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.error(error)
|
|
67
|
+
}
|
|
68
|
+
}))
|
|
69
|
+
extensionMap.current.set(item[idProp], itemExtensions)
|
|
70
|
+
}
|
|
71
|
+
return { ...item, ...itemExtensions }
|
|
72
|
+
}))
|
|
73
|
+
setLoading(false)
|
|
74
|
+
setListWithExtensions(newList)
|
|
75
|
+
}
|
|
76
|
+
update()
|
|
77
|
+
}, [listId])
|
|
78
|
+
|
|
79
|
+
return [listWithExtensions.length ? listWithExtensions : list as ItemWithExtensions<T, E>[], isLoading]
|
|
80
|
+
}
|