lemma-sdk 0.2.30 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +211 -51
  2. package/dist/react/index.d.ts +20 -0
  3. package/dist/react/index.js +10 -0
  4. package/dist/react/useAgentInputSchema.d.ts +19 -0
  5. package/dist/react/useAgentInputSchema.js +73 -0
  6. package/dist/react/useAgentRun.js +18 -20
  7. package/dist/react/useAgentRuns.d.ts +33 -0
  8. package/dist/react/useAgentRuns.js +149 -0
  9. package/dist/react/useAssistantRun.js +10 -9
  10. package/dist/react/useAssistantSession.js +21 -25
  11. package/dist/react/useBulkRecords.js +9 -16
  12. package/dist/react/useConversation.js +24 -8
  13. package/dist/react/useConversations.d.ts +4 -0
  14. package/dist/react/useConversations.js +49 -3
  15. package/dist/react/useCreateRecord.js +9 -16
  16. package/dist/react/useCurrentUser.d.ts +14 -0
  17. package/dist/react/useCurrentUser.js +68 -0
  18. package/dist/react/useDeleteRecord.js +9 -16
  19. package/dist/react/useFlowRunHistory.js +1 -5
  20. package/dist/react/useFlowSession.js +41 -33
  21. package/dist/react/useForeignKeyOptions.js +26 -15
  22. package/dist/react/useFunctionRun.d.ts +19 -0
  23. package/dist/react/useFunctionRun.js +30 -0
  24. package/dist/react/useFunctionRuns.d.ts +33 -0
  25. package/dist/react/useFunctionRuns.js +149 -0
  26. package/dist/react/useFunctionSession.js +37 -29
  27. package/dist/react/useJoinedRecords.js +24 -23
  28. package/dist/react/useMembers.d.ts +4 -0
  29. package/dist/react/useMembers.js +55 -16
  30. package/dist/react/useOrganizationMembers.d.ts +26 -0
  31. package/dist/react/useOrganizationMembers.js +113 -0
  32. package/dist/react/usePodAccess.d.ts +22 -0
  33. package/dist/react/usePodAccess.js +128 -0
  34. package/dist/react/useRecord.js +24 -13
  35. package/dist/react/useRecordForm.js +1 -18
  36. package/dist/react/useRecords.d.ts +2 -0
  37. package/dist/react/useRecords.js +62 -22
  38. package/dist/react/useRelatedRecords.js +28 -21
  39. package/dist/react/useReverseRelatedRecords.js +30 -21
  40. package/dist/react/useSchemaForm.js +1 -13
  41. package/dist/react/useTable.js +24 -13
  42. package/dist/react/useTables.d.ts +4 -0
  43. package/dist/react/useTables.js +57 -15
  44. package/dist/react/useTaskSession.js +11 -22
  45. package/dist/react/useUpdateRecord.js +9 -16
  46. package/dist/react/useWorkflowResume.d.ts +18 -0
  47. package/dist/react/useWorkflowResume.js +45 -0
  48. package/dist/react/useWorkflowRun.d.ts +21 -0
  49. package/dist/react/useWorkflowRun.js +49 -0
  50. package/dist/react/useWorkflowRuns.d.ts +33 -0
  51. package/dist/react/useWorkflowRuns.js +149 -0
  52. package/dist/react/useWorkflowStart.js +20 -27
  53. package/dist/react/utils.d.ts +5 -0
  54. package/dist/react/utils.js +25 -0
  55. package/dist/types.d.ts +1 -0
  56. package/package.json +2 -4
  57. package/dist/react/components/AssistantChrome.d.ts +0 -86
  58. package/dist/react/components/AssistantChrome.js +0 -48
  59. package/dist/react/components/AssistantEmbedded.d.ts +0 -10
  60. package/dist/react/components/AssistantEmbedded.js +0 -15
  61. package/dist/react/components/AssistantExperience.d.ts +0 -96
  62. package/dist/react/components/AssistantExperience.js +0 -1294
  63. package/dist/react/components/assistant-types.d.ts +0 -80
  64. package/dist/react/components/assistant-types.js +0 -1
