@stack-spot/portal-network 0.184.0 → 0.185.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 +2419 -2412
- package/dist/api/account.js +1 -1
- package/dist/api/agent-tools.js +1 -1
- package/dist/api/agent.js +1 -1
- package/dist/api/ai.js +1 -1
- package/dist/api/apiManagement.js +1 -1
- package/dist/api/apiRuntime.js +1 -1
- package/dist/api/cloudAccount.js +1 -1
- package/dist/api/cloudPlatform.js +1 -1
- package/dist/api/cloudPlatformHorizon.js +1 -1
- package/dist/api/cloudRuntimes.js +1 -1
- package/dist/api/cloudServices.js +1 -1
- package/dist/api/codeShift.d.ts +63 -4
- package/dist/api/codeShift.d.ts.map +1 -1
- package/dist/api/codeShift.js +14 -1
- package/dist/api/codeShift.js.map +1 -1
- package/dist/api/content.js +1 -1
- package/dist/api/dataIntegration.js +1 -1
- package/dist/api/discover.js +1 -1
- package/dist/api/genAiInference.js +1 -1
- package/dist/api/insights.js +1 -1
- package/dist/api/notification.js +1 -1
- package/dist/api/secrets.js +1 -1
- package/dist/api/serviceCatalog.js +1 -1
- package/dist/api/workspace-ai.js +1 -1
- package/dist/api/workspace.js +1 -1
- package/dist/api/workspaceManager.js +1 -1
- package/dist/api/workspaceSearchEngine.js +1 -1
- package/dist/client/code-shift.d.ts +22 -0
- package/dist/client/code-shift.d.ts.map +1 -1
- package/dist/client/code-shift.js +28 -1
- package/dist/client/code-shift.js.map +1 -1
- package/package.json +6 -6
- package/scripts/generate-apis.ts +134 -134
- package/src/api/account.ts +8367 -8367
- package/src/api/agent-tools.ts +2169 -2169
- package/src/api/agent.ts +1083 -1083
- package/src/api/ai.ts +3388 -3388
- package/src/api/apiManagement.ts +570 -570
- package/src/api/apiRuntime.ts +2103 -2103
- package/src/api/cloudAccount.ts +1239 -1239
- package/src/api/cloudPlatform.ts +927 -927
- package/src/api/cloudPlatformHorizon.ts +2655 -2655
- package/src/api/cloudRuntimes.ts +2043 -2043
- package/src/api/cloudServices.ts +1445 -1445
- package/src/api/codeShift.ts +3567 -3481
- package/src/api/content.ts +9785 -9785
- package/src/api/dataIntegration.ts +1657 -1657
- package/src/api/discover.ts +435 -435
- package/src/api/eventBus.ts +171 -171
- package/src/api/genAiInference.ts +603 -603
- package/src/api/insights.ts +310 -310
- package/src/api/notification.ts +334 -334
- package/src/api/secrets.ts +342 -342
- package/src/api/serviceCatalog.ts +2908 -2908
- package/src/api/workflows.ts +1669 -1669
- package/src/api/workspace-ai.ts +677 -677
- package/src/api/workspace.ts +5889 -5889
- package/src/api/workspaceManager.ts +2951 -2951
- package/src/api/workspaceSearchEngine.ts +153 -153
- package/src/api-addresses.ts +120 -120
- package/src/apis-itau.json +225 -225
- package/src/apis.json +225 -225
- package/src/client/account.ts +902 -902
- package/src/client/agent-tools.ts +210 -210
- package/src/client/agent.ts +81 -81
- package/src/client/ai.ts +395 -395
- package/src/client/api-management.ts +40 -40
- package/src/client/cloud-account.ts +70 -70
- package/src/client/cloud-platform-horizon.ts +113 -113
- package/src/client/cloud-platform.ts +163 -163
- package/src/client/cloud-runtimes.ts +129 -129
- package/src/client/cloud-services.ts +94 -94
- package/src/client/code-shift.ts +364 -349
- package/src/client/content.ts +538 -538
- package/src/client/data-integration.ts +191 -191
- package/src/client/discover.ts +89 -89
- package/src/client/event-bus.ts +84 -84
- package/src/client/gen-ai-inference.ts +65 -65
- package/src/client/insights.ts +28 -28
- package/src/client/notification.ts +32 -32
- package/src/client/runtime-manager.ts +76 -76
- package/src/client/secrets.ts +60 -60
- package/src/client/types.ts +377 -377
- package/src/client/workflow.ts +83 -83
- package/src/client/workspace-ai.ts +191 -191
- package/src/client/workspace-manager.ts +564 -564
- package/src/client/workspace-search.ts +39 -39
- package/src/client/workspace.ts +480 -480
- package/src/error/DefaultAPIError.ts +151 -151
- package/src/error/FileUploadError.ts +18 -18
- package/src/error/IgnoredErrorCodes.ts +3 -3
- package/src/error/StackspotAPIError.ts +101 -101
- package/src/error/StreamCanceledError.ts +10 -10
- package/src/error/StreamError.ts +7 -7
- package/src/error/StreamJsonError.ts +10 -10
- package/src/error/dictionary/account.ts +58 -58
- package/src/error/dictionary/action-details.ts +20 -20
- package/src/error/dictionary/action.ts +211 -211
- package/src/error/dictionary/agent-tools.ts +75 -75
- package/src/error/dictionary/ai-inference.ts +28 -28
- package/src/error/dictionary/base.ts +22 -22
- package/src/error/dictionary/cloud-platform.ts +82 -82
- package/src/error/dictionary/cnt-fields.ts +14 -14
- package/src/error/dictionary/cnt.ts +103 -103
- package/src/error/dictionary/code-shift.ts +12 -12
- package/src/error/dictionary/rte.ts +24 -24
- package/src/error/dictionary/rtm.ts +10 -10
- package/src/error/dictionary/secrets.ts +14 -14
- package/src/error/dictionary/workspace-ai.ts +10 -10
- package/src/error/dictionary/workspace-details.ts +15 -15
- package/src/error/dictionary/workspace-fields.ts +10 -10
- package/src/error/dictionary/workspace.ts +209 -209
- package/src/error/types.ts +21 -21
- package/src/index.ts +43 -43
- package/src/network/AutoInfiniteQuery.ts +115 -115
- package/src/network/AutoMutation.ts +27 -27
- package/src/network/AutoOperation.ts +73 -73
- package/src/network/AutoQuery.ts +75 -75
- package/src/network/ManualInfiniteQuery.ts +95 -95
- package/src/network/ManualMutation.ts +40 -40
- package/src/network/ManualOperation.ts +52 -52
- package/src/network/ManualQuery.ts +82 -82
- package/src/network/NetworkClient.ts +167 -167
- package/src/network/ReactQueryNetworkClient.ts +312 -312
- package/src/network/react-query-client.ts +14 -14
- package/src/network/types.ts +294 -294
- package/src/types.ts +1 -1
- package/src/utils/StreamedArray.tsx +146 -146
- package/src/utils/StreamedJson.tsx +166 -166
- package/src/utils/remove-authorization-param.ts +6 -6
- package/src/utils/string.ts +19 -19
- package/src/utils/use-extended-list.ts +80 -80
- package/src/utils/use-streamed-array.ts +17 -17
- package/tsconfig.build.json +4 -4
- package/tsconfig.json +10 -10
|
@@ -1,312 +1,312 @@
|
|
|
1
|
-
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
-
|
|
3
|
-
import { Defaults, HttpError, RequestOpts } from '@oazapfts/runtime'
|
|
4
|
-
import { StackspotAPIError } from '../error/StackspotAPIError'
|
|
5
|
-
import { AutoInfiniteQuery } from './AutoInfiniteQuery'
|
|
6
|
-
import { AutoMutation } from './AutoMutation'
|
|
7
|
-
import { AutoQuery } from './AutoQuery'
|
|
8
|
-
import { ManualInfiniteQuery } from './ManualInfiniteQuery'
|
|
9
|
-
import { ManualMutation } from './ManualMutation'
|
|
10
|
-
import { ManualQuery } from './ManualQuery'
|
|
11
|
-
import { NetworkClient } from './NetworkClient'
|
|
12
|
-
import { Env, HTTPMethod, InfiniteQueryConfig, InfiniteQueryObject, InfiniteQueryOptions, MutationObject, OperationConfig, OperationObject, QueryObject } from './types'
|
|
13
|
-
|
|
14
|
-
const DUMMY_ULID = '01HSTZ5471CBG4AW9BFJ0VTVG9'
|
|
15
|
-
const PERMISSION_ERROR = 'permission-error'
|
|
16
|
-
export const DEFAULT_PAGE_SIZE = 20
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* A Network Client that uses oazapfts for making requests and React Query for managing responses.
|
|
20
|
-
*/
|
|
21
|
-
export abstract class ReactQueryNetworkClient extends NetworkClient {
|
|
22
|
-
/**
|
|
23
|
-
* @param baseURL An object with the keys "dev", "stg" and "prd". The values must be the url for each of these environments.
|
|
24
|
-
* @param defaults the `default` object of the API this client is for (provided by the code generated by oazapfts).
|
|
25
|
-
*/
|
|
26
|
-
constructor(baseURL: Record<Env, string>, defaults: Defaults<any>) {
|
|
27
|
-
super(baseURL)
|
|
28
|
-
defaults.baseUrl = ''
|
|
29
|
-
defaults.fetch = (...args) => this.fetch(...args)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
#parseErrorData(error: HttpError) {
|
|
33
|
-
if (typeof error.data === 'string') {
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(error.data)
|
|
36
|
-
} catch { /* empty */ }
|
|
37
|
-
}
|
|
38
|
-
return error.data
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#transformError(error: any): StackspotAPIError {
|
|
42
|
-
if (error instanceof StackspotAPIError) return error
|
|
43
|
-
if (!(error instanceof HttpError)) throw new StackspotAPIError({ status: 0, message: error?.message || `${error}`, stack: error.stack })
|
|
44
|
-
const data = this.#parseErrorData(error)
|
|
45
|
-
if (data.type === PERMISSION_ERROR) {
|
|
46
|
-
// eslint-disable-next-line no-console
|
|
47
|
-
console.error('Error while validating permissions:', data.message)
|
|
48
|
-
return new StackspotAPIError({
|
|
49
|
-
status: 500,
|
|
50
|
-
stack: error.stack,
|
|
51
|
-
message: language => language === 'pt'
|
|
52
|
-
? 'Ocorreu um erro ao validar as permissões.'
|
|
53
|
-
: 'There was an error while validating permissions.',
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
return this.buildStackSpotError(error)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Receives an HttpError and returns a StackspotAPIError.
|
|
61
|
-
* @param error the original HttpError created by oazapfts.
|
|
62
|
-
*/
|
|
63
|
-
protected abstract buildStackSpotError(error: HttpError): StackspotAPIError
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Creates a function that checks the permission for a request generated by an oazapfts function.
|
|
67
|
-
*
|
|
68
|
-
* This is intended to help creating a manual operation.
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```
|
|
72
|
-
* myOperation = this.mutation({
|
|
73
|
-
* // ...
|
|
74
|
-
* permission: this.createPermissionFunctionFor(oazapftsFnForMyOperation)
|
|
75
|
-
* })
|
|
76
|
-
* ```
|
|
77
|
-
* @param fn the function generated by oazapfts.
|
|
78
|
-
* @returns a function that receives the variables of `fn` (if any) and returns a promise that resolves to true if the request is allowed
|
|
79
|
-
* and false otherwise.
|
|
80
|
-
*/
|
|
81
|
-
protected createPermissionFunctionFor<Args extends [variables: Record<string, any>, opts?: RequestOpts] | [opts?: RequestOpts]>(
|
|
82
|
-
fn: (...args: Args) => Promise<any>,
|
|
83
|
-
): (...args: Args extends [opts?: RequestOpts] ? [] : [variables?: Partial<Args[0]>]) => Promise<boolean> {
|
|
84
|
-
return (variables?: any) => fn.length === 2
|
|
85
|
-
? (fn as (variables: Record<string, any>, opts?: RequestOpts) => Promise<any>)(
|
|
86
|
-
variables,
|
|
87
|
-
{ fetch: (...args) => this.#onFetchPermission(...args) },
|
|
88
|
-
)
|
|
89
|
-
: (fn as (opts?: RequestOpts) => Promise<any>)({ fetch: (...args) => this.#onFetchPermission(...args) })
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Creates a function that runs a oazapfts function with an abort signal.
|
|
94
|
-
*
|
|
95
|
-
* This is intended to help creating a manual operation.
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* ```
|
|
99
|
-
* myOperation = this.mutation({
|
|
100
|
-
* // ...
|
|
101
|
-
* request: this.createRequestFunctionFor(oazapftsFnForMyOperation)
|
|
102
|
-
* })
|
|
103
|
-
* ```
|
|
104
|
-
* @param fn the function generated by oazapfts.
|
|
105
|
-
* @returns a function that receives a signal and the variables of `fn` (if any) and returns the same as `fn`.
|
|
106
|
-
*/
|
|
107
|
-
protected createRequestFunctionFor<Args extends [variables: Record<string, any>, opts?: RequestOpts] | [opts?: RequestOpts], Result>(
|
|
108
|
-
fn: (...args: Args) => Promise<Result>,
|
|
109
|
-
): (...args: Args extends [opts?: RequestOpts] ? [signal: AbortSignal] : [signal: AbortSignal, variables: Args[0]]) => Promise<Result> {
|
|
110
|
-
return (signal, variables?: any) => fn.length === 2
|
|
111
|
-
? (fn as (variables: Record<string, any>, opts?: RequestOpts) => Promise<Result>)(variables, { signal })
|
|
112
|
-
: (fn as (opts?: RequestOpts) => Promise<Result>)({ signal })
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async #onFetchPermission(input: string | URL | Request, init?: RequestInit | undefined): Promise<Response> {
|
|
116
|
-
const [path, method, body] = input instanceof Request
|
|
117
|
-
? [input.url, input.method as HTTPMethod, input.body]
|
|
118
|
-
: [`${input}`, (init?.method?.toLowerCase() || 'get') as HTTPMethod, init?.body]
|
|
119
|
-
try {
|
|
120
|
-
/* We allow the dev not to pass any variable when validating a permission. For this reason, oazapfts might generate a path with
|
|
121
|
-
"undefined". If this happens, we must replace the undefined text with a dummy ULID. */
|
|
122
|
-
const fixedPath = path.replace(/\/undefined(\/|$)/g, `/${DUMMY_ULID}$1`)
|
|
123
|
-
const isAllowed = await this.requestPermission(method, fixedPath, body as any)
|
|
124
|
-
return new Response(`${isAllowed}`, { headers: { 'Content-Type': 'application/json' } })
|
|
125
|
-
} catch (error: any) {
|
|
126
|
-
return new Response(JSON.stringify({ type: PERMISSION_ERROR, message: error.message || `${error}` }), { status: 500 })
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Builds a query manually by using a configuration object.
|
|
132
|
-
* @param config the configuration object containing the name, a request function and a permission function.
|
|
133
|
-
*/
|
|
134
|
-
protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
135
|
-
config: OperationConfig<Args, Result>,
|
|
136
|
-
): QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>
|
|
137
|
-
/**
|
|
138
|
-
* Builds a query manually by using a configuration object.
|
|
139
|
-
* @param config the configuration object containing the name and a request function.
|
|
140
|
-
*/
|
|
141
|
-
protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
142
|
-
config: Omit<OperationConfig<Args, Result>, 'permission'>,
|
|
143
|
-
): Omit<QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
|
|
144
|
-
/**
|
|
145
|
-
* Builds a query automatically by using a function generated by oazapfts.
|
|
146
|
-
* @param fn the oazapfts function.
|
|
147
|
-
*/
|
|
148
|
-
protected query<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
|
|
149
|
-
fn: (...args: Args) => Promise<Result>
|
|
150
|
-
): Args extends [variables: infer Variables, opts?: RequestOpts] ? QueryObject<Variables, Result> : QueryObject<void, Result>
|
|
151
|
-
protected query(fnOrConfig: any): QueryObject<any, any> {
|
|
152
|
-
return typeof fnOrConfig === 'function'
|
|
153
|
-
? new AutoQuery(
|
|
154
|
-
{
|
|
155
|
-
apiName: this.constructor.name,
|
|
156
|
-
fn: fnOrConfig,
|
|
157
|
-
onFetchPermission: (...args) => this.#onFetchPermission(...args),
|
|
158
|
-
transformError: error => this.#transformError(error),
|
|
159
|
-
},
|
|
160
|
-
)
|
|
161
|
-
: new ManualQuery({
|
|
162
|
-
...fnOrConfig,
|
|
163
|
-
apiName: this.constructor.name,
|
|
164
|
-
request: fnOrConfig.request,
|
|
165
|
-
permission: fnOrConfig.permission,
|
|
166
|
-
transformError: error => this.#transformError(error),
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Builds a query manually by using a configuration object.
|
|
172
|
-
* @param config the configuration object containing the name, a request function and a permission function.
|
|
173
|
-
*/
|
|
174
|
-
protected infiniteQuery<
|
|
175
|
-
Variables extends Record<string, any>,
|
|
176
|
-
Result,
|
|
177
|
-
PageParamName extends keyof Variables,
|
|
178
|
-
Accumulator extends keyof Result | '',
|
|
179
|
-
>(config: InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
180
|
-
/**
|
|
181
|
-
* Builds a query manually by using a configuration object.
|
|
182
|
-
* @param config the configuration object containing the name and a request function.
|
|
183
|
-
*/
|
|
184
|
-
protected infiniteQuery<
|
|
185
|
-
Variables extends Record<string, any>,
|
|
186
|
-
Result,
|
|
187
|
-
PageParamName extends keyof Variables,
|
|
188
|
-
Accumulator extends keyof Result | '',
|
|
189
|
-
>(
|
|
190
|
-
config: Omit<InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>, 'permission'>,
|
|
191
|
-
): Omit<InfiniteQueryObject<Variables, Result, Accumulator>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
|
|
192
|
-
/**
|
|
193
|
-
* Builds a query automatically by using a function generated by oazapfts.
|
|
194
|
-
* @param fn the oazapfts function.
|
|
195
|
-
* @param options the configuration for the infinite query.
|
|
196
|
-
*/
|
|
197
|
-
protected infiniteQuery<
|
|
198
|
-
Variables extends Record<string, any>,
|
|
199
|
-
Result,
|
|
200
|
-
PageParamName extends keyof Variables,
|
|
201
|
-
Accumulator extends keyof Result,
|
|
202
|
-
>(
|
|
203
|
-
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
204
|
-
options: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
205
|
-
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
206
|
-
/**
|
|
207
|
-
* Builds a query automatically by using a function generated by oazapfts with the variables `page` and `size`.
|
|
208
|
-
* @param fn the oazapfts function that returns an array.
|
|
209
|
-
* @param options optional configuration for the infinite query. By default, it will use the variables page and size of the function
|
|
210
|
-
* passed in the first parameter.
|
|
211
|
-
*/
|
|
212
|
-
protected infiniteQuery<
|
|
213
|
-
Variables extends { page?: number | string, size?: number | string },
|
|
214
|
-
Result extends any[],
|
|
215
|
-
PageParamName extends keyof Variables = 'page',
|
|
216
|
-
Accumulator extends keyof Result | '' = '',
|
|
217
|
-
>(
|
|
218
|
-
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
219
|
-
options?: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
220
|
-
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
221
|
-
/**
|
|
222
|
-
* Builds a query automatically by using a function generated by oazapfts in which the variables `pageParamName` is a string
|
|
223
|
-
* which can be used for dynamic indexing.
|
|
224
|
-
* @param fn the oazapfts function that returns an array.
|
|
225
|
-
* @param options optional configuration for the infinite query. By default, it will use the variables page and size of the function
|
|
226
|
-
* passed in the first parameter.
|
|
227
|
-
*/
|
|
228
|
-
protected infiniteQuery<
|
|
229
|
-
Variables extends Record<string, any>,
|
|
230
|
-
Result,
|
|
231
|
-
PageParamName extends string,
|
|
232
|
-
Accumulator extends keyof Result,
|
|
233
|
-
>(
|
|
234
|
-
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
235
|
-
options: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
236
|
-
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
237
|
-
protected infiniteQuery(
|
|
238
|
-
fnOrConfig: any, options?: Partial<InfiniteQueryOptions<any, any, any, any>>,
|
|
239
|
-
): InfiniteQueryObject<any, any, any> {
|
|
240
|
-
return typeof fnOrConfig === 'function'
|
|
241
|
-
? new AutoInfiniteQuery(
|
|
242
|
-
{
|
|
243
|
-
apiName: this.constructor.name,
|
|
244
|
-
fn: fnOrConfig,
|
|
245
|
-
onFetchPermission: (...args) => this.#onFetchPermission(...args),
|
|
246
|
-
transformError: error => this.#transformError(error),
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
accumulator: options?.accumulator ?? '',
|
|
250
|
-
pageParamName: options?.pageParamName ?? 'page',
|
|
251
|
-
initialPageParam: options?.initialPageParam ?? 1,
|
|
252
|
-
defaultVariables: options?.defaultVariables ?? { size: DEFAULT_PAGE_SIZE },
|
|
253
|
-
getNextPageParam: options?.getNextPageParam ?? (({ variables, lastPage, lastPageParam }) => {
|
|
254
|
-
const size = variables.size ?? 1
|
|
255
|
-
const isLastPageTheLast = lastPage && (
|
|
256
|
-
(Array.isArray(lastPage) && lastPage.length < size)
|
|
257
|
-
|| (options?.accumulator !== undefined && options?.accumulator !== '' && lastPage[options?.accumulator]?.length < size)
|
|
258
|
-
)
|
|
259
|
-
return isLastPageTheLast ? undefined : parseInt(lastPageParam) + 1
|
|
260
|
-
}),
|
|
261
|
-
},
|
|
262
|
-
)
|
|
263
|
-
: new ManualInfiniteQuery({
|
|
264
|
-
...fnOrConfig,
|
|
265
|
-
apiName: this.constructor.name,
|
|
266
|
-
request: fnOrConfig.request,
|
|
267
|
-
permission: fnOrConfig.permission,
|
|
268
|
-
transformError: error => this.#transformError(error),
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Builds a mutation manually by using a configuration object.
|
|
274
|
-
* @param config the configuration object containing the name, a request function and a permission function.
|
|
275
|
-
*/
|
|
276
|
-
protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
277
|
-
config: OperationConfig<Args, Result>,
|
|
278
|
-
): MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>
|
|
279
|
-
/**
|
|
280
|
-
* Builds a mutation manually by using a configuration object.
|
|
281
|
-
* @param config the configuration object containing the name and a request function.
|
|
282
|
-
*/
|
|
283
|
-
protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
284
|
-
config: Omit<OperationConfig<Args, Result>, 'permission'>,
|
|
285
|
-
): Omit<MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>, keyof OperationObject<any>>
|
|
286
|
-
/**
|
|
287
|
-
* Builds a mutation automatically by using a function generated by oazapfts.
|
|
288
|
-
* @param fn the oazapfts function.
|
|
289
|
-
*/
|
|
290
|
-
protected mutation<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
|
|
291
|
-
fn: (...args: Args) => Promise<Result>,
|
|
292
|
-
): Args extends [variables: infer Variables, opts?: RequestOpts] ? MutationObject<Variables, Result> : MutationObject<void, Result>
|
|
293
|
-
protected mutation(fnOrConfig: any): MutationObject<any, any> {
|
|
294
|
-
if (typeof fnOrConfig === 'function') {
|
|
295
|
-
return new AutoMutation(
|
|
296
|
-
{
|
|
297
|
-
apiName: this.constructor.name,
|
|
298
|
-
fn: fnOrConfig,
|
|
299
|
-
onFetchPermission: (...args) => this.#onFetchPermission(...args),
|
|
300
|
-
transformError: error => this.#transformError(error),
|
|
301
|
-
},
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
return new ManualMutation({
|
|
305
|
-
...fnOrConfig,
|
|
306
|
-
apiName: this.constructor.name,
|
|
307
|
-
request: fnOrConfig.request,
|
|
308
|
-
permission: fnOrConfig.permission,
|
|
309
|
-
transformError: error => this.#transformError(error),
|
|
310
|
-
})
|
|
311
|
-
}
|
|
312
|
-
}
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
|
|
3
|
+
import { Defaults, HttpError, RequestOpts } from '@oazapfts/runtime'
|
|
4
|
+
import { StackspotAPIError } from '../error/StackspotAPIError'
|
|
5
|
+
import { AutoInfiniteQuery } from './AutoInfiniteQuery'
|
|
6
|
+
import { AutoMutation } from './AutoMutation'
|
|
7
|
+
import { AutoQuery } from './AutoQuery'
|
|
8
|
+
import { ManualInfiniteQuery } from './ManualInfiniteQuery'
|
|
9
|
+
import { ManualMutation } from './ManualMutation'
|
|
10
|
+
import { ManualQuery } from './ManualQuery'
|
|
11
|
+
import { NetworkClient } from './NetworkClient'
|
|
12
|
+
import { Env, HTTPMethod, InfiniteQueryConfig, InfiniteQueryObject, InfiniteQueryOptions, MutationObject, OperationConfig, OperationObject, QueryObject } from './types'
|
|
13
|
+
|
|
14
|
+
const DUMMY_ULID = '01HSTZ5471CBG4AW9BFJ0VTVG9'
|
|
15
|
+
const PERMISSION_ERROR = 'permission-error'
|
|
16
|
+
export const DEFAULT_PAGE_SIZE = 20
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A Network Client that uses oazapfts for making requests and React Query for managing responses.
|
|
20
|
+
*/
|
|
21
|
+
export abstract class ReactQueryNetworkClient extends NetworkClient {
|
|
22
|
+
/**
|
|
23
|
+
* @param baseURL An object with the keys "dev", "stg" and "prd". The values must be the url for each of these environments.
|
|
24
|
+
* @param defaults the `default` object of the API this client is for (provided by the code generated by oazapfts).
|
|
25
|
+
*/
|
|
26
|
+
constructor(baseURL: Record<Env, string>, defaults: Defaults<any>) {
|
|
27
|
+
super(baseURL)
|
|
28
|
+
defaults.baseUrl = ''
|
|
29
|
+
defaults.fetch = (...args) => this.fetch(...args)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#parseErrorData(error: HttpError) {
|
|
33
|
+
if (typeof error.data === 'string') {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(error.data)
|
|
36
|
+
} catch { /* empty */ }
|
|
37
|
+
}
|
|
38
|
+
return error.data
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#transformError(error: any): StackspotAPIError {
|
|
42
|
+
if (error instanceof StackspotAPIError) return error
|
|
43
|
+
if (!(error instanceof HttpError)) throw new StackspotAPIError({ status: 0, message: error?.message || `${error}`, stack: error.stack })
|
|
44
|
+
const data = this.#parseErrorData(error)
|
|
45
|
+
if (data.type === PERMISSION_ERROR) {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.error('Error while validating permissions:', data.message)
|
|
48
|
+
return new StackspotAPIError({
|
|
49
|
+
status: 500,
|
|
50
|
+
stack: error.stack,
|
|
51
|
+
message: language => language === 'pt'
|
|
52
|
+
? 'Ocorreu um erro ao validar as permissões.'
|
|
53
|
+
: 'There was an error while validating permissions.',
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
return this.buildStackSpotError(error)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Receives an HttpError and returns a StackspotAPIError.
|
|
61
|
+
* @param error the original HttpError created by oazapfts.
|
|
62
|
+
*/
|
|
63
|
+
protected abstract buildStackSpotError(error: HttpError): StackspotAPIError
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a function that checks the permission for a request generated by an oazapfts function.
|
|
67
|
+
*
|
|
68
|
+
* This is intended to help creating a manual operation.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```
|
|
72
|
+
* myOperation = this.mutation({
|
|
73
|
+
* // ...
|
|
74
|
+
* permission: this.createPermissionFunctionFor(oazapftsFnForMyOperation)
|
|
75
|
+
* })
|
|
76
|
+
* ```
|
|
77
|
+
* @param fn the function generated by oazapfts.
|
|
78
|
+
* @returns a function that receives the variables of `fn` (if any) and returns a promise that resolves to true if the request is allowed
|
|
79
|
+
* and false otherwise.
|
|
80
|
+
*/
|
|
81
|
+
protected createPermissionFunctionFor<Args extends [variables: Record<string, any>, opts?: RequestOpts] | [opts?: RequestOpts]>(
|
|
82
|
+
fn: (...args: Args) => Promise<any>,
|
|
83
|
+
): (...args: Args extends [opts?: RequestOpts] ? [] : [variables?: Partial<Args[0]>]) => Promise<boolean> {
|
|
84
|
+
return (variables?: any) => fn.length === 2
|
|
85
|
+
? (fn as (variables: Record<string, any>, opts?: RequestOpts) => Promise<any>)(
|
|
86
|
+
variables,
|
|
87
|
+
{ fetch: (...args) => this.#onFetchPermission(...args) },
|
|
88
|
+
)
|
|
89
|
+
: (fn as (opts?: RequestOpts) => Promise<any>)({ fetch: (...args) => this.#onFetchPermission(...args) })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a function that runs a oazapfts function with an abort signal.
|
|
94
|
+
*
|
|
95
|
+
* This is intended to help creating a manual operation.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```
|
|
99
|
+
* myOperation = this.mutation({
|
|
100
|
+
* // ...
|
|
101
|
+
* request: this.createRequestFunctionFor(oazapftsFnForMyOperation)
|
|
102
|
+
* })
|
|
103
|
+
* ```
|
|
104
|
+
* @param fn the function generated by oazapfts.
|
|
105
|
+
* @returns a function that receives a signal and the variables of `fn` (if any) and returns the same as `fn`.
|
|
106
|
+
*/
|
|
107
|
+
protected createRequestFunctionFor<Args extends [variables: Record<string, any>, opts?: RequestOpts] | [opts?: RequestOpts], Result>(
|
|
108
|
+
fn: (...args: Args) => Promise<Result>,
|
|
109
|
+
): (...args: Args extends [opts?: RequestOpts] ? [signal: AbortSignal] : [signal: AbortSignal, variables: Args[0]]) => Promise<Result> {
|
|
110
|
+
return (signal, variables?: any) => fn.length === 2
|
|
111
|
+
? (fn as (variables: Record<string, any>, opts?: RequestOpts) => Promise<Result>)(variables, { signal })
|
|
112
|
+
: (fn as (opts?: RequestOpts) => Promise<Result>)({ signal })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async #onFetchPermission(input: string | URL | Request, init?: RequestInit | undefined): Promise<Response> {
|
|
116
|
+
const [path, method, body] = input instanceof Request
|
|
117
|
+
? [input.url, input.method as HTTPMethod, input.body]
|
|
118
|
+
: [`${input}`, (init?.method?.toLowerCase() || 'get') as HTTPMethod, init?.body]
|
|
119
|
+
try {
|
|
120
|
+
/* We allow the dev not to pass any variable when validating a permission. For this reason, oazapfts might generate a path with
|
|
121
|
+
"undefined". If this happens, we must replace the undefined text with a dummy ULID. */
|
|
122
|
+
const fixedPath = path.replace(/\/undefined(\/|$)/g, `/${DUMMY_ULID}$1`)
|
|
123
|
+
const isAllowed = await this.requestPermission(method, fixedPath, body as any)
|
|
124
|
+
return new Response(`${isAllowed}`, { headers: { 'Content-Type': 'application/json' } })
|
|
125
|
+
} catch (error: any) {
|
|
126
|
+
return new Response(JSON.stringify({ type: PERMISSION_ERROR, message: error.message || `${error}` }), { status: 500 })
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Builds a query manually by using a configuration object.
|
|
132
|
+
* @param config the configuration object containing the name, a request function and a permission function.
|
|
133
|
+
*/
|
|
134
|
+
protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
135
|
+
config: OperationConfig<Args, Result>,
|
|
136
|
+
): QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>
|
|
137
|
+
/**
|
|
138
|
+
* Builds a query manually by using a configuration object.
|
|
139
|
+
* @param config the configuration object containing the name and a request function.
|
|
140
|
+
*/
|
|
141
|
+
protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
142
|
+
config: Omit<OperationConfig<Args, Result>, 'permission'>,
|
|
143
|
+
): Omit<QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
|
|
144
|
+
/**
|
|
145
|
+
* Builds a query automatically by using a function generated by oazapfts.
|
|
146
|
+
* @param fn the oazapfts function.
|
|
147
|
+
*/
|
|
148
|
+
protected query<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
|
|
149
|
+
fn: (...args: Args) => Promise<Result>
|
|
150
|
+
): Args extends [variables: infer Variables, opts?: RequestOpts] ? QueryObject<Variables, Result> : QueryObject<void, Result>
|
|
151
|
+
protected query(fnOrConfig: any): QueryObject<any, any> {
|
|
152
|
+
return typeof fnOrConfig === 'function'
|
|
153
|
+
? new AutoQuery(
|
|
154
|
+
{
|
|
155
|
+
apiName: this.constructor.name,
|
|
156
|
+
fn: fnOrConfig,
|
|
157
|
+
onFetchPermission: (...args) => this.#onFetchPermission(...args),
|
|
158
|
+
transformError: error => this.#transformError(error),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
: new ManualQuery({
|
|
162
|
+
...fnOrConfig,
|
|
163
|
+
apiName: this.constructor.name,
|
|
164
|
+
request: fnOrConfig.request,
|
|
165
|
+
permission: fnOrConfig.permission,
|
|
166
|
+
transformError: error => this.#transformError(error),
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Builds a query manually by using a configuration object.
|
|
172
|
+
* @param config the configuration object containing the name, a request function and a permission function.
|
|
173
|
+
*/
|
|
174
|
+
protected infiniteQuery<
|
|
175
|
+
Variables extends Record<string, any>,
|
|
176
|
+
Result,
|
|
177
|
+
PageParamName extends keyof Variables,
|
|
178
|
+
Accumulator extends keyof Result | '',
|
|
179
|
+
>(config: InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
180
|
+
/**
|
|
181
|
+
* Builds a query manually by using a configuration object.
|
|
182
|
+
* @param config the configuration object containing the name and a request function.
|
|
183
|
+
*/
|
|
184
|
+
protected infiniteQuery<
|
|
185
|
+
Variables extends Record<string, any>,
|
|
186
|
+
Result,
|
|
187
|
+
PageParamName extends keyof Variables,
|
|
188
|
+
Accumulator extends keyof Result | '',
|
|
189
|
+
>(
|
|
190
|
+
config: Omit<InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>, 'permission'>,
|
|
191
|
+
): Omit<InfiniteQueryObject<Variables, Result, Accumulator>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
|
|
192
|
+
/**
|
|
193
|
+
* Builds a query automatically by using a function generated by oazapfts.
|
|
194
|
+
* @param fn the oazapfts function.
|
|
195
|
+
* @param options the configuration for the infinite query.
|
|
196
|
+
*/
|
|
197
|
+
protected infiniteQuery<
|
|
198
|
+
Variables extends Record<string, any>,
|
|
199
|
+
Result,
|
|
200
|
+
PageParamName extends keyof Variables,
|
|
201
|
+
Accumulator extends keyof Result,
|
|
202
|
+
>(
|
|
203
|
+
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
204
|
+
options: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
205
|
+
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
206
|
+
/**
|
|
207
|
+
* Builds a query automatically by using a function generated by oazapfts with the variables `page` and `size`.
|
|
208
|
+
* @param fn the oazapfts function that returns an array.
|
|
209
|
+
* @param options optional configuration for the infinite query. By default, it will use the variables page and size of the function
|
|
210
|
+
* passed in the first parameter.
|
|
211
|
+
*/
|
|
212
|
+
protected infiniteQuery<
|
|
213
|
+
Variables extends { page?: number | string, size?: number | string },
|
|
214
|
+
Result extends any[],
|
|
215
|
+
PageParamName extends keyof Variables = 'page',
|
|
216
|
+
Accumulator extends keyof Result | '' = '',
|
|
217
|
+
>(
|
|
218
|
+
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
219
|
+
options?: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
220
|
+
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
221
|
+
/**
|
|
222
|
+
* Builds a query automatically by using a function generated by oazapfts in which the variables `pageParamName` is a string
|
|
223
|
+
* which can be used for dynamic indexing.
|
|
224
|
+
* @param fn the oazapfts function that returns an array.
|
|
225
|
+
* @param options optional configuration for the infinite query. By default, it will use the variables page and size of the function
|
|
226
|
+
* passed in the first parameter.
|
|
227
|
+
*/
|
|
228
|
+
protected infiniteQuery<
|
|
229
|
+
Variables extends Record<string, any>,
|
|
230
|
+
Result,
|
|
231
|
+
PageParamName extends string,
|
|
232
|
+
Accumulator extends keyof Result,
|
|
233
|
+
>(
|
|
234
|
+
fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
|
|
235
|
+
options: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
|
|
236
|
+
): InfiniteQueryObject<Variables, Result, Accumulator>
|
|
237
|
+
protected infiniteQuery(
|
|
238
|
+
fnOrConfig: any, options?: Partial<InfiniteQueryOptions<any, any, any, any>>,
|
|
239
|
+
): InfiniteQueryObject<any, any, any> {
|
|
240
|
+
return typeof fnOrConfig === 'function'
|
|
241
|
+
? new AutoInfiniteQuery(
|
|
242
|
+
{
|
|
243
|
+
apiName: this.constructor.name,
|
|
244
|
+
fn: fnOrConfig,
|
|
245
|
+
onFetchPermission: (...args) => this.#onFetchPermission(...args),
|
|
246
|
+
transformError: error => this.#transformError(error),
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
accumulator: options?.accumulator ?? '',
|
|
250
|
+
pageParamName: options?.pageParamName ?? 'page',
|
|
251
|
+
initialPageParam: options?.initialPageParam ?? 1,
|
|
252
|
+
defaultVariables: options?.defaultVariables ?? { size: DEFAULT_PAGE_SIZE },
|
|
253
|
+
getNextPageParam: options?.getNextPageParam ?? (({ variables, lastPage, lastPageParam }) => {
|
|
254
|
+
const size = variables.size ?? 1
|
|
255
|
+
const isLastPageTheLast = lastPage && (
|
|
256
|
+
(Array.isArray(lastPage) && lastPage.length < size)
|
|
257
|
+
|| (options?.accumulator !== undefined && options?.accumulator !== '' && lastPage[options?.accumulator]?.length < size)
|
|
258
|
+
)
|
|
259
|
+
return isLastPageTheLast ? undefined : parseInt(lastPageParam) + 1
|
|
260
|
+
}),
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
: new ManualInfiniteQuery({
|
|
264
|
+
...fnOrConfig,
|
|
265
|
+
apiName: this.constructor.name,
|
|
266
|
+
request: fnOrConfig.request,
|
|
267
|
+
permission: fnOrConfig.permission,
|
|
268
|
+
transformError: error => this.#transformError(error),
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Builds a mutation manually by using a configuration object.
|
|
274
|
+
* @param config the configuration object containing the name, a request function and a permission function.
|
|
275
|
+
*/
|
|
276
|
+
protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
277
|
+
config: OperationConfig<Args, Result>,
|
|
278
|
+
): MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>
|
|
279
|
+
/**
|
|
280
|
+
* Builds a mutation manually by using a configuration object.
|
|
281
|
+
* @param config the configuration object containing the name and a request function.
|
|
282
|
+
*/
|
|
283
|
+
protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
|
|
284
|
+
config: Omit<OperationConfig<Args, Result>, 'permission'>,
|
|
285
|
+
): Omit<MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>, keyof OperationObject<any>>
|
|
286
|
+
/**
|
|
287
|
+
* Builds a mutation automatically by using a function generated by oazapfts.
|
|
288
|
+
* @param fn the oazapfts function.
|
|
289
|
+
*/
|
|
290
|
+
protected mutation<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
|
|
291
|
+
fn: (...args: Args) => Promise<Result>,
|
|
292
|
+
): Args extends [variables: infer Variables, opts?: RequestOpts] ? MutationObject<Variables, Result> : MutationObject<void, Result>
|
|
293
|
+
protected mutation(fnOrConfig: any): MutationObject<any, any> {
|
|
294
|
+
if (typeof fnOrConfig === 'function') {
|
|
295
|
+
return new AutoMutation(
|
|
296
|
+
{
|
|
297
|
+
apiName: this.constructor.name,
|
|
298
|
+
fn: fnOrConfig,
|
|
299
|
+
onFetchPermission: (...args) => this.#onFetchPermission(...args),
|
|
300
|
+
transformError: error => this.#transformError(error),
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
return new ManualMutation({
|
|
305
|
+
...fnOrConfig,
|
|
306
|
+
apiName: this.constructor.name,
|
|
307
|
+
request: fnOrConfig.request,
|
|
308
|
+
permission: fnOrConfig.permission,
|
|
309
|
+
transformError: error => this.#transformError(error),
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/react-query'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The global, unique, Query Client for React Query.
|
|
5
|
-
*/
|
|
6
|
-
export const queryClient = new QueryClient({
|
|
7
|
-
defaultOptions: {
|
|
8
|
-
queries: {
|
|
9
|
-
refetchOnWindowFocus: false,
|
|
10
|
-
retry: false,
|
|
11
|
-
staleTime: Infinity,
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
})
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The global, unique, Query Client for React Query.
|
|
5
|
+
*/
|
|
6
|
+
export const queryClient = new QueryClient({
|
|
7
|
+
defaultOptions: {
|
|
8
|
+
queries: {
|
|
9
|
+
refetchOnWindowFocus: false,
|
|
10
|
+
retry: false,
|
|
11
|
+
staleTime: Infinity,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
})
|