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,33 @@
|
|
|
1
|
+
import type { LemmaClient } from "../client.js";
|
|
2
|
+
import type { FlowRun, FlowStartType, Workflow, WorkflowRunInputs } from "../types.js";
|
|
3
|
+
import { type UseFlowSessionResult } from "./useFlowSession.js";
|
|
4
|
+
export interface UseWorkflowStartOptions {
|
|
5
|
+
client: LemmaClient;
|
|
6
|
+
podId?: string;
|
|
7
|
+
workflowName: string;
|
|
8
|
+
runId?: string | null;
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
autoLoad?: boolean;
|
|
11
|
+
autoPoll?: boolean;
|
|
12
|
+
pollIntervalMs?: number;
|
|
13
|
+
onRun?: (run: FlowRun) => void;
|
|
14
|
+
onError?: (error: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface UseWorkflowStartResult extends Omit<UseFlowSessionResult, "start" | "listHistory"> {
|
|
17
|
+
workflow: Workflow | null;
|
|
18
|
+
startType: FlowStartType | "MANUAL";
|
|
19
|
+
inputSchema: Record<string, unknown> | null;
|
|
20
|
+
inputUiSchema: Record<string, unknown> | null;
|
|
21
|
+
isLoadingWorkflow: boolean;
|
|
22
|
+
isStarting: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
refreshWorkflow: () => Promise<Workflow | null>;
|
|
25
|
+
listHistory: (options?: {
|
|
26
|
+
limit?: number;
|
|
27
|
+
pageToken?: string;
|
|
28
|
+
}) => Promise<FlowRun[]>;
|
|
29
|
+
start: (inputs?: WorkflowRunInputs, options?: {
|
|
30
|
+
forceResume?: boolean;
|
|
31
|
+
}) => Promise<FlowRun>;
|
|
32
|
+
}
|
|
33
|
+
export declare function useWorkflowStart({ client, podId, workflowName, runId, enabled, autoLoad, autoPoll, pollIntervalMs, onRun, onError, }: UseWorkflowStartOptions): UseWorkflowStartResult;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { normalizeRunStatus } from "../run-utils.js";
|
|
3
|
+
import { useFlowSession, } from "./useFlowSession.js";
|
|
4
|
+
function normalizeError(error, fallback) {
|
|
5
|
+
if (error instanceof Error)
|
|
6
|
+
return error;
|
|
7
|
+
return new Error(fallback);
|
|
8
|
+
}
|
|
9
|
+
function resolvePodClient(client, podId) {
|
|
10
|
+
if (!podId || podId === client.podId)
|
|
11
|
+
return client;
|
|
12
|
+
return client.withPod(podId);
|
|
13
|
+
}
|
|
14
|
+
function findFirstFormNode(workflow) {
|
|
15
|
+
if (!workflow?.nodes?.length)
|
|
16
|
+
return null;
|
|
17
|
+
for (const node of workflow.nodes) {
|
|
18
|
+
if (!node || typeof node !== "object")
|
|
19
|
+
continue;
|
|
20
|
+
if (!("config" in node) || typeof node.config !== "object" || !node.config)
|
|
21
|
+
continue;
|
|
22
|
+
if ("input_schema" in node.config) {
|
|
23
|
+
return node;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function isWaitingFlowRun(run) {
|
|
29
|
+
if (!run)
|
|
30
|
+
return false;
|
|
31
|
+
const normalizedStatus = normalizeRunStatus(run.status);
|
|
32
|
+
return normalizedStatus === "WAITING"
|
|
33
|
+
|| !!run.waiting_function_run_id
|
|
34
|
+
|| !!run.waiting_task_id
|
|
35
|
+
|| !!run.waiting_trigger_id;
|
|
36
|
+
}
|
|
37
|
+
export function useWorkflowStart({ client, podId, workflowName, runId = null, enabled = true, autoLoad = true, autoPoll = true, pollIntervalMs = 2000, onRun, onError, }) {
|
|
38
|
+
const [workflow, setWorkflow] = useState(null);
|
|
39
|
+
const [workflowError, setWorkflowError] = useState(null);
|
|
40
|
+
const [isLoadingWorkflow, setIsLoadingWorkflow] = useState(false);
|
|
41
|
+
const [isStarting, setIsStarting] = useState(false);
|
|
42
|
+
const hasWorkflowName = workflowName.trim().length > 0;
|
|
43
|
+
const isEnabled = enabled && hasWorkflowName;
|
|
44
|
+
const session = useFlowSession({
|
|
45
|
+
client,
|
|
46
|
+
podId,
|
|
47
|
+
flowName: workflowName,
|
|
48
|
+
runId,
|
|
49
|
+
autoPoll,
|
|
50
|
+
pollIntervalMs,
|
|
51
|
+
onRun,
|
|
52
|
+
onError,
|
|
53
|
+
});
|
|
54
|
+
const refreshWorkflow = useCallback(async () => {
|
|
55
|
+
if (!isEnabled) {
|
|
56
|
+
setWorkflow(null);
|
|
57
|
+
setWorkflowError(null);
|
|
58
|
+
setIsLoadingWorkflow(false);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
setIsLoadingWorkflow(true);
|
|
62
|
+
setWorkflowError(null);
|
|
63
|
+
try {
|
|
64
|
+
const scopedClient = resolvePodClient(client, podId);
|
|
65
|
+
const nextWorkflow = await scopedClient.workflows.get(workflowName);
|
|
66
|
+
setWorkflow(nextWorkflow);
|
|
67
|
+
return nextWorkflow;
|
|
68
|
+
}
|
|
69
|
+
catch (refreshError) {
|
|
70
|
+
const normalized = normalizeError(refreshError, "Failed to fetch workflow definition.");
|
|
71
|
+
setWorkflowError(normalized);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
setIsLoadingWorkflow(false);
|
|
76
|
+
}
|
|
77
|
+
}, [client, isEnabled, podId, workflowName]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!isEnabled) {
|
|
80
|
+
setWorkflow(null);
|
|
81
|
+
setWorkflowError(null);
|
|
82
|
+
setIsLoadingWorkflow(false);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!autoLoad)
|
|
86
|
+
return;
|
|
87
|
+
void refreshWorkflow();
|
|
88
|
+
}, [autoLoad, isEnabled, refreshWorkflow]);
|
|
89
|
+
const listHistory = useCallback((options = {}) => {
|
|
90
|
+
return session.listHistory({
|
|
91
|
+
flowName: workflowName,
|
|
92
|
+
limit: options.limit,
|
|
93
|
+
pageToken: options.pageToken,
|
|
94
|
+
});
|
|
95
|
+
}, [session, workflowName]);
|
|
96
|
+
const start = useCallback(async (inputs = {}, options = {}) => {
|
|
97
|
+
if (!hasWorkflowName) {
|
|
98
|
+
const missingWorkflowError = new Error("workflowName is required.");
|
|
99
|
+
setWorkflowError(missingWorkflowError);
|
|
100
|
+
throw missingWorkflowError;
|
|
101
|
+
}
|
|
102
|
+
setIsStarting(true);
|
|
103
|
+
setWorkflowError(null);
|
|
104
|
+
try {
|
|
105
|
+
const currentWorkflow = workflow?.name === workflowName
|
|
106
|
+
? workflow
|
|
107
|
+
: await refreshWorkflow();
|
|
108
|
+
const startType = currentWorkflow?.start?.type ?? "MANUAL";
|
|
109
|
+
if (startType === "MANUAL") {
|
|
110
|
+
const created = await session.start({
|
|
111
|
+
flowName: workflowName,
|
|
112
|
+
inputs: {},
|
|
113
|
+
});
|
|
114
|
+
const shouldResume = !!created.id && (options.forceResume === true
|
|
115
|
+
|| Object.keys(inputs).length > 0
|
|
116
|
+
|| isWaitingFlowRun(created));
|
|
117
|
+
if (!shouldResume || !created.id) {
|
|
118
|
+
return created;
|
|
119
|
+
}
|
|
120
|
+
return session.resume({
|
|
121
|
+
runId: created.id,
|
|
122
|
+
inputs,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return session.start({
|
|
126
|
+
flowName: workflowName,
|
|
127
|
+
inputs,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (startError) {
|
|
131
|
+
const normalized = normalizeError(startError, "Failed to start workflow.");
|
|
132
|
+
setWorkflowError(normalized);
|
|
133
|
+
throw normalized;
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
setIsStarting(false);
|
|
137
|
+
}
|
|
138
|
+
}, [hasWorkflowName, refreshWorkflow, session, workflow, workflowName]);
|
|
139
|
+
const formNode = findFirstFormNode(workflow);
|
|
140
|
+
const startType = workflow?.start?.type ?? "MANUAL";
|
|
141
|
+
const error = workflowError ?? session.error;
|
|
142
|
+
return {
|
|
143
|
+
...session,
|
|
144
|
+
workflow,
|
|
145
|
+
startType,
|
|
146
|
+
inputSchema: formNode?.config.input_schema ?? null,
|
|
147
|
+
inputUiSchema: formNode?.config.ui_schema ?? null,
|
|
148
|
+
isLoadingWorkflow,
|
|
149
|
+
isStarting,
|
|
150
|
+
error,
|
|
151
|
+
refreshWorkflow,
|
|
152
|
+
listHistory,
|
|
153
|
+
start,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ForeignKeyReference } from "./datastore-query.js";
|
|
2
|
+
import { type ColumnSchema, type Table } from "./types.js";
|
|
3
|
+
export type RecordSchemaFieldKind = "text" | "textarea" | "number" | "boolean" | "json" | "date" | "datetime" | "select" | "foreign-key" | "uuid";
|
|
4
|
+
export interface RecordSchemaField {
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
kind: RecordSchemaFieldKind;
|
|
8
|
+
column: ColumnSchema;
|
|
9
|
+
required: boolean;
|
|
10
|
+
readOnly: boolean;
|
|
11
|
+
system: boolean;
|
|
12
|
+
computed: boolean;
|
|
13
|
+
auto: boolean;
|
|
14
|
+
options: string[];
|
|
15
|
+
foreignKey: ForeignKeyReference | null;
|
|
16
|
+
}
|
|
17
|
+
export interface BuildRecordPayloadOptions {
|
|
18
|
+
mode?: "create" | "update";
|
|
19
|
+
}
|
|
20
|
+
export interface BuildRecordPayloadResult {
|
|
21
|
+
data: Record<string, unknown>;
|
|
22
|
+
errors: Record<string, string>;
|
|
23
|
+
isValid: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function getRecordFieldKind(column: ColumnSchema): RecordSchemaFieldKind;
|
|
26
|
+
export declare function buildRecordSchemaFields(table: Table): RecordSchemaField[];
|
|
27
|
+
export declare function getEditableRecordFields(table: Table): RecordSchemaField[];
|
|
28
|
+
export declare function formatRecordValueForForm(column: ColumnSchema, value: unknown): unknown;
|
|
29
|
+
export declare function buildRecordFormValues(table: Table, values?: Record<string, unknown>): Record<string, unknown>;
|
|
30
|
+
export declare function buildRecordPayload(table: Table, values: Record<string, unknown>, options?: BuildRecordPayloadOptions): BuildRecordPayloadResult;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { parseForeignKeyReference } from "./datastore-query.js";
|
|
2
|
+
import { DatastoreDataType } from "./types.js";
|
|
3
|
+
function sentenceCase(value) {
|
|
4
|
+
return value
|
|
5
|
+
.replace(/_/g, " ")
|
|
6
|
+
.replace(/\s+/g, " ")
|
|
7
|
+
.trim()
|
|
8
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
9
|
+
}
|
|
10
|
+
function isBlank(value) {
|
|
11
|
+
return value === null || typeof value === "undefined" || (typeof value === "string" && value.trim() === "");
|
|
12
|
+
}
|
|
13
|
+
function isIntegerLike(value) {
|
|
14
|
+
return Number.isInteger(value);
|
|
15
|
+
}
|
|
16
|
+
function getRawColumnDefault(column) {
|
|
17
|
+
return column.default;
|
|
18
|
+
}
|
|
19
|
+
function shouldUseTextarea(column) {
|
|
20
|
+
if (typeof column.max_length === "number" && column.max_length >= 240) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return /(description|content|body|note|instruction|summary|message)/i.test(column.name);
|
|
24
|
+
}
|
|
25
|
+
function toDateInputValue(value) {
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
return value.includes("T") ? value.slice(0, 10) : value;
|
|
28
|
+
}
|
|
29
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
30
|
+
return value.toISOString().slice(0, 10);
|
|
31
|
+
}
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
function toDateTimeLocalInputValue(value) {
|
|
35
|
+
if (typeof value === "string") {
|
|
36
|
+
const timestamp = new Date(value);
|
|
37
|
+
if (!Number.isNaN(timestamp.getTime())) {
|
|
38
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
39
|
+
return [
|
|
40
|
+
timestamp.getFullYear(),
|
|
41
|
+
pad(timestamp.getMonth() + 1),
|
|
42
|
+
pad(timestamp.getDate()),
|
|
43
|
+
].join("-") + `T${pad(timestamp.getHours())}:${pad(timestamp.getMinutes())}`;
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
48
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
49
|
+
return [
|
|
50
|
+
value.getFullYear(),
|
|
51
|
+
pad(value.getMonth() + 1),
|
|
52
|
+
pad(value.getDate()),
|
|
53
|
+
].join("-") + `T${pad(value.getHours())}:${pad(value.getMinutes())}`;
|
|
54
|
+
}
|
|
55
|
+
return String(value);
|
|
56
|
+
}
|
|
57
|
+
export function getRecordFieldKind(column) {
|
|
58
|
+
if (column.foreign_key?.references)
|
|
59
|
+
return "foreign-key";
|
|
60
|
+
if (column.type === DatastoreDataType.ENUM)
|
|
61
|
+
return "select";
|
|
62
|
+
if (column.type === DatastoreDataType.BOOLEAN)
|
|
63
|
+
return "boolean";
|
|
64
|
+
if (column.type === DatastoreDataType.JSON || column.type === DatastoreDataType.VECTOR)
|
|
65
|
+
return "json";
|
|
66
|
+
if (column.type === DatastoreDataType.INTEGER
|
|
67
|
+
|| column.type === DatastoreDataType.FLOAT
|
|
68
|
+
|| column.type === DatastoreDataType.SERIAL) {
|
|
69
|
+
return "number";
|
|
70
|
+
}
|
|
71
|
+
if (column.type === DatastoreDataType.DATE)
|
|
72
|
+
return "date";
|
|
73
|
+
if (column.type === DatastoreDataType.DATETIME)
|
|
74
|
+
return "datetime";
|
|
75
|
+
if (column.type === DatastoreDataType.UUID)
|
|
76
|
+
return "uuid";
|
|
77
|
+
if (column.type === DatastoreDataType.TEXT && shouldUseTextarea(column))
|
|
78
|
+
return "textarea";
|
|
79
|
+
return "text";
|
|
80
|
+
}
|
|
81
|
+
export function buildRecordSchemaFields(table) {
|
|
82
|
+
return table.columns.map((column) => {
|
|
83
|
+
const foreignKey = column.foreign_key?.references
|
|
84
|
+
? parseForeignKeyReference(column.foreign_key.references)
|
|
85
|
+
: null;
|
|
86
|
+
return {
|
|
87
|
+
name: column.name,
|
|
88
|
+
label: sentenceCase(column.name),
|
|
89
|
+
kind: getRecordFieldKind(column),
|
|
90
|
+
column,
|
|
91
|
+
required: column.required === true,
|
|
92
|
+
readOnly: column.system === true || column.computed === true || column.auto === true,
|
|
93
|
+
system: column.system === true,
|
|
94
|
+
computed: column.computed === true,
|
|
95
|
+
auto: column.auto === true,
|
|
96
|
+
options: column.options ?? [],
|
|
97
|
+
foreignKey,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export function getEditableRecordFields(table) {
|
|
102
|
+
return buildRecordSchemaFields(table).filter((field) => !field.readOnly);
|
|
103
|
+
}
|
|
104
|
+
export function formatRecordValueForForm(column, value) {
|
|
105
|
+
const kind = getRecordFieldKind(column);
|
|
106
|
+
if (value === null || typeof value === "undefined") {
|
|
107
|
+
if (kind === "boolean")
|
|
108
|
+
return false;
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
if (kind === "boolean") {
|
|
112
|
+
return Boolean(value);
|
|
113
|
+
}
|
|
114
|
+
if (kind === "json") {
|
|
115
|
+
if (typeof value === "string")
|
|
116
|
+
return value;
|
|
117
|
+
try {
|
|
118
|
+
return JSON.stringify(value, null, 2);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return String(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (kind === "date") {
|
|
125
|
+
return toDateInputValue(value);
|
|
126
|
+
}
|
|
127
|
+
if (kind === "datetime") {
|
|
128
|
+
return toDateTimeLocalInputValue(value);
|
|
129
|
+
}
|
|
130
|
+
if (kind === "number") {
|
|
131
|
+
return String(value);
|
|
132
|
+
}
|
|
133
|
+
return String(value);
|
|
134
|
+
}
|
|
135
|
+
export function buildRecordFormValues(table, values = {}) {
|
|
136
|
+
const next = {};
|
|
137
|
+
buildRecordSchemaFields(table).forEach((field) => {
|
|
138
|
+
const provided = values[field.name];
|
|
139
|
+
const rawDefault = getRawColumnDefault(field.column);
|
|
140
|
+
const source = typeof provided !== "undefined" ? provided : rawDefault;
|
|
141
|
+
next[field.name] = formatRecordValueForForm(field.column, source);
|
|
142
|
+
});
|
|
143
|
+
return next;
|
|
144
|
+
}
|
|
145
|
+
function coerceEmptyValue(mode, name, target) {
|
|
146
|
+
if (mode === "update") {
|
|
147
|
+
target[name] = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function buildRecordPayload(table, values, options = {}) {
|
|
151
|
+
const mode = options.mode ?? "create";
|
|
152
|
+
const data = {};
|
|
153
|
+
const errors = {};
|
|
154
|
+
getEditableRecordFields(table).forEach((field) => {
|
|
155
|
+
const rawValue = values[field.name];
|
|
156
|
+
const value = typeof rawValue === "string" ? rawValue : rawValue;
|
|
157
|
+
if (field.kind === "boolean") {
|
|
158
|
+
data[field.name] = Boolean(rawValue);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (isBlank(value)) {
|
|
162
|
+
if (field.required) {
|
|
163
|
+
errors[field.name] = `${field.label} is required.`;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
coerceEmptyValue(mode, field.name, data);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (field.kind === "number") {
|
|
170
|
+
const parsed = Number(value);
|
|
171
|
+
if (!Number.isFinite(parsed)) {
|
|
172
|
+
errors[field.name] = `${field.label} must be a valid number.`;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if ((field.column.type === DatastoreDataType.INTEGER || field.column.type === DatastoreDataType.SERIAL)
|
|
176
|
+
&& !isIntegerLike(parsed)) {
|
|
177
|
+
errors[field.name] = `${field.label} must be a whole number.`;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
data[field.name] = parsed;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (field.kind === "json") {
|
|
184
|
+
try {
|
|
185
|
+
data[field.name] = typeof value === "string" ? JSON.parse(value) : value;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
errors[field.name] = `${field.label} must be valid JSON.`;
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
data[field.name] = typeof value === "string" ? value : String(value);
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
data,
|
|
196
|
+
errors,
|
|
197
|
+
isValid: Object.keys(errors).length === 0,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type JsonSchemaPrimitiveType = "string" | "number" | "integer" | "boolean" | "object" | "array" | "null";
|
|
2
|
+
export interface JsonSchemaLike {
|
|
3
|
+
type?: JsonSchemaPrimitiveType | JsonSchemaPrimitiveType[];
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
format?: string;
|
|
7
|
+
default?: unknown;
|
|
8
|
+
enum?: unknown[];
|
|
9
|
+
properties?: Record<string, JsonSchemaLike>;
|
|
10
|
+
required?: string[];
|
|
11
|
+
items?: JsonSchemaLike;
|
|
12
|
+
anyOf?: JsonSchemaLike[];
|
|
13
|
+
oneOf?: JsonSchemaLike[];
|
|
14
|
+
allOf?: JsonSchemaLike[];
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
export type SchemaFormFieldKind = "text" | "textarea" | "number" | "boolean" | "select" | "json" | "date" | "datetime" | "email";
|
|
18
|
+
export interface SchemaFormField {
|
|
19
|
+
name: string;
|
|
20
|
+
label: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
kind: SchemaFormFieldKind;
|
|
24
|
+
type: JsonSchemaPrimitiveType | "unknown";
|
|
25
|
+
format?: string;
|
|
26
|
+
options: Array<{
|
|
27
|
+
label: string;
|
|
28
|
+
value: string;
|
|
29
|
+
}>;
|
|
30
|
+
defaultValue?: unknown;
|
|
31
|
+
schema: JsonSchemaLike;
|
|
32
|
+
}
|
|
33
|
+
export interface BuildSchemaFormPayloadResult {
|
|
34
|
+
data: Record<string, unknown>;
|
|
35
|
+
errors: Record<string, string>;
|
|
36
|
+
isValid: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function buildSchemaFormFields(schema: JsonSchemaLike | null | undefined, uiSchema?: Record<string, unknown> | null): SchemaFormField[];
|
|
39
|
+
export declare function formatSchemaFieldValueForForm(field: SchemaFormField, value: unknown): unknown;
|
|
40
|
+
export declare function buildSchemaFormValues(schema: JsonSchemaLike | null | undefined, values?: Record<string, unknown>, uiSchema?: Record<string, unknown> | null): Record<string, unknown>;
|
|
41
|
+
export declare function buildSchemaFormPayload(schema: JsonSchemaLike | null | undefined, values: Record<string, unknown>, uiSchema?: Record<string, unknown> | null): BuildSchemaFormPayloadResult;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
function sentenceCase(value) {
|
|
2
|
+
return value
|
|
3
|
+
.replace(/_/g, " ")
|
|
4
|
+
.replace(/\s+/g, " ")
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/\b\w/g, (match) => match.toUpperCase());
|
|
7
|
+
}
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function isBlank(value) {
|
|
12
|
+
return value === null || typeof value === "undefined" || (typeof value === "string" && value.trim() === "");
|
|
13
|
+
}
|
|
14
|
+
function normalizeSchemaType(schema) {
|
|
15
|
+
if (Array.isArray(schema.type)) {
|
|
16
|
+
const nonNull = schema.type.find((entry) => entry !== "null");
|
|
17
|
+
return nonNull ?? "unknown";
|
|
18
|
+
}
|
|
19
|
+
if (typeof schema.type === "string") {
|
|
20
|
+
return schema.type;
|
|
21
|
+
}
|
|
22
|
+
if (schema.enum?.length) {
|
|
23
|
+
return "string";
|
|
24
|
+
}
|
|
25
|
+
if (schema.properties && isRecord(schema.properties)) {
|
|
26
|
+
return "object";
|
|
27
|
+
}
|
|
28
|
+
if (schema.items) {
|
|
29
|
+
return "array";
|
|
30
|
+
}
|
|
31
|
+
return "unknown";
|
|
32
|
+
}
|
|
33
|
+
function getSchemaFieldKind(schema) {
|
|
34
|
+
if (schema.enum?.length)
|
|
35
|
+
return "select";
|
|
36
|
+
const type = normalizeSchemaType(schema);
|
|
37
|
+
const format = typeof schema.format === "string" ? schema.format : undefined;
|
|
38
|
+
if (type === "boolean")
|
|
39
|
+
return "boolean";
|
|
40
|
+
if (type === "number" || type === "integer")
|
|
41
|
+
return "number";
|
|
42
|
+
if (format === "date")
|
|
43
|
+
return "date";
|
|
44
|
+
if (format === "date-time")
|
|
45
|
+
return "datetime";
|
|
46
|
+
if (format === "email")
|
|
47
|
+
return "email";
|
|
48
|
+
if (type === "object" || type === "array")
|
|
49
|
+
return "json";
|
|
50
|
+
if (format === "textarea")
|
|
51
|
+
return "textarea";
|
|
52
|
+
const title = `${schema.title ?? ""} ${schema.description ?? ""}`.toLowerCase();
|
|
53
|
+
if (/(description|body|content|message|note|summary|instructions)/.test(title)) {
|
|
54
|
+
return "textarea";
|
|
55
|
+
}
|
|
56
|
+
return "text";
|
|
57
|
+
}
|
|
58
|
+
function formatDateInputValue(value) {
|
|
59
|
+
if (typeof value === "string") {
|
|
60
|
+
return value.includes("T") ? value.slice(0, 10) : value;
|
|
61
|
+
}
|
|
62
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
63
|
+
return value.toISOString().slice(0, 10);
|
|
64
|
+
}
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
67
|
+
function formatDateTimeLocalInputValue(value) {
|
|
68
|
+
const date = value instanceof Date ? value : new Date(String(value));
|
|
69
|
+
if (Number.isNaN(date.getTime())) {
|
|
70
|
+
return String(value);
|
|
71
|
+
}
|
|
72
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
73
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
74
|
+
}
|
|
75
|
+
export function buildSchemaFormFields(schema, uiSchema) {
|
|
76
|
+
if (!schema || !isRecord(schema.properties))
|
|
77
|
+
return [];
|
|
78
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
79
|
+
const order = Array.isArray(uiSchema?.["ui:order"])
|
|
80
|
+
? (uiSchema?.["ui:order"]).filter((entry) => typeof entry === "string")
|
|
81
|
+
: [];
|
|
82
|
+
const orderIndex = new Map(order.map((name, index) => [name, index]));
|
|
83
|
+
const fields = Object.entries(schema.properties).map(([name, propertySchema]) => {
|
|
84
|
+
const property = isRecord(propertySchema) ? propertySchema : {};
|
|
85
|
+
const uiFieldConfig = isRecord(uiSchema?.[name]) ? uiSchema?.[name] : {};
|
|
86
|
+
const widget = typeof uiFieldConfig["ui:widget"] === "string" ? uiFieldConfig["ui:widget"] : undefined;
|
|
87
|
+
const kind = widget === "textarea" ? "textarea" : getSchemaFieldKind(property);
|
|
88
|
+
return {
|
|
89
|
+
name,
|
|
90
|
+
label: typeof property.title === "string" && property.title.trim().length > 0
|
|
91
|
+
? property.title
|
|
92
|
+
: sentenceCase(name),
|
|
93
|
+
description: typeof property.description === "string" ? property.description : undefined,
|
|
94
|
+
required: required.includes(name),
|
|
95
|
+
kind,
|
|
96
|
+
type: normalizeSchemaType(property),
|
|
97
|
+
format: typeof property.format === "string" ? property.format : undefined,
|
|
98
|
+
options: (property.enum ?? []).map((value) => ({
|
|
99
|
+
label: typeof value === "string" ? value : JSON.stringify(value),
|
|
100
|
+
value: typeof value === "string" ? value : JSON.stringify(value),
|
|
101
|
+
})),
|
|
102
|
+
defaultValue: property.default,
|
|
103
|
+
schema: property,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
return fields.sort((left, right) => {
|
|
107
|
+
const leftIndex = orderIndex.get(left.name);
|
|
108
|
+
const rightIndex = orderIndex.get(right.name);
|
|
109
|
+
if (typeof leftIndex === "number" && typeof rightIndex === "number") {
|
|
110
|
+
return leftIndex - rightIndex;
|
|
111
|
+
}
|
|
112
|
+
if (typeof leftIndex === "number")
|
|
113
|
+
return -1;
|
|
114
|
+
if (typeof rightIndex === "number")
|
|
115
|
+
return 1;
|
|
116
|
+
return left.label.localeCompare(right.label);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function formatSchemaFieldValueForForm(field, value) {
|
|
120
|
+
if (value === null || typeof value === "undefined") {
|
|
121
|
+
if (field.kind === "boolean")
|
|
122
|
+
return false;
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
if (field.kind === "boolean")
|
|
126
|
+
return Boolean(value);
|
|
127
|
+
if (field.kind === "number")
|
|
128
|
+
return String(value);
|
|
129
|
+
if (field.kind === "date")
|
|
130
|
+
return formatDateInputValue(value);
|
|
131
|
+
if (field.kind === "datetime")
|
|
132
|
+
return formatDateTimeLocalInputValue(value);
|
|
133
|
+
if (field.kind === "json") {
|
|
134
|
+
if (typeof value === "string")
|
|
135
|
+
return value;
|
|
136
|
+
try {
|
|
137
|
+
return JSON.stringify(value, null, 2);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return String(value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return String(value);
|
|
144
|
+
}
|
|
145
|
+
export function buildSchemaFormValues(schema, values = {}, uiSchema) {
|
|
146
|
+
const fields = buildSchemaFormFields(schema, uiSchema);
|
|
147
|
+
const next = {};
|
|
148
|
+
fields.forEach((field) => {
|
|
149
|
+
const provided = values[field.name];
|
|
150
|
+
const source = typeof provided !== "undefined" ? provided : field.defaultValue;
|
|
151
|
+
next[field.name] = formatSchemaFieldValueForForm(field, source);
|
|
152
|
+
});
|
|
153
|
+
return next;
|
|
154
|
+
}
|
|
155
|
+
export function buildSchemaFormPayload(schema, values, uiSchema) {
|
|
156
|
+
const fields = buildSchemaFormFields(schema, uiSchema);
|
|
157
|
+
const data = {};
|
|
158
|
+
const errors = {};
|
|
159
|
+
fields.forEach((field) => {
|
|
160
|
+
const rawValue = values[field.name];
|
|
161
|
+
if (field.kind === "boolean") {
|
|
162
|
+
data[field.name] = Boolean(rawValue);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (isBlank(rawValue)) {
|
|
166
|
+
if (field.required) {
|
|
167
|
+
errors[field.name] = `${field.label} is required.`;
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (field.kind === "number") {
|
|
172
|
+
const parsed = Number(rawValue);
|
|
173
|
+
if (!Number.isFinite(parsed)) {
|
|
174
|
+
errors[field.name] = `${field.label} must be a valid number.`;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (field.type === "integer" && !Number.isInteger(parsed)) {
|
|
178
|
+
errors[field.name] = `${field.label} must be a whole number.`;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
data[field.name] = parsed;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (field.kind === "json") {
|
|
185
|
+
try {
|
|
186
|
+
data[field.name] = typeof rawValue === "string" ? JSON.parse(rawValue) : rawValue;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
errors[field.name] = `${field.label} must be valid JSON.`;
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
data[field.name] = rawValue;
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
data,
|
|
197
|
+
errors,
|
|
198
|
+
isValid: Object.keys(errors).length === 0,
|
|
199
|
+
};
|
|
200
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentResponse, AssistantResponse, AvailableModels, ConversationMessageResponse, ConversationResponse, CreateAgentRequest, CreateAssistantRequest, CreateTaskRequest, FlowRunEntity, FunctionRunResponse, IconUploadResponse, OrganizationInvitationResponse, OrganizationMemberResponse, OrganizationResponse, PodConfigResponse, PodJoinRequestCreateResponse, PodMemberResponse, PodResponse, TaskMessageResponse, TaskResponse, UpdateAgentRequest, UpdateAssistantRequest, UserResponse } from "./openapi_client/index.js";
|
|
1
|
+
import type { AgentResponse, AssistantResponse, AvailableModels, ColumnSchema, ConversationMessageResponse, ConversationResponse, CreateAgentRequest, CreateAssistantRequest, CreateTaskRequest, DatastoreQueryResponse, FlowRunEntity, FlowResponse, FunctionRunResponse, IconUploadResponse, OrganizationInvitationResponse, OrganizationMemberResponse, OrganizationResponse, PodConfigResponse, PodJoinRequestCreateResponse, PodMemberResponse, PodResponse, TableResponse, TaskMessageResponse, TaskResponse, UpdateAgentRequest, UpdateAssistantRequest, UserResponse } from "./openapi_client/index.js";
|
|
2
2
|
/** Public ergonomic types. */
|
|
3
3
|
export interface PageResult<T> {
|
|
4
4
|
items: T[];
|
|
@@ -51,6 +51,10 @@ export type Task = TaskResponse;
|
|
|
51
51
|
export type TaskMessage = TaskMessageResponse;
|
|
52
52
|
export type FunctionRun = FunctionRunResponse;
|
|
53
53
|
export type FlowRun = FlowRunEntity;
|
|
54
|
+
export type Workflow = FlowResponse;
|
|
55
|
+
export type Table = TableResponse;
|
|
56
|
+
export type TableColumn = ColumnSchema;
|
|
57
|
+
export type DatastoreQueryResult = DatastoreQueryResponse;
|
|
54
58
|
export type Pod = PodResponse;
|
|
55
59
|
export type PodConfig = PodConfigResponse;
|
|
56
60
|
export type PodMember = PodMemberResponse;
|