@stack-spot/portal-network 0.1.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.
Files changed (163) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +7 -6
  3. package/src/apis.json +2 -10
  4. package/src/client/account.ts +111 -4
  5. package/src/error/CanceledError.ts +3 -0
  6. package/src/error/DefaultAPIError.ts +27 -1
  7. package/src/error/StackspotAPIError.ts +33 -1
  8. package/src/index.ts +1 -0
  9. package/src/network/AutoInfiniteQuery.ts +104 -0
  10. package/src/network/AutoOperation.ts +7 -1
  11. package/src/network/AutoQuery.ts +2 -1
  12. package/src/network/ManualInfiniteQuery.ts +95 -0
  13. package/src/network/ManualQuery.ts +2 -2
  14. package/src/network/NetworkClient.ts +53 -41
  15. package/src/network/ReactQueryNetworkClient.ts +131 -33
  16. package/src/network/react-query-client.ts +3 -0
  17. package/src/network/types.ts +207 -11
  18. package/src/utils/use-extended-list.ts +80 -0
  19. package/.generate-api.failed.json +0 -1
  20. package/dist/api/account.d.ts +0 -2368
  21. package/dist/api/account.d.ts.map +0 -1
  22. package/dist/api/account.js +0 -1521
  23. package/dist/api/account.js.map +0 -1
  24. package/dist/api/ai.d.ts +0 -1432
  25. package/dist/api/ai.d.ts.map +0 -1
  26. package/dist/api/ai.js +0 -1342
  27. package/dist/api/ai.js.map +0 -1
  28. package/dist/api/apiRuntime.d.ts +0 -922
  29. package/dist/api/apiRuntime.d.ts.map +0 -1
  30. package/dist/api/apiRuntime.js +0 -599
  31. package/dist/api/apiRuntime.js.map +0 -1
  32. package/dist/api/cloudAccount.d.ts +0 -473
  33. package/dist/api/cloudAccount.d.ts.map +0 -1
  34. package/dist/api/cloudAccount.js +0 -300
  35. package/dist/api/cloudAccount.js.map +0 -1
  36. package/dist/api/cloudServices.d.ts +0 -1233
  37. package/dist/api/cloudServices.d.ts.map +0 -1
  38. package/dist/api/cloudServices.js +0 -715
  39. package/dist/api/cloudServices.js.map +0 -1
  40. package/dist/api/insights.d.ts +0 -123
  41. package/dist/api/insights.d.ts.map +0 -1
  42. package/dist/api/insights.js +0 -112
  43. package/dist/api/insights.js.map +0 -1
  44. package/dist/api/plugin.d.ts +0 -368
  45. package/dist/api/plugin.d.ts.map +0 -1
  46. package/dist/api/plugin.js +0 -218
  47. package/dist/api/plugin.js.map +0 -1
  48. package/dist/api/serviceCatalog.d.ts +0 -737
  49. package/dist/api/serviceCatalog.d.ts.map +0 -1
  50. package/dist/api/serviceCatalog.js +0 -611
  51. package/dist/api/serviceCatalog.js.map +0 -1
  52. package/dist/api/workflows.d.ts +0 -366
  53. package/dist/api/workflows.d.ts.map +0 -1
  54. package/dist/api/workflows.js +0 -175
  55. package/dist/api/workflows.js.map +0 -1
  56. package/dist/api/workspace.js +0 -1476
  57. package/dist/api/workspace.js.map +0 -1
  58. package/dist/api/workspaceManager.d.ts +0 -1121
  59. package/dist/api/workspaceManager.d.ts.map +0 -1
  60. package/dist/api/workspaceManager.js +0 -357
  61. package/dist/api/workspaceManager.js.map +0 -1
  62. package/dist/api/workspaceSearchEngine.d.ts +0 -93
  63. package/dist/api/workspaceSearchEngine.d.ts.map +0 -1
  64. package/dist/api/workspaceSearchEngine.js +0 -55
  65. package/dist/api/workspaceSearchEngine.js.map +0 -1
  66. package/dist/apis.json +0 -129
  67. package/dist/client/account.d.ts +0 -46
  68. package/dist/client/account.d.ts.map +0 -1
  69. package/dist/client/account.js +0 -104
  70. package/dist/client/account.js.map +0 -1
  71. package/dist/error/CanceledError.d.ts +0 -5
  72. package/dist/error/CanceledError.d.ts.map +0 -1
  73. package/dist/error/CanceledError.js +0 -7
  74. package/dist/error/CanceledError.js.map +0 -1
  75. package/dist/error/DefaultAPIError.d.ts +0 -9
  76. package/dist/error/DefaultAPIError.d.ts.map +0 -1
  77. package/dist/error/DefaultAPIError.js +0 -58
  78. package/dist/error/DefaultAPIError.js.map +0 -1
  79. package/dist/error/StackspotAPIError.d.ts +0 -19
  80. package/dist/error/StackspotAPIError.d.ts.map +0 -1
  81. package/dist/error/StackspotAPIError.js +0 -39
  82. package/dist/error/StackspotAPIError.js.map +0 -1
  83. package/dist/error/dictionary/account.d.ts +0 -55
  84. package/dist/error/dictionary/account.d.ts.map +0 -1
  85. package/dist/error/dictionary/account.js +0 -55
  86. package/dist/error/dictionary/account.js.map +0 -1
  87. package/dist/error/dictionary/action.d.ts +0 -163
  88. package/dist/error/dictionary/action.d.ts.map +0 -1
  89. package/dist/error/dictionary/action.js +0 -163
  90. package/dist/error/dictionary/action.js.map +0 -1
  91. package/dist/error/dictionary/base.d.ts +0 -21
  92. package/dist/error/dictionary/base.d.ts.map +0 -1
  93. package/dist/error/dictionary/base.js +0 -21
  94. package/dist/error/dictionary/base.js.map +0 -1
  95. package/dist/error/dictionary/cnt-fields.d.ts +0 -13
  96. package/dist/error/dictionary/cnt-fields.d.ts.map +0 -1
  97. package/dist/error/dictionary/cnt-fields.js +0 -13
  98. package/dist/error/dictionary/cnt-fields.js.map +0 -1
  99. package/dist/error/dictionary/cnt.d.ts +0 -79
  100. package/dist/error/dictionary/cnt.d.ts.map +0 -1
  101. package/dist/error/dictionary/cnt.js +0 -79
  102. package/dist/error/dictionary/cnt.js.map +0 -1
  103. package/dist/error/dictionary/rte.d.ts +0 -23
  104. package/dist/error/dictionary/rte.d.ts.map +0 -1
  105. package/dist/error/dictionary/rte.js +0 -23
  106. package/dist/error/dictionary/rte.js.map +0 -1
  107. package/dist/error/dictionary/rtm.d.ts +0 -9
  108. package/dist/error/dictionary/rtm.d.ts.map +0 -1
  109. package/dist/error/dictionary/rtm.js +0 -9
  110. package/dist/error/dictionary/rtm.js.map +0 -1
  111. package/dist/error/dictionary/workspace-fields.d.ts +0 -9
  112. package/dist/error/dictionary/workspace-fields.d.ts.map +0 -1
  113. package/dist/error/dictionary/workspace-fields.js +0 -9
  114. package/dist/error/dictionary/workspace-fields.js.map +0 -1
  115. package/dist/error/dictionary/workspace.d.ts +0 -99
  116. package/dist/error/dictionary/workspace.d.ts.map +0 -1
  117. package/dist/error/dictionary/workspace.js +0 -99
  118. package/dist/error/dictionary/workspace.js.map +0 -1
  119. package/dist/index.d.ts +0 -6
  120. package/dist/index.d.ts.map +0 -1
  121. package/dist/index.js +0 -6
  122. package/dist/index.js.map +0 -1
  123. package/dist/network/AutoMutation.d.ts +0 -10
  124. package/dist/network/AutoMutation.d.ts.map +0 -1
  125. package/dist/network/AutoMutation.js +0 -20
  126. package/dist/network/AutoMutation.js.map +0 -1
  127. package/dist/network/AutoOperation.d.ts +0 -19
  128. package/dist/network/AutoOperation.d.ts.map +0 -1
  129. package/dist/network/AutoOperation.js +0 -99
  130. package/dist/network/AutoOperation.js.map +0 -1
  131. package/dist/network/AutoQuery.d.ts +0 -19
  132. package/dist/network/AutoQuery.d.ts.map +0 -1
  133. package/dist/network/AutoQuery.js +0 -70
  134. package/dist/network/AutoQuery.js.map +0 -1
  135. package/dist/network/ManualMutation.d.ts +0 -11
  136. package/dist/network/ManualMutation.d.ts.map +0 -1
  137. package/dist/network/ManualMutation.js +0 -32
  138. package/dist/network/ManualMutation.js.map +0 -1
  139. package/dist/network/ManualOperation.d.ts +0 -13
  140. package/dist/network/ManualOperation.d.ts.map +0 -1
  141. package/dist/network/ManualOperation.js +0 -53
  142. package/dist/network/ManualOperation.js.map +0 -1
  143. package/dist/network/ManualQuery.d.ts +0 -20
  144. package/dist/network/ManualQuery.d.ts.map +0 -1
  145. package/dist/network/ManualQuery.js +0 -77
  146. package/dist/network/ManualQuery.js.map +0 -1
  147. package/dist/network/NetworkClient.d.ts +0 -20
  148. package/dist/network/NetworkClient.d.ts.map +0 -1
  149. package/dist/network/NetworkClient.js +0 -85
  150. package/dist/network/NetworkClient.js.map +0 -1
  151. package/dist/network/ReactQueryNetworkClient.d.ts +0 -16
  152. package/dist/network/ReactQueryNetworkClient.d.ts.map +0 -1
  153. package/dist/network/ReactQueryNetworkClient.js +0 -125
  154. package/dist/network/ReactQueryNetworkClient.js.map +0 -1
  155. package/dist/network/react-query-client.d.ts +0 -3
  156. package/dist/network/react-query-client.d.ts.map +0 -1
  157. package/dist/network/react-query-client.js +0 -3
  158. package/dist/network/react-query-client.js.map +0 -1
  159. package/dist/network/types.d.ts +0 -55
  160. package/dist/network/types.d.ts.map +0 -1
  161. package/dist/network/types.js +0 -2
  162. package/dist/network/types.js.map +0 -1
  163. package/src/api/plugin.ts +0 -685
