mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
table tools
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Command } from 'cmdk'
|
||||
import { Database, HelpCircle, Layout, Settings } from 'lucide-react'
|
||||
import { Database, HelpCircle, Layout, Settings, Table } from 'lucide-react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { Library } from '@/components/emcn'
|
||||
@@ -119,14 +119,13 @@ export function SearchModal({
|
||||
href: `/workspace/${workspaceId}/knowledge`,
|
||||
hidden: permissionConfig.hideKnowledgeBaseTab,
|
||||
},
|
||||
// TODO: Uncomment when working on tables
|
||||
// {
|
||||
// id: 'tables',
|
||||
// name: 'Tables',
|
||||
// icon: Table,
|
||||
// href: `/workspace/${workspaceId}/tables`,
|
||||
// hidden: permissionConfig.hideTablesTab,
|
||||
// },
|
||||
{
|
||||
id: 'tables',
|
||||
name: 'Tables',
|
||||
icon: Table,
|
||||
href: `/workspace/${workspaceId}/tables`,
|
||||
hidden: permissionConfig.hideTablesTab,
|
||||
},
|
||||
{
|
||||
id: 'help',
|
||||
name: 'Help',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { Database, HelpCircle, Layout, MessageSquare, Plus, Search, Settings } from 'lucide-react'
|
||||
import { Database, HelpCircle, Layout, MessageSquare, Plus, Search, Settings, Table } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||
import { Button, Download, FolderPlus, Library, Loader, Tooltip } from '@/components/emcn'
|
||||
@@ -274,14 +274,13 @@ export const Sidebar = memo(function Sidebar() {
|
||||
href: `/workspace/${workspaceId}/knowledge`,
|
||||
hidden: permissionConfig.hideKnowledgeBaseTab,
|
||||
},
|
||||
// TODO: Uncomment when working on tables
|
||||
// {
|
||||
// id: 'tables',
|
||||
// label: 'Tables',
|
||||
// icon: Table,
|
||||
// href: `/workspace/${workspaceId}/tables`,
|
||||
// hidden: permissionConfig.hideTablesTab,
|
||||
// },
|
||||
{
|
||||
id: 'tables',
|
||||
label: 'Tables',
|
||||
icon: Table,
|
||||
href: `/workspace/${workspaceId}/tables`,
|
||||
hidden: permissionConfig.hideTablesTab,
|
||||
},
|
||||
{
|
||||
id: 'help',
|
||||
label: 'Help',
|
||||
|
||||
@@ -340,6 +340,7 @@ const SERVER_TOOLS = new Set<string>([
|
||||
'set_environment_variables',
|
||||
'make_api_request',
|
||||
'knowledge_base',
|
||||
'user_table',
|
||||
])
|
||||
|
||||
const SIM_WORKFLOW_TOOL_HANDLERS: Record<
|
||||
@@ -497,7 +498,10 @@ async function executeServerToolDirect(
|
||||
enrichedParams.workflowId = context.workflowId
|
||||
}
|
||||
|
||||
const result = await routeExecution(toolName, enrichedParams, { userId: context.userId })
|
||||
const result = await routeExecution(toolName, enrichedParams, {
|
||||
userId: context.userId,
|
||||
workspaceId: context.workspaceId,
|
||||
})
|
||||
return { success: true, output: result }
|
||||
} catch (error) {
|
||||
logger.error('Server tool execution failed', {
|
||||
|
||||
@@ -624,6 +624,21 @@ Supports full and partial execution:
|
||||
},
|
||||
annotations: { destructiveHint: false },
|
||||
},
|
||||
{
|
||||
name: 'sim_table',
|
||||
agentId: 'table',
|
||||
description:
|
||||
'Manage user-defined tables for structured data storage. Supports creating tables with typed schemas, inserting/updating/deleting rows, querying with filters and sorting, and batch operations.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
request: { type: 'string' },
|
||||
context: { type: 'object' },
|
||||
},
|
||||
required: ['request'],
|
||||
},
|
||||
annotations: { destructiveHint: false },
|
||||
},
|
||||
{
|
||||
name: 'sim_custom_tool',
|
||||
agentId: 'custom_tool',
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { z } from 'zod'
|
||||
|
||||
export interface ServerToolContext {
|
||||
userId: string
|
||||
workspaceId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getBlocksMetadataServerTool } from '@/lib/copilot/tools/server/blocks/g
|
||||
import { getTriggerBlocksServerTool } from '@/lib/copilot/tools/server/blocks/get-trigger-blocks'
|
||||
import { searchDocumentationServerTool } from '@/lib/copilot/tools/server/docs/search-documentation'
|
||||
import { knowledgeBaseServerTool } from '@/lib/copilot/tools/server/knowledge/knowledge-base'
|
||||
import { userTableServerTool } from '@/lib/copilot/tools/server/table/user-table'
|
||||
import { makeApiRequestServerTool } from '@/lib/copilot/tools/server/other/make-api-request'
|
||||
import { searchOnlineServerTool } from '@/lib/copilot/tools/server/other/search-online'
|
||||
import { getCredentialsServerTool } from '@/lib/copilot/tools/server/user/get-credentials'
|
||||
@@ -29,6 +30,7 @@ const serverToolRegistry: Record<string, BaseServerTool> = {
|
||||
[getCredentialsServerTool.name]: getCredentialsServerTool,
|
||||
[makeApiRequestServerTool.name]: makeApiRequestServerTool,
|
||||
[knowledgeBaseServerTool.name]: knowledgeBaseServerTool,
|
||||
[userTableServerTool.name]: userTableServerTool,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
371
apps/sim/lib/copilot/tools/server/table/user-table.ts
Normal file
371
apps/sim/lib/copilot/tools/server/table/user-table.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { BaseServerTool, ServerToolContext } from '@/lib/copilot/tools/server/base-tool'
|
||||
import type { UserTableArgs, UserTableResult } from '@/lib/copilot/tools/shared/schemas'
|
||||
import {
|
||||
createTable,
|
||||
deleteTable,
|
||||
getTableById,
|
||||
listTables,
|
||||
insertRow,
|
||||
batchInsertRows,
|
||||
getRowById,
|
||||
queryRows,
|
||||
updateRow,
|
||||
deleteRow,
|
||||
updateRowsByFilter,
|
||||
deleteRowsByFilter,
|
||||
} from '@/lib/table/service'
|
||||
|
||||
const logger = createLogger('UserTableServerTool')
|
||||
|
||||
export const userTableServerTool: BaseServerTool<UserTableArgs, UserTableResult> = {
|
||||
name: 'user_table',
|
||||
async execute(
|
||||
params: UserTableArgs,
|
||||
context?: ServerToolContext
|
||||
): Promise<UserTableResult> {
|
||||
if (!context?.userId) {
|
||||
logger.error('Unauthorized attempt to access user table - no authenticated user context')
|
||||
throw new Error('Authentication required')
|
||||
}
|
||||
|
||||
const { operation, args = {} } = params
|
||||
const workspaceId =
|
||||
context.workspaceId ||
|
||||
((args as Record<string, unknown>).workspaceId as string | undefined)
|
||||
|
||||
try {
|
||||
switch (operation) {
|
||||
case 'create': {
|
||||
if (!args.name) {
|
||||
return { success: false, message: 'Name is required for creating a table' }
|
||||
}
|
||||
if (!args.schema) {
|
||||
return { success: false, message: 'Schema is required for creating a table' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const table = await createTable(
|
||||
{
|
||||
name: args.name,
|
||||
description: args.description,
|
||||
schema: args.schema,
|
||||
workspaceId,
|
||||
userId: context.userId,
|
||||
},
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Created table "${table.name}" (${table.id})`,
|
||||
data: { table },
|
||||
}
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const tables = await listTables(workspaceId)
|
||||
return {
|
||||
success: true,
|
||||
message: `Found ${tables.length} table(s)`,
|
||||
data: { tables, totalCount: tables.length },
|
||||
}
|
||||
}
|
||||
|
||||
case 'get': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
|
||||
const table = await getTableById(args.tableId)
|
||||
if (!table) {
|
||||
return { success: false, message: `Table not found: ${args.tableId}` }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Table "${table.name}" has ${table.rowCount} rows`,
|
||||
data: { table },
|
||||
}
|
||||
}
|
||||
|
||||
case 'get_schema': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
|
||||
const table = await getTableById(args.tableId)
|
||||
if (!table) {
|
||||
return { success: false, message: `Table not found: ${args.tableId}` }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Schema for "${table.name}"`,
|
||||
data: { name: table.name, columns: table.schema.columns },
|
||||
}
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
await deleteTable(args.tableId, requestId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Deleted table ${args.tableId}`,
|
||||
}
|
||||
}
|
||||
|
||||
case 'insert_row': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.data) {
|
||||
return { success: false, message: 'Data is required for inserting a row' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const table = await getTableById(args.tableId)
|
||||
if (!table) {
|
||||
return { success: false, message: `Table not found: ${args.tableId}` }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const row = await insertRow(
|
||||
{ tableId: args.tableId, data: args.data, workspaceId },
|
||||
table,
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Inserted row ${row.id}`,
|
||||
data: { row },
|
||||
}
|
||||
}
|
||||
|
||||
case 'batch_insert_rows': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.rows || args.rows.length === 0) {
|
||||
return { success: false, message: 'Rows array is required and must not be empty' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const table = await getTableById(args.tableId)
|
||||
if (!table) {
|
||||
return { success: false, message: `Table not found: ${args.tableId}` }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const rows = await batchInsertRows(
|
||||
{ tableId: args.tableId, rows: args.rows, workspaceId },
|
||||
table,
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Inserted ${rows.length} rows`,
|
||||
data: { rows, insertedCount: rows.length },
|
||||
}
|
||||
}
|
||||
|
||||
case 'get_row': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.rowId) {
|
||||
return { success: false, message: 'Row ID is required' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const row = await getRowById(args.tableId, args.rowId, workspaceId)
|
||||
if (!row) {
|
||||
return { success: false, message: `Row not found: ${args.rowId}` }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Row ${row.id}`,
|
||||
data: { row },
|
||||
}
|
||||
}
|
||||
|
||||
case 'query_rows': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const result = await queryRows(
|
||||
args.tableId,
|
||||
workspaceId,
|
||||
{
|
||||
filter: args.filter,
|
||||
sort: args.sort,
|
||||
limit: args.limit,
|
||||
offset: args.offset,
|
||||
},
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Returned ${result.rows.length} of ${result.totalCount} rows`,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
case 'update_row': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.rowId) {
|
||||
return { success: false, message: 'Row ID is required' }
|
||||
}
|
||||
if (!args.data) {
|
||||
return { success: false, message: 'Data is required for updating a row' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const table = await getTableById(args.tableId)
|
||||
if (!table) {
|
||||
return { success: false, message: `Table not found: ${args.tableId}` }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const updatedRow = await updateRow(
|
||||
{ tableId: args.tableId, rowId: args.rowId, data: args.data, workspaceId },
|
||||
table,
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Updated row ${updatedRow.id}`,
|
||||
data: { row: updatedRow },
|
||||
}
|
||||
}
|
||||
|
||||
case 'delete_row': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.rowId) {
|
||||
return { success: false, message: 'Row ID is required' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
await deleteRow(args.tableId, args.rowId, workspaceId, requestId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Deleted row ${args.rowId}`,
|
||||
}
|
||||
}
|
||||
|
||||
case 'update_rows_by_filter': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.filter) {
|
||||
return { success: false, message: 'Filter is required for bulk update' }
|
||||
}
|
||||
if (!args.data) {
|
||||
return { success: false, message: 'Data is required for bulk update' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const table = await getTableById(args.tableId)
|
||||
if (!table) {
|
||||
return { success: false, message: `Table not found: ${args.tableId}` }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const result = await updateRowsByFilter(
|
||||
{
|
||||
tableId: args.tableId,
|
||||
filter: args.filter,
|
||||
data: args.data,
|
||||
limit: args.limit,
|
||||
workspaceId,
|
||||
},
|
||||
table,
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Updated ${result.affectedCount} rows`,
|
||||
data: { affectedCount: result.affectedCount, affectedRowIds: result.affectedRowIds },
|
||||
}
|
||||
}
|
||||
|
||||
case 'delete_rows_by_filter': {
|
||||
if (!args.tableId) {
|
||||
return { success: false, message: 'Table ID is required' }
|
||||
}
|
||||
if (!args.filter) {
|
||||
return { success: false, message: 'Filter is required for bulk delete' }
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return { success: false, message: 'Workspace ID is required' }
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const result = await deleteRowsByFilter(
|
||||
{
|
||||
tableId: args.tableId,
|
||||
filter: args.filter,
|
||||
limit: args.limit,
|
||||
workspaceId,
|
||||
},
|
||||
requestId
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Deleted ${result.affectedCount} rows`,
|
||||
data: { affectedCount: result.affectedCount, affectedRowIds: result.affectedRowIds },
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return { success: false, message: `Unknown operation: ${operation}` }
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
logger.error('Table operation failed', { operation, error: errorMessage })
|
||||
return { success: false, message: `Operation failed: ${errorMessage}` }
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -74,6 +74,48 @@ export const KnowledgeBaseResultSchema = z.object({
|
||||
})
|
||||
export type KnowledgeBaseResult = z.infer<typeof KnowledgeBaseResultSchema>
|
||||
|
||||
// user_table - shared schema used by server tool and registry
|
||||
export const UserTableArgsSchema = z.object({
|
||||
operation: z.enum([
|
||||
'create',
|
||||
'list',
|
||||
'get',
|
||||
'get_schema',
|
||||
'delete',
|
||||
'insert_row',
|
||||
'batch_insert_rows',
|
||||
'get_row',
|
||||
'query_rows',
|
||||
'update_row',
|
||||
'delete_row',
|
||||
'update_rows_by_filter',
|
||||
'delete_rows_by_filter',
|
||||
]),
|
||||
args: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
schema: z.any().optional(),
|
||||
tableId: z.string().optional(),
|
||||
rowId: z.string().optional(),
|
||||
data: z.record(z.any()).optional(),
|
||||
rows: z.array(z.record(z.any())).optional(),
|
||||
filter: z.any().optional(),
|
||||
sort: z.record(z.enum(['asc', 'desc'])).optional(),
|
||||
limit: z.number().optional(),
|
||||
offset: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
export type UserTableArgs = z.infer<typeof UserTableArgsSchema>
|
||||
|
||||
export const UserTableResultSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: z.any().optional(),
|
||||
})
|
||||
export type UserTableResult = z.infer<typeof UserTableResultSchema>
|
||||
|
||||
export const GetBlockOutputsInput = z.object({
|
||||
blockIds: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
@@ -126,6 +126,35 @@ export function serializeDocuments(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize table metadata for VFS tables/{name}/meta.json
|
||||
*/
|
||||
export function serializeTableMeta(table: {
|
||||
id: string
|
||||
name: string
|
||||
description?: string | null
|
||||
schema: unknown
|
||||
rowCount: number
|
||||
maxRows: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}): string {
|
||||
return JSON.stringify(
|
||||
{
|
||||
id: table.id,
|
||||
name: table.name,
|
||||
description: table.description || undefined,
|
||||
schema: table.schema,
|
||||
rowCount: table.rowCount,
|
||||
maxRows: table.maxRows,
|
||||
createdAt: table.createdAt.toISOString(),
|
||||
updatedAt: table.updatedAt.toISOString(),
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the static model list from PROVIDER_DEFINITIONS for VFS serialization.
|
||||
* Excludes dynamic providers (ollama, vllm, openrouter) whose models are user-configured.
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
workflowMcpTool,
|
||||
workspaceEnvironment,
|
||||
workflowExecutionLogs,
|
||||
userTableDefinitions,
|
||||
} from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, count, desc, eq, isNull } from 'drizzle-orm'
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
serializeIntegrationSchema,
|
||||
serializeKBMeta,
|
||||
serializeRecentExecutions,
|
||||
serializeTableMeta,
|
||||
serializeWorkflowMeta,
|
||||
} from '@/lib/copilot/vfs/serializers'
|
||||
import type { DeploymentData } from '@/lib/copilot/vfs/serializers'
|
||||
@@ -151,6 +153,7 @@ function getStaticComponentFiles(): Map<string, string> {
|
||||
* workflows/{name}/deployment.json
|
||||
* knowledgebases/{name}/meta.json
|
||||
* knowledgebases/{name}/documents.json
|
||||
* tables/{name}/meta.json
|
||||
* custom-tools/{name}.json
|
||||
* environment/credentials.json
|
||||
* environment/api-keys.json
|
||||
@@ -172,6 +175,7 @@ export class WorkspaceVFS {
|
||||
await Promise.all([
|
||||
this.materializeWorkflows(workspaceId, userId),
|
||||
this.materializeKnowledgeBases(workspaceId),
|
||||
this.materializeTables(workspaceId),
|
||||
this.materializeEnvironment(workspaceId, userId),
|
||||
this.materializeCustomTools(workspaceId),
|
||||
])
|
||||
@@ -363,6 +367,49 @@ export class WorkspaceVFS {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Materialize all user tables in the workspace (metadata only, no row data).
|
||||
*/
|
||||
private async materializeTables(workspaceId: string): Promise<void> {
|
||||
try {
|
||||
const tableRows = await db
|
||||
.select({
|
||||
id: userTableDefinitions.id,
|
||||
name: userTableDefinitions.name,
|
||||
description: userTableDefinitions.description,
|
||||
schema: userTableDefinitions.schema,
|
||||
rowCount: userTableDefinitions.rowCount,
|
||||
maxRows: userTableDefinitions.maxRows,
|
||||
createdAt: userTableDefinitions.createdAt,
|
||||
updatedAt: userTableDefinitions.updatedAt,
|
||||
})
|
||||
.from(userTableDefinitions)
|
||||
.where(eq(userTableDefinitions.workspaceId, workspaceId))
|
||||
|
||||
for (const table of tableRows) {
|
||||
const safeName = sanitizeName(table.name)
|
||||
this.files.set(
|
||||
`tables/${safeName}/meta.json`,
|
||||
serializeTableMeta({
|
||||
id: table.id,
|
||||
name: table.name,
|
||||
description: table.description,
|
||||
schema: table.schema,
|
||||
rowCount: table.rowCount,
|
||||
maxRows: table.maxRows,
|
||||
createdAt: table.createdAt,
|
||||
updatedAt: table.updatedAt,
|
||||
})
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('Failed to materialize tables', {
|
||||
workspaceId,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all deployment configurations for a single workflow.
|
||||
* Returns null if the workflow has no deployments of any kind.
|
||||
|
||||
Reference in New Issue
Block a user