@@ -1,14 +1,5 @@
1
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
- }
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
12
3
  export function useRecord({ client, podId, tableName, recordId = null, enabled = true, autoLoad = true, }) {
13
4
  const [record, setRecord] = useState(null);
14
5
  const [isLoading, setIsLoading] = useState(false);
@@ -16,7 +7,7 @@ export function useRecord({ client, podId, tableName, recordId = null, enabled =
16
7
  const trimmedTableName = tableName.trim();
17
8
  const trimmedRecordId = typeof recordId === "string" ? recordId.trim() : "";
18
9
  const isEnabled = enabled && trimmedTableName.length > 0 && trimmedRecordId.length > 0;
19
- const refresh = useCallback(async (overrides = {}) => {
10
+ const refresh = useCallback(async (overrides = {}, signal) => {
20
11
  const nextRecordId = typeof overrides.recordId === "string"
21
12
  ? overrides.recordId.trim()
22
13
  : trimmedRecordId;
@@ -31,17 +22,22 @@ export function useRecord({ client, podId, tableName, recordId = null, enabled =
31
22
  try {
32
23
  const scopedClient = resolvePodClient(client, podId);
33
24
  const response = await scopedClient.records.get(trimmedTableName, nextRecordId);
25
+ if (signal?.aborted)
26
+ return null;
34
27
  const nextRecord = (response.data ?? null);
35
28
  setRecord(nextRecord);
36
29
  return nextRecord;
37
30
  }
38
31
  catch (refreshError) {
32
+ if (signal?.aborted)
33
+ return null;
39
34
  const normalized = normalizeError(refreshError, "Failed to load record.");
40
35
  setError(normalized);
41
36
  return null;
42
37
  }
43
38
  finally {
44
- setIsLoading(false);
39
+ if (!signal?.aborted)
40
+ setIsLoading(false);
45
41
  }
46
42
  }, [client, enabled, podId, trimmedRecordId, trimmedTableName]);
47
43
  useEffect(() => {
@@ -53,7 +49,22 @@ export function useRecord({ client, podId, tableName, recordId = null, enabled =
53
49
  }
54
50
  if (!autoLoad)
55
51
  return;
56
- void refresh();
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
+ };
57
68
  }, [autoLoad, isEnabled, refresh]);
58
69
  return useMemo(() => ({
59
70
  record,
@@ -1,25 +1,8 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { buildRecordFormValues, buildRecordPayload, } from "../record-form.js";
3
+ import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
3
4
  import { useRecordSchema, } from "./useRecordSchema.js";
4
5
  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
6
  export function useRecordForm({ client, podId, tableName, recordId = null, initialValues = EMPTY_VALUES, mode = "auto", enabled = true, autoLoad = true, onSubmitSuccess, onError, }) {
24
7
  const schema = useRecordSchema({
25
8
  client,
@@ -12,7 +12,9 @@ export interface UseRecordsResult<TRecord extends Record<string, unknown> = Reco
12
12
  total: number;
13
13
  nextPageToken: string | null;
14
14
  isLoading: boolean;
15
+ isLoadingMore: boolean;
15
16
  error: Error | null;
16
17
  refresh: (overrides?: Partial<ListRecordsOptions>) => Promise<TRecord[]>;
18
+ loadMore: (overrides?: Partial<ListRecordsOptions>) => Promise<TRecord[]>;
17
19
  }
18
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>;
@@ -1,27 +1,11 @@
1
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
- }
2
+ import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
20
3
  export function useRecords({ client, podId, tableName, filters, sort, limit = 20, pageToken, offset, sortBy, order, params, enabled = true, autoLoad = true, }) {
21
4
  const [records, setRecords] = useState([]);
22
5
  const [total, setTotal] = useState(0);
23
6
  const [nextPageToken, setNextPageToken] = useState(null);
24
7
  const [isLoading, setIsLoading] = useState(false);
8
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
25
9
  const [error, setError] = useState(null);
26
10
  const trimmedTableName = tableName.trim();
27
11
  const isEnabled = enabled && trimmedTableName.length > 0;
@@ -31,7 +15,7 @@ export function useRecords({ client, podId, tableName, filters, sort, limit = 20
31
15
  const stableFilters = useMemo(() => filters, [filtersKey]);
32
16
  const stableSort = useMemo(() => sort, [sortKey]);
33
17
  const stableParams = useMemo(() => params, [paramsKey]);
34
- const refresh = useCallback(async (overrides = {}) => {
18
+ const refresh = useCallback(async (overrides = {}, signal) => {
35
19
  if (!isEnabled) {
36
20
  setRecords([]);
37
21
  setTotal(0);
@@ -54,6 +38,8 @@ export function useRecords({ client, podId, tableName, filters, sort, limit = 20
54
38
  order: overrides.order ?? order,
55
39
  params: overrides.params ?? stableParams,
56
40
  });
41
+ if (signal?.aborted)
42
+ return [];
57
43
  const nextRecords = (response.items ?? []);
58
44
  setRecords(nextRecords);
59
45
  setTotal(response.total ?? nextRecords.length);
@@ -61,12 +47,15 @@ export function useRecords({ client, podId, tableName, filters, sort, limit = 20
61
47
  return nextRecords;
62
48
  }
63
49
  catch (refreshError) {
50
+ if (signal?.aborted)
51
+ return [];
64
52
  const normalized = normalizeError(refreshError, "Failed to load records.");
65
53
  setError(normalized);
66
54
  return [];
67
55
  }
68
56
  finally {
69
- setIsLoading(false);
57
+ if (!signal?.aborted)
58
+ setIsLoading(false);
70
59
  }
71
60
  }, [
72
61
  client,
@@ -82,6 +71,39 @@ export function useRecords({ client, podId, tableName, filters, sort, limit = 20
82
71
  stableSort,
83
72
  trimmedTableName,
84
73
  ]);
74
+ const loadMore = useCallback(async (overrides = {}) => {
75
+ if (!isEnabled || !nextPageToken || isLoading || isLoadingMore) {
76
+ return [];
77
+ }
78
+ setIsLoadingMore(true);
79
+ setError(null);
80
+ try {
81
+ const scopedClient = resolvePodClient(client, podId);
82
+ const response = await scopedClient.records.list(trimmedTableName, {
83
+ filters: stableFilters,
84
+ sort: stableSort,
85
+ limit: overrides.limit ?? limit,
86
+ pageToken: nextPageToken,
87
+ offset: overrides.offset,
88
+ sortBy: overrides.sortBy ?? sortBy,
89
+ order: overrides.order ?? order,
90
+ params: overrides.params ?? stableParams,
91
+ });
92
+ const moreRecords = (response.items ?? []);
93
+ setRecords((previous) => [...previous, ...moreRecords]);
94
+ setTotal(response.total ?? records.length + moreRecords.length);
95
+ setNextPageToken(response.next_page_token ?? null);
96
+ return moreRecords;
97
+ }
98
+ catch (loadError) {
99
+ const normalized = normalizeError(loadError, "Failed to load more records.");
100
+ setError(normalized);
101
+ return [];
102
+ }
103
+ finally {
104
+ setIsLoadingMore(false);
105
+ }
106
+ }, [client, isEnabled, isLoading, isLoadingMore, limit, nextPageToken, offset, order, podId, records.length, sortBy, stableFilters, stableParams, stableSort, trimmedTableName]);
85
107
  useEffect(() => {
86
108
  if (!isEnabled) {
87
109
  setRecords([]);
@@ -89,18 +111,36 @@ export function useRecords({ client, podId, tableName, filters, sort, limit = 20
89
111
  setNextPageToken(null);
90
112
  setError(null);
91
113
  setIsLoading(false);
114
+ setIsLoadingMore(false);
92
115
  return;
93
116
  }
94
117
  if (!autoLoad)
95
118
  return;
96
- void refresh();
119
+ const controller = new AbortController();
120
+ let cancelled = false;
121
+ (async () => {
122
+ try {
123
+ await refresh({}, controller.signal);
124
+ }
125
+ catch {
126
+ if (!cancelled) {
127
+ setError(normalizeError(new Error("Failed to load records."), "Failed to load records."));
128
+ }
129
+ }
130
+ })();
131
+ return () => {
132
+ cancelled = true;
133
+ controller.abort();
134
+ };
97
135
  }, [autoLoad, isEnabled, refresh]);
98
136
  return useMemo(() => ({
99
137
  records,
100
138
  total,
101
139
  nextPageToken,
102
140
  isLoading,
141
+ isLoadingMore,
103
142
  error,
104
143
  refresh,
105
- }), [error, isLoading, nextPageToken, records, refresh, total]);
144
+ loadMore,
145
+ }), [error, isLoading, isLoadingMore, loadMore, nextPageToken, records, refresh, total]);
106
146
  }
@@ -1,23 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { buildJoinedRecordsQuery, parseForeignKeyReference } from "../datastore-query.js";
3
- function resolvePodClient(client, podId) {
4
- if (!podId || podId === client.podId)
5
- return client;
6
- return client.withPod(podId);
7
- }
8
- function normalizeError(error, fallback) {
9
- if (error instanceof Error)
10
- return error;
11
- return new Error(fallback);
12
- }
13
- function stringifyComparable(value) {
14
- try {
15
- return JSON.stringify(value);
16
- }
17
- catch {
18
- return String(value);
19
- }
20
- }
3
+ import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
21
4
  function sentenceCase(value) {
22
5
  return value
23
6
  .replace(/[_\.]/g, " ")
@@ -82,7 +65,7 @@ export function useRelatedRecords({ client, podId, tableName, baseFields, includ
82
65
  const stableInclude = useMemo(() => include, [includeKey]);
83
66
  const stableBaseFields = useMemo(() => baseFields, [baseFieldsKey]);
84
67
  const isEnabled = enabled && trimmedTableName.length > 0 && stableInclude.length > 0;
85
- const refresh = useCallback(async () => {
68
+ const refresh = useCallback(async (signal) => {
86
69
  if (!isEnabled) {
87
70
  setRecords([]);
88
71
  setColumns([]);
@@ -98,6 +81,8 @@ export function useRelatedRecords({ client, podId, tableName, baseFields, includ
98
81
  try {
99
82
  const scopedClient = resolvePodClient(client, podId);
100
83
  const nextBaseTable = await scopedClient.tables.get(trimmedTableName);
84
+ if (signal?.aborted)
85
+ return [];
101
86
  if (nextBaseTable.enable_rls) {
102
87
  throw new Error(`Related record queries are not supported for RLS-enabled table "${trimmedTableName}". Use table-scoped record APIs instead.`);
103
88
  }
@@ -105,6 +90,8 @@ export function useRelatedRecords({ client, podId, tableName, baseFields, includ
105
90
  const resolvedBaseFields = (stableBaseFields?.length ? stableBaseFields : pickDefaultBaseFields(nextBaseTable))
106
91
  .filter((field, index, allFields) => field.trim().length > 0 && allFields.indexOf(field) === index);
107
92
  const resolvedIncludes = await Promise.all(stableInclude.map(async (entry, index) => {
93
+ if (signal?.aborted)
94
+ throw new Error("Aborted");
108
95
  const baseColumn = nextBaseTable.columns.find((column) => column.name === entry.foreignKey) ?? null;
109
96
  const reference = baseColumn?.foreign_key?.references
110
97
  ? parseForeignKeyReference(baseColumn.foreign_key.references)
@@ -163,6 +150,8 @@ export function useRelatedRecords({ client, podId, tableName, baseFields, includ
163
150
  setSql(nextSql);
164
151
  setIncludes(resolvedIncludes.map(({ alias: _alias, ...rest }) => rest));
165
152
  const response = await scopedClient.datastore.query(nextSql);
153
+ if (signal?.aborted)
154
+ return [];
166
155
  const nextColumns = [
167
156
  ...resolvedBaseFields.map((field) => ({
168
157
  key: field,
@@ -196,12 +185,15 @@ export function useRelatedRecords({ client, podId, tableName, baseFields, includ
196
185
  return nextRecords;
197
186
  }
198
187
  catch (refreshError) {
188
+ if (signal?.aborted)
189
+ return [];
199
190
  const normalized = normalizeError(refreshError, "Failed to load related records.");
200
191
  setError(normalized);
201
192
  return [];
202
193
  }
203
194
  finally {
204
- setIsLoading(false);
195
+ if (!signal?.aborted)
196
+ setIsLoading(false);
205
197
  }
206
198
  }, [client, isEnabled, limit, offset, podId, stableBaseFields, stableInclude, trimmedTableName]);
207
199
  useEffect(() => {
@@ -217,7 +209,22 @@ export function useRelatedRecords({ client, podId, tableName, baseFields, includ
217
209
  }
218
210
  if (!autoLoad)
219
211
  return;
220
- void refresh();
212
+ const controller = new AbortController();
213
+ let cancelled = false;
214
+ (async () => {
215
+ try {
216
+ await refresh(controller.signal);
217
+ }
218
+ catch {
219
+ if (!cancelled) {
220
+ setError(normalizeError(new Error("Failed to load related records."), "Failed to load related records."));
221
+ }
222
+ }
223
+ })();
224
+ return () => {
225
+ cancelled = true;
226
+ controller.abort();
227
+ };
221
228
  }, [autoLoad, isEnabled, refresh]);
222
229
  return useMemo(() => ({
223
230
  records,
@@ -1,23 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { parseForeignKeyReference } from "../datastore-query.js";
3
- function resolvePodClient(client, podId) {
4
- if (!podId || podId === client.podId)
5
- return client;
6
- return client.withPod(podId);
7
- }
8
- function normalizeError(error, fallback) {
9
- if (error instanceof Error)
10
- return error;
11
- return new Error(fallback);
12
- }
13
- function stringifyComparable(value) {
14
- try {
15
- return JSON.stringify(value);
16
- }
17
- catch {
18
- return String(value);
19
- }
20
- }
3
+ import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
21
4
  function sentenceCase(value) {
22
5
  return value
23
6
  .replace(/[_\.]/g, " ")
@@ -62,7 +45,7 @@ export function useReverseRelatedRecords({ client, podId, tableName, recordId =
62
45
  const stableRelation = useMemo(() => relation, [relationKey]);
63
46
  const stableFields = useMemo(() => fields, [fieldsKey]);
64
47
  const isEnabled = enabled && trimmedTableName.length > 0 && trimmedRecordId.length > 0;
65
- const refresh = useCallback(async () => {
48
+ const refresh = useCallback(async (signal) => {
66
49
  if (!isEnabled) {
67
50
  setParentTable(null);
68
51
  setRelatedTable(null);
@@ -85,10 +68,14 @@ export function useReverseRelatedRecords({ client, podId, tableName, recordId =
85
68
  scopedClient.tables.list({ limit: tablesLimit }),
86
69
  scopedClient.records.get(trimmedTableName, trimmedRecordId),
87
70
  ]);
71
+ if (signal?.aborted)
72
+ return [];
88
73
  const listedTables = tablesResponse.items ?? [];
89
74
  const nextParentTable = listedTables.find((tableEntry) => tableEntry.name === trimmedTableName)
90
75
  ?? await scopedClient.tables.get(trimmedTableName);
91
76
  const nextParentRecord = parentRecordResponse.data ?? null;
77
+ if (signal?.aborted)
78
+ return [];
92
79
  setParentTable(nextParentTable);
93
80
  setParentRecord(nextParentRecord);
94
81
  const nextRelations = listedTables.flatMap((candidateTable) => candidateTable.columns.flatMap((column) => {
@@ -123,6 +110,8 @@ export function useReverseRelatedRecords({ client, podId, tableName, recordId =
123
110
  ?? await scopedClient.tables.get(nextSelectedRelation.tableName);
124
111
  const referenceValue = nextParentRecord?.[nextSelectedRelation.referencedColumn]
125
112
  ?? (nextSelectedRelation.referencedColumn === "id" ? trimmedRecordId : undefined);
113
+ if (signal?.aborted)
114
+ return [];
126
115
  setRelatedTable(nextRelatedTable);
127
116
  if (typeof referenceValue === "undefined" || referenceValue === null) {
128
117
  setColumns([]);
@@ -144,6 +133,8 @@ export function useReverseRelatedRecords({ client, podId, tableName, recordId =
144
133
  sortBy,
145
134
  order,
146
135
  });
136
+ if (signal?.aborted)
137
+ return [];
147
138
  const nextRecords = (response.items ?? []);
148
139
  setColumns(resolvedFields.map((field) => ({
149
140
  key: field,
@@ -156,12 +147,15 @@ export function useReverseRelatedRecords({ client, podId, tableName, recordId =
156
147
  return nextRecords;
157
148
  }
158
149
  catch (refreshError) {
150
+ if (signal?.aborted)
151
+ return [];
159
152
  const normalized = normalizeError(refreshError, "Failed to load reverse-related records.");
160
153
  setError(normalized);
161
154
  return [];
162
155
  }
163
156
  finally {
164
- setIsLoading(false);
157
+ if (!signal?.aborted)
158
+ setIsLoading(false);
165
159
  }
166
160
  }, [
167
161
  client,
@@ -194,7 +188,22 @@ export function useReverseRelatedRecords({ client, podId, tableName, recordId =
194
188
  }
195
189
  if (!autoLoad)
196
190
  return;
197
- void refresh();
191
+ const controller = new AbortController();
192
+ let cancelled = false;
193
+ (async () => {
194
+ try {
195
+ await refresh(controller.signal);
196
+ }
197
+ catch {
198
+ if (!cancelled) {
199
+ setError(normalizeError(new Error("Failed to load reverse-related records."), "Failed to load reverse-related records."));
200
+ }
201
+ }
202
+ })();
203
+ return () => {
204
+ cancelled = true;
205
+ controller.abort();
206
+ };
198
207
  }, [autoLoad, isEnabled, refresh]);
199
208
  return useMemo(() => ({
200
209
  parentTable,
@@ -1,18 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { buildSchemaFormFields, buildSchemaFormPayload, buildSchemaFormValues, } from "../schema-form.js";
3
- function normalizeError(error, fallback) {
4
- if (error instanceof Error)
5
- return error;
6
- return new Error(fallback);
7
- }
8
- function stringifyComparable(value) {
9
- try {
10
- return JSON.stringify(value);
11
- }
12
- catch {
13
- return String(value);
14
- }
15
- }
3
+ import { normalizeError, stringifyComparable } from "./utils.js";
16
4
  const EMPTY_VALUES = {};
17
5
  export function useSchemaForm({ schema = null, uiSchema = null, initialValues = EMPTY_VALUES, enabled = true, onSubmit, onError, }) {
18
6
  const [values, setValuesState] = useState({});
@@ -1,21 +1,12 @@
1
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
- }
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
12
3
  export function useTable({ client, podId, tableName, enabled = true, autoLoad = true, }) {
13
4
  const [table, setTable] = useState(null);
14
5
  const [isLoading, setIsLoading] = useState(false);
15
6
  const [error, setError] = useState(null);
16
7
  const trimmedTableName = tableName.trim();
17
8
  const isEnabled = enabled && trimmedTableName.length > 0;
18
- const refresh = useCallback(async () => {
9
+ const refresh = useCallback(async (signal) => {
19
10
  if (!isEnabled) {
20
11
  setTable(null);
21
12
  setError(null);
@@ -27,16 +18,21 @@ export function useTable({ client, podId, tableName, enabled = true, autoLoad =
27
18
  try {
28
19
  const scopedClient = resolvePodClient(client, podId);
29
20
  const nextTable = await scopedClient.tables.get(trimmedTableName);
21
+ if (signal?.aborted)
22
+ return null;
30
23
  setTable(nextTable);
31
24
  return nextTable;
32
25
  }
33
26
  catch (refreshError) {
27
+ if (signal?.aborted)
28
+ return null;
34
29
  const normalized = normalizeError(refreshError, "Failed to load table.");
35
30
  setError(normalized);
36
31
  return null;
37
32
  }
38
33
  finally {
39
- setIsLoading(false);
34
+ if (!signal?.aborted)
35
+ setIsLoading(false);
40
36
  }
41
37
  }, [client, isEnabled, podId, trimmedTableName]);
42
38
  useEffect(() => {
@@ -48,7 +44,22 @@ export function useTable({ client, podId, tableName, enabled = true, autoLoad =
48
44
  }
49
45
  if (!autoLoad)
50
46
  return;
51
- void refresh();
47
+ const controller = new AbortController();
48
+ let cancelled = false;
49
+ (async () => {
50
+ try {
51
+ await refresh(controller.signal);
52
+ }
53
+ catch {
54
+ if (!cancelled) {
55
+ setError(normalizeError(new Error("Failed to load table."), "Failed to load table."));
56
+ }
57
+ }
58
+ })();
59
+ return () => {
60
+ cancelled = true;
61
+ controller.abort();
62
+ };
52
63
  }, [autoLoad, isEnabled, refresh]);
53
64
  return useMemo(() => ({
54
65
  table,
@@ -13,10 +13,14 @@ export interface UseTablesResult {
13
13
  total: number;
14
14
  nextPageToken: string | null;
15
15
  isLoading: boolean;
16
+ isLoadingMore: boolean;
16
17
  error: Error | null;
17
18
  refresh: (overrides?: {
18
19
  limit?: number;
19
20
  pageToken?: string;
20
21
  }) => Promise<Table[]>;
22
+ loadMore: (overrides?: {
23
+ limit?: number;
24
+ }) => Promise<Table[]>;
21
25
  }
22
26
  export declare function useTables({ client, podId, enabled, autoLoad, limit, pageToken, }: UseTablesOptions): UseTablesResult;
@@ -1,21 +1,13 @@
1
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
- }
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
12
3
  export function useTables({ client, podId, enabled = true, autoLoad = true, limit = 100, pageToken, }) {
13
4
  const [tables, setTables] = useState([]);
14
5
  const [total, setTotal] = useState(0);
15
6
  const [nextPageToken, setNextPageToken] = useState(null);
16
7
  const [isLoading, setIsLoading] = useState(false);
8
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
17
9
  const [error, setError] = useState(null);
18
- const refresh = useCallback(async (overrides = {}) => {
10
+ const refresh = useCallback(async (overrides = {}, signal) => {
19
11
  if (!enabled) {
20
12
  setTables([]);
21
13
  setTotal(0);
@@ -32,21 +24,53 @@ export function useTables({ client, podId, enabled = true, autoLoad = true, limi
32
24
  limit: overrides.limit ?? limit,
33
25
  pageToken: overrides.pageToken ?? pageToken,
34
26
  });
27
+ if (signal?.aborted)
28
+ return [];
35
29
  const nextTables = response.items ?? [];
36
30
  setTables(nextTables);
37
- setTotal(nextTables.length);
31
+ setTotal(response.total ?? nextTables.length);
38
32
  setNextPageToken(response.next_page_token ?? null);
39
33
  return nextTables;
40
34
  }
41
35
  catch (refreshError) {
36
+ if (signal?.aborted)
37
+ return [];
42
38
  const normalized = normalizeError(refreshError, "Failed to load tables.");
43
39
  setError(normalized);
44
40
  return [];
45
41
  }
46
42
  finally {
47
- setIsLoading(false);
43
+ if (!signal?.aborted)
44
+ setIsLoading(false);
48
45
  }
49
46
  }, [client, enabled, limit, pageToken, podId]);
47
+ const loadMore = useCallback(async (overrides = {}) => {
48
+ if (!enabled || !nextPageToken || isLoading || isLoadingMore) {
49
+ return [];
50
+ }
51
+ setIsLoadingMore(true);
52
+ setError(null);
53
+ try {
54
+ const scopedClient = resolvePodClient(client, podId);
55
+ const response = await scopedClient.tables.list({
56
+ limit: overrides.limit ?? limit,
57
+ pageToken: nextPageToken,
58
+ });
59
+ const moreTables = response.items ?? [];
60
+ setTables((previous) => [...previous, ...moreTables]);
61
+ setTotal(response.total ?? tables.length + moreTables.length);
62
+ setNextPageToken(response.next_page_token ?? null);
63
+ return moreTables;
64
+ }
65
+ catch (loadError) {
66
+ const normalized = normalizeError(loadError, "Failed to load more tables.");
67
+ setError(normalized);
68
+ return [];
69
+ }
70
+ finally {
71
+ setIsLoadingMore(false);
72
+ }
73
+ }, [client, enabled, isLoading, isLoadingMore, limit, nextPageToken, podId, tables.length]);
50
74
  useEffect(() => {
51
75
  if (!enabled) {
52
76
  setTables([]);
@@ -54,18 +78,36 @@ export function useTables({ client, podId, enabled = true, autoLoad = true, limi
54
78
  setNextPageToken(null);
55
79
  setError(null);
56
80
  setIsLoading(false);
81
+ setIsLoadingMore(false);
57
82
  return;
58
83
  }
59
84
  if (!autoLoad)
60
85
  return;
61
- void refresh();
86
+ const controller = new AbortController();
87
+ let cancelled = false;
88
+ (async () => {
89
+ try {
90
+ await refresh({}, controller.signal);
91
+ }
92
+ catch {
93
+ if (!cancelled) {
94
+ setError(normalizeError(new Error("Failed to load tables."), "Failed to load tables."));
95
+ }
96
+ }
97
+ })();
98
+ return () => {
99
+ cancelled = true;
100
+ controller.abort();
101
+ };
62
102
  }, [autoLoad, enabled, refresh]);
63
103
  return useMemo(() => ({
64
104
  tables,
65
105
  total,
66
106
  nextPageToken,
67
107
  isLoading,
108
+ isLoadingMore,
68
109
  error,
69
110
  refresh,
70
- }), [error, isLoading, nextPageToken, refresh, tables, total]);
111
+ loadMore,
112
+ }), [error, isLoading, isLoadingMore, loadMore, nextPageToken, refresh, tables, total]);
71
113
  }