@@ -1,17 +1,36 @@
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, RequestOptions, RequestWithBody, SessionManager } from './types'
3
+ import { Env, HTTPMethod, SessionManager } from './types'
5
4
 
6
- export class NetworkClient {
5
+ /**
6
+ * A set of methods for performing network requests to an API.
7
+ *
8
+ * The requests are authenticated unless there's no session available.
9
+ *
10
+ * In order for a network client to work properly, the general setup for the class `NetworkClient` must be made before any request is
11
+ * attempted:
12
+ *
13
+ * ```
14
+ * NetworkClient.setup(mySessionManager, currentEnv)
15
+ * ```
16
+ */
17
+ export abstract class NetworkClient {
7
18
  private baseURL: Record<Env, string>
8
19
  private static sessionManager?: SessionManager
9
20
  private static env?: Env
10
21
 
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
+ */
11
25
  constructor(baseURL: Record<Env, string>) {
12
26
  this.baseURL = baseURL
13
27
  }
14
28
 
29
+ /**
30
+ * Sets up all network clients. Must be called before attempting to make any request.
31
+ * @param sessionManager An object with functions capable of checking, retrieving and ending the current session.
32
+ * @param env The environment to send the requests to.
33
+ */
15
34
  static setup(sessionManager: SessionManager, env: Env) {
16
35
  NetworkClient.sessionManager = sessionManager
17
36
  NetworkClient.env = env
@@ -21,6 +40,11 @@ export class NetworkClient {
21
40
  return new Error('Please, call "NetworkClient.setup(sessionManager, env)" before attempting to make a request.')
22
41
  }
23
42
 
43
+ /**
44
+ * Builds a URL with the `baseUrl` of this network client and the `path` passed as parameter.
45
+ * @param path the path to the resource.
46
+ * @returns a full URL.
47
+ */
24
48
  protected resolveURL(path: string) {
25
49
  if (!NetworkClient.env) throw this.uninitializedError()
26
50
  // paths must not start with "/", otherwise, the base url will not be fully appended to it.
@@ -30,6 +54,13 @@ export class NetworkClient {
30
54
  return new URL(fixedPath, fixedBaseUrl)
31
55
  }
32
56
 
57
+ /**
58
+ * Verifies if the current user is allowed to send the given request.
59
+ * @param method the request's method.
60
+ * @param path the path to the resource.
61
+ * @param body the request's body.
62
+ * @returns a promise that resolves to true if it's allowed or false otherwise.
63
+ */
33
64
  protected requestPermission(method: HTTPMethod, path: string, body?: string | object): Promise<boolean> {
34
65
  return requestPermission(method, this.resolveURL(path).toString(), body)
35
66
  }
@@ -40,52 +71,33 @@ export class NetworkClient {
40
71
  return sessionManager
41
72
  }
42
73
 
43
- private sendRequest(path: string, method: HTTPMethod, request?: RequestWithBody): Promise<Response> {
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> {
44
82
  const sessionManager = this.getSessionManager()
45
- const url = this.resolveURL(path)
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()
46
89
  try {
47
- if (request?.params) {
48
- Object.keys(request.params).forEach((key) => {
49
- const value = request?.params?.[key]
50
- if (value !== undefined) url.searchParams.set(key, value)
51
- })
52
- }
53
- const isFormData = request?.body instanceof FormData
54
- const isJsonContent = (typeof request?.body === 'object') && !isFormData
55
- const body = isJsonContent ? JSON.stringify(request.body) : request?.body as string | FormData
56
- const defaultHeaders: Record<string, string> = isJsonContent ? { 'Content-Type': 'application/json' } : {}
57
- const headers = mergeHeaders(defaultHeaders, request?.headers)
58
- return (
59
- sessionManager.hasSession()
60
- ? sessionManager.getSession().fetch(url, { method: method.toUpperCase(), headers, body })
61
- : fetch(url, { method: method.toUpperCase(), headers, body })
62
- )
90
+ return sessionManager.hasSession() ? sessionManager.getSession().fetch(inputWithBaseUrl, init) : fetch(inputWithBaseUrl, init)
63
91
  } catch (error) {
64
92
  if (error instanceof AuthenticationError) sessionManager.endSession()
65
93
  throw error
66
94
  }
67
95
  }
68
96
 
69
- protected get<T>(path: string, request?: RequestOptions): Promise<T> {
70
- return this.sendRequest(path, 'get', request) as Promise<T>
71
- }
72
-
73
- protected post<T>(path: string, request?: RequestWithBody): Promise<T> {
74
- return this.sendRequest(path, 'post', request) as Promise<T>
75
- }
76
-
77
- protected put<T>(path: string, request?: RequestWithBody): Promise<T> {
78
- return this.sendRequest(path, 'put', request) as Promise<T>
79
- }
80
-
81
- protected patch<T>(path: string, request?: RequestWithBody): Promise<T> {
82
- return this.sendRequest(path, 'patch', request) as Promise<T>
83
- }
84
-
85
- protected delete<T>(path: string, request?: RequestWithBody): Promise<T> {
86
- return this.sendRequest(path, 'delete', request) as Promise<T>
87
- }
88
-
97
+ /**
98
+ * Checks whether or not the current account is freemium.
99
+ * @returns true if it's a freemium account, false otherwise.
100
+ */
89
101
  protected isFreemium() {
90
102
  const sessionManager = this.getSessionManager()
91
103
  return sessionManager.hasSession() && !!sessionManager.getSession().getTokenData().freemium_status
@@ -2,39 +2,31 @@
2
2
 
3
3
  import { Defaults, HttpError, RequestOpts } from '@oazapfts/runtime'
4
4
  import { StackspotAPIError } from '../error/StackspotAPIError'
5
+ import { AutoInfiniteQuery } from './AutoInfiniteQuery'
5
6
  import { AutoMutation } from './AutoMutation'
6
7
  import { AutoQuery } from './AutoQuery'
8
+ import { ManualInfiniteQuery } from './ManualInfiniteQuery'
7
9
  import { ManualMutation } from './ManualMutation'
8
10
  import { ManualQuery } from './ManualQuery'
9
11
  import { NetworkClient } from './NetworkClient'
10
- import { Env, HTTPMethod, MutationObject, OperationConfig, OperationObject, QueryObject } from './types'
12
+ import { Env, HTTPMethod, InfiniteQueryConfig, InfiniteQueryObject, InfiniteQueryOptions, MutationObject, OperationConfig, OperationObject, QueryObject } from './types'
11
13
 
12
14
  const DUMMY_ULID = '01HSTZ5471CBG4AW9BFJ0VTVG9'
13
15
  const PERMISSION_ERROR = 'permission-error'
16
+ export const DEFAULT_PAGE_SIZE = 20
14
17
 
18
+ /**
19
+ * A Network Client that uses oazapfts for making requests and React Query for managing responses.
20
+ */
15
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
+ */
16
26
  constructor(baseURL: Record<Env, string>, defaults: Defaults<any>) {
17
27
  super(baseURL)
18
28
  defaults.baseUrl = ''
19
- // eslint-disable-next-line arrow-body-style
20
- defaults.fetch = (...args) => {
21
- return this.#onFetch(...args)
22
- }
23
- }
24
-
25
- #getMappedHeaders(headers: HeadersInit | undefined) {
26
- if (!headers) return undefined
27
- if (headers instanceof Headers) {
28
- const headersMap: Record<string, string> = {}
29
- headers.forEach((value, key) => headersMap[key] = value)
30
- return headersMap
31
- }
32
- if (Array.isArray(headers)) {
33
- const headersMap: Record<string, string> = {}
34
- headers.forEach(([key, value]) => headersMap[key] = value)
35
- return headersMap
36
- }
37
- return headers
29
+ defaults.fetch = (...args) => this.fetch(...args)
38
30
  }
39
31
 
40
32
  #parseErrorData(error: HttpError) {
@@ -73,13 +65,10 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
73
65
  }
74
66
  )
75
67
 
76
- #onFetch(input: string | URL | Request, init?: RequestInit | undefined): Promise<Response> {
77
- const [path, method, body, headers] = input instanceof Request
78
- ? [input.url, input.method as HTTPMethod, input.body, input.headers]
79
- : [`${input}`, (init?.method?.toLowerCase() || 'get') as HTTPMethod, init?.body, init?.headers]
80
- return this[method](path, { body: body as any, headers: this.#getMappedHeaders(headers) })
81
- }
82
-
68
+ /**
69
+ * Receives an HttpError and returns a StackspotAPIError.
70
+ * @param error the original HttpError created by oazapfts.
71
+ */
83
72
  protected abstract buildStackSpotError(error: HttpError): StackspotAPIError
84
73
 
85
74
  async #onFetchPermission(input: string | URL | Request, init?: RequestInit | undefined): Promise<Response> {
@@ -97,15 +86,27 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
97
86
  }
98
87
  }
99
88
 
100
- protected query<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
101
- fn: (...args: Args) => Promise<Result>
102
- ): Args extends [variables: infer Variables, opts?: RequestOpts] ? QueryObject<Variables, Result> : QueryObject<void, Result>
89
+ /**
90
+ * Builds a query manually by using a configuration object.
91
+ * @param config the configuration object containing the name, a request function and a permission function.
92
+ */
103
93
  protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
104
94
  config: OperationConfig<Args, Result>,
105
95
  ): QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>
