lemma-sdk 0.2.28 → 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 +241 -201
- package/bin/lemma-sdk.js +108 -0
- package/dist/browser/lemma-client.js +125 -4
- package/dist/client.d.ts +2 -0
- package/dist/client.js +3 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +2 -2
- package/dist/datastore-query.d.ts +54 -0
- package/dist/datastore-query.js +157 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/namespaces/datastore.d.ts +9 -0
- package/dist/namespaces/datastore.js +13 -0
- package/dist/namespaces/records.d.ts +1 -1
- package/dist/openapi_client/index.d.ts +4 -0
- package/dist/openapi_client/index.js +1 -0
- package/dist/openapi_client/models/ConvertedArtifactResponse.d.ts +6 -0
- package/dist/openapi_client/models/ConvertedFileResponse.d.ts +10 -0
- package/dist/openapi_client/models/ConvertedFileResponse.js +1 -0
- package/dist/openapi_client/models/CreateFolderRequest.d.ts +3 -1
- package/dist/openapi_client/models/FlowRunEntity.d.ts +1 -0
- package/dist/openapi_client/models/ScheduledFlowStart.d.ts +3 -6
- package/dist/openapi_client/models/ScheduledFlowStartType.d.ts +4 -0
- package/dist/openapi_client/models/ScheduledFlowStartType.js +9 -0
- package/dist/openapi_client/models/WorkflowInstallRequest.d.ts +5 -0
- package/dist/openapi_client/models/WorkflowTimeInstallConfig.d.ts +19 -0
- package/dist/openapi_client/models/WorkflowTimeInstallConfig.js +1 -0
- package/dist/openapi_client/services/FilesService.d.ts +27 -1
- package/dist/openapi_client/services/FilesService.js +69 -1
- package/dist/openapi_client/services/WorkflowsService.d.ts +1 -1
- package/dist/openapi_client/services/WorkflowsService.js +1 -1
- package/dist/react/assistant-output.d.ts +6 -0
- package/dist/react/assistant-output.js +90 -0
- package/dist/react/index.d.ts +62 -8
- package/dist/react/index.js +31 -4
- package/dist/react/useAgentInputSchema.d.ts +19 -0
- package/dist/react/useAgentInputSchema.js +73 -0
- package/dist/react/useAgentRun.d.ts +17 -0
- package/dist/react/useAgentRun.js +56 -0
- package/dist/react/useAgentRuns.d.ts +33 -0
- package/dist/react/useAgentRuns.js +149 -0
- package/dist/react/useAssistantRun.d.ts +9 -0
- package/dist/react/useAssistantRun.js +28 -17
- package/dist/react/useAssistantSession.d.ts +5 -0
- package/dist/react/useAssistantSession.js +135 -86
- package/dist/react/useBulkRecords.d.ts +20 -0
- package/dist/react/useBulkRecords.js +65 -0
- package/dist/react/useConversation.d.ts +18 -0
- package/dist/react/useConversation.js +75 -0
- package/dist/react/useConversationMessages.d.ts +59 -0
- package/dist/react/useConversationMessages.js +167 -0
- package/dist/react/useConversations.d.ts +52 -0
- package/dist/react/useConversations.js +228 -0
- package/dist/react/useCreateRecord.d.ts +18 -0
- package/dist/react/useCreateRecord.js +51 -0
- package/dist/react/useCurrentUser.d.ts +14 -0
- package/dist/react/useCurrentUser.js +68 -0
- package/dist/react/useDeleteRecord.d.ts +21 -0
- package/dist/react/useDeleteRecord.js +52 -0
- package/dist/react/useFlowRunHistory.js +1 -5
- package/dist/react/useFlowSession.js +41 -33
- package/dist/react/useForeignKeyOptions.d.ts +31 -0
- package/dist/react/useForeignKeyOptions.js +161 -0
- 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.d.ts +18 -0
- package/dist/react/useJoinedRecords.js +80 -0
- package/dist/react/useMembers.d.ts +26 -0
- package/dist/react/useMembers.js +98 -0
- 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.d.ts +18 -0
- package/dist/react/useRecord.js +75 -0
- package/dist/react/useRecordForm.d.ts +42 -0
- package/dist/react/useRecordForm.js +221 -0
- package/dist/react/useRecordSchema.d.ts +20 -0
- package/dist/react/useRecordSchema.js +24 -0
- package/dist/react/useRecords.d.ts +20 -0
- package/dist/react/useRecords.js +146 -0
- package/dist/react/useRelatedRecords.d.ts +43 -0
- package/dist/react/useRelatedRecords.js +239 -0
- package/dist/react/useReverseRelatedRecords.d.ts +47 -0
- package/dist/react/useReverseRelatedRecords.js +235 -0
- package/dist/react/useSchemaForm.d.ts +24 -0
- package/dist/react/useSchemaForm.js +104 -0
- package/dist/react/useTable.d.ts +16 -0
- package/dist/react/useTable.js +70 -0
- package/dist/react/useTables.d.ts +26 -0
- package/dist/react/useTables.js +113 -0
- package/dist/react/useTaskSession.js +11 -22
- package/dist/react/useUpdateRecord.d.ts +21 -0
- package/dist/react/useUpdateRecord.js +55 -0
- 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.d.ts +33 -0
- package/dist/react/useWorkflowStart.js +148 -0
- package/dist/react/utils.d.ts +5 -0
- package/dist/react/utils.js +25 -0
- package/dist/record-form.d.ts +30 -0
- package/dist/record-form.js +199 -0
- package/dist/schema-form.d.ts +41 -0
- package/dist/schema-form.js +200 -0
- package/dist/types.d.ts +6 -1
- package/package.json +11 -8
- 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/styles.css +0 -2407
- /package/dist/{react/components/assistant-types.js → openapi_client/models/ConvertedArtifactResponse.js} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { DatastoreMessageResponse } from "../types.js";
|
|
3
|
+
export interface UseDeleteRecordOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
tableName: string;
|
|
7
|
+
recordId?: string | null;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
onSuccess?: (response: DatastoreMessageResponse) => void;
|
|
10
|
+
onError?: (error: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface UseDeleteRecordResult {
|
|
13
|
+
isSubmitting: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
lastMessage: string | null;
|
|
16
|
+
remove: (overrides?: {
|
|
17
|
+
recordId?: string | null;
|
|
18
|
+
}) => Promise<boolean>;
|
|
19
|
+
reset: () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function useDeleteRecord({ client, podId, tableName, recordId, enabled, onSuccess, onError, }: UseDeleteRecordOptions): UseDeleteRecordResult;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { normalizeError, resolvePodClient } from "./utils.js";
|
|
3
|
+
export function useDeleteRecord({ client, podId, tableName, recordId = null, enabled = true, onSuccess, onError, }) {
|
|
4
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
5
|
+
const [error, setError] = useState(null);
|
|
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]);
|
|
11
|
+
const trimmedTableName = tableName.trim();
|
|
12
|
+
const trimmedRecordId = typeof recordId === "string" ? recordId.trim() : "";
|
|
13
|
+
const isEnabled = enabled && trimmedTableName.length > 0;
|
|
14
|
+
const remove = useCallback(async (overrides = {}) => {
|
|
15
|
+
const nextRecordId = typeof overrides.recordId === "string"
|
|
16
|
+
? overrides.recordId.trim()
|
|
17
|
+
: trimmedRecordId;
|
|
18
|
+
if (!isEnabled || nextRecordId.length === 0) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
setIsSubmitting(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
25
|
+
const response = await scopedClient.records.delete(trimmedTableName, nextRecordId);
|
|
26
|
+
setLastMessage(response.message ?? "Record deleted.");
|
|
27
|
+
onSuccessRef.current?.(response);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (mutationError) {
|
|
31
|
+
const normalized = normalizeError(mutationError, "Failed to delete record.");
|
|
32
|
+
setError(normalized);
|
|
33
|
+
onErrorRef.current?.(mutationError);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
setIsSubmitting(false);
|
|
38
|
+
}
|
|
39
|
+
}, [client, isEnabled, podId, trimmedRecordId, trimmedTableName]);
|
|
40
|
+
const reset = useCallback(() => {
|
|
41
|
+
setError(null);
|
|
42
|
+
setIsSubmitting(false);
|
|
43
|
+
setLastMessage(null);
|
|
44
|
+
}, []);
|
|
45
|
+
return useMemo(() => ({
|
|
46
|
+
isSubmitting,
|
|
47
|
+
error,
|
|
48
|
+
lastMessage,
|
|
49
|
+
remove,
|
|
50
|
+
reset,
|
|
51
|
+
}), [error, isSubmitting, lastMessage, remove, reset]);
|
|
52
|
+
}
|
|
@@ -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,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import { type ForeignKeyReference } from "../datastore-query.js";
|
|
3
|
+
import type { ColumnSchema, Table } from "../types.js";
|
|
4
|
+
export interface ForeignKeyOption {
|
|
5
|
+
value: unknown;
|
|
6
|
+
label: string;
|
|
7
|
+
record: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface UseForeignKeyOptionsOptions {
|
|
10
|
+
client: LemmaClient;
|
|
11
|
+
podId?: string;
|
|
12
|
+
tableName: string;
|
|
13
|
+
columnName: string;
|
|
14
|
+
labelField?: string;
|
|
15
|
+
labelFields?: string[];
|
|
16
|
+
search?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
autoLoad?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface UseForeignKeyOptionsResult {
|
|
22
|
+
table: Table | null;
|
|
23
|
+
column: ColumnSchema | null;
|
|
24
|
+
reference: ForeignKeyReference | null;
|
|
25
|
+
labelField: string | null;
|
|
26
|
+
options: ForeignKeyOption[];
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
refresh: () => Promise<ForeignKeyOption[]>;
|
|
30
|
+
}
|
|
31
|
+
export declare function useForeignKeyOptions({ client, podId, tableName, columnName, labelField, labelFields, search, limit, enabled, autoLoad, }: UseForeignKeyOptionsOptions): UseForeignKeyOptionsResult;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { parseForeignKeyReference } from "../datastore-query.js";
|
|
3
|
+
import { normalizeError, resolvePodId } from "./utils.js";
|
|
4
|
+
const EMPTY_LABEL_FIELDS = [];
|
|
5
|
+
function readRecordValue(record, field) {
|
|
6
|
+
if (!field)
|
|
7
|
+
return undefined;
|
|
8
|
+
return record[field];
|
|
9
|
+
}
|
|
10
|
+
function pickResolvedLabelField(records, referenceColumn, explicitLabelField, explicitLabelFields) {
|
|
11
|
+
const candidates = [
|
|
12
|
+
explicitLabelField,
|
|
13
|
+
...(explicitLabelFields ?? []),
|
|
14
|
+
"name",
|
|
15
|
+
"title",
|
|
16
|
+
"label",
|
|
17
|
+
"email",
|
|
18
|
+
"slug",
|
|
19
|
+
referenceColumn,
|
|
20
|
+
"id",
|
|
21
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
if (records.some((record) => {
|
|
24
|
+
const value = record[candidate];
|
|
25
|
+
return typeof value === "string"
|
|
26
|
+
? value.trim().length > 0
|
|
27
|
+
: typeof value !== "undefined" && value !== null;
|
|
28
|
+
})) {
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function matchesSearch(record, search, fields) {
|
|
35
|
+
const normalized = search.trim().toLowerCase();
|
|
36
|
+
if (!normalized)
|
|
37
|
+
return true;
|
|
38
|
+
return fields.some((field) => {
|
|
39
|
+
const value = record[field];
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
return value.toLowerCase().includes(normalized);
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
44
|
+
return String(value).toLowerCase().includes(normalized);
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function useForeignKeyOptions({ client, podId, tableName, columnName, labelField, labelFields = EMPTY_LABEL_FIELDS, search, limit = 50, enabled = true, autoLoad = true, }) {
|
|
50
|
+
const [table, setTable] = useState(null);
|
|
51
|
+
const [column, setColumn] = useState(null);
|
|
52
|
+
const [reference, setReference] = useState(null);
|
|
53
|
+
const [resolvedLabelField, setResolvedLabelField] = useState(null);
|
|
54
|
+
const [options, setOptions] = useState([]);
|
|
55
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
56
|
+
const [error, setError] = useState(null);
|
|
57
|
+
const labelFieldsKey = useMemo(() => JSON.stringify(labelFields), [labelFields]);
|
|
58
|
+
const stableLabelFields = useMemo(() => labelFields, [labelFieldsKey]);
|
|
59
|
+
const refresh = useCallback(async (signal) => {
|
|
60
|
+
if (!enabled)
|
|
61
|
+
return [];
|
|
62
|
+
setIsLoading(true);
|
|
63
|
+
setError(null);
|
|
64
|
+
try {
|
|
65
|
+
const resolvedPodId = resolvePodId(client, podId);
|
|
66
|
+
const scopedClient = resolvedPodId === client.podId ? client : client.withPod(resolvedPodId);
|
|
67
|
+
const nextTable = await scopedClient.tables.get(tableName);
|
|
68
|
+
if (signal?.aborted)
|
|
69
|
+
return [];
|
|
70
|
+
const nextColumn = nextTable.columns.find((entry) => entry.name === columnName) ?? null;
|
|
71
|
+
const nextReference = nextColumn?.foreign_key?.references
|
|
72
|
+
? parseForeignKeyReference(nextColumn.foreign_key.references)
|
|
73
|
+
: null;
|
|
74
|
+
setTable(nextTable);
|
|
75
|
+
setColumn(nextColumn);
|
|
76
|
+
setReference(nextReference);
|
|
77
|
+
if (!nextColumn) {
|
|
78
|
+
throw new Error(`Column "${columnName}" was not found on table "${tableName}".`);
|
|
79
|
+
}
|
|
80
|
+
if (!nextReference) {
|
|
81
|
+
setResolvedLabelField(null);
|
|
82
|
+
setOptions([]);
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
const canFilterOnServer = !!labelField && !!search?.trim();
|
|
86
|
+
const response = await scopedClient.records.list(nextReference.table, {
|
|
87
|
+
limit: canFilterOnServer ? Math.max(limit, 100) : (search ? Math.max(limit * 5, 100) : limit),
|
|
88
|
+
filters: canFilterOnServer
|
|
89
|
+
? [{ field: labelField, op: "ilike", value: `%${search?.trim()}%` }]
|
|
90
|
+
: undefined,
|
|
91
|
+
});
|
|
92
|
+
if (signal?.aborted)
|
|
93
|
+
return [];
|
|
94
|
+
const records = response.items ?? [];
|
|
95
|
+
const nextResolvedLabelField = pickResolvedLabelField(records, nextReference.column, labelField, stableLabelFields);
|
|
96
|
+
const searchableFields = Array.from(new Set([nextResolvedLabelField, ...stableLabelFields, nextReference.column, "id"]
|
|
97
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0)));
|
|
98
|
+
const filteredRecords = canFilterOnServer || !search?.trim()
|
|
99
|
+
? records
|
|
100
|
+
: records.filter((record) => matchesSearch(record, search, searchableFields));
|
|
101
|
+
const nextOptions = filteredRecords
|
|
102
|
+
.slice(0, limit)
|
|
103
|
+
.map((record) => {
|
|
104
|
+
const value = readRecordValue(record, nextReference.column);
|
|
105
|
+
const labelValue = readRecordValue(record, nextResolvedLabelField);
|
|
106
|
+
return {
|
|
107
|
+
value,
|
|
108
|
+
label: typeof labelValue === "string" && labelValue.trim().length > 0
|
|
109
|
+
? labelValue
|
|
110
|
+
: String(value ?? ""),
|
|
111
|
+
record,
|
|
112
|
+
};
|
|
113
|
+
})
|
|
114
|
+
.filter((option) => typeof option.value !== "undefined" && option.value !== null);
|
|
115
|
+
setResolvedLabelField(nextResolvedLabelField);
|
|
116
|
+
setOptions(nextOptions);
|
|
117
|
+
return nextOptions;
|
|
118
|
+
}
|
|
119
|
+
catch (refreshError) {
|
|
120
|
+
if (signal?.aborted)
|
|
121
|
+
return [];
|
|
122
|
+
const normalized = normalizeError(refreshError, "Failed to load foreign key options.");
|
|
123
|
+
setError(normalized);
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (!signal?.aborted)
|
|
128
|
+
setIsLoading(false);
|
|
129
|
+
}
|
|
130
|
+
}, [client, columnName, enabled, labelField, limit, podId, search, stableLabelFields, tableName]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!enabled || !autoLoad)
|
|
133
|
+
return;
|
|
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
|
+
};
|
|
150
|
+
}, [autoLoad, enabled, refresh]);
|
|
151
|
+
return useMemo(() => ({
|
|
152
|
+
table,
|
|
153
|
+
column,
|
|
154
|
+
reference,
|
|
155
|
+
labelField: resolvedLabelField,
|
|
156
|
+
options,
|
|
157
|
+
isLoading,
|
|
158
|
+
error,
|
|
159
|
+
refresh,
|
|
160
|
+
}), [column, error, isLoading, options, reference, refresh, resolvedLabelField, table]);
|
|
161
|
+
}
|
|
@@ -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;
|