lemma-sdk 0.2.34 → 0.2.36

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 (68) hide show
  1. package/README.md +112 -114
  2. package/dist/browser/lemma-client.js +176 -14
  3. package/dist/namespaces/integrations.d.ts +10 -2
  4. package/dist/namespaces/integrations.js +17 -2
  5. package/dist/openapi_client/index.d.ts +10 -2
  6. package/dist/openapi_client/index.js +1 -0
  7. package/dist/openapi_client/models/DatastoreDataType.d.ts +2 -0
  8. package/dist/openapi_client/models/DatastoreDataType.js +2 -0
  9. package/dist/openapi_client/models/FeedbackCategory.d.ts +7 -0
  10. package/dist/openapi_client/models/FeedbackCategory.js +12 -0
  11. package/dist/openapi_client/models/FlowRunEntity.d.ts +1 -0
  12. package/dist/openapi_client/models/IntegrationHelperAgentRequest.d.ts +13 -0
  13. package/dist/openapi_client/models/IntegrationHelperAgentResponse.d.ts +25 -0
  14. package/dist/openapi_client/models/OperationDetail.d.ts +3 -0
  15. package/dist/openapi_client/models/OperationDetailsBatchRequest.d.ts +9 -0
  16. package/dist/openapi_client/models/OperationDetailsBatchRequest.js +1 -0
  17. package/dist/openapi_client/models/OperationDetailsBatchResponse.d.ts +18 -0
  18. package/dist/openapi_client/models/OperationDetailsBatchResponse.js +1 -0
  19. package/dist/openapi_client/models/OperationDiscoverResponse.d.ts +26 -0
  20. package/dist/openapi_client/models/OperationDiscoverResponse.js +1 -0
  21. package/dist/openapi_client/models/OperationSummary.d.ts +3 -0
  22. package/dist/openapi_client/models/OrganizationCreateRequest.d.ts +2 -0
  23. package/dist/openapi_client/models/OrganizationInvitationRequest.d.ts +1 -0
  24. package/dist/openapi_client/models/OrganizationInvitationResponse.d.ts +3 -0
  25. package/dist/openapi_client/models/OrganizationMessageResponse.d.ts +1 -0
  26. package/dist/openapi_client/models/OrganizationResponse.d.ts +2 -0
  27. package/dist/openapi_client/models/OrganizationSlugAvailabilityResponse.d.ts +7 -0
  28. package/dist/openapi_client/models/OrganizationSlugAvailabilityResponse.js +1 -0
  29. package/dist/openapi_client/models/ReportFeedbackRequest.d.ts +30 -0
  30. package/dist/openapi_client/models/ReportFeedbackRequest.js +1 -0
  31. package/dist/openapi_client/models/ReportFeedbackResponse.d.ts +29 -0
  32. package/dist/openapi_client/models/ReportFeedbackResponse.js +1 -0
  33. package/dist/openapi_client/models/WorkflowInstallListResponse.d.ts +4 -0
  34. package/dist/openapi_client/models/WorkflowInstallListResponse.js +1 -0
  35. package/dist/openapi_client/services/AgentToolsService.d.ts +20 -0
  36. package/dist/openapi_client/services/AgentToolsService.js +36 -0
  37. package/dist/openapi_client/services/ApplicationsService.d.ts +10 -9
  38. package/dist/openapi_client/services/ApplicationsService.js +11 -10
  39. package/dist/openapi_client/services/IntegrationsService.d.ts +3 -2
  40. package/dist/openapi_client/services/IntegrationsService.js +4 -2
  41. package/dist/openapi_client/services/OrganizationsService.d.ts +18 -0
  42. package/dist/openapi_client/services/OrganizationsService.js +40 -0
  43. package/dist/openapi_client/services/WorkflowsService.d.ts +10 -0
  44. package/dist/openapi_client/services/WorkflowsService.js +21 -0
  45. package/dist/react/index.d.ts +14 -0
  46. package/dist/react/index.js +7 -0
  47. package/dist/react/sql-utils.d.ts +8 -0
  48. package/dist/react/sql-utils.js +62 -0
  49. package/dist/react/useCreateFolder.d.ts +22 -0
  50. package/dist/react/useCreateFolder.js +47 -0
  51. package/dist/react/useDatastoreQuery.d.ts +22 -0
  52. package/dist/react/useDatastoreQuery.js +67 -0
  53. package/dist/react/useDeleteFile.d.ts +19 -0
  54. package/dist/react/useDeleteFile.js +51 -0
  55. package/dist/react/useGlobalSearch.d.ts +62 -0
  56. package/dist/react/useGlobalSearch.js +170 -0
  57. package/dist/react/useRecordAggregates.d.ts +35 -0
  58. package/dist/react/useRecordAggregates.js +126 -0
  59. package/dist/react/useTaskSession.js +3 -2
  60. package/dist/react/useUpdateFile.d.ts +29 -0
  61. package/dist/react/useUpdateFile.js +51 -0
  62. package/dist/react/useUploadFile.d.ts +24 -0
  63. package/dist/react/useUploadFile.js +46 -0
  64. package/package.json +1 -1
  65. package/dist/openapi_client/models/AppDescriptorResponse.d.ts +0 -5
  66. package/dist/openapi_client/models/OperationListResponse.d.ts +0 -6
  67. /package/dist/openapi_client/models/{AppDescriptorResponse.js → IntegrationHelperAgentRequest.js} +0 -0
  68. /package/dist/openapi_client/models/{OperationListResponse.js → IntegrationHelperAgentResponse.js} +0 -0
