@stack-spot/portal-network 0.1.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 (186) hide show
  1. package/.generate-api.failed.json +1 -0
  2. package/dist/api/account.d.ts +2368 -0
  3. package/dist/api/account.d.ts.map +1 -0
  4. package/dist/api/account.js +1521 -0
  5. package/dist/api/account.js.map +1 -0
  6. package/dist/api/ai.d.ts +1432 -0
  7. package/dist/api/ai.d.ts.map +1 -0
  8. package/dist/api/ai.js +1342 -0
  9. package/dist/api/ai.js.map +1 -0
  10. package/dist/api/apiRuntime.d.ts +922 -0
  11. package/dist/api/apiRuntime.d.ts.map +1 -0
  12. package/dist/api/apiRuntime.js +599 -0
  13. package/dist/api/apiRuntime.js.map +1 -0
  14. package/dist/api/cloudAccount.d.ts +473 -0
  15. package/dist/api/cloudAccount.d.ts.map +1 -0
  16. package/dist/api/cloudAccount.js +300 -0
  17. package/dist/api/cloudAccount.js.map +1 -0
  18. package/dist/api/cloudServices.d.ts +1233 -0
  19. package/dist/api/cloudServices.d.ts.map +1 -0
  20. package/dist/api/cloudServices.js +715 -0
  21. package/dist/api/cloudServices.js.map +1 -0
  22. package/dist/api/insights.d.ts +123 -0
  23. package/dist/api/insights.d.ts.map +1 -0
  24. package/dist/api/insights.js +112 -0
  25. package/dist/api/insights.js.map +1 -0
  26. package/dist/api/plugin.d.ts +368 -0
  27. package/dist/api/plugin.d.ts.map +1 -0
  28. package/dist/api/plugin.js +218 -0
  29. package/dist/api/plugin.js.map +1 -0
  30. package/dist/api/serviceCatalog.d.ts +737 -0
  31. package/dist/api/serviceCatalog.d.ts.map +1 -0
  32. package/dist/api/serviceCatalog.js +611 -0
  33. package/dist/api/serviceCatalog.js.map +1 -0
  34. package/dist/api/workflows.d.ts +366 -0
  35. package/dist/api/workflows.d.ts.map +1 -0
  36. package/dist/api/workflows.js +175 -0
  37. package/dist/api/workflows.js.map +1 -0
  38. package/dist/api/workspace.js +1476 -0
  39. package/dist/api/workspace.js.map +1 -0
  40. package/dist/api/workspaceManager.d.ts +1121 -0
  41. package/dist/api/workspaceManager.d.ts.map +1 -0
  42. package/dist/api/workspaceManager.js +357 -0
  43. package/dist/api/workspaceManager.js.map +1 -0
  44. package/dist/api/workspaceSearchEngine.d.ts +93 -0
  45. package/dist/api/workspaceSearchEngine.d.ts.map +1 -0
  46. package/dist/api/workspaceSearchEngine.js +55 -0
  47. package/dist/api/workspaceSearchEngine.js.map +1 -0
  48. package/dist/apis.json +129 -0
  49. package/dist/client/account.d.ts +46 -0
  50. package/dist/client/account.d.ts.map +1 -0
  51. package/dist/client/account.js +104 -0
  52. package/dist/client/account.js.map +1 -0
  53. package/dist/error/CanceledError.d.ts +5 -0
  54. package/dist/error/CanceledError.d.ts.map +1 -0
  55. package/dist/error/CanceledError.js +7 -0
  56. package/dist/error/CanceledError.js.map +1 -0
  57. package/dist/error/DefaultAPIError.d.ts +9 -0
  58. package/dist/error/DefaultAPIError.d.ts.map +1 -0
  59. package/dist/error/DefaultAPIError.js +58 -0
  60. package/dist/error/DefaultAPIError.js.map +1 -0
  61. package/dist/error/StackspotAPIError.d.ts +19 -0
  62. package/dist/error/StackspotAPIError.d.ts.map +1 -0
  63. package/dist/error/StackspotAPIError.js +39 -0
  64. package/dist/error/StackspotAPIError.js.map +1 -0
  65. package/dist/error/dictionary/account.d.ts +55 -0
  66. package/dist/error/dictionary/account.d.ts.map +1 -0
  67. package/dist/error/dictionary/account.js +55 -0
  68. package/dist/error/dictionary/account.js.map +1 -0
  69. package/dist/error/dictionary/action.d.ts +163 -0
  70. package/dist/error/dictionary/action.d.ts.map +1 -0
  71. package/dist/error/dictionary/action.js +163 -0
  72. package/dist/error/dictionary/action.js.map +1 -0
  73. package/dist/error/dictionary/base.d.ts +21 -0
  74. package/dist/error/dictionary/base.d.ts.map +1 -0
  75. package/dist/error/dictionary/base.js +21 -0
  76. package/dist/error/dictionary/base.js.map +1 -0
  77. package/dist/error/dictionary/cnt-fields.d.ts +13 -0
  78. package/dist/error/dictionary/cnt-fields.d.ts.map +1 -0
  79. package/dist/error/dictionary/cnt-fields.js +13 -0
  80. package/dist/error/dictionary/cnt-fields.js.map +1 -0
  81. package/dist/error/dictionary/cnt.d.ts +79 -0
  82. package/dist/error/dictionary/cnt.d.ts.map +1 -0
  83. package/dist/error/dictionary/cnt.js +79 -0
  84. package/dist/error/dictionary/cnt.js.map +1 -0
  85. package/dist/error/dictionary/rte.d.ts +23 -0
  86. package/dist/error/dictionary/rte.d.ts.map +1 -0
  87. package/dist/error/dictionary/rte.js +23 -0
  88. package/dist/error/dictionary/rte.js.map +1 -0
  89. package/dist/error/dictionary/rtm.d.ts +9 -0
  90. package/dist/error/dictionary/rtm.d.ts.map +1 -0
  91. package/dist/error/dictionary/rtm.js +9 -0
  92. package/dist/error/dictionary/rtm.js.map +1 -0
  93. package/dist/error/dictionary/workspace-fields.d.ts +9 -0
  94. package/dist/error/dictionary/workspace-fields.d.ts.map +1 -0
  95. package/dist/error/dictionary/workspace-fields.js +9 -0
  96. package/dist/error/dictionary/workspace-fields.js.map +1 -0
  97. package/dist/error/dictionary/workspace.d.ts +99 -0
  98. package/dist/error/dictionary/workspace.d.ts.map +1 -0
  99. package/dist/error/dictionary/workspace.js +99 -0
  100. package/dist/error/dictionary/workspace.js.map +1 -0
  101. package/dist/index.d.ts +6 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +6 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/network/AutoMutation.d.ts +10 -0
  106. package/dist/network/AutoMutation.d.ts.map +1 -0
  107. package/dist/network/AutoMutation.js +20 -0
  108. package/dist/network/AutoMutation.js.map +1 -0
  109. package/dist/network/AutoOperation.d.ts +19 -0
  110. package/dist/network/AutoOperation.d.ts.map +1 -0
  111. package/dist/network/AutoOperation.js +99 -0
  112. package/dist/network/AutoOperation.js.map +1 -0
  113. package/dist/network/AutoQuery.d.ts +19 -0
  114. package/dist/network/AutoQuery.d.ts.map +1 -0
  115. package/dist/network/AutoQuery.js +70 -0
  116. package/dist/network/AutoQuery.js.map +1 -0
  117. package/dist/network/ManualMutation.d.ts +11 -0
  118. package/dist/network/ManualMutation.d.ts.map +1 -0
  119. package/dist/network/ManualMutation.js +32 -0
  120. package/dist/network/ManualMutation.js.map +1 -0
  121. package/dist/network/ManualOperation.d.ts +13 -0
  122. package/dist/network/ManualOperation.d.ts.map +1 -0
  123. package/dist/network/ManualOperation.js +53 -0
  124. package/dist/network/ManualOperation.js.map +1 -0
  125. package/dist/network/ManualQuery.d.ts +20 -0
  126. package/dist/network/ManualQuery.d.ts.map +1 -0
  127. package/dist/network/ManualQuery.js +77 -0
  128. package/dist/network/ManualQuery.js.map +1 -0
  129. package/dist/network/NetworkClient.d.ts +20 -0
  130. package/dist/network/NetworkClient.d.ts.map +1 -0
  131. package/dist/network/NetworkClient.js +85 -0
  132. package/dist/network/NetworkClient.js.map +1 -0
  133. package/dist/network/ReactQueryNetworkClient.d.ts +16 -0
  134. package/dist/network/ReactQueryNetworkClient.d.ts.map +1 -0
  135. package/dist/network/ReactQueryNetworkClient.js +125 -0
  136. package/dist/network/ReactQueryNetworkClient.js.map +1 -0
  137. package/dist/network/react-query-client.d.ts +3 -0
  138. package/dist/network/react-query-client.d.ts.map +1 -0
  139. package/dist/network/react-query-client.js +3 -0
  140. package/dist/network/react-query-client.js.map +1 -0
  141. package/dist/network/types.d.ts +55 -0
  142. package/dist/network/types.d.ts.map +1 -0
  143. package/dist/network/types.js +2 -0
  144. package/dist/network/types.js.map +1 -0
  145. package/package.json +56 -0
  146. package/readme.md +1 -0
  147. package/scripts/generate-apis.ts +134 -0
  148. package/src/api/account.ts +4793 -0
  149. package/src/api/ai.ts +3121 -0
  150. package/src/api/apiRuntime.ts +2029 -0
  151. package/src/api/cloudAccount.ts +1133 -0
  152. package/src/api/cloudServices.ts +2872 -0
  153. package/src/api/insights.ts +264 -0
  154. package/src/api/plugin.ts +685 -0
  155. package/src/api/serviceCatalog.ts +2908 -0
  156. package/src/api/workflows.ts +709 -0
  157. package/src/api/workspace.ts +5178 -0
  158. package/src/api/workspaceManager.ts +1516 -0
  159. package/src/api/workspaceSearchEngine.ts +153 -0
  160. package/src/apis.json +129 -0
  161. package/src/client/account.ts +52 -0
  162. package/src/error/CanceledError.ts +7 -0
  163. package/src/error/DefaultAPIError.ts +51 -0
  164. package/src/error/StackspotAPIError.ts +31 -0
  165. package/src/error/dictionary/account.ts +56 -0
  166. package/src/error/dictionary/action.ts +206 -0
  167. package/src/error/dictionary/base.ts +22 -0
  168. package/src/error/dictionary/cnt-fields.ts +14 -0
  169. package/src/error/dictionary/cnt.ts +80 -0
  170. package/src/error/dictionary/rte.ts +24 -0
  171. package/src/error/dictionary/rtm.ts +10 -0
  172. package/src/error/dictionary/workspace-fields.ts +10 -0
  173. package/src/error/dictionary/workspace.ts +100 -0
  174. package/src/index.ts +5 -0
  175. package/src/network/AutoMutation.ts +25 -0
  176. package/src/network/AutoOperation.ts +88 -0
  177. package/src/network/AutoQuery.ts +80 -0
  178. package/src/network/ManualMutation.ts +41 -0
  179. package/src/network/ManualOperation.ts +55 -0
  180. package/src/network/ManualQuery.ts +93 -0
  181. package/src/network/NetworkClient.ts +93 -0
  182. package/src/network/ReactQueryNetworkClient.ts +154 -0
  183. package/src/network/react-query-client.ts +3 -0
  184. package/src/network/types.ts +82 -0
  185. package/tsconfig.build.json +4 -0
  186. package/tsconfig.json +10 -0
