@stigmer/react 0.0.100 → 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 (262) hide show
  1. package/agent/AgentPicker.js +1 -1
  2. package/agent/AgentPicker.js.map +1 -1
  3. package/agent/__tests__/useDefaultAgent.test.d.ts +2 -0
  4. package/agent/__tests__/useDefaultAgent.test.d.ts.map +1 -0
  5. package/agent/__tests__/useDefaultAgent.test.js +252 -0
  6. package/agent/__tests__/useDefaultAgent.test.js.map +1 -0
  7. package/agent/useAgent.d.ts +2 -0
  8. package/agent/useAgent.d.ts.map +1 -1
  9. package/agent/useAgent.js +14 -37
  10. package/agent/useAgent.js.map +1 -1
  11. package/agent/useAgentCount.d.ts +1 -1
  12. package/agent/useAgentCount.d.ts.map +1 -1
  13. package/agent/useAgentList.d.ts +2 -2
  14. package/agent/useAgentList.d.ts.map +1 -1
  15. package/agent/useDefaultAgent.d.ts +6 -2
  16. package/agent/useDefaultAgent.d.ts.map +1 -1
  17. package/agent/useDefaultAgent.js +51 -31
  18. package/agent/useDefaultAgent.js.map +1 -1
  19. package/agent-instance/useAgentInstance.d.ts +2 -0
  20. package/agent-instance/useAgentInstance.d.ts.map +1 -1
  21. package/agent-instance/useAgentInstance.js +6 -33
  22. package/agent-instance/useAgentInstance.js.map +1 -1
  23. package/agent-instance/useAgentInstanceList.d.ts +2 -0
  24. package/agent-instance/useAgentInstanceList.d.ts.map +1 -1
  25. package/agent-instance/useAgentInstanceList.js +22 -40
  26. package/agent-instance/useAgentInstanceList.js.map +1 -1
  27. package/api-key/useApiKeyList.d.ts +2 -0
  28. package/api-key/useApiKeyList.d.ts.map +1 -1
  29. package/api-key/useApiKeyList.js +3 -27
  30. package/api-key/useApiKeyList.js.map +1 -1
  31. package/composer/ComposerToolbar.d.ts +1 -5
  32. package/composer/ComposerToolbar.d.ts.map +1 -1
  33. package/composer/ComposerToolbar.js +3 -4
  34. package/composer/ComposerToolbar.js.map +1 -1
  35. package/composer/ContextChip.d.ts +1 -1
  36. package/composer/ContextChip.d.ts.map +1 -1
  37. package/composer/ContextChip.js +1 -0
  38. package/composer/ContextChip.js.map +1 -1
  39. package/composer/SessionComposer.d.ts.map +1 -1
  40. package/composer/SessionComposer.js +77 -4
  41. package/composer/SessionComposer.js.map +1 -1
  42. package/composer/icons.d.ts +1 -0
  43. package/composer/icons.d.ts.map +1 -1
  44. package/composer/icons.js +3 -0
  45. package/composer/icons.js.map +1 -1
  46. package/environment/useEnvironment.d.ts +2 -0
  47. package/environment/useEnvironment.d.ts.map +1 -1
  48. package/environment/useEnvironment.js +6 -33
  49. package/environment/useEnvironment.js.map +1 -1
  50. package/environment/useEnvironmentList.d.ts +2 -0
  51. package/environment/useEnvironmentList.d.ts.map +1 -1
  52. package/environment/useEnvironmentList.js +23 -53
  53. package/environment/useEnvironmentList.js.map +1 -1
  54. package/execution/ArtifactPreviewModal.js +1 -1
  55. package/execution/ArtifactPreviewModal.js.map +1 -1
  56. package/execution/useArtifactContent.d.ts +4 -2
  57. package/execution/useArtifactContent.d.ts.map +1 -1
  58. package/execution/useArtifactContent.js +23 -45
  59. package/execution/useArtifactContent.js.map +1 -1
  60. package/iam-policy/usePrincipalsCount.d.ts +2 -0
  61. package/iam-policy/usePrincipalsCount.d.ts.map +1 -1
  62. package/iam-policy/usePrincipalsCount.js +7 -46
  63. package/iam-policy/usePrincipalsCount.js.map +1 -1
  64. package/iam-policy/useResourceAccess.d.ts +2 -0
  65. package/iam-policy/useResourceAccess.d.ts.map +1 -1
  66. package/iam-policy/useResourceAccess.js +12 -47
  67. package/iam-policy/useResourceAccess.js.map +1 -1
  68. package/identity-provider/useIdentityProvider.d.ts +2 -0
  69. package/identity-provider/useIdentityProvider.d.ts.map +1 -1
  70. package/identity-provider/useIdentityProvider.js +3 -33
  71. package/identity-provider/useIdentityProvider.js.map +1 -1
  72. package/identity-provider/useIdentityProviderList.d.ts +2 -0
  73. package/identity-provider/useIdentityProviderList.d.ts.map +1 -1
  74. package/identity-provider/useIdentityProviderList.js +7 -33
  75. package/identity-provider/useIdentityProviderList.js.map +1 -1
  76. package/index.d.ts +2 -2
  77. package/index.d.ts.map +1 -1
  78. package/index.js +1 -1
  79. package/index.js.map +1 -1
  80. package/internal/__tests__/useFetch.test.d.ts +2 -0
  81. package/internal/__tests__/useFetch.test.d.ts.map +1 -0
  82. package/internal/__tests__/useFetch.test.js +95 -0
  83. package/internal/__tests__/useFetch.test.js.map +1 -0
  84. package/internal/useFetch.d.ts +51 -0
  85. package/internal/useFetch.d.ts.map +1 -0
  86. package/internal/useFetch.js +75 -0
  87. package/internal/useFetch.js.map +1 -0
  88. package/invitation/useInvitationPreview.d.ts +2 -0
  89. package/invitation/useInvitationPreview.d.ts.map +1 -1
  90. package/invitation/useInvitationPreview.js +5 -35
  91. package/invitation/useInvitationPreview.js.map +1 -1
  92. package/invitation/useOrgInvitations.d.ts +2 -0
  93. package/invitation/useOrgInvitations.d.ts.map +1 -1
  94. package/invitation/useOrgInvitations.js +6 -34
  95. package/invitation/useOrgInvitations.js.map +1 -1
  96. package/library/ResourceListView.d.ts +2 -2
  97. package/library/ResourceListView.d.ts.map +1 -1
  98. package/library/ResourceListView.js +1 -1
  99. package/library/ResourceListView.js.map +1 -1
  100. package/library/useDetectSkillPackage.d.ts +1 -1
  101. package/library/useDetectSkillPackage.d.ts.map +1 -1
  102. package/mcp-server/McpServerPicker.js +1 -1
  103. package/mcp-server/McpServerPicker.js.map +1 -1
  104. package/mcp-server/useMcpServer.d.ts +2 -0
  105. package/mcp-server/useMcpServer.d.ts.map +1 -1
  106. package/mcp-server/useMcpServer.js +13 -37
  107. package/mcp-server/useMcpServer.js.map +1 -1
  108. package/mcp-server/useMcpServerCount.d.ts +1 -1
  109. package/mcp-server/useMcpServerCount.d.ts.map +1 -1
  110. package/mcp-server/useMcpServerList.d.ts +2 -2
  111. package/mcp-server/useMcpServerList.d.ts.map +1 -1
  112. package/mcp-server/useOAuthGrantStatus.d.ts +2 -0
  113. package/mcp-server/useOAuthGrantStatus.d.ts.map +1 -1
  114. package/mcp-server/useOAuthGrantStatus.js +14 -61
  115. package/mcp-server/useOAuthGrantStatus.js.map +1 -1
  116. package/mcp-server/useOrgOAuthApp.d.ts +2 -0
  117. package/mcp-server/useOrgOAuthApp.d.ts.map +1 -1
  118. package/mcp-server/useOrgOAuthApp.js +19 -43
  119. package/mcp-server/useOrgOAuthApp.js.map +1 -1
  120. package/oauth-app/useOAuthAppList.d.ts +2 -0
  121. package/oauth-app/useOAuthAppList.d.ts.map +1 -1
  122. package/oauth-app/useOAuthAppList.js +6 -34
  123. package/oauth-app/useOAuthAppList.js.map +1 -1
  124. package/organization/useOrganization.d.ts +2 -0
  125. package/organization/useOrganization.d.ts.map +1 -1
  126. package/organization/useOrganization.js +3 -33
  127. package/organization/useOrganization.js.map +1 -1
  128. package/package.json +4 -4
  129. package/platform-client/usePlatformClient.d.ts +2 -0
  130. package/platform-client/usePlatformClient.d.ts.map +1 -1
  131. package/platform-client/usePlatformClient.js +3 -33
  132. package/platform-client/usePlatformClient.js.map +1 -1
  133. package/platform-client/usePlatformClientList.d.ts +2 -0
  134. package/platform-client/usePlatformClientList.d.ts.map +1 -1
  135. package/platform-client/usePlatformClientList.js +6 -34
  136. package/platform-client/usePlatformClientList.js.map +1 -1
  137. package/runner/RunnerListPanel.d.ts.map +1 -1
  138. package/runner/RunnerListPanel.js +21 -5
  139. package/runner/RunnerListPanel.js.map +1 -1
  140. package/runner/__tests__/phase.test.d.ts +2 -0
  141. package/runner/__tests__/phase.test.d.ts.map +1 -0
  142. package/runner/__tests__/phase.test.js +33 -0
  143. package/runner/__tests__/phase.test.js.map +1 -0
  144. package/runner/__tests__/useRunnerList.test.d.ts +2 -0
  145. package/runner/__tests__/useRunnerList.test.d.ts.map +1 -0
  146. package/runner/__tests__/useRunnerList.test.js +68 -0
  147. package/runner/__tests__/useRunnerList.test.js.map +1 -0
  148. package/runner/index.d.ts +3 -1
  149. package/runner/index.d.ts.map +1 -1
  150. package/runner/index.js +2 -1
  151. package/runner/index.js.map +1 -1
  152. package/runner/phase.d.ts +13 -0
  153. package/runner/phase.d.ts.map +1 -1
  154. package/runner/phase.js +15 -0
  155. package/runner/phase.js.map +1 -1
  156. package/runner/useRunnerCredential.d.ts +66 -0
  157. package/runner/useRunnerCredential.d.ts.map +1 -0
  158. package/runner/useRunnerCredential.js +50 -0
  159. package/runner/useRunnerCredential.js.map +1 -0
  160. package/runner/useRunnerList.d.ts +21 -0
  161. package/runner/useRunnerList.d.ts.map +1 -1
  162. package/runner/useRunnerList.js +9 -36
  163. package/runner/useRunnerList.js.map +1 -1
  164. package/search/useResourceCount.d.ts +2 -1
  165. package/search/useResourceCount.d.ts.map +1 -1
  166. package/search/useResourceCount.js +13 -37
  167. package/search/useResourceCount.js.map +1 -1
  168. package/search/useResourceList.d.ts +2 -1
  169. package/search/useResourceList.d.ts.map +1 -1
  170. package/search/useResourceList.js +26 -46
  171. package/search/useResourceList.js.map +1 -1
  172. package/search/useResourceSearch.d.ts +4 -2
  173. package/search/useResourceSearch.d.ts.map +1 -1
  174. package/search/useResourceSearch.js +7 -27
  175. package/search/useResourceSearch.js.map +1 -1
  176. package/session/useNewSessionFlow.d.ts.map +1 -1
  177. package/session/useNewSessionFlow.js +26 -1
  178. package/session/useNewSessionFlow.js.map +1 -1
  179. package/session/useSession.d.ts +3 -1
  180. package/session/useSession.d.ts.map +1 -1
  181. package/session/useSession.js +3 -33
  182. package/session/useSession.js.map +1 -1
  183. package/session/useSessionExecutions.d.ts +3 -1
  184. package/session/useSessionExecutions.d.ts.map +1 -1
  185. package/session/useSessionExecutions.js +6 -34
  186. package/session/useSessionExecutions.js.map +1 -1
  187. package/session/useSessionList.d.ts +5 -3
  188. package/session/useSessionList.d.ts.map +1 -1
  189. package/session/useSessionList.js +9 -31
  190. package/session/useSessionList.js.map +1 -1
  191. package/skill/SkillPicker.js +1 -1
  192. package/skill/SkillPicker.js.map +1 -1
  193. package/skill/useSkill.d.ts +2 -0
  194. package/skill/useSkill.d.ts.map +1 -1
  195. package/skill/useSkill.js +13 -37
  196. package/skill/useSkill.js.map +1 -1
  197. package/skill/useSkillCount.d.ts +1 -1
  198. package/skill/useSkillCount.d.ts.map +1 -1
  199. package/skill/useSkillList.d.ts +2 -2
  200. package/skill/useSkillList.d.ts.map +1 -1
  201. package/src/agent/AgentPicker.tsx +1 -1
  202. package/src/agent/__tests__/useDefaultAgent.test.tsx +308 -0
  203. package/src/agent/useAgent.ts +19 -41
  204. package/src/agent/useAgentCount.ts +1 -1
  205. package/src/agent/useAgentList.ts +2 -2
  206. package/src/agent/useDefaultAgent.ts +67 -35
  207. package/src/agent-instance/useAgentInstance.ts +13 -37
  208. package/src/agent-instance/useAgentInstanceList.ts +31 -47
  209. package/src/api-key/useApiKeyList.ts +9 -32
  210. package/src/composer/ComposerToolbar.tsx +1 -22
  211. package/src/composer/ContextChip.tsx +2 -1
  212. package/src/composer/SessionComposer.tsx +206 -5
  213. package/src/composer/icons.tsx +27 -0
  214. package/src/environment/useEnvironment.ts +13 -37
  215. package/src/environment/useEnvironmentList.ts +31 -58
  216. package/src/execution/ArtifactPreviewModal.tsx +2 -2
  217. package/src/execution/useArtifactContent.ts +48 -65
  218. package/src/iam-policy/usePrincipalsCount.ts +17 -53
  219. package/src/iam-policy/useResourceAccess.ts +18 -55
  220. package/src/identity-provider/useIdentityProvider.ts +9 -39
  221. package/src/identity-provider/useIdentityProviderList.ts +14 -40
  222. package/src/index.ts +4 -0
  223. package/src/internal/__tests__/useFetch.test.ts +133 -0
  224. package/src/internal/useFetch.ts +121 -0
  225. package/src/invitation/useInvitationPreview.ts +14 -40
  226. package/src/invitation/useOrgInvitations.ts +15 -41
  227. package/src/library/ResourceListView.tsx +3 -3
  228. package/src/library/useDetectSkillPackage.ts +1 -1
  229. package/src/mcp-server/McpServerPicker.tsx +1 -1
  230. package/src/mcp-server/useMcpServer.ts +17 -42
  231. package/src/mcp-server/useMcpServerCount.ts +1 -1
  232. package/src/mcp-server/useMcpServerList.ts +2 -2
  233. package/src/mcp-server/useOAuthGrantStatus.ts +31 -69
  234. package/src/mcp-server/useOrgOAuthApp.ts +34 -51
  235. package/src/oauth-app/useOAuthAppList.ts +15 -41
  236. package/src/organization/useOrganization.ts +9 -38
  237. package/src/platform-client/usePlatformClient.ts +9 -39
  238. package/src/platform-client/usePlatformClientList.ts +14 -42
  239. package/src/runner/RunnerListPanel.tsx +49 -41
  240. package/src/runner/__tests__/phase.test.ts +35 -0
  241. package/src/runner/__tests__/useRunnerList.test.tsx +96 -0
  242. package/src/runner/index.ts +7 -0
  243. package/src/runner/phase.ts +16 -0
  244. package/src/runner/useRunnerCredential.ts +89 -0
  245. package/src/runner/useRunnerList.ts +36 -44
  246. package/src/search/useResourceCount.ts +20 -48
  247. package/src/search/useResourceList.ts +40 -57
  248. package/src/search/useResourceSearch.ts +22 -43
  249. package/src/session/useNewSessionFlow.ts +35 -1
  250. package/src/session/useSession.ts +10 -39
  251. package/src/session/useSessionExecutions.ts +20 -46
  252. package/src/session/useSessionList.ts +21 -42
  253. package/src/skill/SkillPicker.tsx +1 -1
  254. package/src/skill/useSkill.ts +17 -42
  255. package/src/skill/useSkillCount.ts +1 -1
  256. package/src/skill/useSkillList.ts +2 -2
  257. package/src/usage/useOrgUsageReport.ts +18 -46
  258. package/styles.css +1 -1
  259. package/usage/useOrgUsageReport.d.ts +2 -0
  260. package/usage/useOrgUsageReport.d.ts.map +1 -1
  261. package/usage/useOrgUsageReport.js +5 -35
  262. package/usage/useOrgUsageReport.js.map +1 -1