@@ -0,0 +1,170 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { escapeSqlString, quoteIdentifierPath, renderIdentifierPath, renderRecordFilters } from "./sql-utils.js";
3
+ import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
4
+ function buildTableSearchQuery(source, query) {
5
+ const fields = Array.from(new Set([
6
+ "id",
7
+ ...source.searchFields,
8
+ source.displayField ?? "id",
9
+ source.subtitleField ?? "",
10
+ ].filter((value) => value.trim().length > 0)));
11
+ const searchClauses = source.searchFields.map((field) => `${renderIdentifierPath(field)} ILIKE '%${escapeSqlString(query)}%'`);
12
+ const filterClause = renderRecordFilters(source.filters);
13
+ return [
14
+ `SELECT ${fields.map((field) => renderIdentifierPath(field)).join(", ")}`,
15
+ `FROM ${quoteIdentifierPath(source.tableName)}`,
16
+ `WHERE (${searchClauses.join(" OR ")})${filterClause ? ` AND ${filterClause}` : ""}`,
17
+ `LIMIT ${source.limit ?? 8}`,
18
+ ].join(" ");
19
+ }
20
+ function readString(value) {
21
+ if (typeof value === "string" && value.trim().length > 0)
22
+ return value;
23
+ if (typeof value === "number" || typeof value === "boolean")
24
+ return String(value);
25
+ return null;
26
+ }
27
+ export function useGlobalSearch({ client, podId, query = "", tables = [], files, enabled = true, autoLoad = true, minQueryLength = 1, }) {
28
+ const [results, setResults] = useState([]);
29
+ const [sourceErrors, setSourceErrors] = useState({});
30
+ const [isLoading, setIsLoading] = useState(false);
31
+ const [error, setError] = useState(null);
32
+ const trimmedQuery = query.trim();
33
+ const tablesKey = stringifyComparable(tables);
34
+ const filesKey = stringifyComparable(files);
35
+ const stableTables = useMemo(() => tables, [tablesKey]);
36
+ const stableFiles = useMemo(() => files, [filesKey]);
37
+ const reset = useCallback(() => {
38
+ setResults([]);
39
+ setSourceErrors({});
40
+ setError(null);
41
+ setIsLoading(false);
42
+ }, []);
43
+ const search = useCallback(async (overrides = {}, signal) => {
44
+ const nextQuery = (overrides.query ?? trimmedQuery).trim();
45
+ if (!enabled || nextQuery.length < minQueryLength) {
46
+ reset();
47
+ return [];
48
+ }
49
+ setIsLoading(true);
50
+ setError(null);
51
+ try {
52
+ const scopedClient = resolvePodClient(client, podId);
53
+ const searchTasks = [];
54
+ stableTables.forEach((source, index) => {
55
+ const sourceKey = source.key?.trim() || source.tableName;
56
+ searchTasks.push((async () => {
57
+ try {
58
+ const sql = buildTableSearchQuery(source, nextQuery);
59
+ const response = await scopedClient.datastore.query(sql);
60
+ const rows = response.items ?? [];
61
+ const mapped = rows.map((record) => {
62
+ const displayField = source.displayField ?? source.searchFields[0] ?? "id";
63
+ const subtitleField = source.subtitleField;
64
+ const id = readString(record.id) ?? `${source.tableName}-${index}`;
65
+ return {
66
+ kind: "record",
67
+ sourceKey,
68
+ sourceLabel: source.label ?? source.tableName,
69
+ tableName: source.tableName,
70
+ id,
71
+ title: readString(record[displayField]) ?? id,
72
+ subtitle: subtitleField ? readString(record[subtitleField]) : null,
73
+ record,
74
+ };
75
+ });
76
+ return { key: sourceKey, results: mapped };
77
+ }
78
+ catch (tableError) {
79
+ return {
80
+ key: sourceKey,
81
+ results: [],
82
+ error: normalizeError(tableError, `Failed to search ${source.tableName}.`),
83
+ };
84
+ }
85
+ })());
86
+ });
87
+ if (stableFiles !== false && stableFiles?.enabled !== false) {
88
+ searchTasks.push((async () => {
89
+ const sourceKey = "files";
90
+ try {
91
+ const response = await scopedClient.files.search(nextQuery, {
92
+ limit: stableFiles?.limit ?? 8,
93
+ searchMethod: stableFiles?.searchMethod,
94
+ });
95
+ const mapped = (response.results ?? []).map((result) => ({
96
+ kind: "file",
97
+ sourceKey,
98
+ sourceLabel: stableFiles?.label ?? "Files",
99
+ path: result.path,
100
+ title: result.path.split("/").filter(Boolean).pop() || result.path,
101
+ subtitle: typeof result.content === "string" && result.content.trim().length > 0
102
+ ? result.content.slice(0, 160)
103
+ : null,
104
+ result,
105
+ }));
106
+ return { key: sourceKey, results: mapped };
107
+ }
108
+ catch (fileError) {
109
+ return {
110
+ key: sourceKey,
111
+ results: [],
112
+ error: normalizeError(fileError, "Failed to search files."),
113
+ };
114
+ }
115
+ })());
116
+ }
117
+ const settled = await Promise.all(searchTasks);
118
+ if (signal?.aborted)
119
+ return [];
120
+ const nextErrors = {};
121
+ const nextResults = settled.flatMap((entry) => {
122
+ if (entry.error) {
123
+ nextErrors[entry.key] = entry.error;
124
+ }
125
+ return entry.results;
126
+ });
127
+ setSourceErrors(nextErrors);
128
+ setResults(nextResults);
129
+ return nextResults;
130
+ }
131
+ catch (searchError) {
132
+ if (signal?.aborted)
133
+ return [];
134
+ const normalized = normalizeError(searchError, "Failed to run global search.");
135
+ setError(normalized);
136
+ setResults([]);
137
+ return [];
138
+ }
139
+ finally {
140
+ if (!signal?.aborted)
141
+ setIsLoading(false);
142
+ }
143
+ }, [client, enabled, minQueryLength, podId, reset, stableFiles, stableTables, trimmedQuery]);
144
+ useEffect(() => {
145
+ if (!enabled || !autoLoad)
146
+ return;
147
+ if (trimmedQuery.length < minQueryLength) {
148
+ reset();
149
+ return;
150
+ }
151
+ const controller = new AbortController();
152
+ void search({}, controller.signal);
153
+ return () => controller.abort();
154
+ }, [autoLoad, enabled, minQueryLength, reset, search, trimmedQuery]);
155
+ return useMemo(() => {
156
+ const recordResults = results.filter((entry) => entry.kind === "record");
157
+ const fileResults = results.filter((entry) => entry.kind === "file");
158
+ return {
159
+ results,
160
+ recordResults,
161
+ fileResults,
162
+ totalResults: results.length,
163
+ sourceErrors,
164
+ isLoading,
165
+ error,
166
+ search,
167
+ reset,
168
+ };
169
+ }, [error, isLoading, reset, results, search, sourceErrors]);
170
+ }
@@ -0,0 +1,35 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { RecordFilter } from "../types.js";
3
+ export interface RecordAggregateMetric {
4
+ key: string;
5
+ op: "count" | "sum" | "avg" | "min" | "max";
6
+ field?: string;
7
+ distinct?: boolean;
8
+ }
9
+ export interface RecordAggregateOrderBy {
10
+ field: string;
11
+ direction?: "asc" | "desc";
12
+ }
13
+ export interface UseRecordAggregatesOptions {
14
+ client: LemmaClient;
15
+ podId?: string;
16
+ tableName: string;
17
+ metrics: RecordAggregateMetric[];
18
+ groupBy?: string | string[];
19
+ filters?: RecordFilter[];
20
+ limit?: number;
21
+ offset?: number;
22
+ orderBy?: RecordAggregateOrderBy[];
23
+ enabled?: boolean;
24
+ autoLoad?: boolean;
25
+ }
26
+ export interface UseRecordAggregatesResult<TRow extends Record<string, unknown> = Record<string, unknown>> {
27
+ rows: TRow[];
28
+ row: TRow | null;
29
+ total: number;
30
+ sql: string;
31
+ isLoading: boolean;
32
+ error: Error | null;
33
+ refresh: () => Promise<TRow[]>;
34
+ }
35
+ export declare function useRecordAggregates<TRow extends Record<string, unknown> = Record<string, unknown>>({ client, podId, tableName, metrics, groupBy, filters, limit, offset, orderBy, enabled, autoLoad, }: UseRecordAggregatesOptions): UseRecordAggregatesResult<TRow>;
@@ -0,0 +1,126 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { encodeSqlValue, quoteIdentifierPath, renderIdentifierPath, renderRecordFilters } from "./sql-utils.js";
3
+ import { normalizeError, resolvePodClient, stringifyComparable } from "./utils.js";
4
+ function buildMetricExpression(metric) {
5
+ const fn = metric.op.toUpperCase();
6
+ if (metric.op === "count" && !metric.field) {
7
+ return `${fn}(*) AS ${quoteIdentifierPath(metric.key)}`;
8
+ }
9
+ if (!metric.field) {
10
+ throw new Error(`Aggregate metric "${metric.key}" requires a field.`);
11
+ }
12
+ const renderedField = renderIdentifierPath(metric.field);
13
+ const distinct = metric.distinct ? "DISTINCT " : "";
14
+ return `${fn}(${distinct}${renderedField}) AS ${quoteIdentifierPath(metric.key)}`;
15
+ }
16
+ function buildRecordAggregatesQuery({ tableName, metrics, groupBy, filters, limit, offset, orderBy, }) {
17
+ const groups = (Array.isArray(groupBy) ? groupBy : groupBy ? [groupBy] : [])
18
+ .map((field) => field.trim())
19
+ .filter((field) => field.length > 0);
20
+ const selectParts = [
21
+ ...groups.map((field) => `${renderIdentifierPath(field)} AS ${quoteIdentifierPath(field)}`),
22
+ ...metrics.map((metric) => buildMetricExpression(metric)),
23
+ ];
24
+ const whereClause = renderRecordFilters(filters);
25
+ const orderClause = orderBy?.length
26
+ ? ` ORDER BY ${orderBy.map((entry) => `${renderIdentifierPath(entry.field)} ${(entry.direction ?? "desc").toUpperCase()}`).join(", ")}`
27
+ : "";
28
+ const groupClause = groups.length
29
+ ? ` GROUP BY ${groups.map((field) => renderIdentifierPath(field)).join(", ")}`
30
+ : "";
31
+ const limitClause = typeof limit === "number" ? ` LIMIT ${encodeSqlValue(limit)}` : "";
32
+ const offsetClause = typeof offset === "number" ? ` OFFSET ${encodeSqlValue(offset)}` : "";
33
+ return [
34
+ `SELECT ${selectParts.join(", ")}`,
35
+ `FROM ${quoteIdentifierPath(tableName)}`,
36
+ whereClause ? `WHERE ${whereClause}` : "",
37
+ groupClause,
38
+ orderClause,
39
+ limitClause,
40
+ offsetClause,
41
+ ].filter(Boolean).join(" ");
42
+ }
43
+ export function useRecordAggregates({ client, podId, tableName, metrics, groupBy, filters = [], limit, offset, orderBy, enabled = true, autoLoad = true, }) {
44
+ const [rows, setRows] = useState([]);
45
+ const [total, setTotal] = useState(0);
46
+ const [sql, setSql] = useState("");
47
+ const [isLoading, setIsLoading] = useState(false);
48
+ const [error, setError] = useState(null);
49
+ const metricsKey = stringifyComparable(metrics);
50
+ const filtersKey = stringifyComparable(filters);
51
+ const groupByKey = stringifyComparable(groupBy);
52
+ const orderByKey = stringifyComparable(orderBy);
53
+ const stableMetrics = useMemo(() => metrics, [metricsKey]);
54
+ const stableFilters = useMemo(() => filters, [filtersKey]);
55
+ const stableGroupBy = useMemo(() => groupBy, [groupByKey]);
56
+ const stableOrderBy = useMemo(() => orderBy, [orderByKey]);
57
+ const trimmedTableName = tableName.trim();
58
+ const isEnabled = enabled && trimmedTableName.length > 0 && stableMetrics.length > 0;
59
+ const refresh = useCallback(async (signal) => {
60
+ if (!isEnabled) {
61
+ setRows([]);
62
+ setTotal(0);
63
+ setSql("");
64
+ setError(null);
65
+ setIsLoading(false);
66
+ return [];
67
+ }
68
+ setIsLoading(true);
69
+ setError(null);
70
+ try {
71
+ const scopedClient = resolvePodClient(client, podId);
72
+ const nextSql = buildRecordAggregatesQuery({
73
+ tableName: trimmedTableName,
74
+ metrics: stableMetrics,
75
+ groupBy: stableGroupBy,
76
+ filters: stableFilters,
77
+ limit,
78
+ offset,
79
+ orderBy: stableOrderBy,
80
+ });
81
+ setSql(nextSql);
82
+ const response = await scopedClient.datastore.query(nextSql);
83
+ if (signal?.aborted)
84
+ return [];
85
+ const nextRows = (response.items ?? []);
86
+ setRows(nextRows);
87
+ setTotal(response.total ?? nextRows.length);
88
+ return nextRows;
89
+ }
90
+ catch (aggregateError) {
91
+ if (signal?.aborted)
92
+ return [];
93
+ const normalized = normalizeError(aggregateError, "Failed to load record aggregates.");
94
+ setError(normalized);
95
+ return [];
96
+ }
97
+ finally {
98
+ if (!signal?.aborted)
99
+ setIsLoading(false);
100
+ }
101
+ }, [client, isEnabled, limit, offset, podId, stableFilters, stableGroupBy, stableMetrics, stableOrderBy, trimmedTableName]);
102
+ useEffect(() => {
103
+ if (!isEnabled) {
104
+ setRows([]);
105
+ setTotal(0);
106
+ setSql("");
107
+ setError(null);
108
+ setIsLoading(false);
109
+ return;
110
+ }
111
+ if (!autoLoad)
112
+ return;
113
+ const controller = new AbortController();
114
+ void refresh(controller.signal);
115
+ return () => controller.abort();
116
+ }, [autoLoad, isEnabled, refresh]);
117
+ return useMemo(() => ({
118
+ rows,
119
+ row: rows[0] ?? null,
120
+ total,
121
+ sql,
122
+ isLoading,
123
+ error,
124
+ refresh,
125
+ }), [error, isLoading, refresh, rows, sql, total]);
126
+ }
@@ -30,12 +30,13 @@ export function useTaskSession({ client, podId, taskId: externalTaskId = null, a
30
30
  }
31
31
  }, []);