@@ -0,0 +1,80 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+
3
+ import { DefinedUseQueryResult, UseQueryOptions, UseSuspenseQueryResult, useQuery, useSuspenseQuery } from '@tanstack/react-query'
4
+ import { StackspotAPIError } from '../error/StackspotAPIError'
5
+ import { AutoOperation } from './AutoOperation'
6
+ import { queryClient } from './react-query-client'
7
+ import { AutoQueryObjectParams, QueryObject } from './types'
8
+
9
+ export class AutoQuery<Variables, Result> extends AutoOperation<Variables> implements QueryObject<Variables, Result> {
10
+ /**
11
+ * Prevents the same request from being triggered more than once if subsequent calls are made before the first ends.
12
+ */
13
+ private currentRequests = new Map<Variables | undefined, Promise<Result>>()
14
+ constructor(params: AutoQueryObjectParams<Variables, Result>) {
15
+ super(params)
16
+ }
17
+
18
+ private createQueryFn(variables: Variables | undefined) {
19
+ return async () => {
20
+ if (!this.currentRequests.has(variables)) this.currentRequests.set(variables, this.callFn(variables))
21
+ const result = await this.currentRequests.get(variables)
22
+ this.currentRequests.delete(variables)
23
+ return result!
24
+ }
25
+ }
26
+
27
+ query(...[variables]: Variables extends void ? [] : [variables: Variables]) {
28
+ return queryClient.fetchQuery({
29
+ queryKey: this.getKey(variables),
30
+ queryFn: this.createQueryFn(variables),
31
+ })
32
+ }
33
+
34
+ #useQueryResult<T extends boolean>(
35
+ suspense: T,
36
+ ...args: Variables extends void
37
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
38
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
39
+ ) {
40
+ /* `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
41
+ variables and the 2nd is the RequestOpts. If it doesn't accept variables, its arity is one: it accepts only the RequestOpts.
42
+ We can use this information to determine what the type of `args` actually is at runtime. If variables are accepted, than the 1st
43
+ argument is the variables and the 2nd is the query options, otherwise, it has a single argument, which is the query options. */
44
+ const [variables, options] = this.fn.length > 1
45
+ ? args as [Variables, Omit<UseQueryOptions, 'queryFn' | 'queryKey'> | undefined]
46
+ : [undefined, args[0] as Omit<UseQueryOptions, 'queryFn' | 'queryKey'> | undefined]
47
+ const use = suspense ? useSuspenseQuery : useQuery
48
+ return use({
49
+ ...options,
50
+ queryKey: this.getKey(variables),
51
+ queryFn: this.createQueryFn(variables),
52
+ }, queryClient) as T extends true ? UseSuspenseQueryResult<Result, StackspotAPIError> : DefinedUseQueryResult<Result, StackspotAPIError>
53
+ }
54
+
55
+ useQuery(
56
+ ...args: Variables extends void
57
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
58
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
59
+ ) {
60
+ const result = this.#useQueryResult(true, ...args)
61
+ return result.data
62
+ }
63
+
64
+ useStatefulQuery(
65
+ ...args: Variables extends void
66
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
67
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
68
+ ) {
69
+ const result = this.#useQueryResult(false, ...args)
70
+ return [result.data, result.isPending, result.error as StackspotAPIError | undefined, result] as const
71
+ }
72
+
73
+ invalidate(variables?: Partial<Variables>) {
74
+ return queryClient.invalidateQueries({ queryKey: this.getKey(variables) })
75
+ }
76
+
77
+ getKey(variables?: Partial<Variables>) {
78
+ return [this.apiName, this.fn.name, variables]
79
+ }
80
+ }
@@ -0,0 +1,41 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+
3
+ import { UseMutationOptions, useMutation } from '@tanstack/react-query'
4
+ import { StackspotAPIError } from '../error/StackspotAPIError'
5
+ import { ManualOperation } from './ManualOperation'
6
+ import { queryClient } from './react-query-client'
7
+ import { FullOperationConfig, MutationObject } from './types'
8
+
9
+ export class ManualMutation<
10
+ Variables extends Record<string, any> | void,
11
+ Result
12
+ > extends ManualOperation<Variables> implements MutationObject<Variables, Result> {
13
+ constructor(config: FullOperationConfig<Variables extends void ? [AbortSignal] : [AbortSignal, Variables], Result>) {
14
+ super(config)
15
+ }
16
+
17
+ private async makeRequest(variables?: Record<string, any>) {
18
+ const abortController = new AbortController()
19
+ if (!this.abortMap.has(variables)) this.abortMap.set(variables, [])
20
+ this.abortMap.get(variables)?.push(abortController)
21
+ try {
22
+ return await this.config.request(
23
+ ...[abortController.signal, variables] as Variables extends void ? [AbortSignal] : [AbortSignal, Variables],
24
+ )
25
+ } finally {
26
+ this.abortMap.delete(variables)
27
+ }
28
+ }
29
+
30
+ mutate(...args: Variables extends void ? [] : [variables: Variables]) {
31
+ return this.makeRequest(...args)
32
+ }
33
+
34
+ useMutation(options?: Omit<UseMutationOptions<Result, StackspotAPIError, Variables>, 'mutationFn'>) {
35
+ const result = useMutation<Result, StackspotAPIError, Variables>({
36
+ ...options,
37
+ mutationFn: (...args: any) => this.makeRequest(...args),
38
+ }, queryClient)
39
+ return [result.mutateAsync as any, result.isPending, result.error as StackspotAPIError | undefined, result as any] as const
40
+ }
41
+ }
@@ -0,0 +1,55 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+
3
+ import { UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query'
4
+ import { CanceledError } from '../error/CanceledError'
5
+ import { StackspotAPIError } from '../error/StackspotAPIError'
6
+ import { queryClient } from './react-query-client'
7
+ import { FullOperationConfig, OperationObject } from './types'
8
+
9
+ export abstract class ManualOperation<Variables extends Record<string, any> | void> implements OperationObject<Variables> {
10
+ protected config: FullOperationConfig<Variables extends void ? [AbortSignal] : [AbortSignal, Variables], any>
11
+ protected abortMap = new Map<Record<string, any> | undefined, AbortController[]>()
12
+
13
+ constructor(config: FullOperationConfig<Variables extends void ? [AbortSignal] : [AbortSignal, Variables], any>) {
14
+ this.config = config
15
+ }
16
+
17
+ isAllowed(...[variables]: Variables extends void ? [] : [variables?: Partial<Variables>]) {
18
+ return queryClient.fetchQuery({
19
+ queryKey: this.getPermissionKey(variables as Variables),
20
+ // @ts-ignore the following is correct. TS can't correctly infer the conditional type here =(
21
+ queryFn: () => this.config.permission(variables),
22
+ })
23
+ }
24
+
25
+ useAllowed(
26
+ ...args: Variables extends void
27
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
28
+ : [variables?: Partial<Variables>, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
29
+ ) {
30
+ const [variables, options] = args.length === 2 ? args : [undefined, args[0]]
31
+ const result = useQuery({
32
+ ...options,
33
+ queryKey: this.getPermissionKey(variables as Variables),
34
+ // @ts-ignore the following is correct. TS can't correctly infer the conditional type here =(
35
+ queryFn: () => this.config.permission(variables),
36
+ }, queryClient) as UseQueryResult<boolean, StackspotAPIError>
37
+ return [result.data, result.isPending, result.error, result] as const
38
+ }
39
+
40
+ getPermissionKey(variables?: Partial<Variables>) {
41
+ return [this.config.apiName, `${this.config.name}.permission`, variables]
42
+ }
43
+
44
+ cancel(variables?: Partial<Variables> | undefined) {
45
+ if (variables && this.abortMap.has(variables)) {
46
+ this.abortMap.get(variables)?.forEach(c => c.abort(new CanceledError()))
47
+ return true
48
+ }
49
+ if (!variables && this.abortMap.size) {
50
+ this.abortMap.forEach(controllers => controllers.forEach(c => c.abort(new CanceledError())))
51
+ return true
52
+ }
53
+ return false
54
+ }
55
+ }
@@ -0,0 +1,93 @@
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+
3
+ import { DefinedUseQueryResult, UseQueryOptions, UseSuspenseQueryResult, useQuery, useSuspenseQuery } from '@tanstack/react-query'
4
+ import { StackspotAPIError } from '../error/StackspotAPIError'
5
+ import { ManualOperation } from './ManualOperation'
6
+ import { queryClient } from './react-query-client'
7
+ import { FullOperationConfig, QueryObject } from './types'
8
+
9
+ export class ManualQuery<
10
+ Variables extends Record<string, any> | void,
11
+ Result
12
+ > extends ManualOperation<Variables> implements QueryObject<Variables, Result> {
13
+ /**
14
+ * Prevents the same request from being triggered more than once if subsequent calls are made before the first ends.
15
+ */
16
+ private currentRequests = new Map<Record<string, any> | undefined, Promise<Result>>()
17
+
18
+ constructor(config: FullOperationConfig<Variables extends void ? [AbortSignal] : [AbortSignal, Variables], Result>) {
19
+ super(config)
20
+ }
21
+
22
+ private async makeRequest(variables: Record<string, any> | undefined) {
23
+ const abortController = new AbortController()
24
+ this.abortMap.set(variables, [abortController])
25
+ try {
26
+ return await this.config.request(
27
+ ...[abortController.signal, variables] as Variables extends void ? [AbortSignal] : [AbortSignal, Variables],
28
+ )
29
+ } finally {
30
+ this.abortMap.delete(variables)
31
+ this.currentRequests.delete(variables)
32
+ }
33
+ }
34
+
35
+ private createQueryFn(variables: Record<string, any> | undefined) {
36
+ return () => {
37
+ if (!this.currentRequests.has(variables)) this.currentRequests.set(variables, this.makeRequest(variables))
38
+ return this.currentRequests.get(variables)!
39
+ }
40
+ }
41
+
42
+ query(...[variables]: Variables extends void ? [] : [variables: Variables]) {
43
+ return queryClient.fetchQuery({
44
+ queryKey: this.getKey(variables as Variables),
45
+ queryFn: this.createQueryFn(variables),
46
+ })
47
+ }
48
+
49
+ #useQueryResult<T extends boolean>(
50
+ suspense: T,
51
+ ...args: Variables extends void
52
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
53
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
54
+ ) {
55
+ /* `this.config.request` is a function with arity 1 or 2. If it accepts variables, its arity is 2: the 1st parameter is the
56
+ AbortSignal and the 2nd is the variables. If it doesn't accept variables, its arity is one: it accepts only the AbortSignal.
57
+ We can use this information to determine what the type of `args` actually is at runtime. If variables are accepted, than the 1st
58
+ argument is the variables and the 2nd is the query options, otherwise, it has a single argument, which is the query options. */
59
+ const [variables, options] = this.config.request.length === 2 ? args : [undefined, args[0]]
60
+ const use = suspense ? useSuspenseQuery : useQuery
61
+ return use({
62
+ ...options,
63
+ queryKey: this.getKey(variables as Variables),
64
+ queryFn: this.createQueryFn(variables),
65
+ }, queryClient) as T extends true ? UseSuspenseQueryResult<Result, StackspotAPIError> : DefinedUseQueryResult<Result, StackspotAPIError>
66
+ }
67
+
68
+ useQuery(
69
+ ...args: Variables extends void
70
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
71
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
72
+ ) {
73
+ const result = this.#useQueryResult(false, ...args)
74
+ return result.data
75
+ }
76
+
77
+ useStatefulQuery(
78
+ ...args: Variables extends void
79
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
80
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
81
+ ) {
82
+ const result = this.#useQueryResult(false, ...args)
83
+ return [result.data, result.isPending, result.error as StackspotAPIError | undefined, result] as const
84
+ }
85
+
86
+ invalidate(variables?: Partial<Variables>) {
87
+ return queryClient.invalidateQueries({ queryKey: this.getKey(variables) })
88
+ }
89
+
90
+ getKey(variables?: Partial<Variables>) {
91
+ return [this.config.apiName, this.config.name, variables]
92
+ }
93
+ }
@@ -0,0 +1,93 @@
1
+ import { mergeHeaders } from '@oazapfts/runtime/headers'
2
+ import { AuthenticationError } from '@stack-spot/auth'
3
+ import { requestPermission } from '@stack-spot/opa'
4
+ import { Env, HTTPMethod, RequestOptions, RequestWithBody, SessionManager } from './types'
5
+
6
+ export class NetworkClient {
7
+ private baseURL: Record<Env, string>
8
+ private static sessionManager?: SessionManager
9
+ private static env?: Env
10
+
11
+ constructor(baseURL: Record<Env, string>) {
12
+ this.baseURL = baseURL
13
+ }
14
+
15
+ static setup(sessionManager: SessionManager, env: Env) {
16
+ NetworkClient.sessionManager = sessionManager
17
+ NetworkClient.env = env
18
+ }
19
+
20
+ private uninitializedError() {
21
+ return new Error('Please, call "NetworkClient.setup(sessionManager, env)" before attempting to make a request.')
22
+ }
23
+
24
+ protected resolveURL(path: string) {
25
+ if (!NetworkClient.env) throw this.uninitializedError()
26
+ // paths must not start with "/", otherwise, the base url will not be fully appended to it.
27
+ const fixedPath = path.replace(/^\//, '')
28
+ // the baseUrl must end with "/", otherwise, the last of its part will get replaced by the path, instead of concatenated with.
29
+ const fixedBaseUrl = this.baseURL[NetworkClient.env].replace(/([^/])$/, '$1/')
30
+ return new URL(fixedPath, fixedBaseUrl)
31
+ }
32
+
33
+ protected requestPermission(method: HTTPMethod, path: string, body?: string | object): Promise<boolean> {
34
+ return requestPermission(method, this.resolveURL(path).toString(), body)
35
+ }
36
+
37
+ private getSessionManager() {
38
+ const sessionManager = NetworkClient.sessionManager
39
+ if (!sessionManager) throw this.uninitializedError()
40
+ return sessionManager
41
+ }
42
+
43
+ private sendRequest(path: string, method: HTTPMethod, request?: RequestWithBody): Promise<Response> {
44
+ const sessionManager = this.getSessionManager()
45
+ const url = this.resolveURL(path)
46
+ 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
+ )
63
+ } catch (error) {
64
+ if (error instanceof AuthenticationError) sessionManager.endSession()
65
+ throw error
66
+ }
67
+ }
68
+
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
+
89
+ protected isFreemium() {
90
+ const sessionManager = this.getSessionManager()
91
+ return sessionManager.hasSession() && !!sessionManager.getSession().getTokenData().freemium_status
92
+ }
93
+ }
@@ -0,0 +1,154 @@
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 { AutoMutation } from './AutoMutation'
6
+ import { AutoQuery } from './AutoQuery'
7
+ import { ManualMutation } from './ManualMutation'
8
+ import { ManualQuery } from './ManualQuery'
9
+ import { NetworkClient } from './NetworkClient'
10
+ import { Env, HTTPMethod, MutationObject, OperationConfig, OperationObject, QueryObject } from './types'
11
+
12
+ const DUMMY_ULID = '01HSTZ5471CBG4AW9BFJ0VTVG9'
13
+ const PERMISSION_ERROR = 'permission-error'
14
+
15
+ export abstract class ReactQueryNetworkClient extends NetworkClient {
16
+ constructor(baseURL: Record<Env, string>, defaults: Defaults<any>) {
17
+ super(baseURL)
18
+ 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
38
+ }
39
+
40
+ #parseErrorData(error: HttpError) {
41
+ if (typeof error.data === 'string') {
42
+ try {
43
+ return JSON.parse(error.data)
44
+ } catch { /* empty */ }
45
+ }
46
+ return error.data
47
+ }
48
+
49
+ #transformError(error: any): StackspotAPIError {
50
+ if (!(error instanceof HttpError)) throw new StackspotAPIError({ status: 0, message: error?.message || `${error}`, stack: error.stack })
51
+ const data = this.#parseErrorData(error)
52
+ if (data.type === PERMISSION_ERROR) {
53
+ // eslint-disable-next-line no-console
54
+ console.error('Error while validating permissions:', data.message)
55
+ return new StackspotAPIError({
56
+ status: 500,
57
+ stack: error.stack,
58
+ message: language => language === 'pt'
59
+ ? 'Ocorreu um erro ao validar as permissões.'
60
+ : 'There was an error while validating permissions.',
61
+ })
62
+ }
63
+ return this.buildStackSpotError(error)
64
+ }
65
+
66
+ #withErrorTreatment = <Args extends any[], Result>(block: (...args: Args) => Promise<Result>): ((...args: Args) => Promise<Result>) => (
67
+ async (...args) => {
68
+ try {
69
+ return await block(...args)
70
+ } catch (error: any) {
71
+ throw this.#transformError(error)
72
+ }
73
+ }
74
+ )
75
+
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
+
83
+ protected abstract buildStackSpotError(error: HttpError): StackspotAPIError
84
+
85
+ async #onFetchPermission(input: string | URL | Request, init?: RequestInit | undefined): Promise<Response> {
86
+ const [path, method, body] = input instanceof Request
87
+ ? [input.url, input.method as HTTPMethod, input.body]
88
+ : [`${input}`, (init?.method?.toLowerCase() || 'get') as HTTPMethod, init?.body]
89
+ try {
90
+ /* We allow the dev not to pass any variable when validating a permission. For this reason, oazapfts might generate a path with
91
+ "undefined". If this happens, we must replace the undefined text with a dummy ULID. */
92
+ const fixedPath = path.replace(/\/undefined(\/|$)/g, `/${DUMMY_ULID}$1`)
93
+ const isAllowed = await this.requestPermission(method, fixedPath, body as any)
94
+ return new Response(`${isAllowed}`, { headers: { 'Content-Type': 'application/json' } })
95
+ } catch (error: any) {
96
+ return new Response(JSON.stringify({ type: PERMISSION_ERROR, message: error.message || `${error}` }), { status: 500 })
97
+ }
98
+ }
99
+
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>
103
+ protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
104
+ config: OperationConfig<Args, Result>,
105
+ ): QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>
106
+ protected query<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
107
+ config: Omit<OperationConfig<Args, Result>, 'permission'>,
108
+ ): Omit<QueryObject<Args extends [AbortSignal] ? void : Args[1], Result>, 'isAllowed' | 'useAllowed' | 'getPermissionKey'>
109
+ protected query(fnOrConfig: any): QueryObject<any, any> {
110
+ return typeof fnOrConfig === 'function'
111
+ ? new AutoQuery(
112
+ {
113
+ apiName: this.constructor.name,
114
+ fn: fnOrConfig,
115
+ onFetchPermission: (...args) => this.#onFetchPermission(...args),
116
+ transformError: error => this.#transformError(error),
117
+ },
118
+ )
119
+ : new ManualQuery({
120
+ ...fnOrConfig,
121
+ apiName: this.constructor.name,
122
+ request: this.#withErrorTreatment(fnOrConfig.request),
123
+ permission: fnOrConfig.permission ? this.#withErrorTreatment(fnOrConfig.permission) : undefined,
124
+ })
125
+ }
126
+
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>
130
+ protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
131
+ config: OperationConfig<Args, Result>,
132
+ ): MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>
133
+ protected mutation<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result>(
134
+ config: Omit<OperationConfig<Args, Result>, 'permission'>,
135
+ ): Omit<MutationObject<Args extends [AbortSignal] ? void : Args[1], Result>, keyof OperationObject<any>>
136
+ protected mutation(fnOrConfig: any): MutationObject<any, any> {
137
+ if (typeof fnOrConfig === 'function') {
138
+ return new AutoMutation(
139
+ {
140
+ apiName: this.constructor.name,
141
+ fn: fnOrConfig,
142
+ onFetchPermission: (...args) => this.#onFetchPermission(...args),
143
+ transformError: error => this.#transformError(error),
144
+ },
145
+ )
146
+ }
147
+ return new ManualMutation({
148
+ ...fnOrConfig,
149
+ apiName: this.constructor.name,
150
+ request: this.#withErrorTreatment(fnOrConfig.request),
151
+ permission: fnOrConfig.permission ? this.#withErrorTreatment(fnOrConfig.permission) : undefined,
152
+ })
153
+ }
154
+ }
@@ -0,0 +1,3 @@
1
+ import { QueryClient } from '@tanstack/react-query'
2
+
3
+ export const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 3 } } })
@@ -0,0 +1,82 @@
1
+ import type { RequestOpts } from '@oazapfts/runtime'
2
+ import { Session } from '@stack-spot/auth'
3
+ import { MutateOptions, UseMutationOptions, UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'
4
+ import { StackspotAPIError } from '../error/StackspotAPIError'
5
+
6
+ export interface SessionManager {
7
+ hasSession(): boolean,
8
+ endSession(): void,
9
+ getSession(): Session,
10
+ }
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
+ export type Env = 'dev' | 'stg' | 'prd'
22
+
23
+ export type HTTPMethod = 'post' | 'patch' | 'delete' | 'put' | 'get'
24
+
25
+ export interface OperationConfig<Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result> {
26
+ name: string,
27
+ request: (...args: Args) => Promise<Result>,
28
+ permission: (...args: Args extends [AbortSignal] ? [] : [Args[1]]) => Promise<boolean>,
29
+ }
30
+
31
+ export interface FullOperationConfig<
32
+ Args extends [AbortSignal, Record<string, any>] | [AbortSignal], Result
33
+ > extends OperationConfig<Args, Result> {
34
+ apiName: string,
35
+ }
36
+
37
+ export interface OperationObject<Variables> {
38
+ isAllowed: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => Promise<boolean>,
39
+ useAllowed: (
40
+ ...args: Variables extends void
41
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
42
+ : [variables?: Partial<Variables>, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
43
+ ) => Readonly<[boolean | undefined, boolean, StackspotAPIError | null | undefined, UseQueryResult<boolean, StackspotAPIError>]>,
44
+ getPermissionKey: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => any[],
45
+ cancel: (variables?: Partial<Variables>) => boolean,
46
+ }
47
+
48
+ export interface QueryObject<Variables, Result> extends OperationObject<Variables> {
49
+ query: (...args: Variables extends void ? [] : [variables: Variables]) => Promise<Result>,
50
+ useQuery: (
51
+ ...args: Variables extends void
52
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
53
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
54
+ ) => Result | undefined,
55
+ useStatefulQuery: (
56
+ ...args: Variables extends void
57
+ ? [options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
58
+ : [variables: Variables, options?: Omit<UseQueryOptions, 'queryFn' | 'queryKey'>]
59
+ ) => Readonly<[Result | undefined, boolean, StackspotAPIError | null | undefined, UseQueryResult<Result, StackspotAPIError>]>,
60
+ invalidate: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => Promise<void>,
61
+ getKey: (...args: Variables extends void ? [] : [variables?: Partial<Variables>]) => any[],
62
+ }
63
+
64
+ export interface AutoQueryObjectParams<Variables, Result> {
65
+ apiName: string,
66
+ fn: ((variables: Variables, opts?: RequestOpts) => Promise<Result>) | ((opts?: RequestOpts) => Promise<Result>),
67
+ onFetchPermission: typeof fetch,
68
+ transformError?: (error: any) => StackspotAPIError,
69
+ }
70
+
71
+ export interface MutationObject<Variables, Result> extends OperationObject<Variables> {
72
+ mutate: (...args: Variables extends void ? [] : [variables: Variables]) => Promise<Result>,
73
+ useMutation: (options?: Omit<UseMutationOptions<Result, StackspotAPIError, Variables>, 'mutationFn'>) =>
74
+ Readonly<[
75
+ (...args: Variables extends void
76
+ ? [options?: MutateOptions<Result, StackspotAPIError>]
77
+ : [variables: Variables, options?: MutateOptions<Result, StackspotAPIError, Variables>]
78
+ ) => Promise<Result>,
79
+ boolean, StackspotAPIError | undefined,
80
+ UseMutationResult<Result, StackspotAPIError, Variables>,
81
+ ]>,
82
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig",
3
+ "include": ["src"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "outDir": "dist",
6
+ "esModuleInterop": true,
7
+ "stripInternal": true,
8
+ },
9
+ "include": ["src", "scripts"]
10
+ }