lemma-sdk 0.2.28 → 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/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 -2407
|
@@ -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,59 @@
|
|
|
1
|
+
import { useCallback, 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 useDeleteRecord({ client, podId, tableName, recordId = null, enabled = true, onSuccess, onError, }) {
|
|
13
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [lastMessage, setLastMessage] = useState(null);
|
|
16
|
+
const trimmedTableName = tableName.trim();
|
|
17
|
+
const trimmedRecordId = typeof recordId === "string" ? recordId.trim() : "";
|
|
18
|
+
const isEnabled = enabled && trimmedTableName.length > 0;
|
|
19
|
+
const remove = useCallback(async (overrides = {}) => {
|
|
20
|
+
const nextRecordId = typeof overrides.recordId === "string"
|
|
21
|
+
? overrides.recordId.trim()
|
|
22
|
+
: trimmedRecordId;
|
|
23
|
+
if (!isEnabled || nextRecordId.length === 0) {
|
|
24
|
+
const disabledError = new Error("Record deletion requires a table and record ID.");
|
|
25
|
+
setError(disabledError);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
setIsSubmitting(true);
|
|
29
|
+
setError(null);
|
|
30
|
+
try {
|
|
31
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
32
|
+
const response = await scopedClient.records.delete(trimmedTableName, nextRecordId);
|
|
33
|
+
setLastMessage(response.message ?? "Record deleted.");
|
|
34
|
+
onSuccess?.(response);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch (mutationError) {
|
|
38
|
+
const normalized = normalizeError(mutationError, "Failed to delete record.");
|
|
39
|
+
setError(normalized);
|
|
40
|
+
onError?.(mutationError);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
setIsSubmitting(false);
|
|
45
|
+
}
|
|
46
|
+
}, [client, isEnabled, onError, onSuccess, podId, trimmedRecordId, trimmedTableName]);
|
|
47
|
+
const reset = useCallback(() => {
|
|
48
|
+
setError(null);
|
|
49
|
+
setIsSubmitting(false);
|
|
50
|
+
setLastMessage(null);
|
|
51
|
+
}, []);
|
|
52
|
+
return useMemo(() => ({
|
|
53
|
+
isSubmitting,
|
|
54
|
+
error,
|
|
55
|
+
lastMessage,
|
|
56
|
+
remove,
|
|
57
|
+
reset,
|
|
58
|
+
}), [error, isSubmitting, lastMessage, remove, reset]);
|
|
59
|
+
}
|
|
@@ -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,150 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { parseForeignKeyReference } from "../datastore-query.js";
|
|
3
|
+
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
|
+
function readRecordValue(record, field) {
|
|
17
|
+
if (!field)
|
|
18
|
+
return undefined;
|
|
19
|
+
return record[field];
|
|
20
|
+
}
|
|
21
|
+
function pickResolvedLabelField(records, referenceColumn, explicitLabelField, explicitLabelFields) {
|
|
22
|
+
const candidates = [
|
|
23
|
+
explicitLabelField,
|
|
24
|
+
...(explicitLabelFields ?? []),
|
|
25
|
+
"name",
|
|
26
|
+
"title",
|
|
27
|
+
"label",
|
|
28
|
+
"email",
|
|
29
|
+
"slug",
|
|
30
|
+
referenceColumn,
|
|
31
|
+
"id",
|
|
32
|
+
].filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (records.some((record) => {
|
|
35
|
+
const value = record[candidate];
|
|
36
|
+
return typeof value === "string"
|
|
37
|
+
? value.trim().length > 0
|
|
38
|
+
: typeof value !== "undefined" && value !== null;
|
|
39
|
+
})) {
|
|
40
|
+
return candidate;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function matchesSearch(record, search, fields) {
|
|
46
|
+
const normalized = search.trim().toLowerCase();
|
|
47
|
+
if (!normalized)
|
|
48
|
+
return true;
|
|
49
|
+
return fields.some((field) => {
|
|
50
|
+
const value = record[field];
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
return value.toLowerCase().includes(normalized);
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
55
|
+
return String(value).toLowerCase().includes(normalized);
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function useForeignKeyOptions({ client, podId, tableName, columnName, labelField, labelFields = EMPTY_LABEL_FIELDS, search, limit = 50, enabled = true, autoLoad = true, }) {
|
|
61
|
+
const [table, setTable] = useState(null);
|
|
62
|
+
const [column, setColumn] = useState(null);
|
|
63
|
+
const [reference, setReference] = useState(null);
|
|
64
|
+
const [resolvedLabelField, setResolvedLabelField] = useState(null);
|
|
65
|
+
const [options, setOptions] = useState([]);
|
|
66
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
67
|
+
const [error, setError] = useState(null);
|
|
68
|
+
const labelFieldsKey = useMemo(() => JSON.stringify(labelFields), [labelFields]);
|
|
69
|
+
const stableLabelFields = useMemo(() => labelFields, [labelFieldsKey]);
|
|
70
|
+
const refresh = useCallback(async () => {
|
|
71
|
+
if (!enabled)
|
|
72
|
+
return [];
|
|
73
|
+
setIsLoading(true);
|
|
74
|
+
setError(null);
|
|
75
|
+
try {
|
|
76
|
+
const resolvedPodId = resolvePodId(client, podId);
|
|
77
|
+
const scopedClient = resolvedPodId === client.podId ? client : client.withPod(resolvedPodId);
|
|
78
|
+
const nextTable = await scopedClient.tables.get(tableName);
|
|
79
|
+
const nextColumn = nextTable.columns.find((entry) => entry.name === columnName) ?? null;
|
|
80
|
+
const nextReference = nextColumn?.foreign_key?.references
|
|
81
|
+
? parseForeignKeyReference(nextColumn.foreign_key.references)
|
|
82
|
+
: null;
|
|
83
|
+
setTable(nextTable);
|
|
84
|
+
setColumn(nextColumn);
|
|
85
|
+
setReference(nextReference);
|
|
86
|
+
if (!nextColumn) {
|
|
87
|
+
throw new Error(`Column "${columnName}" was not found on table "${tableName}".`);
|
|
88
|
+
}
|
|
89
|
+
if (!nextReference) {
|
|
90
|
+
setResolvedLabelField(null);
|
|
91
|
+
setOptions([]);
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
const canFilterOnServer = !!labelField && !!search?.trim();
|
|
95
|
+
const response = await scopedClient.records.list(nextReference.table, {
|
|
96
|
+
limit: canFilterOnServer ? Math.max(limit, 100) : (search ? Math.max(limit * 5, 100) : limit),
|
|
97
|
+
filters: canFilterOnServer
|
|
98
|
+
? [{ field: labelField, op: "ilike", value: `%${search?.trim()}%` }]
|
|
99
|
+
: undefined,
|
|
100
|
+
});
|
|
101
|
+
const records = response.items ?? [];
|
|
102
|
+
const nextResolvedLabelField = pickResolvedLabelField(records, nextReference.column, labelField, stableLabelFields);
|
|
103
|
+
const searchableFields = Array.from(new Set([nextResolvedLabelField, ...stableLabelFields, nextReference.column, "id"]
|
|
104
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0)));
|
|
105
|
+
const filteredRecords = canFilterOnServer || !search?.trim()
|
|
106
|
+
? records
|
|
107
|
+
: records.filter((record) => matchesSearch(record, search, searchableFields));
|
|
108
|
+
const nextOptions = filteredRecords
|
|
109
|
+
.slice(0, limit)
|
|
110
|
+
.map((record) => {
|
|
111
|
+
const value = readRecordValue(record, nextReference.column);
|
|
112
|
+
const labelValue = readRecordValue(record, nextResolvedLabelField);
|
|
113
|
+
return {
|
|
114
|
+
value,
|
|
115
|
+
label: typeof labelValue === "string" && labelValue.trim().length > 0
|
|
116
|
+
? labelValue
|
|
117
|
+
: String(value ?? ""),
|
|
118
|
+
record,
|
|
119
|
+
};
|
|
120
|
+
})
|
|
121
|
+
.filter((option) => typeof option.value !== "undefined" && option.value !== null);
|
|
122
|
+
setResolvedLabelField(nextResolvedLabelField);
|
|
123
|
+
setOptions(nextOptions);
|
|
124
|
+
return nextOptions;
|
|
125
|
+
}
|
|
126
|
+
catch (refreshError) {
|
|
127
|
+
const normalized = normalizeError(refreshError, "Failed to load foreign key options.");
|
|
128
|
+
setError(normalized);
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
setIsLoading(false);
|
|
133
|
+
}
|
|
134
|
+
}, [client, columnName, enabled, labelField, limit, podId, search, stableLabelFields, tableName]);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!enabled || !autoLoad)
|
|
137
|
+
return;
|
|
138
|
+
void refresh();
|
|
139
|
+
}, [autoLoad, enabled, refresh]);
|
|
140
|
+
return useMemo(() => ({
|
|
141
|
+
table,
|
|
142
|
+
column,
|
|
143
|
+
reference,
|
|
144
|
+
labelField: resolvedLabelField,
|
|
145
|
+
options,
|
|
146
|
+
isLoading,
|
|
147
|
+
error,
|
|
148
|
+
refresh,
|
|
149
|
+
}), [column, error, isLoading, options, reference, refresh, resolvedLabelField, table]);
|
|
150
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import { type JoinedRecordsQueryDefinition } from "../datastore-query.js";
|
|
3
|
+
export interface UseJoinedRecordsOptions {
|
|
4
|
+
client: LemmaClient;
|
|
5
|
+
podId?: string;
|
|
6
|
+
query: JoinedRecordsQueryDefinition;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
autoLoad?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UseJoinedRecordsResult<TRecord extends Record<string, unknown> = Record<string, unknown>> {
|
|
11
|
+
records: TRecord[];
|
|
12
|
+
total: number;
|
|
13
|
+
sql: string;
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
error: Error | null;
|
|
16
|
+
refresh: () => Promise<TRecord[]>;
|
|
17
|
+
}
|
|
18
|
+
export declare function useJoinedRecords<TRecord extends Record<string, unknown> = Record<string, unknown>>({ client, podId, query, enabled, autoLoad, }: UseJoinedRecordsOptions): UseJoinedRecordsResult<TRecord>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { buildJoinedRecordsQuery } from "../datastore-query.js";
|
|
3
|
+
function resolvePodId(client, podId) {
|
|
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
|
+
}
|
|
10
|
+
function normalizeError(error, fallback) {
|
|
11
|
+
if (error instanceof Error)
|
|
12
|
+
return error;
|
|
13
|
+
return new Error(fallback);
|
|
14
|
+
}
|
|
15
|
+
function stringifyComparable(value) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return String(value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function useJoinedRecords({ client, podId, query, enabled = true, autoLoad = true, }) {
|
|
24
|
+
const [records, setRecords] = useState([]);
|
|
25
|
+
const [total, setTotal] = useState(0);
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
27
|
+
const [error, setError] = useState(null);
|
|
28
|
+
const queryKey = stringifyComparable(query);
|
|
29
|
+
const stableQuery = useMemo(() => query, [queryKey]);
|
|
30
|
+
const sql = useMemo(() => buildJoinedRecordsQuery(stableQuery), [stableQuery]);
|
|
31
|
+
const refresh = useCallback(async () => {
|
|
32
|
+
if (!enabled) {
|
|
33
|
+
setRecords([]);
|
|
34
|
+
setTotal(0);
|
|
35
|
+
setError(null);
|
|
36
|
+
setIsLoading(false);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
setIsLoading(true);
|
|
40
|
+
setError(null);
|
|
41
|
+
try {
|
|
42
|
+
const resolvedPodId = resolvePodId(client, podId);
|
|
43
|
+
const scopedClient = resolvedPodId === client.podId ? client : client.withPod(resolvedPodId);
|
|
44
|
+
const response = await scopedClient.datastore.query(sql);
|
|
45
|
+
const nextRecords = (response.items ?? []);
|
|
46
|
+
setRecords(nextRecords);
|
|
47
|
+
setTotal(response.total ?? nextRecords.length);
|
|
48
|
+
return nextRecords;
|
|
49
|
+
}
|
|
50
|
+
catch (refreshError) {
|
|
51
|
+
const normalized = normalizeError(refreshError, "Failed to load joined records.");
|
|
52
|
+
setError(normalized);
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setIsLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}, [client, enabled, podId, sql]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!enabled) {
|
|
61
|
+
setRecords([]);
|
|
62
|
+
setTotal(0);
|
|
63
|
+
setError(null);
|
|
64
|
+
setIsLoading(false);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!autoLoad)
|
|
68
|
+
return;
|
|
69
|
+
void refresh();
|
|
70
|
+
}, [autoLoad, enabled, refresh]);
|
|
71
|
+
return useMemo(() => ({
|
|
72
|
+
records,
|
|
73
|
+
total,
|
|
74
|
+
sql,
|
|
75
|
+
isLoading,
|
|
76
|
+
error,
|
|
77
|
+
refresh,
|
|
78
|
+
}), [error, isLoading, records, refresh, sql, total]);
|
|
79
|
+
}
|
|
@@ -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;
|