96
+ /**
97
+ * Builds a query manually by using a configuration object.
98
+ * @param config the configuration object containing the name and a request function.
99
+ */
106
100
  protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
107
101
  config: Omit<OperationConfig<Args, Result>, 'permission'>,
108
102
  ): Omit<QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
103
+ /**
104
+ * Builds a query automatically by using a function generated by oazapfts.
105
+ * @param fn the oazapfts function.
106
+ */
107
+ protected query<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
108
+ fn: (...args: Args) => Promise<Result>
109
+ ): Args extends [variables: infer Variables, opts?: RequestOpts] ? QueryObject<Variables, Result> : QueryObject<void, Result>
109
110
  protected query(fnOrConfig: any): QueryObject<any, any> {
110
111
  return typeof fnOrConfig === 'function'
111
112
  ? new AutoQuery(
@@ -124,15 +125,112 @@ export abstract class ReactQueryNetworkClient extends NetworkClient {
124
125
  })
125
126
  }
126
127
 
127
- protected mutation<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
128
- fn: (...args: Args) => Promise<Result>,
129
- ): Args extends [variables: infer Variables, opts?: RequestOpts] ? MutationObject<Variables, Result> : MutationObject<void, Result>
128
+ /**
129
+ * Builds a query manually by using a configuration object.
130
+ * @param config the configuration object containing the name, a request function and a permission function.
131
+ */
132
+ protected infiniteQuery<
133
+ Variables extends Record<string, any>,
134
+ Result,
135
+ PageParamName extends keyof Variables,
136
+ Accumulator extends keyof Result | '',
137
+ >(config: InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>): InfiniteQueryObject<Variables, Result, Accumulator>
138
+ /**
139
+ * Builds a query manually by using a configuration object.
140
+ * @param config the configuration object containing the name and a request function.
141
+ */
142
+ protected infiniteQuery<
143
+ Variables extends Record<string, any>,
144
+ Result,
145
+ PageParamName extends keyof Variables,
146
+ Accumulator extends keyof Result | '',
147
+ >(
148
+ config: Omit<InfiniteQueryConfig<Variables, Result, PageParamName, Accumulator>, 'permission'>,
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>
164
+ /**
165
+ * Builds a query automatically by using a function generated by oazapfts with the variables `page` and `size`.
166
+ * @param fn the oazapfts function that returns an array.
167
+ * @param options optional configuration for the infinite query. By default, it will use the variables page and size of the function
168
+ * passed in the first parameter.
169
+ */
170
+ protected infiniteQuery<
171
+ Variables extends { page?: number, size?: number },
172
+ Result extends any[],
173
+ PageParamName extends keyof Variables = 'page',
174
+ Accumulator extends keyof Result | '' = '',
175
+ >(
176
+ fn: (variables: Variables, opts?: RequestOpts) => Promise<Result>,
177
+ options?: Partial<InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>>,
178
+ ): InfiniteQueryObject<Variables, Result, Accumulator>
179
+ protected infiniteQuery(
180
+ fnOrConfig: any, options?: Partial<InfiniteQueryOptions<any, any, any, any>>,
181
+ ): InfiniteQueryObject<any, any, any> {
182
+ return typeof fnOrConfig === 'function'
183
+ ? new AutoInfiniteQuery(
184
+ {
185
+ apiName: this.constructor.name,
186
+ fn: fnOrConfig,
187
+ onFetchPermission: (...args) => this.#onFetchPermission(...args),
188
+ transformError: error => this.#transformError(error),
189
+ },
190
+ {
191
+ accumulator: options?.accumulator ?? '',
192
+ pageParamName: options?.pageParamName ?? 'page',
193
+ initialPageParam: options?.initialPageParam ?? 1,
194
+ defaultVariables: options?.defaultVariables ?? { size: DEFAULT_PAGE_SIZE },
195
+ getNextPageParam: options?.getNextPageParam ?? (({ variables, lastPage, lastPageParam }) => {
196
+ const size = variables.size ?? 1
197
+ const isLastPageTheLast = lastPage && (
198
+ (Array.isArray(lastPage) && lastPage.length < size)
199
+ || (options?.accumulator !== undefined && options?.accumulator !== '' && lastPage[options?.accumulator]?.length < size)
200
+ )
201
+ return isLastPageTheLast ? undefined : lastPageParam + 1
202
+ }),
203
+ },
204
+ )
205
+ : new ManualInfiniteQuery({
206
+ ...fnOrConfig,
207
+ apiName: this.constructor.name,
208
+ request: this.#withErrorTreatment(fnOrConfig.request),
209
+ permission: fnOrConfig.permission ? this.#withErrorTreatment(fnOrConfig.permission) : undefined,
210
+ })
211
+ }
212
+
213
+ /**
214
+ * Builds a mutation manually by using a configuration object.
215
+ * @param config the configuration object containing the name, a request function and a permission function.
216
+ */
130
217
  protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
131
218
  config: OperationConfig<Args, Result>,
132
219
  ): MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>
