lemma-sdk 0.2.27 → 0.2.30
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 +113 -233
- 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/ConvertedArtifactResponse.js +1 -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/components/AssistantExperience.js +6 -4
- package/dist/react/index.d.ts +42 -8
- package/dist/react/index.js +21 -4
- package/dist/react/useAgentRun.d.ts +17 -0
- package/dist/react/useAgentRun.js +58 -0
- package/dist/react/useAssistantRun.d.ts +9 -0
- package/dist/react/useAssistantRun.js +19 -9
- package/dist/react/useAssistantSession.d.ts +5 -0
- package/dist/react/useAssistantSession.js +123 -70
- package/dist/react/useBulkRecords.d.ts +20 -0
- package/dist/react/useBulkRecords.js +72 -0
- package/dist/react/useConversation.d.ts +18 -0
- package/dist/react/useConversation.js +59 -0
- package/dist/react/useConversationMessages.d.ts +59 -0
- package/dist/react/useConversationMessages.js +167 -0
- package/dist/react/useConversations.d.ts +48 -0
- package/dist/react/useConversations.js +182 -0
- package/dist/react/useCreateRecord.d.ts +18 -0
- package/dist/react/useCreateRecord.js +58 -0
- package/dist/react/useDeleteRecord.d.ts +21 -0
- package/dist/react/useDeleteRecord.js +59 -0
- package/dist/react/useForeignKeyOptions.d.ts +31 -0
- package/dist/react/useForeignKeyOptions.js +150 -0
- package/dist/react/useJoinedRecords.d.ts +18 -0
- package/dist/react/useJoinedRecords.js +79 -0
- package/dist/react/useMembers.d.ts +22 -0
- package/dist/react/useMembers.js +59 -0
- package/dist/react/useRecord.d.ts +18 -0
- package/dist/react/useRecord.js +64 -0
- package/dist/react/useRecordForm.d.ts +42 -0
- package/dist/react/useRecordForm.js +238 -0
- package/dist/react/useRecordSchema.d.ts +20 -0
- package/dist/react/useRecordSchema.js +24 -0
- package/dist/react/useRecords.d.ts +18 -0
- package/dist/react/useRecords.js +106 -0
- package/dist/react/useRelatedRecords.d.ts +43 -0
- package/dist/react/useRelatedRecords.js +232 -0
- package/dist/react/useReverseRelatedRecords.d.ts +47 -0
- package/dist/react/useReverseRelatedRecords.js +226 -0
- package/dist/react/useSchemaForm.d.ts +24 -0
- package/dist/react/useSchemaForm.js +116 -0
- package/dist/react/useTable.d.ts +16 -0
- package/dist/react/useTable.js +59 -0
- package/dist/react/useTables.d.ts +22 -0
- package/dist/react/useTables.js +71 -0
- package/dist/react/useUpdateRecord.d.ts +21 -0
- package/dist/react/useUpdateRecord.js +62 -0
- package/dist/react/useWorkflowStart.d.ts +33 -0
- package/dist/react/useWorkflowStart.js +155 -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 +5 -1
- package/package.json +10 -5
- package/dist/react/styles.css +0 -2401
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { PodMember } from "../types.js";
|
|
3
|
+
export interface UseMembersOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
autoLoad?: boolean;
|
|
8
|
+
limit?: number;
|
|
9
|
+
pageToken?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface UseMembersResult {
|
|
12
|
+
members: PodMember[];
|
|
13
|
+
total: number;
|
|
14
|
+
nextPageToken: string | null;
|
|
15
|
+
isLoading: boolean;
|
|
16
|
+
error: Error | null;
|
|
17
|
+
refresh: (overrides?: {
|
|
18
|
+
limit?: number;
|
|
19
|
+
pageToken?: string;
|
|
20
|
+
}) => Promise<PodMember[]>;
|
|
21
|
+
}
|
|
22
|
+
export declare function useMembers({ client, podId, enabled, autoLoad, limit, pageToken, }: UseMembersOptions): UseMembersResult;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
function resolvePodId(client, podId) {
|
|
3
|
+
const resolved = podId ?? client.podId;
|
|
4
|
+
if (!resolved) {
|
|
5
|
+
throw new Error("podId is required. Pass podId or set it on LemmaClient.");
|
|
6
|
+
}
|
|
7
|
+
return resolved;
|
|
8
|
+
}
|
|
9
|
+
function normalizeError(error, fallback) {
|
|
10
|
+
if (error instanceof Error)
|
|
11
|
+
return error;
|
|
12
|
+
return new Error(fallback);
|
|
13
|
+
}
|
|
14
|
+
export function useMembers({ client, podId, enabled = true, autoLoad = true, limit = 100, pageToken, }) {
|
|
15
|
+
const [members, setMembers] = useState([]);
|
|
16
|
+
const [total, setTotal] = useState(0);
|
|
17
|
+
const [nextPageToken, setNextPageToken] = useState(null);
|
|
18
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const refresh = useCallback(async (overrides = {}) => {
|
|
21
|
+
if (!enabled)
|
|
22
|
+
return [];
|
|
23
|
+
setIsLoading(true);
|
|
24
|
+
setError(null);
|
|
25
|
+
try {
|
|
26
|
+
const resolvedPodId = resolvePodId(client, podId);
|
|
27
|
+
const response = await client.podMembers.list(resolvedPodId, {
|
|
28
|
+
limit: overrides.limit ?? limit,
|
|
29
|
+
pageToken: overrides.pageToken ?? pageToken,
|
|
30
|
+
});
|
|
31
|
+
const nextMembers = response.items ?? [];
|
|
32
|
+
setMembers(nextMembers);
|
|
33
|
+
setTotal(response.total ?? nextMembers.length);
|
|
34
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
35
|
+
return nextMembers;
|
|
36
|
+
}
|
|
37
|
+
catch (refreshError) {
|
|
38
|
+
const normalized = normalizeError(refreshError, "Failed to load pod members.");
|
|
39
|
+
setError(normalized);
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}, [client, enabled, limit, pageToken, podId]);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!enabled || !autoLoad)
|
|
48
|
+
return;
|
|
49
|
+
void refresh();
|
|
50
|
+
}, [autoLoad, enabled, refresh]);
|
|
51
|
+
return useMemo(() => ({
|
|
52
|
+
members,
|
|
53
|
+
total,
|
|
54
|
+
nextPageToken,
|
|
55
|
+
isLoading,
|
|
56
|
+
error,
|
|
57
|
+
refresh,
|
|
58
|
+
}), [error, isLoading, members, nextPageToken, refresh, total]);
|
|
59
|
+
}
|
|
@@ -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,64 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
function resolvePodClient(client, podId) {
|
|
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
|
+
}
|
|
12
|
+
export function useRecord({ client, podId, tableName, recordId = null, enabled = true, autoLoad = true, }) {
|
|
13
|
+
const [record, setRecord] = useState(null);
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
15
|
+
const [error, setError] = useState(null);
|
|
16
|
+
const trimmedTableName = tableName.trim();
|
|
17
|
+
const trimmedRecordId = typeof recordId === "string" ? recordId.trim() : "";
|
|
18
|
+
const isEnabled = enabled && trimmedTableName.length > 0 && trimmedRecordId.length > 0;
|
|
19
|
+
const refresh = useCallback(async (overrides = {}) => {
|
|
20
|
+
const nextRecordId = typeof overrides.recordId === "string"
|
|
21
|
+
? overrides.recordId.trim()
|
|
22
|
+
: trimmedRecordId;
|
|
23
|
+
if (!enabled || trimmedTableName.length === 0 || nextRecordId.length === 0) {
|
|
24
|
+
setRecord(null);
|
|
25
|
+
setError(null);
|
|
26
|
+
setIsLoading(false);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
setIsLoading(true);
|
|
30
|
+
setError(null);
|
|
31
|
+
try {
|
|
32
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
33
|
+
const response = await scopedClient.records.get(trimmedTableName, nextRecordId);
|
|
34
|
+
const nextRecord = (response.data ?? null);
|
|
35
|
+
setRecord(nextRecord);
|
|
36
|
+
return nextRecord;
|
|
37
|
+
}
|
|
38
|
+
catch (refreshError) {
|
|
39
|
+
const normalized = normalizeError(refreshError, "Failed to load record.");
|
|
40
|
+
setError(normalized);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
setIsLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}, [client, enabled, podId, trimmedRecordId, trimmedTableName]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!isEnabled) {
|
|
49
|
+
setRecord(null);
|
|
50
|
+
setError(null);
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!autoLoad)
|
|
55
|
+
return;
|
|
56
|
+
void refresh();
|
|
57
|
+
}, [autoLoad, isEnabled, refresh]);
|
|
58
|
+
return useMemo(() => ({
|
|
59
|
+
record,
|
|
60
|
+
isLoading,
|
|
61
|
+
error,
|
|
62
|
+
refresh,
|
|
63
|
+
}), [error, isLoading, record, refresh]);
|
|
64
|
+
}
|
|
@@ -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,238 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { buildRecordFormValues, buildRecordPayload, } from "../record-form.js";
|
|
3
|
+
import { useRecordSchema, } from "./useRecordSchema.js";
|
|
4
|
+
const EMPTY_VALUES = {};
|
|
5
|
+
function normalizeError(error, fallback) {
|
|
6
|
+
if (error instanceof Error)
|
|
7
|
+
return error;
|
|
8
|
+
return new Error(fallback);
|
|
9
|
+
}
|
|
10
|
+
function resolvePodClient(client, podId) {
|
|
11
|
+
if (!podId || podId === client.podId)
|
|
12
|
+
return client;
|
|
13
|
+
return client.withPod(podId);
|
|
14
|
+
}
|
|
15
|
+
function stringifyComparable(value) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return String(value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function useRecordForm({ client, podId, tableName, recordId = null, initialValues = EMPTY_VALUES, mode = "auto", enabled = true, autoLoad = true, onSubmitSuccess, onError, }) {
|
|
24
|
+
const schema = useRecordSchema({
|
|
25
|
+
client,
|
|
26
|
+
podId,
|
|
27
|
+
tableName,
|
|
28
|
+
enabled,
|
|
29
|
+
autoLoad,
|
|
30
|
+
});
|
|
31
|
+
const [record, setRecord] = useState(null);
|
|
32
|
+
const [values, setValuesState] = useState({});
|
|
33
|
+
const [baselineValues, setBaselineValues] = useState({});
|
|
34
|
+
const [fieldErrors, setFieldErrors] = useState({});
|
|
35
|
+
const [recordError, setRecordError] = useState(null);
|
|
36
|
+
const [isLoadingRecord, setIsLoadingRecord] = useState(false);
|
|
37
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
38
|
+
const initialValuesKey = stringifyComparable(initialValues);
|
|
39
|
+
const stableInitialValues = useMemo(() => initialValues, [initialValuesKey]);
|
|
40
|
+
const schemaTable = schema.table;
|
|
41
|
+
const refreshSchema = schema.refresh;
|
|
42
|
+
const hydrateValues = useCallback((source) => {
|
|
43
|
+
if (!schemaTable)
|
|
44
|
+
return;
|
|
45
|
+
const nextValues = buildRecordFormValues(schemaTable, source);
|
|
46
|
+
setValuesState(nextValues);
|
|
47
|
+
setBaselineValues(nextValues);
|
|
48
|
+
setFieldErrors({});
|
|
49
|
+
}, [schemaTable]);
|
|
50
|
+
const refreshRecord = useCallback(async () => {
|
|
51
|
+
if (!enabled || !recordId) {
|
|
52
|
+
setRecord(null);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
setIsLoadingRecord(true);
|
|
56
|
+
setRecordError(null);
|
|
57
|
+
try {
|
|
58
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
59
|
+
const response = await scopedClient.records.get(tableName, recordId);
|
|
60
|
+
const nextRecord = response.data ?? null;
|
|
61
|
+
setRecord(nextRecord);
|
|
62
|
+
return nextRecord;
|
|
63
|
+
}
|
|
64
|
+
catch (refreshError) {
|
|
65
|
+
const normalized = normalizeError(refreshError, "Failed to load record.");
|
|
66
|
+
setRecordError(normalized);
|
|
67
|
+
onError?.(refreshError);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setIsLoadingRecord(false);
|
|
72
|
+
}
|
|
73
|
+
}, [client, enabled, onError, podId, recordId, tableName]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!enabled) {
|
|
76
|
+
setRecord(null);
|
|
77
|
+
setValuesState({});
|
|
78
|
+
setBaselineValues({});
|
|
79
|
+
setFieldErrors({});
|
|
80
|
+
setRecordError(null);
|
|
81
|
+
setIsLoadingRecord(false);
|
|
82
|
+
setIsSubmitting(false);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!autoLoad || !recordId)
|
|
86
|
+
return;
|
|
87
|
+
void refreshRecord();
|
|
88
|
+
}, [autoLoad, enabled, recordId, refreshRecord]);
|
|
89
|
+
const recordKey = stringifyComparable(record);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!schemaTable)
|
|
92
|
+
return;
|
|
93
|
+
const source = {
|
|
94
|
+
...(record ?? {}),
|
|
95
|
+
...stableInitialValues,
|
|
96
|
+
};
|
|
97
|
+
hydrateValues(source);
|
|
98
|
+
}, [hydrateValues, recordKey, schemaTable, stableInitialValues]);
|
|
99
|
+
const refresh = useCallback(async () => {
|
|
100
|
+
const nextTable = await refreshSchema();
|
|
101
|
+
if (recordId) {
|
|
102
|
+
const nextRecord = await refreshRecord();
|
|
103
|
+
if (nextTable && nextRecord) {
|
|
104
|
+
hydrateValues({
|
|
105
|
+
...nextRecord,
|
|
106
|
+
...stableInitialValues,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (nextTable) {
|
|
112
|
+
hydrateValues(stableInitialValues);
|
|
113
|
+
}
|
|
114
|
+
}, [hydrateValues, recordId, refreshRecord, refreshSchema, stableInitialValues]);
|
|
115
|
+
const setValue = useCallback((fieldName, value) => {
|
|
116
|
+
setValuesState((current) => ({
|
|
117
|
+
...current,
|
|
118
|
+
[fieldName]: value,
|
|
119
|
+
}));
|
|
120
|
+
setFieldErrors((current) => {
|
|
121
|
+
if (!(fieldName in current))
|
|
122
|
+
return current;
|
|
123
|
+
const next = { ...current };
|
|
124
|
+
delete next[fieldName];
|
|
125
|
+
return next;
|
|
126
|
+
});
|
|
127
|
+
}, []);
|
|
128
|
+
const setValues = useCallback((nextValues) => {
|
|
129
|
+
setValuesState((current) => ({
|
|
130
|
+
...current,
|
|
131
|
+
...nextValues,
|
|
132
|
+
}));
|
|
133
|
+
}, []);
|
|
134
|
+
const reset = useCallback((nextValues) => {
|
|
135
|
+
if (!schemaTable)
|
|
136
|
+
return;
|
|
137
|
+
const source = nextValues ?? {
|
|
138
|
+
...(record ?? {}),
|
|
139
|
+
...stableInitialValues,
|
|
140
|
+
};
|
|
141
|
+
hydrateValues(source);
|
|
142
|
+
}, [hydrateValues, record, schemaTable, stableInitialValues]);
|
|
143
|
+
const validate = useCallback(() => {
|
|
144
|
+
if (!schemaTable)
|
|
145
|
+
return false;
|
|
146
|
+
const resolvedMode = mode === "auto" ? (recordId ? "update" : "create") : mode;
|
|
147
|
+
const result = buildRecordPayload(schemaTable, values, { mode: resolvedMode });
|
|
148
|
+
setFieldErrors(result.errors);
|
|
149
|
+
return result.isValid;
|
|
150
|
+
}, [mode, recordId, schemaTable, values]);
|
|
151
|
+
const submit = useCallback(async (overrides = {}) => {
|
|
152
|
+
if (!schemaTable) {
|
|
153
|
+
const error = new Error("Record schema is not loaded.");
|
|
154
|
+
setRecordError(error);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const resolvedMode = overrides.mode ?? (mode === "auto" ? (recordId ? "update" : "create") : mode);
|
|
158
|
+
const payload = buildRecordPayload(schemaTable, values, { mode: resolvedMode });
|
|
159
|
+
setFieldErrors(payload.errors);
|
|
160
|
+
if (!payload.isValid) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
setIsSubmitting(true);
|
|
164
|
+
setRecordError(null);
|
|
165
|
+
try {
|
|
166
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
167
|
+
const response = resolvedMode === "update" && recordId
|
|
168
|
+
? await scopedClient.records.update(tableName, recordId, payload.data)
|
|
169
|
+
: await scopedClient.records.create(tableName, payload.data);
|
|
170
|
+
const nextRecord = response.data ?? null;
|
|
171
|
+
setRecord(nextRecord);
|
|
172
|
+
hydrateValues({
|
|
173
|
+
...(nextRecord ?? {}),
|
|
174
|
+
...stableInitialValues,
|
|
175
|
+
});
|
|
176
|
+
onSubmitSuccess?.(nextRecord ?? {}, response);
|
|
177
|
+
return nextRecord;
|
|
178
|
+
}
|
|
179
|
+
catch (submitError) {
|
|
180
|
+
const normalized = normalizeError(submitError, "Failed to save record.");
|
|
181
|
+
setRecordError(normalized);
|
|
182
|
+
onError?.(submitError);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
setIsSubmitting(false);
|
|
187
|
+
}
|
|
188
|
+
}, [client, hydrateValues, mode, onError, onSubmitSuccess, podId, recordId, schemaTable, stableInitialValues, tableName, values]);
|
|
189
|
+
const isDirty = useMemo(() => {
|
|
190
|
+
return stringifyComparable(values) !== stringifyComparable(baselineValues);
|
|
191
|
+
}, [baselineValues, values]);
|
|
192
|
+
return useMemo(() => ({
|
|
193
|
+
table: schema.table,
|
|
194
|
+
fields: schema.fields,
|
|
195
|
+
editableFields: schema.editableFields,
|
|
196
|
+
defaults: schema.defaults,
|
|
197
|
+
values,
|
|
198
|
+
baselineValues,
|
|
199
|
+
record,
|
|
200
|
+
fieldErrors,
|
|
201
|
+
isLoadingSchema: schema.isLoading,
|
|
202
|
+
isLoadingRecord,
|
|
203
|
+
isSubmitting,
|
|
204
|
+
isDirty,
|
|
205
|
+
error: schema.error ?? recordError,
|
|
206
|
+
refreshSchema: schema.refresh,
|
|
207
|
+
refreshRecord,
|
|
208
|
+
refresh,
|
|
209
|
+
setValue,
|
|
210
|
+
setValues,
|
|
211
|
+
reset,
|
|
212
|
+
validate,
|
|
213
|
+
submit,
|
|
214
|
+
}), [
|
|
215
|
+
baselineValues,
|
|
216
|
+
fieldErrors,
|
|
217
|
+
isDirty,
|
|
218
|
+
isLoadingRecord,
|
|
219
|
+
isSubmitting,
|
|
220
|
+
record,
|
|
221
|
+
recordError,
|
|
222
|
+
refresh,
|
|
223
|
+
refreshRecord,
|
|
224
|
+
reset,
|
|
225
|
+
schema.defaults,
|
|
226
|
+
schema.editableFields,
|
|
227
|
+
schema.error,
|
|
228
|
+
schema.fields,
|
|
229
|
+
schema.isLoading,
|
|
230
|
+
schema.refresh,
|
|
231
|
+
schema.table,
|
|
232
|
+
setValue,
|
|
233
|
+
setValues,
|
|
234
|
+
submit,
|
|
235
|
+
validate,
|
|
236
|
+
values,
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
@@ -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,18 @@
|
|
|
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
|
+
error: Error | null;
|
|
16
|
+
refresh: (overrides?: Partial<ListRecordsOptions>) => Promise<TRecord[]>;
|
|
17
|
+
}
|
|
18
|
+
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>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
function resolvePodClient(client, podId) {
|
|
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
|
+
}
|
|
12
|
+
function stringifyComparable(value) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return String(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function useRecords({ client, podId, tableName, filters, sort, limit = 20, pageToken, offset, sortBy, order, params, enabled = true, autoLoad = true, }) {
|
|
21
|
+
const [records, setRecords] = useState([]);
|
|
22
|
+
const [total, setTotal] = useState(0);
|
|
23
|
+
const [nextPageToken, setNextPageToken] = useState(null);
|
|
24
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
+
const [error, setError] = useState(null);
|
|
26
|
+
const trimmedTableName = tableName.trim();
|
|
27
|
+
const isEnabled = enabled && trimmedTableName.length > 0;
|
|
28
|
+
const filtersKey = stringifyComparable(filters);
|
|
29
|
+
const sortKey = stringifyComparable(sort);
|
|
30
|
+
const paramsKey = stringifyComparable(params);
|
|
31
|
+
const stableFilters = useMemo(() => filters, [filtersKey]);
|
|
32
|
+
const stableSort = useMemo(() => sort, [sortKey]);
|
|
33
|
+
const stableParams = useMemo(() => params, [paramsKey]);
|
|
34
|
+
const refresh = useCallback(async (overrides = {}) => {
|
|
35
|
+
if (!isEnabled) {
|
|
36
|
+
setRecords([]);
|
|
37
|
+
setTotal(0);
|
|
38
|
+
setNextPageToken(null);
|
|
39
|
+
setError(null);
|
|
40
|
+
setIsLoading(false);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
setIsLoading(true);
|
|
44
|
+
setError(null);
|
|
45
|
+
try {
|
|
46
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
47
|
+
const response = await scopedClient.records.list(trimmedTableName, {
|
|
48
|
+
filters: overrides.filters ?? stableFilters,
|
|
49
|
+
sort: overrides.sort ?? stableSort,
|
|
50
|
+
limit: overrides.limit ?? limit,
|
|
51
|
+
pageToken: overrides.pageToken ?? pageToken,
|
|
52
|
+
offset: overrides.offset ?? offset,
|
|
53
|
+
sortBy: overrides.sortBy ?? sortBy,
|
|
54
|
+
order: overrides.order ?? order,
|
|
55
|
+
params: overrides.params ?? stableParams,
|
|
56
|
+
});
|
|
57
|
+
const nextRecords = (response.items ?? []);
|
|
58
|
+
setRecords(nextRecords);
|
|
59
|
+
setTotal(response.total ?? nextRecords.length);
|
|
60
|
+
setNextPageToken(response.next_page_token ?? null);
|
|
61
|
+
return nextRecords;
|
|
62
|
+
}
|
|
63
|
+
catch (refreshError) {
|
|
64
|
+
const normalized = normalizeError(refreshError, "Failed to load records.");
|
|
65
|
+
setError(normalized);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}, [
|
|
72
|
+
client,
|
|
73
|
+
isEnabled,
|
|
74
|
+
limit,
|
|
75
|
+
offset,
|
|
76
|
+
order,
|
|
77
|
+
pageToken,
|
|
78
|
+
podId,
|
|
79
|
+
sortBy,
|
|
80
|
+
stableFilters,
|
|
81
|
+
stableParams,
|
|
82
|
+
stableSort,
|
|
83
|
+
trimmedTableName,
|
|
84
|
+
]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!isEnabled) {
|
|
87
|
+
setRecords([]);
|
|
88
|
+
setTotal(0);
|
|
89
|
+
setNextPageToken(null);
|
|
90
|
+
setError(null);
|
|
91
|
+
setIsLoading(false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!autoLoad)
|
|
95
|
+
return;
|
|
96
|
+
void refresh();
|
|
97
|
+
}, [autoLoad, isEnabled, refresh]);
|
|
98
|
+
return useMemo(() => ({
|
|
99
|
+
records,
|
|
100
|
+
total,
|
|
101
|
+
nextPageToken,
|
|
102
|
+
isLoading,
|
|
103
|
+
error,
|
|
104
|
+
refresh,
|
|
105
|
+
}), [error, isLoading, nextPageToken, records, refresh, total]);
|
|
106
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { Table } from "../types.js";
|
|
3
|
+
export interface RelatedRecordsInclude {
|
|
4
|
+
foreignKey: string;
|
|
5
|
+
as?: string;
|
|
6
|
+
fields?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface RelatedRecordsResolvedInclude {
|
|
9
|
+
foreignKey: string;
|
|
10
|
+
relationKey: string;
|
|
11
|
+
relatedTable: string;
|
|
12
|
+
relatedColumn: string;
|
|
13
|
+
fields: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface RelatedRecordsColumn {
|
|
16
|
+
key: string;
|
|
17
|
+
field: string;
|
|
18
|
+
label: string;
|
|
19
|
+
source: "base" | "related";
|
|
20
|
+
relationKey?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface UseRelatedRecordsOptions {
|
|
23
|
+
client: LemmaClient;
|
|
24
|
+
podId?: string;
|
|
25
|
+
tableName: string;
|
|
26
|
+
baseFields?: string[];
|
|
27
|
+
include: RelatedRecordsInclude[];
|
|
28
|
+
limit?: number;
|
|
29
|
+
offset?: number;
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
autoLoad?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface UseRelatedRecordsResult<TRow extends Record<string, unknown> = Record<string, unknown>> {
|
|
34
|
+
records: TRow[];
|
|
35
|
+
columns: RelatedRecordsColumn[];
|
|
36
|
+
sql: string;
|
|
37
|
+
includes: RelatedRecordsResolvedInclude[];
|
|
38
|
+
baseTable: Table | null;
|
|
39
|
+
isLoading: boolean;
|
|
40
|
+
error: Error | null;
|
|
41
|
+
refresh: () => Promise<TRow[]>;
|
|
42
|
+
}
|
|
43
|
+
export declare function useRelatedRecords<TRow extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, baseFields, include, limit, offset, enabled, autoLoad, }: UseRelatedRecordsOptions): UseRelatedRecordsResult<TRow>;
|