lemma-sdk 0.2.30 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -51
- package/dist/react/index.d.ts +20 -0
- package/dist/react/index.js +10 -0
- package/dist/react/useAgentInputSchema.d.ts +19 -0
- package/dist/react/useAgentInputSchema.js +73 -0
- package/dist/react/useAgentRun.js +18 -20
- package/dist/react/useAgentRuns.d.ts +33 -0
- package/dist/react/useAgentRuns.js +149 -0
- package/dist/react/useAssistantRun.js +10 -9
- package/dist/react/useAssistantSession.js +21 -25
- package/dist/react/useBulkRecords.js +9 -16
- package/dist/react/useConversation.js +24 -8
- package/dist/react/useConversations.d.ts +4 -0
- package/dist/react/useConversations.js +49 -3
- package/dist/react/useCreateRecord.js +9 -16
- package/dist/react/useCurrentUser.d.ts +14 -0
- package/dist/react/useCurrentUser.js +68 -0
- package/dist/react/useDeleteRecord.js +9 -16
- package/dist/react/useFlowRunHistory.js +1 -5
- package/dist/react/useFlowSession.js +41 -33
- package/dist/react/useForeignKeyOptions.js +26 -15
- package/dist/react/useFunctionRun.d.ts +19 -0
- package/dist/react/useFunctionRun.js +30 -0
- package/dist/react/useFunctionRuns.d.ts +33 -0
- package/dist/react/useFunctionRuns.js +149 -0
- package/dist/react/useFunctionSession.js +37 -29
- package/dist/react/useJoinedRecords.js +24 -23
- package/dist/react/useMembers.d.ts +4 -0
- package/dist/react/useMembers.js +55 -16
- package/dist/react/useOrganizationMembers.d.ts +26 -0
- package/dist/react/useOrganizationMembers.js +113 -0
- package/dist/react/usePodAccess.d.ts +22 -0
- package/dist/react/usePodAccess.js +128 -0
- package/dist/react/useRecord.js +24 -13
- package/dist/react/useRecordForm.js +1 -18
- package/dist/react/useRecords.d.ts +2 -0
- package/dist/react/useRecords.js +62 -22
- package/dist/react/useRelatedRecords.js +28 -21
- package/dist/react/useReverseRelatedRecords.js +30 -21
- package/dist/react/useSchemaForm.js +1 -13
- package/dist/react/useTable.js +24 -13
- package/dist/react/useTables.d.ts +4 -0
- package/dist/react/useTables.js +57 -15
- package/dist/react/useTaskSession.js +11 -22
- package/dist/react/useUpdateRecord.js +9 -16
- package/dist/react/useWorkflowResume.d.ts +18 -0
- package/dist/react/useWorkflowResume.js +45 -0
- package/dist/react/useWorkflowRun.d.ts +21 -0
- package/dist/react/useWorkflowRun.js +49 -0
- package/dist/react/useWorkflowRuns.d.ts +33 -0
- package/dist/react/useWorkflowRuns.js +149 -0
- package/dist/react/useWorkflowStart.js +20 -27
- package/dist/react/utils.d.ts +5 -0
- package/dist/react/utils.js +25 -0
- package/dist/types.d.ts +1 -0
- package/package.json +2 -4
- package/dist/react/components/AssistantChrome.d.ts +0 -86
- package/dist/react/components/AssistantChrome.js +0 -48
- package/dist/react/components/AssistantEmbedded.d.ts +0 -10
- package/dist/react/components/AssistantEmbedded.js +0 -15
- package/dist/react/components/AssistantExperience.d.ts +0 -96
- package/dist/react/components/AssistantExperience.js +0 -1294
- package/dist/react/components/assistant-types.d.ts +0 -80
- package/dist/react/components/assistant-types.js +0 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { normalizeError } from "./utils.js";
|
|
3
|
+
export function useCurrentUser({ client, enabled = true, autoLoad = true, }) {
|
|
4
|
+
const [user, setUser] = useState(null);
|
|
5
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
6
|
+
const [error, setError] = useState(null);
|
|
7
|
+
const refresh = useCallback(async (signal) => {
|
|
8
|
+
if (!enabled) {
|
|
9
|
+
setUser(null);
|
|
10
|
+
setError(null);
|
|
11
|
+
setIsLoading(false);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
setIsLoading(true);
|
|
15
|
+
setError(null);
|
|
16
|
+
try {
|
|
17
|
+
const nextUser = await client.users.current();
|
|
18
|
+
if (signal?.aborted)
|
|
19
|
+
return null;
|
|
20
|
+
setUser(nextUser);
|
|
21
|
+
return nextUser;
|
|
22
|
+
}
|
|
23
|
+
catch (refreshError) {
|
|
24
|
+
if (signal?.aborted)
|
|
25
|
+
return null;
|
|
26
|
+
const normalized = normalizeError(refreshError, "Failed to load current user.");
|
|
27
|
+
setError(normalized);
|
|
28
|
+
setUser(null);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
if (!signal?.aborted)
|
|
33
|
+
setIsLoading(false);
|
|
34
|
+
}
|
|
35
|
+
}, [client, enabled]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!enabled) {
|
|
38
|
+
setUser(null);
|
|
39
|
+
setError(null);
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!autoLoad)
|
|
44
|
+
return;
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
let cancelled = false;
|
|
47
|
+
(async () => {
|
|
48
|
+
try {
|
|
49
|
+
await refresh(controller.signal);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
if (!cancelled) {
|
|
53
|
+
setError(normalizeError(new Error("Failed to load current user."), "Failed to load current user."));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
return () => {
|
|
58
|
+
cancelled = true;
|
|
59
|
+
controller.abort();
|
|
60
|
+
};
|
|
61
|
+
}, [autoLoad, enabled, refresh]);
|
|
62
|
+
return useMemo(() => ({
|
|
63
|
+
user,
|
|
64
|
+
isLoading,
|
|
65
|
+
error,
|
|
66
|
+
refresh,
|
|
67
|
+
}), [error, isLoading, refresh, user]);
|
|
68
|
+
}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from "react";
|
|
2
|
-
|
|
3
|
-
if (!podId || podId === client.podId)
|
|
4
|
-
return client;
|
|
5
|
-
return client.withPod(podId);
|
|
6
|
-
}
|
|
7
|
-
function normalizeError(error, fallback) {
|
|
8
|
-
if (error instanceof Error)
|
|
9
|
-
return error;
|
|
10
|
-
return new Error(fallback);
|
|
11
|
-
}
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { normalizeError, resolvePodClient } from "./utils.js";
|
|
12
3
|
export function useDeleteRecord({ client, podId, tableName, recordId = null, enabled = true, onSuccess, onError, }) {
|
|
13
4
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
14
5
|
const [error, setError] = useState(null);
|
|
15
6
|
const [lastMessage, setLastMessage] = useState(null);
|
|
7
|
+
const onSuccessRef = useRef(onSuccess);
|
|
8
|
+
const onErrorRef = useRef(onError);
|
|
9
|
+
useEffect(() => { onSuccessRef.current = onSuccess; }, [onSuccess]);
|
|
10
|
+
useEffect(() => { onErrorRef.current = onError; }, [onError]);
|
|
16
11
|
const trimmedTableName = tableName.trim();
|
|
17
12
|
const trimmedRecordId = typeof recordId === "string" ? recordId.trim() : "";
|
|
18
13
|
const isEnabled = enabled && trimmedTableName.length > 0;
|
|
@@ -21,8 +16,6 @@ export function useDeleteRecord({ client, podId, tableName, recordId = null, ena
|
|
|
21
16
|
? overrides.recordId.trim()
|
|
22
17
|
: trimmedRecordId;
|
|
23
18
|
if (!isEnabled || nextRecordId.length === 0) {
|
|
24
|
-
const disabledError = new Error("Record deletion requires a table and record ID.");
|
|
25
|
-
setError(disabledError);
|
|
26
19
|
return false;
|
|
27
20
|
}
|
|
28
21
|
setIsSubmitting(true);
|
|
@@ -31,19 +24,19 @@ export function useDeleteRecord({ client, podId, tableName, recordId = null, ena
|
|
|
31
24
|
const scopedClient = resolvePodClient(client, podId);
|
|
32
25
|
const response = await scopedClient.records.delete(trimmedTableName, nextRecordId);
|
|
33
26
|
setLastMessage(response.message ?? "Record deleted.");
|
|
34
|
-
|
|
27
|
+
onSuccessRef.current?.(response);
|
|
35
28
|
return true;
|
|
36
29
|
}
|
|
37
30
|
catch (mutationError) {
|
|
38
31
|
const normalized = normalizeError(mutationError, "Failed to delete record.");
|
|
39
32
|
setError(normalized);
|
|
40
|
-
|
|
33
|
+
onErrorRef.current?.(mutationError);
|
|
41
34
|
return false;
|
|
42
35
|
}
|
|
43
36
|
finally {
|
|
44
37
|
setIsSubmitting(false);
|
|
45
38
|
}
|
|
46
|
-
}, [client, isEnabled,
|
|
39
|
+
}, [client, isEnabled, podId, trimmedRecordId, trimmedTableName]);
|
|
47
40
|
const reset = useCallback(() => {
|
|
48
41
|
setError(null);
|
|
49
42
|
setIsSubmitting(false);
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
|
|
3
|
-
if (error instanceof Error)
|
|
4
|
-
return error;
|
|
5
|
-
return new Error(fallback);
|
|
6
|
-
}
|
|
2
|
+
import { normalizeError } from "./utils.js";
|
|
7
3
|
export function useFlowRunHistory({ session, flowName, limit = 100, autoRefresh = true, }) {
|
|
8
4
|
const { listHistory, run: liveRun, runId: liveRunId, setRunId } = session;
|
|
9
5
|
const [runs, setRuns] = useState([]);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { isTerminalFlowStatus, normalizeRunStatus, sleep } from "../run-utils.js";
|
|
3
|
-
|
|
4
|
-
const resolved = podId ?? client.podId;
|
|
5
|
-
if (!resolved) {
|
|
6
|
-
throw new Error("podId is required. Pass podId or set it on LemmaClient.");
|
|
7
|
-
}
|
|
8
|
-
return resolved;
|
|
9
|
-
}
|
|
3
|
+
import { normalizeError, resolvePodClient, resolvePodId } from "./utils.js";
|
|
10
4
|
function resolveFlowName(base, override) {
|
|
11
5
|
const resolved = override ?? base;
|
|
12
6
|
if (!resolved) {
|
|
@@ -21,17 +15,16 @@ function resolveRunId(base, override) {
|
|
|
21
15
|
}
|
|
22
16
|
return resolved;
|
|
23
17
|
}
|
|
24
|
-
function normalizeError(error, fallback) {
|
|
25
|
-
if (error instanceof Error)
|
|
26
|
-
return error;
|
|
27
|
-
return new Error(fallback);
|
|
28
|
-
}
|
|
29
18
|
export function useFlowSession({ client, podId, flowName, runId: initialRunId = null, autoPoll = true, pollIntervalMs = 2000, onRun, onError, }) {
|
|
30
19
|
const [runId, setRunIdState] = useState(initialRunId);
|
|
31
20
|
const [run, setRun] = useState(null);
|
|
32
21
|
const [status, setStatus] = useState(undefined);
|
|
33
22
|
const [isPolling, setIsPolling] = useState(false);
|
|
34
23
|
const [error, setError] = useState(null);
|
|
24
|
+
const onRunRef = useRef(onRun);
|
|
25
|
+
const onErrorRef = useRef(onError);
|
|
26
|
+
useEffect(() => { onRunRef.current = onRun; }, [onRun]);
|
|
27
|
+
useEffect(() => { onErrorRef.current = onError; }, [onError]);
|
|
35
28
|
const setRunId = useCallback((nextRunId) => {
|
|
36
29
|
setRunIdState(nextRunId);
|
|
37
30
|
if (!nextRunId) {
|
|
@@ -49,21 +42,21 @@ export function useFlowSession({ client, podId, flowName, runId: initialRunId =
|
|
|
49
42
|
setRun(nextRun);
|
|
50
43
|
const nextStatus = normalizeRunStatus(nextRun.status);
|
|
51
44
|
setStatus(nextStatus);
|
|
52
|
-
|
|
45
|
+
onRunRef.current?.(nextRun);
|
|
53
46
|
return nextRun;
|
|
54
47
|
}
|
|
55
48
|
catch (refreshError) {
|
|
56
49
|
const normalized = normalizeError(refreshError, "Failed to fetch flow run.");
|
|
57
50
|
setError(normalized);
|
|
58
|
-
|
|
51
|
+
onErrorRef.current?.(refreshError);
|
|
59
52
|
return null;
|
|
60
53
|
}
|
|
61
|
-
}, [client,
|
|
54
|
+
}, [client, podId, runId]);
|
|
62
55
|
const listHistory = useCallback(async (options = {}) => {
|
|
63
56
|
try {
|
|
64
|
-
client
|
|
57
|
+
const scopedClient = resolvePodClient(client, resolvePodId(client, podId));
|
|
65
58
|
const name = resolveFlowName(flowName, options.flowName);
|
|
66
|
-
const response = await
|
|
59
|
+
const response = await scopedClient.workflows.runs.list(name, {
|
|
67
60
|
limit: options.limit,
|
|
68
61
|
pageToken: options.pageToken,
|
|
69
62
|
});
|
|
@@ -72,25 +65,25 @@ export function useFlowSession({ client, podId, flowName, runId: initialRunId =
|
|
|
72
65
|
catch (listError) {
|
|
73
66
|
const normalized = normalizeError(listError, "Failed to list flow runs.");
|
|
74
67
|
setError(normalized);
|
|
75
|
-
|
|
68
|
+
onErrorRef.current?.(listError);
|
|
76
69
|
return [];
|
|
77
70
|
}
|
|
78
|
-
}, [client, flowName,
|
|
71
|
+
}, [client, flowName, podId]);
|
|
79
72
|
const start = useCallback(async (options = {}) => {
|
|
80
73
|
setError(null);
|
|
81
|
-
client
|
|
74
|
+
const scopedClient = resolvePodClient(client, resolvePodId(client, podId));
|
|
82
75
|
const name = resolveFlowName(flowName, options.flowName);
|
|
83
|
-
const created = await
|
|
76
|
+
const created = await scopedClient.workflows.runs.start(name, options.inputs);
|
|
84
77
|
setRun(created);
|
|
85
78
|
setRunIdState(created.id ?? null);
|
|
86
79
|
const nextStatus = normalizeRunStatus(created.status);
|
|
87
80
|
setStatus(nextStatus);
|
|
88
|
-
|
|
81
|
+
onRunRef.current?.(created);
|
|
89
82
|
if (options.connect !== false && created.id) {
|
|
90
83
|
await refresh(created.id);
|
|
91
84
|
}
|
|
92
85
|
return created;
|
|
93
|
-
}, [client, flowName,
|
|
86
|
+
}, [client, flowName, podId, refresh]);
|
|
94
87
|
const resume = useCallback(async (options) => {
|
|
95
88
|
setError(null);
|
|
96
89
|
const resolvedPodId = resolvePodId(client, podId);
|
|
@@ -100,12 +93,12 @@ export function useFlowSession({ client, podId, flowName, runId: initialRunId =
|
|
|
100
93
|
setRunIdState(resumed.id ?? id);
|
|
101
94
|
const nextStatus = normalizeRunStatus(resumed.status);
|
|
102
95
|
setStatus(nextStatus);
|
|
103
|
-
|
|
96
|
+
onRunRef.current?.(resumed);
|
|
104
97
|
if (options.connect !== false) {
|
|
105
98
|
await refresh(resumed.id ?? id);
|
|
106
99
|
}
|
|
107
100
|
return resumed;
|
|
108
|
-
}, [client,
|
|
101
|
+
}, [client, podId, refresh, runId]);
|
|
109
102
|
const cancel = useCallback(async (explicitRunId) => {
|
|
110
103
|
try {
|
|
111
104
|
const resolvedPodId = resolvePodId(client, podId);
|
|
@@ -116,9 +109,9 @@ export function useFlowSession({ client, podId, flowName, runId: initialRunId =
|
|
|
116
109
|
catch (cancelError) {
|
|
117
110
|
const normalized = normalizeError(cancelError, "Failed to cancel flow run.");
|
|
118
111
|
setError(normalized);
|
|
119
|
-
|
|
112
|
+
onErrorRef.current?.(cancelError);
|
|
120
113
|
}
|
|
121
|
-
}, [client,
|
|
114
|
+
}, [client, podId, refresh, runId]);
|
|
122
115
|
const retry = useCallback(async (explicitRunId) => {
|
|
123
116
|
try {
|
|
124
117
|
const resolvedPodId = resolvePodId(client, podId);
|
|
@@ -129,14 +122,29 @@ export function useFlowSession({ client, podId, flowName, runId: initialRunId =
|
|
|
129
122
|
catch (retryError) {
|
|
130
123
|
const normalized = normalizeError(retryError, "Failed to retry flow run.");
|
|
131
124
|
setError(normalized);
|
|
132
|
-
|
|
125
|
+
onErrorRef.current?.(retryError);
|
|
133
126
|
}
|
|
134
|
-
}, [client,
|
|
127
|
+
}, [client, podId, refresh, runId]);
|
|
135
128
|
useEffect(() => {
|
|
136
129
|
if (!runId) {
|
|
137
130
|
return;
|
|
138
131
|
}
|
|
139
|
-
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
let cancelled = false;
|
|
134
|
+
(async () => {
|
|
135
|
+
try {
|
|
136
|
+
await refresh(runId);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
if (!cancelled) {
|
|
140
|
+
// refresh handles errors internally
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
return () => {
|
|
145
|
+
cancelled = true;
|
|
146
|
+
controller.abort();
|
|
147
|
+
};
|
|
140
148
|
}, [refresh, runId]);
|
|
141
149
|
useEffect(() => {
|
|
142
150
|
if (!autoPoll || !runId) {
|
|
@@ -170,14 +178,14 @@ export function useFlowSession({ client, podId, flowName, runId: initialRunId =
|
|
|
170
178
|
void loop().catch((pollError) => {
|
|
171
179
|
const normalized = normalizeError(pollError, "Failed while polling flow run.");
|
|
172
180
|
setError(normalized);
|
|
173
|
-
|
|
181
|
+
onErrorRef.current?.(pollError);
|
|
174
182
|
setIsPolling(false);
|
|
175
183
|
});
|
|
176
184
|
return () => {
|
|
177
185
|
active = false;
|
|
178
186
|
abortController.abort();
|
|
179
187
|
};
|
|
180
|
-
}, [autoPoll,
|
|
188
|
+
}, [autoPoll, pollIntervalMs, refresh, runId]);
|
|
181
189
|
return {
|
|
182
190
|
runId,
|
|
183
191
|
run,
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { parseForeignKeyReference } from "../datastore-query.js";
|
|
3
|
+
import { normalizeError, resolvePodId } from "./utils.js";
|
|
3
4
|
const EMPTY_LABEL_FIELDS = [];
|
|
4
|
-
function resolvePodId(client, podId) {
|
|
5
|
-
const resolved = podId ?? client.podId;
|
|
6
|
-
if (!resolved) {
|
|
7
|
-
throw new Error("podId is required. Pass podId or set it on LemmaClient.");
|
|
8
|
-
}
|
|
9
|
-
return resolved;
|
|
10
|
-
}
|
|
11
|
-
function normalizeError(error, fallback) {
|
|
12
|
-
if (error instanceof Error)
|
|
13
|
-
return error;
|
|
14
|
-
return new Error(fallback);
|
|
15
|
-
}
|
|
16
5
|
function readRecordValue(record, field) {
|
|
17
6
|
if (!field)
|
|
18
7
|
return undefined;
|
|
@@ -67,7 +56,7 @@ export function useForeignKeyOptions({ client, podId, tableName, columnName, lab
|
|
|
67
56
|
const [error, setError] = useState(null);
|
|
68
57
|
const labelFieldsKey = useMemo(() => JSON.stringify(labelFields), [labelFields]);
|
|
69
58
|
const stableLabelFields = useMemo(() => labelFields, [labelFieldsKey]);
|
|
70
|
-
const refresh = useCallback(async () => {
|
|
59
|
+
const refresh = useCallback(async (signal) => {
|
|
71
60
|
if (!enabled)
|
|
72
61
|
return [];
|
|
73
62
|
setIsLoading(true);
|
|
@@ -76,6 +65,8 @@ export function useForeignKeyOptions({ client, podId, tableName, columnName, lab
|
|
|
76
65
|
const resolvedPodId = resolvePodId(client, podId);
|
|
77
66
|
const scopedClient = resolvedPodId === client.podId ? client : client.withPod(resolvedPodId);
|
|
78
67
|
const nextTable = await scopedClient.tables.get(tableName);
|
|
68
|
+
if (signal?.aborted)
|
|
69
|
+
return [];
|
|
79
70
|
const nextColumn = nextTable.columns.find((entry) => entry.name === columnName) ?? null;
|
|
80
71
|
const nextReference = nextColumn?.foreign_key?.references
|
|
81
72
|
? parseForeignKeyReference(nextColumn.foreign_key.references)
|
|
@@ -98,6 +89,8 @@ export function useForeignKeyOptions({ client, podId, tableName, columnName, lab
|
|
|
98
89
|
? [{ field: labelField, op: "ilike", value: `%${search?.trim()}%` }]
|
|
99
90
|
: undefined,
|
|
100
91
|
});
|
|
92
|
+
if (signal?.aborted)
|
|
93
|
+
return [];
|
|
101
94
|
const records = response.items ?? [];
|
|
102
95
|
const nextResolvedLabelField = pickResolvedLabelField(records, nextReference.column, labelField, stableLabelFields);
|
|
103
96
|
const searchableFields = Array.from(new Set([nextResolvedLabelField, ...stableLabelFields, nextReference.column, "id"]
|
|
@@ -124,18 +117,36 @@ export function useForeignKeyOptions({ client, podId, tableName, columnName, lab
|
|
|
124
117
|
return nextOptions;
|
|
125
118
|
}
|
|
126
119
|
catch (refreshError) {
|
|
120
|
+
if (signal?.aborted)
|
|
121
|
+
return [];
|
|
127
122
|
const normalized = normalizeError(refreshError, "Failed to load foreign key options.");
|
|
128
123
|
setError(normalized);
|
|
129
124
|
return [];
|
|
130
125
|
}
|
|
131
126
|
finally {
|
|
132
|
-
|
|
127
|
+
if (!signal?.aborted)
|
|
128
|
+
setIsLoading(false);
|
|
133
129
|
}
|
|
134
130
|
}, [client, columnName, enabled, labelField, limit, podId, search, stableLabelFields, tableName]);
|
|
135
131
|
useEffect(() => {
|
|
136
132
|
if (!enabled || !autoLoad)
|
|
137
133
|
return;
|
|
138
|
-
|
|
134
|
+
const controller = new AbortController();
|
|
135
|
+
let cancelled = false;
|
|
136
|
+
(async () => {
|
|
137
|
+
try {
|
|
138
|
+
await refresh(controller.signal);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
if (!cancelled) {
|
|
142
|
+
setError(normalizeError(new Error("Failed to load foreign key options."), "Failed to load foreign key options."));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
return () => {
|
|
147
|
+
cancelled = true;
|
|
148
|
+
controller.abort();
|
|
149
|
+
};
|
|
139
150
|
}, [autoLoad, enabled, refresh]);
|
|
140
151
|
return useMemo(() => ({
|
|
141
152
|
table,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FunctionRun } from "../types.js";
|
|
2
|
+
import { type UseFunctionSessionOptions, type UseFunctionSessionResult } from "./useFunctionSession.js";
|
|
3
|
+
export interface UseFunctionRunOptions extends UseFunctionSessionOptions {
|
|
4
|
+
}
|
|
5
|
+
export interface UseFunctionRunResult extends Omit<UseFunctionSessionResult, "start" | "listHistory"> {
|
|
6
|
+
output: FunctionRun["output_data"];
|
|
7
|
+
finalOutput: FunctionRun["output_data"];
|
|
8
|
+
isFinished: boolean;
|
|
9
|
+
start: (input?: Record<string, unknown>, options?: {
|
|
10
|
+
functionName?: string;
|
|
11
|
+
connect?: boolean;
|
|
12
|
+
}) => Promise<FunctionRun>;
|
|
13
|
+
listRuns: (options?: {
|
|
14
|
+
functionName?: string;
|
|
15
|
+
limit?: number;
|
|
16
|
+
pageToken?: string;
|
|
17
|
+
}) => Promise<FunctionRun[]>;
|
|
18
|
+
}
|
|
19
|
+
export declare function useFunctionRun(options: UseFunctionRunOptions): UseFunctionRunResult;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { isTerminalFunctionStatus, normalizeRunStatus } from "../run-utils.js";
|
|
3
|
+
import { useFunctionSession, } from "./useFunctionSession.js";
|
|
4
|
+
export function useFunctionRun(options) {
|
|
5
|
+
const session = useFunctionSession(options);
|
|
6
|
+
const start = useCallback(async (input, startOptions = {}) => {
|
|
7
|
+
return session.start({
|
|
8
|
+
functionName: startOptions.functionName,
|
|
9
|
+
input,
|
|
10
|
+
connect: startOptions.connect,
|
|
11
|
+
});
|
|
12
|
+
}, [session]);
|
|
13
|
+
const listRuns = useCallback(async (listOptions = {}) => {
|
|
14
|
+
return session.listHistory(listOptions);
|
|
15
|
+
}, [session]);
|
|
16
|
+
return useMemo(() => {
|
|
17
|
+
const normalizedStatus = normalizeRunStatus(session.status);
|
|
18
|
+
const isFinished = isTerminalFunctionStatus(normalizedStatus);
|
|
19
|
+
const output = session.run?.output_data ?? null;
|
|
20
|
+
const finalOutput = isFinished ? output : null;
|
|
21
|
+
return {
|
|
22
|
+
...session,
|
|
23
|
+
output,
|
|
24
|
+
finalOutput,
|
|
25
|
+
isFinished,
|
|
26
|
+
start,
|
|
27
|
+
listRuns,
|
|
28
|
+
};
|
|
29
|
+
}, [listRuns, session, start]);
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { FunctionRun } from "../types.js";
|
|
3
|
+
export interface UseFunctionRunsOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
functionName: string;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
autoLoad?: boolean;
|
|
9
|
+
limit?: number;
|
|
10
|
+
pageToken?: string;
|
|
11
|
+
initialRunId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface UseFunctionRunsResult {
|
|
14
|
+
runs: FunctionRun[];
|
|
15
|
+
total: number;
|
|
16
|
+
nextPageToken: string | null;
|
|
17
|
+
selectedRunId: string | null;
|
|
18
|
+
effectiveSelectedRunId: string | null;
|
|
19
|
+
selectedRun: FunctionRun | null;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
isLoadingMore: boolean;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
selectRun: (runId: string | null) => void;
|
|
24
|
+
clearSelection: () => void;
|
|
25
|
+
refresh: (overrides?: {
|
|
26
|
+
limit?: number;
|
|
27
|
+
pageToken?: string;
|
|
28
|
+
}) => Promise<FunctionRun[]>;
|
|
29
|
+
loadMore: (overrides?: {
|
|
30
|
+
limit?: number;
|
|
31
|
+
}) => Promise<FunctionRun[]>;
|
|
32
|
+
}
|
|
33
|
+
export declare function useFunctionRuns({ client, podId, functionName, enabled, autoLoad, limit, pageToken, initialRunId, }: UseFunctionRunsOptions): UseFunctionRunsResult;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { normalizeError, resolvePodClient } from "./utils.js";
|
|
3
|
+
export function useFunctionRuns({ client, podId, functionName, enabled = true, autoLoad = true, limit = 100, pageToken, initialRunId = null, }) {
|
|
4
|
+
const [runs, setRuns] = useState([]);
|
|
5
|
+
const [total, setTotal] = useState(0);
|
|
6
|
+
const [nextPageToken, setNextPageToken] = useState(null);
|
|
7
|
+
const [selectedRunId, setSelectedRunId] = useState(initialRunId);
|
|
8
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
9
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const trimmedFunctionName = functionName.trim();
|
|
12
|
+
const isEnabled = enabled && trimmedFunctionName.length > 0;
|
|
13
|
+
const refresh = useCallback(async (overrides = {}, signal) => {
|
|
14
|
+
if (!isEnabled) {
|
|
15
|
+
setRuns([]);
|
|
16
|
+
setTotal(0);
|
|
17
|
+
setNextPageToken(null);
|
|
18
|
+
setError(null);
|
|
19
|
+
setIsLoading(false);
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
setIsLoading(true);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
26
|
+
const response = await scopedClient.functions.runs.list(trimmedFunctionName, {
|
|
27
|
+
limit: overrides.limit ?? limit,
|
|
28
|
+
pageToken: overrides.pageToken ?? pageToken,
|
|
29
|
+
});
|
|
30
|
+
if (signal?.aborted)
|
|
31
|
+
return [];
|
|
32
|
+
const nextRuns = response.items ?? [];
|
|
33
|
+
setRuns(nextRuns);
|
|
34
|
+
setTotal(response.total ?? nextRuns.length);
|
|
35
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
36
|
+
setSelectedRunId((current) => (current && nextRuns.some((run) => run.id === current) ? current : initialRunId));
|
|
37
|
+
return nextRuns;
|
|
38
|
+
}
|
|
39
|
+
catch (refreshError) {
|
|
40
|
+
if (signal?.aborted)
|
|
41
|
+
return [];
|
|
42
|
+
const normalized = normalizeError(refreshError, "Failed to load function runs.");
|
|
43
|
+
setError(normalized);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (!signal?.aborted)
|
|
48
|
+
setIsLoading(false);
|
|
49
|
+
}
|
|
50
|
+
}, [client, initialRunId, isEnabled, limit, pageToken, podId, trimmedFunctionName]);
|
|
51
|
+
const loadMore = useCallback(async (overrides = {}) => {
|
|
52
|
+
if (!isEnabled || !nextPageToken || isLoading || isLoadingMore) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
setIsLoadingMore(true);
|
|
56
|
+
setError(null);
|
|
57
|
+
try {
|
|
58
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
59
|
+
const response = await scopedClient.functions.runs.list(trimmedFunctionName, {
|
|
60
|
+
limit: overrides.limit ?? limit,
|
|
61
|
+
pageToken: nextPageToken,
|
|
62
|
+
});
|
|
63
|
+
const moreRuns = response.items ?? [];
|
|
64
|
+
setRuns((previous) => [...previous, ...moreRuns]);
|
|
65
|
+
setTotal(response.total ?? runs.length + moreRuns.length);
|
|
66
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
67
|
+
return moreRuns;
|
|
68
|
+
}
|
|
69
|
+
catch (loadError) {
|
|
70
|
+
const normalized = normalizeError(loadError, "Failed to load more function runs.");
|
|
71
|
+
setError(normalized);
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
setIsLoadingMore(false);
|
|
76
|
+
}
|
|
77
|
+
}, [client, isEnabled, isLoading, isLoadingMore, limit, nextPageToken, podId, trimmedFunctionName]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setSelectedRunId(initialRunId);
|
|
80
|
+
}, [initialRunId]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!isEnabled) {
|
|
83
|
+
setRuns([]);
|
|
84
|
+
setTotal(0);
|
|
85
|
+
setNextPageToken(null);
|
|
86
|
+
setError(null);
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
setIsLoadingMore(false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!autoLoad)
|
|
92
|
+
return;
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
let cancelled = false;
|
|
95
|
+
(async () => {
|
|
96
|
+
try {
|
|
97
|
+
await refresh({}, controller.signal);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
if (!cancelled) {
|
|
101
|
+
setError(normalizeError(new Error("Failed to load function runs."), "Failed to load function runs."));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
controller.abort();
|
|
108
|
+
};
|
|
109
|
+
}, [autoLoad, isEnabled, refresh]);
|
|
110
|
+
const selectRun = useCallback((runId) => {
|
|
111
|
+
setSelectedRunId(runId);
|
|
112
|
+
}, []);
|
|
113
|
+
const clearSelection = useCallback(() => {
|
|
114
|
+
setSelectedRunId(null);
|
|
115
|
+
}, []);
|
|
116
|
+
return useMemo(() => {
|
|
117
|
+
const effectiveSelectedRunId = selectedRunId ?? runs[0]?.id ?? null;
|
|
118
|
+
const selectedRun = effectiveSelectedRunId
|
|
119
|
+
? runs.find((run) => run.id === effectiveSelectedRunId) ?? null
|
|
120
|
+
: null;
|
|
121
|
+
return {
|
|
122
|
+
runs,
|
|
123
|
+
total,
|
|
124
|
+
nextPageToken,
|
|
125
|
+
selectedRunId,
|
|
126
|
+
effectiveSelectedRunId,
|
|
127
|
+
selectedRun,
|
|
128
|
+
isLoading,
|
|
129
|
+
isLoadingMore,
|
|
130
|
+
error,
|
|
131
|
+
selectRun,
|
|
132
|
+
clearSelection,
|
|
133
|
+
refresh,
|
|
134
|
+
loadMore,
|
|
135
|
+
};
|
|
136
|
+
}, [
|
|
137
|
+
clearSelection,
|
|
138
|
+
error,
|
|
139
|
+
isLoading,
|
|
140
|
+
isLoadingMore,
|
|
141
|
+
loadMore,
|
|
142
|
+
nextPageToken,
|
|
143
|
+
refresh,
|
|
144
|
+
runs,
|
|
145
|
+
selectRun,
|
|
146
|
+
selectedRunId,
|
|
147
|
+
total,
|
|
148
|
+
]);
|
|
149
|
+
}
|