@@ -1,10 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import { GetPrincipalsCountInputSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
6
5
  import { useStigmer } from "../hooks";
7
- import { toError } from "../internal/toError";
6
+ import { useFetch } from "../internal/useFetch";
8
7
 
9
8
  /** Return value of {@link usePrincipalsCount}. */
10
9
  export interface UsePrincipalsCountReturn {
@@ -12,6 +11,8 @@ export interface UsePrincipalsCountReturn {
12
11
  readonly count: number;
13
12
  /** `true` while the fetch is in flight. */
14
13
  readonly isLoading: boolean;
14
+ /** `true` while a background refetch is in flight and stale data is shown. */
15
+ readonly isRefetching: boolean;
15
16
  /** Error from the last failed request, or `null` when healthy. */
16
17
  readonly error: Error | null;
17
18
  /** Re-fetch the count from the server. */
@@ -41,56 +42,19 @@ export function usePrincipalsCount(
41
42
  principalKind: string = "identity_account",
42
43
  ): UsePrincipalsCountReturn {
43
44
  const stigmer = useStigmer();
44
- const [count, setCount] = useState(0);
45
- const [isLoading, setIsLoading] = useState(!!orgId);
46
- const [error, setError] = useState<Error | null>(null);
47
- const [fetchKey, setFetchKey] = useState(0);
48
45
 
49
- const [prevOrgId, setPrevOrgId] = useState(orgId);
50
- if (orgId !== prevOrgId) {
51
- setPrevOrgId(orgId);
52
- if (orgId) {
53
- setIsLoading(true);
54
- setCount(0);
55
- setError(null);
56
- } else {
57
- setIsLoading(false);
58
- setCount(0);
59
- setError(null);
60
- }
61
- }
62
-
63
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
64
-
65
- useEffect(() => {
66
- if (!orgId) return;
67
-
68
- const cancelled = { current: false };
69
-
70
- stigmer.iamPolicy
71
- .getPrincipalsCount(
72
- create(GetPrincipalsCountInputSchema, {
73
- orgId,
74
- principalKind,
75
- }),
76
- )
77
- .then(
78
- (result) => {
79
- if (cancelled.current) return;
80
- setCount(result.count);
81
- setIsLoading(false);
82
- },
83
- (err) => {
84
- if (cancelled.current) return;
85
- setError(toError(err));
86
- setIsLoading(false);
87
- },
88
- );
89
-
90
- return () => {
91
- cancelled.current = true;
92
- };
93
- }, [orgId, principalKind, stigmer, fetchKey]);
94
-
95
- return { count, isLoading, error, refetch };
46
+ const { data: count, isLoading, isRefetching, error, refetch } = useFetch(
47
+ orgId
48
+ ? () =>
49
+ stigmer.iamPolicy
50
+ .getPrincipalsCount(
51
+ create(GetPrincipalsCountInputSchema, { orgId, principalKind }),
52
+ )
53
+ .then((r) => r.count)
54
+ : null,
55
+ [orgId, principalKind, stigmer],
56
+ 0,
57
+ );
58
+
59
+ return { count, isLoading, isRefetching, error, refetch };
96
60
  }
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import type { PrincipalAccess } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
6
5
  import {
@@ -8,7 +7,7 @@ import {
8
7
  } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
9
8
  import { ApiResourceRefSchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
10
9
  import { useStigmer } from "../hooks";
11
- import { toError } from "../internal/toError";
10
+ import { useFetch } from "../internal/useFetch";
12
11
 
13
12
  /** Resource reference for the access query. */
14
13
  export interface ResourceAccessRef {
@@ -30,6 +29,8 @@ export interface UseResourceAccessReturn {
30
29
  readonly members: readonly PrincipalAccess[];
31
30
  /** `true` while the fetch is in flight. */
32
31
  readonly isLoading: boolean;
32
+ /** `true` while a background refetch is in flight and stale data is shown. */
33
+ readonly isRefetching: boolean;
33
34
  /** Error from the last failed request, or `null` when healthy. */
34
35
  readonly error: Error | null;
35
36
  /** Re-fetch the access list from the server. */
@@ -66,63 +67,25 @@ export function useResourceAccess(
66
67
  options?: UseResourceAccessOptions,
67
68
  ): UseResourceAccessReturn {
68
69
  const stigmer = useStigmer();
69
- const [members, setMembers] = useState<PrincipalAccess[]>([]);
70
- const [isLoading, setIsLoading] = useState(!!resource);
71
- const [error, setError] = useState<Error | null>(null);
72
- const [fetchKey, setFetchKey] = useState(0);
73
-
74
70
  const kind = resource?.kind ?? null;
75
71
  const id = resource?.id ?? null;
76
72
  const includeInherited = options?.includeInherited ?? false;
77
73
 
78
- const [prevKey, setPrevKey] = useState<string | null>(
79
- kind && id ? `${kind}:${id}` : null,
74
+ const { data: members, isLoading, isRefetching, error, refetch } = useFetch(
75
+ kind && id
76
+ ? () => {
77
+ const input = create(ListResourceAccessInputSchema, {
78
+ resource: create(ApiResourceRefSchema, { kind, id }),
79
+ includeInherited,
80
+ });
81
+ return stigmer.iamPolicy
82
+ .listResourceAccessByPrincipal(input)
83
+ .then((r) => [...r.entries]);
84
+ }
85
+ : null,
86
+ [kind, id, includeInherited, stigmer],
87
+ [] as PrincipalAccess[],
80
88
  );
81
- const currentKey = kind && id ? `${kind}:${id}` : null;
82
- if (currentKey !== prevKey) {
83
- setPrevKey(currentKey);
84
- if (currentKey) {
85
- setIsLoading(true);
86
- setMembers([]);
87
- setError(null);
88
- } else {
89
- setIsLoading(false);
90
- setMembers([]);
91
- setError(null);
92
- }
93
- }
94
-
95
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
96
-
97
- useEffect(() => {
98
- if (!kind || !id) return;
99
-
100
- const cancelled = { current: false };
101
-
102
- const input = create(ListResourceAccessInputSchema, {
103
- resource: create(ApiResourceRefSchema, { kind, id }),
104
- includeInherited,
105
- });
106
-
107
- stigmer.iamPolicy
108
- .listResourceAccessByPrincipal(input)
109
- .then(
110
- (result) => {
111
- if (cancelled.current) return;
112
- setMembers([...result.entries]);
113
- setIsLoading(false);
114
- },
115
- (err) => {
116
- if (cancelled.current) return;
117
- setError(toError(err));
118
- setIsLoading(false);
119
- },
120
- );
121
-
122
- return () => {
123
- cancelled.current = true;
124
- };
125
- }, [kind, id, includeInherited, stigmer, fetchKey]);
126
89
 
127
- return { members, isLoading, error, refetch };
90
+ return { members, isLoading, isRefetching, error, refetch };
128
91
  }
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import type { IdentityProvider } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
5
4
  import { useStigmer } from "../hooks";
6
- import { toError } from "../internal/toError";
5
+ import { useFetch } from "../internal/useFetch";
7
6
 
8
7
  /** Return value of {@link useIdentityProvider}. */
9
8
  export interface UseIdentityProviderReturn {
@@ -11,6 +10,8 @@ export interface UseIdentityProviderReturn {
11
10
  readonly identityProvider: IdentityProvider | null;
12
11
  /** `true` while the initial fetch or a refetch is in flight. */
13
12
  readonly isLoading: boolean;
13
+ /** `true` while a background refetch is in flight and stale data is shown. */
14
+ readonly isRefetching: boolean;
14
15
  /** Error from the last failed request, or `null` when healthy. */
15
16
  readonly error: Error | null;
16
17
  /** Discard cached data and re-fetch from the server. */
@@ -44,43 +45,12 @@ export function useIdentityProvider(
44
45
  id: string | null,
45
46
  ): UseIdentityProviderReturn {
46
47
  const stigmer = useStigmer();
47
- const [identityProvider, setIdentityProvider] =
48
- useState<IdentityProvider | null>(null);
49
- const [isLoading, setIsLoading] = useState(false);
50
- const [error, setError] = useState<Error | null>(null);
51
- const [fetchKey, setFetchKey] = useState(0);
52
48
 
53
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
49
+ const { data: identityProvider, isLoading, isRefetching, error, refetch } = useFetch(
50
+ id ? () => stigmer.identityProvider.get(id) : null,
51
+ [id, stigmer],
52
+ null as IdentityProvider | null,
53
+ );
54
54
 
55
- useEffect(() => {
56
- if (!id) {
57
- setIdentityProvider(null);
58
- setIsLoading(false);
59
- setError(null);
60
- return;
61
- }
62
-
63
- const cancelled = { current: false };
64
- setIsLoading(true);
65
- setError(null);
66
-
67
- stigmer.identityProvider.get(id).then(
68
- (result) => {
69
- if (cancelled.current) return;
70
- setIdentityProvider(result);
71
- setIsLoading(false);
72
- },
73
- (err) => {
74
- if (cancelled.current) return;
75
- setError(toError(err));
76
- setIsLoading(false);
77
- },
78
- );
79
-
80
- return () => {
81
- cancelled.current = true;
82
- };
83
- }, [id, stigmer, fetchKey]);
84
-
85
- return { identityProvider, isLoading, error, refetch };
55
+ return { identityProvider, isLoading, isRefetching, error, refetch };
86
56
  }
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import type { IdentityProvider } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/api_pb";
6
5
  import { ListIdentityProvidersByOrgInputSchema } from "@stigmer/protos/ai/stigmer/iam/identityprovider/v1/io_pb";
7
6
  import { useStigmer } from "../hooks";
8
- import { toError } from "../internal/toError";
7
+ import { useFetch } from "../internal/useFetch";
9
8
 
10
9
  /** Return value of {@link useIdentityProviderList}. */
11
10
  export interface UseIdentityProviderListReturn {
@@ -13,6 +12,8 @@ export interface UseIdentityProviderListReturn {
13
12
  readonly identityProviders: readonly IdentityProvider[];
14
13
  /** `true` while the initial fetch or a refetch is in flight. */
15
14
  readonly isLoading: boolean;
15
+ /** `true` while a background refetch is in flight and stale data is shown. */
16
+ readonly isRefetching: boolean;
16
17
  /** Error from the last failed request, or `null` when healthy. */
17
18
  readonly error: Error | null;
18
19
  /** Discard cached data and re-fetch from the server. */
@@ -57,44 +58,17 @@ export function useIdentityProviderList(
57
58
  org: string | null,
58
59
  ): UseIdentityProviderListReturn {
59
60
  const stigmer = useStigmer();
60
- const [identityProviders, setIdentityProviders] = useState<
61
- IdentityProvider[]
62
- >([]);
63
- const [isLoading, setIsLoading] = useState(false);
64
- const [error, setError] = useState<Error | null>(null);
65
- const [fetchKey, setFetchKey] = useState(0);
66
61
 
67
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
62
+ const { data: identityProviders, isLoading, isRefetching, error, refetch } = useFetch(
63
+ org
64
+ ? () =>
65
+ stigmer.identityProvider
66
+ .listByOrg(create(ListIdentityProvidersByOrgInputSchema, { org }))
67
+ .then((r) => [...r.entries])
68
+ : null,
69
+ [org, stigmer],
70
+ [] as IdentityProvider[],
71
+ );
68
72
 
69
- useEffect(() => {
70
- if (!org) {
71
- setIdentityProviders([]);
72
- setIsLoading(false);
73
- setError(null);
74
- return;
75
- }
76
-
77
- const cancelled = { current: false };
78
- setIsLoading(true);
79
- setError(null);
80
-
81
- stigmer.identityProvider.listByOrg(create(ListIdentityProvidersByOrgInputSchema, { org })).then(
82
- (result) => {
83
- if (cancelled.current) return;
84
- setIdentityProviders([...result.entries]);
85
- setIsLoading(false);
86
- },
87
- (err) => {
88
- if (cancelled.current) return;
89
- setError(toError(err));
90
- setIsLoading(false);
91
- },
92
- );
93
-
94
- return () => {
95
- cancelled.current = true;
96
- };
97
- }, [org, stigmer, fetchKey]);
98
-
99
- return { identityProviders, isLoading, error, refetch };
73
+ return { identityProviders, isLoading, isRefetching, error, refetch };
100
74
  }
package/src/index.ts CHANGED
@@ -682,6 +682,7 @@ export type {
682
682
  export {
683
683
  useRunnerList,
684
684
  useLaunchLocalRunner,
685
+ useRunnerCredential,
685
686
  useStopRunner,
686
687
  useDeleteRunner,
687
688
  RunnerPicker,
@@ -689,6 +690,7 @@ export {
689
690
  phaseLabel,
690
691
  phaseDotColor,
691
692
  isActivePhase,
693
+ isTransitionalPhase,
692
694
  PHASE_SORT_ORDER,
693
695
  } from "./runner";
694
696
  export type {
@@ -697,6 +699,8 @@ export type {
697
699
  UseLaunchLocalRunnerOptions,
698
700
  UseLaunchLocalRunnerReturn,
699
701
  LaunchLocalRunnerResult,
702
+ RunnerCredential,
703
+ UseRunnerCredentialReturn,
700
704
  StopRunnerInput,
701
705
  UseStopRunnerReturn,
702
706
  UseDeleteRunnerReturn,
@@ -0,0 +1,133 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { useFetch } from "../useFetch";
4
+
5
+ async function flush(): Promise<void> {
6
+ await act(async () => {
7
+ await Promise.resolve();
8
+ });
9
+ }
10
+
11
+ describe("useFetch — refetchInterval", () => {
12
+ beforeEach(() => {
13
+ vi.useFakeTimers();
14
+ });
15
+
16
+ afterEach(() => {
17
+ vi.useRealTimers();
18
+ });
19
+
20
+ it("polls at the specified interval", async () => {
21
+ let callCount = 0;
22
+ const fetchFn = vi.fn(async () => ++callCount);
23
+
24
+ const { result } = renderHook(() =>
25
+ useFetch(fetchFn, [], 0, { refetchInterval: 1000 }),
26
+ );
27
+
28
+ // Flush the initial dep-change fetch.
29
+ await flush();
30
+ expect(fetchFn).toHaveBeenCalledTimes(1);
31
+ expect(result.current.data).toBe(1);
32
+
33
+ // Advance by one interval — should trigger a poll.
34
+ await act(async () => {
35
+ await vi.advanceTimersByTimeAsync(1000);
36
+ });
37
+ expect(fetchFn).toHaveBeenCalledTimes(2);
38
+ expect(result.current.data).toBe(2);
39
+
40
+ // Another interval tick.
41
+ await act(async () => {
42
+ await vi.advanceTimersByTimeAsync(1000);
43
+ });
44
+ expect(fetchFn).toHaveBeenCalledTimes(3);
45
+ });
46
+
47
+ it("does not poll when refetchInterval is false", async () => {
48
+ const fetchFn = vi.fn(async () => "data");
49
+
50
+ renderHook(() => useFetch(fetchFn, [], "", { refetchInterval: false }));
51
+
52
+ await flush();
53
+ expect(fetchFn).toHaveBeenCalledTimes(1);
54
+
55
+ await act(async () => {
56
+ await vi.advanceTimersByTimeAsync(10_000);
57
+ });
58
+ expect(fetchFn).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it("does not poll when fetchFn is null", async () => {
62
+ const { result } = renderHook(() =>
63
+ useFetch(null, [], "init", { refetchInterval: 1000 }),
64
+ );
65
+
66
+ await act(async () => {
67
+ await vi.advanceTimersByTimeAsync(5000);
68
+ });
69
+
70
+ expect(result.current.data).toBe("init");
71
+ expect(result.current.isLoading).toBe(false);
72
+ });
73
+
74
+ it("cleans up interval on unmount", async () => {
75
+ const fetchFn = vi.fn(async () => "data");
76
+
77
+ const { unmount } = renderHook(() =>
78
+ useFetch(fetchFn, [], "", { refetchInterval: 1000 }),
79
+ );
80
+
81
+ await flush();
82
+ expect(fetchFn).toHaveBeenCalledTimes(1);
83
+
84
+ unmount();
85
+
86
+ await act(async () => {
87
+ await vi.advanceTimersByTimeAsync(5000);
88
+ });
89
+ expect(fetchFn).toHaveBeenCalledTimes(1);
90
+ });
91
+
92
+ it("skips poll tick while a fetch is in flight", async () => {
93
+ let resolveInflight: ((v: string) => void) | null = null;
94
+
95
+ const fetchFn = vi.fn(async () => "first");
96
+
97
+ const { result } = renderHook(() =>
98
+ useFetch(fetchFn, [], "", { refetchInterval: 500 }),
99
+ );
100
+
101
+ // Initial fetch resolves immediately.
102
+ await flush();
103
+ expect(result.current.data).toBe("first");
104
+ expect(fetchFn).toHaveBeenCalledTimes(1);
105
+
106
+ // Make the next fetch hang until manually resolved.
107
+ fetchFn.mockImplementation(
108
+ () =>
109
+ new Promise<string>((resolve) => {
110
+ resolveInflight = resolve;
111
+ }),
112
+ );
113
+
114
+ // First interval tick triggers a new fetch that hangs.
115
+ await act(async () => {
116
+ await vi.advanceTimersByTimeAsync(500);
117
+ });
118
+ expect(fetchFn).toHaveBeenCalledTimes(2);
119
+
120
+ // Another tick fires but fetch is still in flight — should be skipped.
121
+ await act(async () => {
122
+ await vi.advanceTimersByTimeAsync(500);
123
+ });
124
+ expect(fetchFn).toHaveBeenCalledTimes(2);
125
+
126
+ // Resolve the in-flight fetch.
127
+ await act(async () => {
128
+ resolveInflight!("second");
129
+ await Promise.resolve();
130
+ });
131
+ expect(result.current.data).toBe("second");
132
+ });
133
+ });
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import { type DependencyList, useCallback, useEffect, useRef, useState } from "react";
4
+ import { toError } from "./toError";
5
+
6
+ /** Options for {@link useFetch}. */
7
+ export interface UseFetchOptions {
8
+ /**
9
+ * Poll interval in milliseconds. When set to a positive number, the
10
+ * hook re-fetches on a timer. Set to `false` or `0` to disable.
11
+ *
12
+ * The timer is paused while a fetch is already in flight to prevent
13
+ * request piling on slow connections.
14
+ */
15
+ readonly refetchInterval?: number | false;
16
+ }
17
+
18
+ /** Return value of {@link useFetch}. */
19
+ export interface UseFetchReturn<T> {
20
+ /** The most recently resolved data, or `initialData` before the first success. */
21
+ readonly data: T;
22
+ /**
23
+ * `true` only when no data has been fetched yet (first load).
24
+ * Once data arrives, subsequent refetches keep this `false`.
25
+ */
26
+ readonly isLoading: boolean;
27
+ /**
28
+ * `true` while a background refetch is in flight and stale data is
29
+ * being shown. Always `false` during the initial load.
30
+ */
31
+ readonly isRefetching: boolean;
32
+ /** Error from the last failed request, or `null` when healthy. */
33
+ readonly error: Error | null;
34
+ /** Imperatively trigger a re-fetch. Stale data remains visible. */
35
+ readonly refetch: () => void;
36
+ }
37
+
38
+ /**
39
+ * Generic data-fetching hook with stale-while-revalidate semantics.
40
+ *
41
+ * - Pass `null` for `fetchFn` to disable fetching (idle state).
42
+ * - On the **first** fetch, `isLoading` is `true` so consumers can
43
+ * show a skeleton.
44
+ * - On **subsequent** fetches (refetch or dep change after first success),
45
+ * `isLoading` stays `false` and `isRefetching` becomes `true` —
46
+ * existing data remains visible, preventing skeleton flash.
47
+ * - On error during a refetch, stale data is preserved.
48
+ *
49
+ * @param fetchFn Async function that returns data, or `null` to skip.
50
+ * @param deps Dependency list — a new fetch fires when any dep changes.
51
+ * @param initialData Value returned before the first successful fetch.
52
+ * @param options Optional configuration (e.g. polling interval).
53
+ *
54
+ * @internal Not part of the public `@stigmer/react` API.
55
+ */
56
+ export function useFetch<T>(
57
+ fetchFn: (() => Promise<T>) | null,
58
+ deps: DependencyList,
59
+ initialData: T,
60
+ options?: UseFetchOptions,
61
+ ): UseFetchReturn<T> {
62
+ const [data, setData] = useState<T>(initialData);
63
+ const [error, setError] = useState<Error | null>(null);
64
+ const [fetchKey, setFetchKey] = useState(0);
65
+
66
+ const hasDataRef = useRef(false);
67
+ const isFetchingRef = useRef(false);
68
+ const [isFetching, setIsFetching] = useState(false);
69
+
70
+ const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
71
+
72
+ useEffect(() => {
73
+ if (!fetchFn) {
74
+ setData(initialData);
75
+ setIsFetching(false);
76
+ isFetchingRef.current = false;
77
+ setError(null);
78
+ hasDataRef.current = false;
79
+ return;
80
+ }
81
+
82
+ const cancelled = { current: false };
83
+ setIsFetching(true);
84
+ isFetchingRef.current = true;
85
+ setError(null);
86
+
87
+ fetchFn().then(
88
+ (result) => {
89
+ if (cancelled.current) return;
90
+ setData(result);
91
+ hasDataRef.current = true;
92
+ setIsFetching(false);
93
+ isFetchingRef.current = false;
94
+ },
95
+ (err) => {
96
+ if (cancelled.current) return;
97
+ setError(toError(err));
98
+ setIsFetching(false);
99
+ isFetchingRef.current = false;
100
+ },
101
+ );
102
+
103
+ return () => {
104
+ cancelled.current = true;
105
+ };
106
+ }, [...deps, fetchKey]);
107
+
108
+ const refetchInterval = options?.refetchInterval;
109
+ useEffect(() => {
110
+ if (!refetchInterval || refetchInterval <= 0 || !fetchFn) return;
111
+ const id = setInterval(() => {
112
+ if (!isFetchingRef.current) refetch();
113
+ }, refetchInterval);
114
+ return () => clearInterval(id);
115
+ }, [refetchInterval, fetchFn, refetch]);
116
+
117
+ const isLoading = isFetching && !hasDataRef.current;
118
+ const isRefetching = isFetching && hasDataRef.current;
119
+
120
+ return { data, isLoading, isRefetching, error, refetch };
121
+ }
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useEffect, useState } from "react";
4
3
  import { create } from "@bufbuild/protobuf";
5
4
  import type { InvitationPreview } from "@stigmer/protos/ai/stigmer/iam/invitation/v1/io_pb";
6
5
  import { InvitationTokenInputSchema } from "@stigmer/protos/ai/stigmer/iam/invitation/v1/io_pb";
7
6
  import { useStigmer } from "../hooks";
8
- import { toError } from "../internal/toError";
7
+ import { useFetch } from "../internal/useFetch";
9
8
 
10
9
  /** Return value of {@link useInvitationPreview}. */
11
10
  export interface UseInvitationPreviewReturn {
@@ -13,6 +12,8 @@ export interface UseInvitationPreviewReturn {
13
12
  readonly preview: InvitationPreview | null;
14
13
  /** `true` while the initial fetch or a refetch is in flight. */
15
14
  readonly isLoading: boolean;
15
+ /** `true` while a background refetch is in flight and stale data is shown. */
16
+ readonly isRefetching: boolean;
16
17
  /** Error from the last failed request, or `null` when healthy. */
17
18
  readonly error: Error | null;
18
19
  /** Discard cached data and re-fetch the preview from the server. */
@@ -60,44 +61,17 @@ export function useInvitationPreview(
60
61
  token: string | null,
61
62
  ): UseInvitationPreviewReturn {
62
63
  const stigmer = useStigmer();
63
- const [preview, setPreview] = useState<InvitationPreview | null>(null);
64
- const [isLoading, setIsLoading] = useState(false);
65
- const [error, setError] = useState<Error | null>(null);
66
- const [fetchKey, setFetchKey] = useState(0);
67
64
 
68
- const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
65
+ const { data: preview, isLoading, isRefetching, error, refetch } = useFetch(
66
+ token
67
+ ? () =>
68
+ stigmer.invitation.getByToken(
69
+ create(InvitationTokenInputSchema, { token }),
70
+ )
71
+ : null,
72
+ [token, stigmer],
73
+ null as InvitationPreview | null,
74
+ );
69
75
 
70
- useEffect(() => {
71
- if (!token) {
72
- setPreview(null);
73
- setIsLoading(false);
74
- setError(null);
75
- return;
76
- }
77
-
78
- const cancelled = { current: false };
79
- setIsLoading(true);
80
- setError(null);
81
-
82
- stigmer.invitation
83
- .getByToken(create(InvitationTokenInputSchema, { token }))
84
- .then(
85
- (result) => {
86
- if (cancelled.current) return;
87
- setPreview(result);
88
- setIsLoading(false);
89
- },
90
- (err) => {
91
- if (cancelled.current) return;
92
- setError(toError(err));
93
- setIsLoading(false);
94
- },
95
- );
96
-
97
- return () => {
98
- cancelled.current = true;
99
- };
100
- }, [token, stigmer, fetchKey]);
101
-
102
- return { preview, isLoading, error, refetch };
76
+ return { preview, isLoading, isRefetching, error, refetch };
103
77
  }