32
32
  const setTaskId = useCallback((nextTaskId) => {
33
- abortRef.current?.abort();
34
- abortRef.current = null;
35
33
  setTaskIdState((currentTaskId) => {
36
34
  if (currentTaskId === nextTaskId) {
37
35
  return currentTaskId;
38
36
  }
37
+ abortRef.current?.abort();
38
+ abortRef.current = null;
39
+ taskIdRef.current = nextTaskId;
39
40
  setError(null);
40
41
  setIsStreaming(false);
41
42
  if (!nextTaskId) {
@@ -0,0 +1,29 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { FileResponse } from "../types.js";
3
+ export interface UpdateFileInput {
4
+ file?: Blob;
5
+ name?: string;
6
+ description?: string;
7
+ directoryPath?: string;
8
+ parentId?: string;
9
+ newPath?: string;
10
+ searchEnabled?: boolean;
11
+ }
12
+ export interface UseUpdateFileOptions {
13
+ client: LemmaClient;
14
+ podId?: string;
15
+ path?: string | null;
16
+ enabled?: boolean;
17
+ onSuccess?: (file: FileResponse) => void;
18
+ onError?: (error: unknown) => void;
19
+ }
20
+ export interface UseUpdateFileResult<TFile extends FileResponse = FileResponse> {
21
+ updatedFile: TFile | null;
22
+ isSubmitting: boolean;
23
+ error: Error | null;
24
+ update: (input?: UpdateFileInput, overrides?: {
25
+ path?: string | null;
26
+ }) => Promise<TFile | null>;
27
+ reset: () => void;
28
+ }
29
+ export declare function useUpdateFile<TFile extends FileResponse = FileResponse>({ client, podId, path, enabled, onSuccess, onError, }: UseUpdateFileOptions): UseUpdateFileResult<TFile>;
@@ -0,0 +1,51 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
3
+ export function useUpdateFile({ client, podId, path = null, enabled = true, onSuccess, onError, }) {
4
+ const [updatedFile, setUpdatedFile] = useState(null);
5
+ const [isSubmitting, setIsSubmitting] = useState(false);
6
+ const [error, setError] = useState(null);
7
+ const onSuccessRef = useRef(onSuccess);
8
+ const onErrorRef = useRef(onError);
9
+ useEffect(() => { onSuccessRef.current = onSuccess; }, [onSuccess]);
10
+ useEffect(() => { onErrorRef.current = onError; }, [onError]);
11
+ const trimmedPath = typeof path === "string" ? path.trim() : "";
12
+ const isEnabled = enabled && trimmedPath.length > 0;
13
+ const update = useCallback(async (input = {}, overrides = {}) => {
14
+ const nextPath = typeof overrides.path === "string"
15
+ ? overrides.path.trim()
16
+ : trimmedPath;
17
+ if (!isEnabled || nextPath.length === 0) {
18
+ return null;
19
+ }
20
+ setIsSubmitting(true);
21
+ setError(null);
22
+ try {
23
+ const scopedClient = resolvePodClient(client, podId);
24
+ const nextFile = await scopedClient.files.update(nextPath, input);
25
+ setUpdatedFile(nextFile);
26
+ onSuccessRef.current?.(nextFile);
27
+ return nextFile;
28
+ }
29
+ catch (updateError) {
30
+ const normalized = normalizeError(updateError, "Failed to update file.");
31
+ setError(normalized);
32
+ onErrorRef.current?.(updateError);
33
+ return null;
34
+ }
35
+ finally {
36
+ setIsSubmitting(false);
37
+ }
38
+ }, [client, isEnabled, podId, trimmedPath]);
39
+ const reset = useCallback(() => {
40
+ setUpdatedFile(null);
41
+ setError(null);
42
+ setIsSubmitting(false);
43
+ }, []);
44
+ return useMemo(() => ({
45
+ updatedFile,
46
+ isSubmitting,
47
+ error,
48
+ update,
49
+ reset,
50
+ }), [error, isSubmitting, reset, update, updatedFile]);
51
+ }
@@ -0,0 +1,24 @@
1
+ import type { LemmaClient } from "../client.js";
2
+ import type { FileResponse } from "../types.js";
3
+ export interface UploadFileInput {
4
+ name?: string;
5
+ directoryPath?: string;
6
+ parentId?: string;
7
+ searchEnabled?: boolean;
8
+ description?: string;
9
+ }
10
+ export interface UseUploadFileOptions {
11
+ client: LemmaClient;
12
+ podId?: string;
13
+ enabled?: boolean;
14
+ onSuccess?: (file: FileResponse) => void;
15
+ onError?: (error: unknown) => void;
16
+ }
17
+ export interface UseUploadFileResult<TFile extends FileResponse = FileResponse> {
18
+ uploadedFile: TFile | null;
19
+ isSubmitting: boolean;
20
+ error: Error | null;
21
+ upload: (file: Blob, options?: UploadFileInput) => Promise<TFile | null>;
22
+ reset: () => void;
23
+ }
24
+ export declare function useUploadFile<TFile extends FileResponse = FileResponse>({ client, podId, enabled, onSuccess, onError, }: UseUploadFileOptions): UseUploadFileResult<TFile>;
@@ -0,0 +1,46 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { normalizeError, resolvePodClient } from "./utils.js";
3
+ export function useUploadFile({ client, podId, enabled = true, onSuccess, onError, }) {
4
+ const [uploadedFile, setUploadedFile] = useState(null);
5
+ const [isSubmitting, setIsSubmitting] = useState(false);
6
+ const [error, setError] = useState(null);
7
+ const onSuccessRef = useRef(onSuccess);
8
+ const onErrorRef = useRef(onError);
9
+ useEffect(() => { onSuccessRef.current = onSuccess; }, [onSuccess]);
10
+ useEffect(() => { onErrorRef.current = onError; }, [onError]);
11
+ const upload = useCallback(async (file, options = {}) => {
12
+ if (!enabled) {
13
+ return null;
14
+ }
15
+ setIsSubmitting(true);
16
+ setError(null);
17
+ try {
18
+ const scopedClient = resolvePodClient(client, podId);
19
+ const nextFile = await scopedClient.files.upload(file, options);
20
+ setUploadedFile(nextFile);
21
+ onSuccessRef.current?.(nextFile);
22
+ return nextFile;
23
+ }
24
+ catch (uploadError) {
25
+ const normalized = normalizeError(uploadError, "Failed to upload file.");
26
+ setError(normalized);
27
+ onErrorRef.current?.(uploadError);
28
+ return null;
29
+ }
30
+ finally {
31
+ setIsSubmitting(false);
32
+ }
33
+ }, [client, enabled, podId]);
34
+ const reset = useCallback(() => {
35
+ setUploadedFile(null);
36
+ setError(null);
37
+ setIsSubmitting(false);
38
+ }, []);
39
+ return useMemo(() => ({
40
+ uploadedFile,
41
+ isSubmitting,
42
+ error,
43
+ upload,
44
+ reset,
45
+ }), [error, isSubmitting, reset, uploadedFile, upload]);
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemma-sdk",
3
- "version": "0.2.34",
3
+ "version": "0.2.36",
4
4
  "description": "Official TypeScript SDK for Lemma pod-scoped APIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,5 +0,0 @@
1
- export type AppDescriptorResponse = {
2
- app_name: string;
3
- description: string;
4
- operation_count: number;
5
- };
@@ -1,6 +0,0 @@
1
- import type { OperationSummary } from './OperationSummary.js';
2
- export type OperationListResponse = {
3
- items: Array<OperationSummary>;
4
- limit: number;
5
- next_page_token?: (string | null);
6
- };