220
+ /**
221
+ * Builds a mutation manually by using a configuration object.
222
+ * @param config the configuration object containing the name and a request function.
223
+ */
133
224
  protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
134
225
  config: Omit<OperationConfig<Args, Result>, 'permission'>,
135
226
  ): Omit<MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>, keyof OperationObject<any>>
227
+ /**
228
+ * Builds a mutation automatically by using a function generated by oazapfts.
229
+ * @param fn the oazapfts function.
230
+ */
231
+ protected mutation<Args extends [opts?: RequestOpts] | [variables: Record<string, any>, opts?: RequestOpts], Result>(
232
+ fn: (...args: Args) => Promise<Result>,
233
+ ): Args extends [variables: infer Variables, opts?: RequestOpts] ? MutationObject<Variables, Result> : MutationObject<void, Result>
136
234
  protected mutation(fnOrConfig: any): MutationObject<any, any> {
137
235
  if (typeof fnOrConfig === 'function') {
138
236
  return new AutoMutation(
@@ -1,3 +1,6 @@
1
1
  import { QueryClient } from '@tanstack/react-query'
2
2
 
3
+ /**
4
+ * The global, unique, Query Client for React Query.
5
+ */
3
6
  export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 3 } } })
@@ -1,6 +1,6 @@
1
1
  import type { RequestOpts } from '@oazapfts/runtime'
