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,128 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { ApiError } from "../http.js";
|
|
3
|
+
import { normalizeError, resolvePodId } from "./utils.js";
|
|
4
|
+
function isMissingAccessError(error) {
|
|
5
|
+
return error instanceof ApiError && (error.statusCode === 403 || error.statusCode === 404);
|
|
6
|
+
}
|
|
7
|
+
export function usePodAccess({ client, podId, enabled = true, autoLoad = true, }) {
|
|
8
|
+
const [status, setStatus] = useState("idle");
|
|
9
|
+
const [user, setUser] = useState(null);
|
|
10
|
+
const [member, setMember] = useState(null);
|
|
11
|
+
const [joinRequest, setJoinRequest] = useState(null);
|
|
12
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
13
|
+
const [isRequestingAccess, setIsRequestingAccess] = useState(false);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const refresh = useCallback(async () => {
|
|
16
|
+
if (!enabled) {
|
|
17
|
+
setStatus("idle");
|
|
18
|
+
setUser(null);
|
|
19
|
+
setMember(null);
|
|
20
|
+
setJoinRequest(null);
|
|
21
|
+
setError(null);
|
|
22
|
+
setIsLoading(false);
|
|
23
|
+
return "idle";
|
|
24
|
+
}
|
|
25
|
+
setStatus("checking");
|
|
26
|
+
setIsLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
try {
|
|
29
|
+
const resolvedPodId = resolvePodId(client, podId);
|
|
30
|
+
const currentUser = await client.users.current();
|
|
31
|
+
setUser(currentUser);
|
|
32
|
+
try {
|
|
33
|
+
const nextMember = await client.podMembers.get(resolvedPodId, currentUser.id);
|
|
34
|
+
setMember(nextMember);
|
|
35
|
+
setJoinRequest(null);
|
|
36
|
+
setStatus("member");
|
|
37
|
+
return "member";
|
|
38
|
+
}
|
|
39
|
+
catch (membershipError) {
|
|
40
|
+
if (!isMissingAccessError(membershipError)) {
|
|
41
|
+
throw membershipError;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
setMember(null);
|
|
45
|
+
try {
|
|
46
|
+
const request = await client.podJoinRequests.me(resolvedPodId);
|
|
47
|
+
setJoinRequest(request);
|
|
48
|
+
const nextStatus = request?.status === "PENDING" ? "pending" : "missing";
|
|
49
|
+
setStatus(nextStatus);
|
|
50
|
+
return nextStatus;
|
|
51
|
+
}
|
|
52
|
+
catch (joinRequestError) {
|
|
53
|
+
if (!isMissingAccessError(joinRequestError)) {
|
|
54
|
+
throw joinRequestError;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
setJoinRequest(null);
|
|
58
|
+
setStatus("missing");
|
|
59
|
+
return "missing";
|
|
60
|
+
}
|
|
61
|
+
catch (refreshError) {
|
|
62
|
+
const normalized = normalizeError(refreshError, "Failed to check pod access.");
|
|
63
|
+
setError(normalized);
|
|
64
|
+
setStatus("error");
|
|
65
|
+
return "error";
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
setIsLoading(false);
|
|
69
|
+
}
|
|
70
|
+
}, [client, enabled, podId]);
|
|
71
|
+
const requestAccess = useCallback(async () => {
|
|
72
|
+
setIsRequestingAccess(true);
|
|
73
|
+
setError(null);
|
|
74
|
+
try {
|
|
75
|
+
const resolvedPodId = resolvePodId(client, podId);
|
|
76
|
+
const request = await client.podJoinRequests.create(resolvedPodId);
|
|
77
|
+
setJoinRequest(request);
|
|
78
|
+
setMember(null);
|
|
79
|
+
setStatus(request.status === "PENDING" ? "pending" : "missing");
|
|
80
|
+
return request;
|
|
81
|
+
}
|
|
82
|
+
catch (requestError) {
|
|
83
|
+
const normalized = normalizeError(requestError, "Failed to request pod access.");
|
|
84
|
+
setError(normalized);
|
|
85
|
+
setStatus("error");
|
|
86
|
+
throw normalized;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
setIsRequestingAccess(false);
|
|
90
|
+
}
|
|
91
|
+
}, [client, podId]);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!enabled) {
|
|
94
|
+
setStatus("idle");
|
|
95
|
+
setUser(null);
|
|
96
|
+
setMember(null);
|
|
97
|
+
setJoinRequest(null);
|
|
98
|
+
setError(null);
|
|
99
|
+
setIsLoading(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!autoLoad)
|
|
103
|
+
return;
|
|
104
|
+
void refresh();
|
|
105
|
+
}, [autoLoad, enabled, refresh]);
|
|
106
|
+
return useMemo(() => ({
|
|
107
|
+
status,
|
|
108
|
+
hasAccess: status === "member",
|
|
109
|
+
user,
|
|
110
|
+
member,
|
|
111
|
+
joinRequest,
|
|
112
|
+
isLoading,
|
|
113
|
+
isRequestingAccess,
|
|
114
|
+
error,
|
|
115
|
+
refresh,
|
|
116
|
+
requestAccess,
|
|
117
|
+
}), [
|
|
118
|
+
error,
|
|
119
|
+
isLoading,
|
|
120
|
+
isRequestingAccess,
|
|
121
|
+
joinRequest,
|
|
122
|
+
member,
|
|
123
|
+
refresh,
|
|
124
|
+
requestAccess,
|
|
125
|
+
status,
|
|
126
|
+
user,
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
export interface UseRecordOptions {
|
|
3
|
+
client: LemmaClient;
|
|
4
|
+
podId?: string;
|
|
5
|
+
tableName: string;
|
|
6
|
+
recordId?: string | null;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
autoLoad?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UseRecordResult<TRecord extends Record<string, unknown> = Record<string, unknown>> {
|
|
11
|
+
record: TRecord | null;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
error: Error | null;
|
|
14
|
+
refresh: (overrides?: {
|
|
15
|
+
recordId?: string | null;
|
|
16
|
+
}) => Promise<TRecord | null>;
|
|
17
|
+
}
|
|
18
|
+
export declare function useRecord<TRecord extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, recordId, enabled, autoLoad, }: UseRecordOptions): UseRecordResult<TRecord>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { normalizeError, resolvePodClient } from "./utils.js";
|
|
3
|
+
export function useRecord({ client, podId, tableName, recordId = null, enabled = true, autoLoad = true, }) {
|
|
4
|
+
const [record, setRecord] = useState(null);
|
|
5
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
6
|
+
const [error, setError] = useState(null);
|
|
7
|
+
const trimmedTableName = tableName.trim();
|
|
8
|
+
const trimmedRecordId = typeof recordId === "string" ? recordId.trim() : "";
|
|
9
|
+
const isEnabled = enabled && trimmedTableName.length > 0 && trimmedRecordId.length > 0;
|
|
10
|
+
const refresh = useCallback(async (overrides = {}, signal) => {
|
|
11
|
+
const nextRecordId = typeof overrides.recordId === "string"
|
|
12
|
+
? overrides.recordId.trim()
|
|
13
|
+
: trimmedRecordId;
|
|
14
|
+
if (!enabled || trimmedTableName.length === 0 || nextRecordId.length === 0) {
|
|
15
|
+
setRecord(null);
|
|
16
|
+
setError(null);
|
|
17
|
+
setIsLoading(false);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
setIsLoading(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
try {
|
|
23
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
24
|
+
const response = await scopedClient.records.get(trimmedTableName, nextRecordId);
|
|
25
|
+
if (signal?.aborted)
|
|
26
|
+
return null;
|
|
27
|
+
const nextRecord = (response.data ?? null);
|
|
28
|
+
setRecord(nextRecord);
|
|
29
|
+
return nextRecord;
|
|
30
|
+
}
|
|
31
|
+
catch (refreshError) {
|
|
32
|
+
if (signal?.aborted)
|
|
33
|
+
return null;
|
|
34
|
+
const normalized = normalizeError(refreshError, "Failed to load record.");
|
|
35
|
+
setError(normalized);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
if (!signal?.aborted)
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
}
|
|
42
|
+
}, [client, enabled, podId, trimmedRecordId, trimmedTableName]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!isEnabled) {
|
|
45
|
+
setRecord(null);
|
|
46
|
+
setError(null);
|
|
47
|
+
setIsLoading(false);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!autoLoad)
|
|
51
|
+
return;
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
let cancelled = false;
|
|
54
|
+
(async () => {
|
|
55
|
+
try {
|
|
56
|
+
await refresh({}, controller.signal);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
if (!cancelled) {
|
|
60
|
+
setError(normalizeError(new Error("Failed to load record."), "Failed to load record."));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return () => {
|
|
65
|
+
cancelled = true;
|
|
66
|
+
controller.abort();
|
|
67
|
+
};
|
|
68
|
+
}, [autoLoad, isEnabled, refresh]);
|
|
69
|
+
return useMemo(() => ({
|
|
70
|
+
record,
|
|
71
|
+
isLoading,
|
|
72
|
+
error,
|
|
73
|
+
refresh,
|
|
74
|
+
}), [error, isLoading, record, refresh]);
|
|
75
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import { type RecordSchemaField } from "../record-form.js";
|
|
3
|
+
import type { RecordResponse } from "../types.js";
|
|
4
|
+
import { type UseRecordSchemaResult } from "./useRecordSchema.js";
|
|
5
|
+
export interface UseRecordFormOptions {
|
|
6
|
+
client: LemmaClient;
|
|
7
|
+
podId?: string;
|
|
8
|
+
tableName: string;
|
|
9
|
+
recordId?: string | null;
|
|
10
|
+
initialValues?: Record<string, unknown>;
|
|
11
|
+
mode?: "auto" | "create" | "update";
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
autoLoad?: boolean;
|
|
14
|
+
onSubmitSuccess?: (record: Record<string, unknown>, response: RecordResponse) => void;
|
|
15
|
+
onError?: (error: unknown) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface UseRecordFormResult {
|
|
18
|
+
table: UseRecordSchemaResult["table"];
|
|
19
|
+
fields: RecordSchemaField[];
|
|
20
|
+
editableFields: RecordSchemaField[];
|
|
21
|
+
defaults: Record<string, unknown>;
|
|
22
|
+
values: Record<string, unknown>;
|
|
23
|
+
baselineValues: Record<string, unknown>;
|
|
24
|
+
record: Record<string, unknown> | null;
|
|
25
|
+
fieldErrors: Record<string, string>;
|
|
26
|
+
isLoadingSchema: boolean;
|
|
27
|
+
isLoadingRecord: boolean;
|
|
28
|
+
isSubmitting: boolean;
|
|
29
|
+
isDirty: boolean;
|
|
30
|
+
error: Error | null;
|
|
31
|
+
refreshSchema: UseRecordSchemaResult["refresh"];
|
|
32
|
+
refreshRecord: () => Promise<Record<string, unknown> | null>;
|
|
33
|
+
refresh: () => Promise<void>;
|
|
34
|
+
setValue: (fieldName: string, value: unknown) => void;
|
|
35
|
+
setValues: (values: Record<string, unknown>) => void;
|
|
36
|
+
reset: (nextValues?: Record<string, unknown>) => void;
|
|
37
|
+
validate: () => boolean;
|
|
38
|
+
submit: (overrides?: {
|
|
39
|
+
mode?: "create" | "update";
|
|
40
|
+
}) => Promise<Record<string, unknown> | null>;
|
|
41
|
+
}
|
|
42
|
+
export declare function useRecordForm({ client, podId, tableName, recordId, initialValues, mode, enabled, autoLoad, onSubmitSuccess, onError, }: UseRecordFormOptions): UseRecordFormResult;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { buildRecordFormValues, buildRecordPayload, } from "../record-form.js";
|
|
3
|
+
import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
|
|
4
|
+
import { useRecordSchema, } from "./useRecordSchema.js";
|
|
5
|
+
const EMPTY_VALUES = {};
|
|
6
|
+
export function useRecordForm({ client, podId, tableName, recordId = null, initialValues = EMPTY_VALUES, mode = "auto", enabled = true, autoLoad = true, onSubmitSuccess, onError, }) {
|
|
7
|
+
const schema = useRecordSchema({
|
|
8
|
+
client,
|
|
9
|
+
podId,
|
|
10
|
+
tableName,
|
|
11
|
+
enabled,
|
|
12
|
+
autoLoad,
|
|
13
|
+
});
|
|
14
|
+
const [record, setRecord] = useState(null);
|
|
15
|
+
const [values, setValuesState] = useState({});
|
|
16
|
+
const [baselineValues, setBaselineValues] = useState({});
|
|
17
|
+
const [fieldErrors, setFieldErrors] = useState({});
|
|
18
|
+
const [recordError, setRecordError] = useState(null);
|
|
19
|
+
const [isLoadingRecord, setIsLoadingRecord] = useState(false);
|
|
20
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
21
|
+
const initialValuesKey = stringifyComparable(initialValues);
|
|
22
|
+
const stableInitialValues = useMemo(() => initialValues, [initialValuesKey]);
|
|
23
|
+
const schemaTable = schema.table;
|
|
24
|
+
const refreshSchema = schema.refresh;
|
|
25
|
+
const hydrateValues = useCallback((source) => {
|
|
26
|
+
if (!schemaTable)
|
|
27
|
+
return;
|
|
28
|
+
const nextValues = buildRecordFormValues(schemaTable, source);
|
|
29
|
+
setValuesState(nextValues);
|
|
30
|
+
setBaselineValues(nextValues);
|
|
31
|
+
setFieldErrors({});
|
|
32
|
+
}, [schemaTable]);
|
|
33
|
+
const refreshRecord = useCallback(async () => {
|
|
34
|
+
if (!enabled || !recordId) {
|
|
35
|
+
setRecord(null);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
setIsLoadingRecord(true);
|
|
39
|
+
setRecordError(null);
|
|
40
|
+
try {
|
|
41
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
42
|
+
const response = await scopedClient.records.get(tableName, recordId);
|
|
43
|
+
const nextRecord = response.data ?? null;
|
|
44
|
+
setRecord(nextRecord);
|
|
45
|
+
return nextRecord;
|
|
46
|
+
}
|
|
47
|
+
catch (refreshError) {
|
|
48
|
+
const normalized = normalizeError(refreshError, "Failed to load record.");
|
|
49
|
+
setRecordError(normalized);
|
|
50
|
+
onError?.(refreshError);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setIsLoadingRecord(false);
|
|
55
|
+
}
|
|
56
|
+
}, [client, enabled, onError, podId, recordId, tableName]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!enabled) {
|
|
59
|
+
setRecord(null);
|
|
60
|
+
setValuesState({});
|
|
61
|
+
setBaselineValues({});
|
|
62
|
+
setFieldErrors({});
|
|
63
|
+
setRecordError(null);
|
|
64
|
+
setIsLoadingRecord(false);
|
|
65
|
+
setIsSubmitting(false);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!autoLoad || !recordId)
|
|
69
|
+
return;
|
|
70
|
+
void refreshRecord();
|
|
71
|
+
}, [autoLoad, enabled, recordId, refreshRecord]);
|
|
72
|
+
const recordKey = stringifyComparable(record);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!schemaTable)
|
|
75
|
+
return;
|
|
76
|
+
const source = {
|
|
77
|
+
...(record ?? {}),
|
|
78
|
+
...stableInitialValues,
|
|
79
|
+
};
|
|
80
|
+
hydrateValues(source);
|
|
81
|
+
}, [hydrateValues, recordKey, schemaTable, stableInitialValues]);
|
|
82
|
+
const refresh = useCallback(async () => {
|
|
83
|
+
const nextTable = await refreshSchema();
|
|
84
|
+
if (recordId) {
|
|
85
|
+
const nextRecord = await refreshRecord();
|
|
86
|
+
if (nextTable && nextRecord) {
|
|
87
|
+
hydrateValues({
|
|
88
|
+
...nextRecord,
|
|
89
|
+
...stableInitialValues,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (nextTable) {
|
|
95
|
+
hydrateValues(stableInitialValues);
|
|
96
|
+
}
|
|
97
|
+
}, [hydrateValues, recordId, refreshRecord, refreshSchema, stableInitialValues]);
|
|
98
|
+
const setValue = useCallback((fieldName, value) => {
|
|
99
|
+
setValuesState((current) => ({
|
|
100
|
+
...current,
|
|
101
|
+
[fieldName]: value,
|
|
102
|
+
}));
|
|
103
|
+
setFieldErrors((current) => {
|
|
104
|
+
if (!(fieldName in current))
|
|
105
|
+
return current;
|
|
106
|
+
const next = { ...current };
|
|
107
|
+
delete next[fieldName];
|
|
108
|
+
return next;
|
|
109
|
+
});
|
|
110
|
+
}, []);
|
|
111
|
+
const setValues = useCallback((nextValues) => {
|
|
112
|
+
setValuesState((current) => ({
|
|
113
|
+
...current,
|
|
114
|
+
...nextValues,
|
|
115
|
+
}));
|
|
116
|
+
}, []);
|
|
117
|
+
const reset = useCallback((nextValues) => {
|
|
118
|
+
if (!schemaTable)
|
|
119
|
+
return;
|
|
120
|
+
const source = nextValues ?? {
|
|
121
|
+
...(record ?? {}),
|
|
122
|
+
...stableInitialValues,
|
|
123
|
+
};
|
|
124
|
+
hydrateValues(source);
|
|
125
|
+
}, [hydrateValues, record, schemaTable, stableInitialValues]);
|
|
126
|
+
const validate = useCallback(() => {
|
|
127
|
+
if (!schemaTable)
|
|
128
|
+
return false;
|
|
129
|
+
const resolvedMode = mode === "auto" ? (recordId ? "update" : "create") : mode;
|
|
130
|
+
const result = buildRecordPayload(schemaTable, values, { mode: resolvedMode });
|
|
131
|
+
setFieldErrors(result.errors);
|
|
132
|
+
return result.isValid;
|
|
133
|
+
}, [mode, recordId, schemaTable, values]);
|
|
134
|
+
const submit = useCallback(async (overrides = {}) => {
|
|
135
|
+
if (!schemaTable) {
|
|
136
|
+
const error = new Error("Record schema is not loaded.");
|
|
137
|
+
setRecordError(error);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const resolvedMode = overrides.mode ?? (mode === "auto" ? (recordId ? "update" : "create") : mode);
|
|
141
|
+
const payload = buildRecordPayload(schemaTable, values, { mode: resolvedMode });
|
|
142
|
+
setFieldErrors(payload.errors);
|
|
143
|
+
if (!payload.isValid) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
setIsSubmitting(true);
|
|
147
|
+
setRecordError(null);
|
|
148
|
+
try {
|
|
149
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
150
|
+
const response = resolvedMode === "update" && recordId
|
|
151
|
+
? await scopedClient.records.update(tableName, recordId, payload.data)
|
|
152
|
+
: await scopedClient.records.create(tableName, payload.data);
|
|
153
|
+
const nextRecord = response.data ?? null;
|
|
154
|
+
setRecord(nextRecord);
|
|
155
|
+
hydrateValues({
|
|
156
|
+
...(nextRecord ?? {}),
|
|
157
|
+
...stableInitialValues,
|
|
158
|
+
});
|
|
159
|
+
onSubmitSuccess?.(nextRecord ?? {}, response);
|
|
160
|
+
return nextRecord;
|
|
161
|
+
}
|
|
162
|
+
catch (submitError) {
|
|
163
|
+
const normalized = normalizeError(submitError, "Failed to save record.");
|
|
164
|
+
setRecordError(normalized);
|
|
165
|
+
onError?.(submitError);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
setIsSubmitting(false);
|
|
170
|
+
}
|
|
171
|
+
}, [client, hydrateValues, mode, onError, onSubmitSuccess, podId, recordId, schemaTable, stableInitialValues, tableName, values]);
|
|
172
|
+
const isDirty = useMemo(() => {
|
|
173
|
+
return stringifyComparable(values) !== stringifyComparable(baselineValues);
|
|
174
|
+
}, [baselineValues, values]);
|
|
175
|
+
return useMemo(() => ({
|
|
176
|
+
table: schema.table,
|
|
177
|
+
fields: schema.fields,
|
|
178
|
+
editableFields: schema.editableFields,
|
|
179
|
+
defaults: schema.defaults,
|
|
180
|
+
values,
|
|
181
|
+
baselineValues,
|
|
182
|
+
record,
|
|
183
|
+
fieldErrors,
|
|
184
|
+
isLoadingSchema: schema.isLoading,
|
|
185
|
+
isLoadingRecord,
|
|
186
|
+
isSubmitting,
|
|
187
|
+
isDirty,
|
|
188
|
+
error: schema.error ?? recordError,
|
|
189
|
+
refreshSchema: schema.refresh,
|
|
190
|
+
refreshRecord,
|
|
191
|
+
refresh,
|
|
192
|
+
setValue,
|
|
193
|
+
setValues,
|
|
194
|
+
reset,
|
|
195
|
+
validate,
|
|
196
|
+
submit,
|
|
197
|
+
}), [
|
|
198
|
+
baselineValues,
|
|
199
|
+
fieldErrors,
|
|
200
|
+
isDirty,
|
|
201
|
+
isLoadingRecord,
|
|
202
|
+
isSubmitting,
|
|
203
|
+
record,
|
|
204
|
+
recordError,
|
|
205
|
+
refresh,
|
|
206
|
+
refreshRecord,
|
|
207
|
+
reset,
|
|
208
|
+
schema.defaults,
|
|
209
|
+
schema.editableFields,
|
|
210
|
+
schema.error,
|
|
211
|
+
schema.fields,
|
|
212
|
+
schema.isLoading,
|
|
213
|
+
schema.refresh,
|
|
214
|
+
schema.table,
|
|
215
|
+
setValue,
|
|
216
|
+
setValues,
|
|
217
|
+
submit,
|
|
218
|
+
validate,
|
|
219
|
+
values,
|
|
220
|
+
]);
|
|
221
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import { type RecordSchemaField } from "../record-form.js";
|
|
3
|
+
import type { Table } from "../types.js";
|
|
4
|
+
export interface UseRecordSchemaOptions {
|
|
5
|
+
client: LemmaClient;
|
|
6
|
+
podId?: string;
|
|
7
|
+
tableName: string;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
autoLoad?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface UseRecordSchemaResult {
|
|
12
|
+
table: Table | null;
|
|
13
|
+
fields: RecordSchemaField[];
|
|
14
|
+
editableFields: RecordSchemaField[];
|
|
15
|
+
defaults: Record<string, unknown>;
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
refresh: () => Promise<Table | null>;
|
|
19
|
+
}
|
|
20
|
+
export declare function useRecordSchema({ client, podId, tableName, enabled, autoLoad, }: UseRecordSchemaOptions): UseRecordSchemaResult;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { buildRecordFormValues, buildRecordSchemaFields, getEditableRecordFields, } from "../record-form.js";
|
|
3
|
+
import { useTable } from "./useTable.js";
|
|
4
|
+
export function useRecordSchema({ client, podId, tableName, enabled = true, autoLoad = true, }) {
|
|
5
|
+
const tableState = useTable({
|
|
6
|
+
client,
|
|
7
|
+
podId,
|
|
8
|
+
tableName,
|
|
9
|
+
enabled,
|
|
10
|
+
autoLoad,
|
|
11
|
+
});
|
|
12
|
+
const fields = useMemo(() => (tableState.table ? buildRecordSchemaFields(tableState.table) : []), [tableState.table]);
|
|
13
|
+
const editableFields = useMemo(() => (tableState.table ? getEditableRecordFields(tableState.table) : []), [tableState.table]);
|
|
14
|
+
const defaults = useMemo(() => (tableState.table ? buildRecordFormValues(tableState.table) : {}), [tableState.table]);
|
|
15
|
+
return useMemo(() => ({
|
|
16
|
+
table: tableState.table,
|
|
17
|
+
fields,
|
|
18
|
+
editableFields,
|
|
19
|
+
defaults,
|
|
20
|
+
isLoading: tableState.isLoading,
|
|
21
|
+
error: tableState.error,
|
|
22
|
+
refresh: tableState.refresh,
|
|
23
|
+
}), [defaults, editableFields, fields, tableState.error, tableState.isLoading, tableState.refresh, tableState.table]);
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { ListRecordsOptions } from "../types.js";
|
|
3
|
+
export interface UseRecordsOptions extends ListRecordsOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
tableName: string;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
autoLoad?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UseRecordsResult<TRecord extends Record<string, unknown> = Record<string, unknown>> {
|
|
11
|
+
records: TRecord[];
|
|
12
|
+
total: number;
|
|
13
|
+
nextPageToken: string | null;
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
isLoadingMore: boolean;
|
|
16
|
+
error: Error | null;
|
|
17
|
+
refresh: (overrides?: Partial<ListRecordsOptions>) => Promise<TRecord[]>;
|
|
18
|
+
loadMore: (overrides?: Partial<ListRecordsOptions>) => Promise<TRecord[]>;
|
|
19
|
+
}
|
|
20
|
+
export declare function useRecords<TRecord extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, filters, sort, limit, pageToken, offset, sortBy, order, params, enabled, autoLoad, }: UseRecordsOptions): UseRecordsResult<TRecord>;
|