2
2
  import { Session } from '@stack-spot/auth'
3
- import { MutateOptions, UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'
3
+ import { InfiniteData, MutateOptions, UseInfiniteQueryOptions, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'
4
4
  import { StackspotAPIError } from '../error/StackspotAPIError'
5
5
 
6
6
  export interface SessionManager {
@@ -9,58 +9,238 @@ 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
- }
16
-
17
- export interface RequestWithBody extends RequestOptions {
18
- body?: string | object | FormData,
19
- }
20
-
21
12
  export type Env = 'dev' | 'stg' | 'prd'
22
13
 
23
14
  export type HTTPMethod = 'post' | 'patch' | 'delete' | 'put' | 'get'
24
15
 
25
16
  export interface OperationConfig<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result> {
17
+ /**
18
+ * The operation name. To be used as key in React Query.
19
+ */
26
20
  name: string,
21
+ /**
22
+ * The function to run once the operation is called.
23
+ * @param abortSignal the AbortSignal to pass to any request made.
24
+ * @param variables if this operation accepts variables, the second parameter is the variables object.
25
+ * @returns the operation result (this should not be a Response object, but it could be its body).
26
+ */
27
27
  request: (...args: Args) => Promise<Result>,
28
+ /**
29
+ * The function to run once one asks if the operation is allowed.
30
+ * @param variables if this operation accepts variables, the single parameter is the variables object.
31
+ * @returns a promise that resolves to true if the operation is allowed and false otherwise.
32
+ */
28
33
  permission: (...args: Args extends [AbortSignal] ? [] : [Args[1]]) => Promise<boolean>,
29
34
  }
30
35
 
36
+
37
+ export interface InfiniteQueryOptions<Variables, Result, PageParamName extends keyof Variables, Accumulator extends keyof Result | ''> {
38
+ /**
39
+ * The name of the variable that controls the current page.
40
+ */
41
+ pageParamName: PageParamName,
42
+ /**
43
+ * The name of the property in the Result that contains the items of a page. If the result itself is the array of items, use an empty
44
+ * string ('').
45
+ */
46
+ accumulator?: Accumulator,
47
+ /**
48
+ * The initial value for page variable.
49
+ */
50
+ initialPageParam: Variables[PageParamName],
51
+ /**
52
+ * Some defaults to be used when a variable is not specified.
53
+ */
54
+ defaultVariables?: Partial<Variables>,
55
+ /**
56
+ * A function that determines the value of the page variable in order to get the next page.
57
+ * @parar context the current context with the variables and page details.
58
+ * @returns undefined or null if there are no more pages. The next value for the page variable otherwise.
59
+ */
60
+ getNextPageParam: (context: {
61
+ /**
62
+ * the original set of variables passed to the hook `useInfiniteQuery`.
63
+ */
64
+ variables: Variables,
65
+ /**
66
+ * the last page Result.
67
+ */
68
+ lastPage: Result,
69
+ /**
70
+ * the Results of each page.
71
+ */
72
+ allPages: Result[],
73
+ /**
74
+ * the value of the page variable used to retrieve the last page.
75
+ */
76
+ lastPageParam: Variables[PageParamName],
77
+ /**
78
+ * the values of the page variable for requesting each of the pages.
79
+ */
80
+ allPageParams: Variables[PageParamName][],
81
+ }) => Variables[PageParamName] | undefined | null,
82
+ }
83
+
31
84
  export interface FullOperationConfig<
32
85
  Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result
33
86
  > extends OperationConfig<Args, Result> {
87
+ /**
88
+ * The name of the API where this operation is called: used for composing a query key.
89
+ */
34
90
  apiName: string,
35
91
  }
36
92
 
93
+ export type InfiniteQueryConfig<
94
+ Variables extends Record<string, any>,
95
+ Result,
96
+ PageParamName extends keyof Variables,
97
+ Accumulator extends keyof Result | '',
98
+ > = OperationConfig<[AbortSignal, Variables], Result> & InfiniteQueryOptions<Variables, Result, PageParamName, Accumulator>
99
+
37
100
  export interface OperationObject<Variables> {
101
+ /**
102
+ * Checks if this operation is allowed for the user currently logged in.
103
+ * @param variables the variables for the operation, if a required url parameter is not provided, it's replaced by a default string.
104
+ * @returns a promise that resolves to true if the operation is allowed or false otherwise.
105
+ * @throws `StackspotAPIError`
106
+ */
38
107
  isAllowed: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => Promise<boolean>,
108
+ /**
109
+ * Same as `isAllowed`, but a React Hook instead.
110
+ * @param args if this operation accepts variables, the first argument should be the variables and the second the query options
111
+ * (optional). If it doesn't accept variables, the single optional argument must be the query options. Moreover, if a required url
112
+ * parameter is not provided, it's replaced by a default string.
113
+ * @returns an array with 4 items:
114
+ * - [0]: true if allowed, false if not allowed, undefined if not fetched.
115
+ * - [1]: true if pending, false otherwise.
116
+ * - [2]: a StackspotAPIError if an error happens, null or undefined otherwise.
117
+ * - [3]: the original UseQueryResult object, from ReactQuery.
118
+ */
39
119
  useAllowed: (
40
120
  ...args: Variables extends void
41
121
  ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
42
122
  : [variables?: Partial<Variables>, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
43
123
  ) => Readonly<[boolean | undefined, boolean, StackspotAPIError | null | undefined, UseQueryResult<boolean, StackspotAPIError>]>,
124
+ /**
125
+ * Gets the key for the query used to verify if this operation is allowed.
126
+ *
127
+ * Attention: invalidating this key won't clear the cache, since the `@stack-spot/opa` library doesn't support individual cache
128
+ * invalidation.
129
+ * @param args if this operation accepts variables, the first argument should be the variables and the second the query options
130
+ * (optional). If it doesn't accept variables, the single optional argument must be the query options. Moreover, if a required url
131
+ * parameter is not provided, it's replaced by a default string.
132
+ * @returns the query key.
133
+ */
44
134
  getPermissionKey: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => any[],
135
+ /**
136
+ * Cancels the request if it hasn't finished yet.
137
+ *
138
+ * A canceled request will fail by throwing a `CanceledError`.
139
+ *
140
+ * @param variables the variables for the operation. If not provided, this will cancel all requests in progress for this operation.
141
+ * @returns true if any request has been canceled, false otherwise.
142
+ */
45
143
  cancel: (variables?: Partial<Variables>) => boolean,
46
144
  }
47
145
 
48
146
  export interface QueryObject<Variables, Result> extends OperationObject<Variables> {
147
+ /**
148
+ * Runs the query operation.
149
+ * @param variables the request variables if this query accepts variables.
150
+ * @returns a Promise with the response data
151
+ * @throws `StackspotAPIError`
152
+ */
49
153
  query: (...args: Variables extends void ? [] : [variables: Variables]) => Promise<Result>,
154
+ /**
155
+ * Same as `query`, but, instead of an async function, this is a React Hook that expects a React Suspense environment.
156
+ *
157
+ * If you want a hook that performs a stateful query instead of relying in Suspense, call `useStatefulQuery` instead.
158
+ *
159
+ * @param args if this query accepts variables, the first argument should be the variables and the second the query options
160
+ * (optional). If it doesn't accept variables, the single optional argument must be the query options.
161
+ * @returns the response data or undefined if not fetched.
162
+ * @throws `StackspotAPIError`
163
+ * @throws `Promise<Result>`
164
+ */
50
165
  useQuery: (
51
166
  ...args: Variables extends void
52
167
  ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
53
168
  : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
54
169
  ) => Result | undefined,
170
+ /**
171
+ * Same as `query`, but a React Hook instead.
172
+ * @param args if this query accepts variables, the first argument should be the variables and the second the query options
173
+ * (optional). If it doesn't accept variables, the single optional argument must be the query options.
174
+ * @returns an array with 4 items:
175
+ * - [0]: the response data or undefined if not fetched.
176
+ * - [1]: true if pending, false otherwise.
177
+ * - [2]: a StackspotAPIError if an error happens, null or undefined otherwise.
178
+ * - [3]: the original UseQueryResult object, from ReactQuery.
179
+ */
55
180
  useStatefulQuery: (
56
181
  ...args: Variables extends void
57
182
  ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
58
183
  : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
59
184
  ) => Readonly<[Result | undefined, boolean, StackspotAPIError | null | undefined, UseQueryResult<Result, StackspotAPIError>]>,
185
+ /**
186
+ * Invalidates the cache for this query. A specific query can be invalidated by passing an argument (variables).
187
+ * @param variables if this query accepts variables, a query with specific variables can be invalidated.
188
+ * @returns a promise that resolves once the cache is invalidated.
189
+ */
60
190
  invalidate: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => Promise<void>,
191
+ /**
192
+ * Gets the key for this query.
193
+ * @param variables the variables for the query.
194
+ * @returns the query key.
195
+ */
61
196
  getKey: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => any[],
62
197
  }
63
198
 
199
+ export type UseInfiniteQueryObjectOptions = Omit<UseInfiniteQueryOptions, 'queryFn' | 'queryKey' | 'getNextPageParam' | 'initialPageParam'>
200
+
201
+ export interface InfiniteQueryObject<Variables, Result, Accumulator extends keyof Result | ''> extends QueryObject<Variables, Result> {
202
+ /**
203
+ * Builds up a list with the result of multiple pages. This relies on React Suspense for error and first-loading feedbacks.
204
+ *
205
+ * Equivalent to React Query's `useSuspenseInfiniteQuery`.
206
+ * @param variables the variables for the query.
207
+ * @param options the options for the query.
208
+ * @returns an array with 2 items:
209
+ * - [0]: the list of items.
210
+ * - [1]: the original UseInfiniteQueryResult object, from ReactQuery.
211
+ */
212
+ useInfiniteQuery: (
213
+ ...args: Partial<Variables> extends Variables
214
+ ? [variables?: Variables, options?: UseInfiniteQueryObjectOptions]
215
+ : [variables: Variables, options?: UseInfiniteQueryObjectOptions]
216
+ ) => Readonly<[
217
+ (Accumulator extends keyof Result ? Result[Accumulator] : Result) | undefined,
218
+ UseInfiniteQueryResult<InfiniteData<Result>, StackspotAPIError>,
219
+ ]>,
220
+ /**
221
+ * Builds up a list with the result of multiple pages.
222
+ *
223
+ * Equivalent to React Query's `useInfiniteQuery`.
224
+ * @param variables the variables for the query.
225
+ * @param options the options for the query.
226
+ * @returns an array with 4 items:
227
+ * - [0]: the list of items.
228
+ * - [1]: true if fetching the first page, false otherwise.
229
+ * - [2]: a StackspotAPIError if an error happens, null or undefined otherwise.
230
+ * - [3]: the original UseInfiniteQueryResult object, from ReactQuery.
231
+ */
232
+ useStatefulInfiniteQuery: (
233
+ ...args: Partial<Variables> extends Variables
234
+ ? [variables?: Variables, options?: Omit<UseInfiniteQueryOptions, 'queryFn' | 'queryKey' | 'getNextPageParam' | 'initialPageParam'>]
235
+ : [variables: Variables, options?: Omit<UseInfiniteQueryOptions, 'queryFn' | 'queryKey' | 'getNextPageParam' | 'initialPageParam'>]
236
+ ) => Readonly<[
237
+ (Accumulator extends keyof Result ? Result[Accumulator] : Result) | undefined,
238
+ boolean,
239
+ StackspotAPIError | undefined | null,
240
+ UseInfiniteQueryResult<InfiniteData<Result>, StackspotAPIError>,
241
+ ]>,
242
+ }
243
+
64
244
  export interface AutoQueryObjectParams<Variables, Result> {
65
245
  apiName: string,
66
246
  fn: ((variables: Variables, opts?: RequestOpts) => Promise<Result>) | ((opts?: RequestOpts) => Promise<Result>),
@@ -69,14 +249,30 @@ export interface AutoQueryObjectParams<Variables, Result> {
69
249
  }
70
250
 
71
251
  export interface MutationObject<Variables, Result> extends OperationObject<Variables> {
252
+ /**
253
+ * Runs the mutation and returns a promise.
254
+ * @param args the variables for the mutation.
255
+ * @returns a promise that resolves to the response's data.
256
+ */
72
257
  mutate: (...args: Variables extends void ? [] : [variables: Variables]) => Promise<Result>,
258
+ /**
259
+ * A React hook equivalent to React Query's `useMutation`. It creates a mutation function and the mutation states.
260
+ * @param options the options for `useMutation`
261
+ * @returns an array with 4 items:
262
+ * - [0]: the mutation function (equivalent to `mutateAsync` from the result of a `useMutation`).
263
+ * - [1]: true if pending, false otherwise.
264
+ * - [2]: a StackspotAPIError if an error happens, undefined otherwise.
265
+ * - [3]: the original UseMutationResult object, from ReactQuery.
266
+ *
267
+ */
73
268
  useMutation: (options?: Omit<UseMutationOptions<Result, StackspotAPIError, Variables>, 'mutationFn'>) =>
74
269
  Readonly<[
75
270
  (...args: Variables extends void
76
271
  ? [options?: MutateOptions<Result, StackspotAPIError>]
77
272
  : [variables: Variables, options?: MutateOptions<Result, StackspotAPIError, Variables>]
78
273
  ) => Promise<Result>,
79
- boolean, StackspotAPIError | undefined,
274
+ boolean,
275
+ StackspotAPIError | undefined,
80
276
  UseMutationResult<Result, StackspotAPIError, Variables>,
81
277
  ]>,
82
278
  }