mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-22 05:18:08 -05:00
refactoring
This commit is contained in:
@@ -3,8 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { deleteTable, getTableById } from '@/lib/table'
|
||||
import type { TableSchemaData } from '../utils'
|
||||
import { deleteTable, getTableById, type TableSchema } from '@/lib/table'
|
||||
import {
|
||||
checkTableAccess,
|
||||
checkTableWriteAccess,
|
||||
@@ -106,7 +105,7 @@ export async function GET(request: NextRequest, { params }: TableRouteParams) {
|
||||
|
||||
logger.info(`[${requestId}] Retrieved table ${tableId} for user ${authResult.userId}`)
|
||||
|
||||
const schemaData = table.schema as TableSchemaData
|
||||
const schemaData = table.schema as TableSchema
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -6,8 +6,8 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkHybridAuth } from '@/lib/auth/hybrid'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { createTable, listTables, TABLE_LIMITS } from '@/lib/table'
|
||||
import { normalizeColumn, type TableSchemaData } from './utils'
|
||||
import { createTable, listTables, TABLE_LIMITS, type TableSchema } from '@/lib/table'
|
||||
import { normalizeColumn } from './utils'
|
||||
|
||||
const logger = createLogger('TableAPI')
|
||||
|
||||
@@ -192,7 +192,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Normalize schema to ensure all fields have explicit defaults
|
||||
const normalizedSchema: TableSchemaData = {
|
||||
const normalizedSchema: TableSchema = {
|
||||
columns: params.schema.columns.map(normalizeColumn),
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ export async function GET(request: NextRequest) {
|
||||
success: true,
|
||||
data: {
|
||||
tables: tables.map((t) => {
|
||||
const schemaData = t.schema as TableSchemaData
|
||||
const schemaData = t.schema as TableSchema
|
||||
return {
|
||||
...t,
|
||||
schema: {
|
||||
|
||||
@@ -3,12 +3,15 @@ import { userTableDefinitions } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { ColumnDefinition, TableSchema } from '@/lib/table'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
const logger = createLogger('TableUtils')
|
||||
|
||||
/**
|
||||
* Represents the core data structure for a user-defined table.
|
||||
* Represents the core data structure for a user-defined table as stored in the database.
|
||||
*
|
||||
* This extends the base TableDefinition with DB-specific fields like createdBy and deletedAt.
|
||||
*/
|
||||
export interface TableData {
|
||||
/** Unique identifier for the table */
|
||||
@@ -22,7 +25,7 @@ export interface TableData {
|
||||
/** Optional description of the table's purpose */
|
||||
description?: string | null
|
||||
/** JSON schema defining the table's column structure */
|
||||
schema: TableSchemaData
|
||||
schema: TableSchema
|
||||
/** Maximum number of rows allowed in this table */
|
||||
maxRows: number
|
||||
/** Current number of rows in the table */
|
||||
@@ -35,28 +38,6 @@ export interface TableData {
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema structure for table columns stored in the database.
|
||||
*/
|
||||
export interface TableSchemaData {
|
||||
/** Array of column definitions */
|
||||
columns: TableColumnData[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single column definition in the table schema.
|
||||
*/
|
||||
export interface TableColumnData {
|
||||
/** Name of the column */
|
||||
name: string
|
||||
/** Data type of the column */
|
||||
type: 'string' | 'number' | 'boolean' | 'date' | 'json'
|
||||
/** Whether this column is required */
|
||||
required?: boolean
|
||||
/** Whether this column must have unique values */
|
||||
unique?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Result returned when a user has access to a table.
|
||||
*/
|
||||
@@ -492,7 +473,7 @@ export function serverErrorResponse(
|
||||
* @param col - The column definition to normalize
|
||||
* @returns A normalized column with explicit required and unique values
|
||||
*/
|
||||
export function normalizeColumn(col: TableColumnData): TableColumnData {
|
||||
export function normalizeColumn(col: ColumnDefinition): ColumnDefinition {
|
||||
return {
|
||||
name: col.name,
|
||||
type: col.type,
|
||||
|
||||
@@ -2,33 +2,21 @@
|
||||
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { ArrowDownAZ, ArrowUpAZ, Plus, X } from 'lucide-react'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { Button, Combobox, Input } from '@/components/emcn'
|
||||
import type { FilterCondition } from '@/lib/table/filters/constants'
|
||||
import { conditionsToFilter } from '@/lib/table/filters/builder-utils'
|
||||
import type { FilterCondition, SortCondition } from '@/lib/table/filters/constants'
|
||||
import { useFilterBuilder } from '@/lib/table/filters/use-builder'
|
||||
|
||||
/**
|
||||
* Represents a sort configuration.
|
||||
*/
|
||||
export interface SortConfig {
|
||||
/** Column to sort by */
|
||||
column: string
|
||||
/** Sort direction */
|
||||
direction: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter value structure for API queries.
|
||||
*/
|
||||
type FilterValue = string | number | boolean | null | FilterValue[] | { [key: string]: FilterValue }
|
||||
import type { JsonValue } from '@/lib/table/types'
|
||||
|
||||
/**
|
||||
* Query options for the table API.
|
||||
*/
|
||||
export interface QueryOptions {
|
||||
/** Filter criteria or null for no filter, keys are column names, values are filter values */
|
||||
filter: Record<string, FilterValue> | null
|
||||
filter: Record<string, JsonValue> | null
|
||||
/** Sort configuration or null for default sort */
|
||||
sort: SortConfig | null
|
||||
sort: SortCondition | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,111 +41,6 @@ interface TableQueryBuilderProps {
|
||||
onAddRow: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string value into its appropriate type.
|
||||
*
|
||||
* @param value - String value to parse
|
||||
* @returns Parsed value (boolean, null, number, or string)
|
||||
*/
|
||||
function parseValue(value: string): string | number | boolean | null {
|
||||
if (value === 'true') return true
|
||||
if (value === 'false') return false
|
||||
if (value === 'null') return null
|
||||
if (!Number.isNaN(Number(value)) && value !== '') return Number(value)
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a comma-separated string into an array of values.
|
||||
*
|
||||
* @param value - Comma-separated string
|
||||
* @returns Array of parsed values
|
||||
*/
|
||||
function parseArrayValue(value: string): FilterValue[] {
|
||||
return value.split(',').map((v) => {
|
||||
const trimmed = v.trim()
|
||||
return parseValue(trimmed)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts builder filter conditions to a MongoDB-style filter object.
|
||||
*
|
||||
* Iterates through an array of filter conditions, combining them into a filter expression object
|
||||
* that is compatible with MongoDB's query format. Supports both "AND" and "OR" logical groupings:
|
||||
*
|
||||
* - "AND" conditions are grouped together in objects.
|
||||
* - "OR" conditions start new groups; groups are merged under a single `$or` array.
|
||||
*
|
||||
* @param conditions - The list of filter conditions specified by the user.
|
||||
* @returns A filter object to send to the API, or null if there are no conditions.
|
||||
*
|
||||
* @example
|
||||
* [
|
||||
* { logicalOperator: 'and', column: 'age', operator: 'gt', value: '18' },
|
||||
* { logicalOperator: 'or', column: 'role', operator: 'eq', value: 'admin' }
|
||||
* ]
|
||||
* // =>
|
||||
* {
|
||||
* $or: [
|
||||
* { age: { $gt: 18 } },
|
||||
* { role: 'admin' }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
function conditionsToFilter(conditions: FilterCondition[]): Record<string, FilterValue> | null {
|
||||
// Return null if there are no filter conditions.
|
||||
if (conditions.length === 0) return null
|
||||
|
||||
// Groups for $or logic; each group is an AND-combined object.
|
||||
const orGroups: Record<string, FilterValue>[] = []
|
||||
// Current group of AND'ed conditions.
|
||||
let currentAndGroup: Record<string, FilterValue> = {}
|
||||
|
||||
conditions.forEach((condition, index) => {
|
||||
const { column, operator, value } = condition
|
||||
const operatorKey = `$${operator}`
|
||||
|
||||
// Parse value as per operator: 'in' receives an array, others get a primitive value.
|
||||
let parsedValue: FilterValue = value
|
||||
if (operator === 'in') {
|
||||
parsedValue = parseArrayValue(value)
|
||||
} else {
|
||||
parsedValue = parseValue(value)
|
||||
}
|
||||
|
||||
// For 'eq', value is direct (shorthand), otherwise use a key for the operator.
|
||||
const conditionObj: FilterValue =
|
||||
operator === 'eq' ? parsedValue : { [operatorKey]: parsedValue }
|
||||
|
||||
// Group logic:
|
||||
// - First condition or 'and': add to the current AND group.
|
||||
// - 'or': finalize current AND group and start a new one.
|
||||
if (index === 0 || condition.logicalOperator === 'and') {
|
||||
currentAndGroup[column] = conditionObj
|
||||
} else if (condition.logicalOperator === 'or') {
|
||||
if (Object.keys(currentAndGroup).length > 0) {
|
||||
// Finalize and push the previous AND group to $or groups.
|
||||
orGroups.push({ ...currentAndGroup })
|
||||
}
|
||||
// Start a new AND group for subsequent conditions.
|
||||
currentAndGroup = { [column]: conditionObj }
|
||||
}
|
||||
})
|
||||
|
||||
// Push the last AND group, if any, to the orGroups list.
|
||||
if (Object.keys(currentAndGroup).length > 0) {
|
||||
orGroups.push(currentAndGroup)
|
||||
}
|
||||
|
||||
// If multiple groups exist, return as a $or query; otherwise, return the single group.
|
||||
if (orGroups.length > 1) {
|
||||
return { $or: orGroups }
|
||||
}
|
||||
|
||||
return orGroups[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for building filter and sort queries for table data.
|
||||
*
|
||||
@@ -178,7 +61,7 @@ function conditionsToFilter(conditions: FilterCondition[]): Record<string, Filte
|
||||
*/
|
||||
export function TableQueryBuilder({ columns, onApply, onAddRow }: TableQueryBuilderProps) {
|
||||
const [conditions, setConditions] = useState<FilterCondition[]>([])
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig | null>(null)
|
||||
const [sortCondition, setSortCondition] = useState<SortCondition | null>(null)
|
||||
|
||||
const columnOptions = useMemo(
|
||||
() => columns.map((col) => ({ value: col.name, label: col.name })),
|
||||
@@ -200,46 +83,47 @@ export function TableQueryBuilder({ columns, onApply, onAddRow }: TableQueryBuil
|
||||
})
|
||||
|
||||
/**
|
||||
* Adds a sort configuration.
|
||||
* Adds a sort condition.
|
||||
*/
|
||||
const handleAddSort = useCallback(() => {
|
||||
setSortConfig({
|
||||
setSortCondition({
|
||||
id: nanoid(),
|
||||
column: columns[0]?.name || '',
|
||||
direction: 'asc',
|
||||
})
|
||||
}, [columns])
|
||||
|
||||
/**
|
||||
* Removes the sort configuration.
|
||||
* Removes the sort condition.
|
||||
*/
|
||||
const handleRemoveSort = useCallback(() => {
|
||||
setSortConfig(null)
|
||||
setSortCondition(null)
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Applies the current filter and sort configuration.
|
||||
* Applies the current filter and sort conditions.
|
||||
*/
|
||||
const handleApply = useCallback(() => {
|
||||
const filter = conditionsToFilter(conditions)
|
||||
onApply({
|
||||
filter,
|
||||
sort: sortConfig,
|
||||
sort: sortCondition,
|
||||
})
|
||||
}, [conditions, sortConfig, onApply])
|
||||
}, [conditions, sortCondition, onApply])
|
||||
|
||||
/**
|
||||
* Clears all filters and sort configuration.
|
||||
* Clears all filters and sort conditions.
|
||||
*/
|
||||
const handleClear = useCallback(() => {
|
||||
setConditions([])
|
||||
setSortConfig(null)
|
||||
setSortCondition(null)
|
||||
onApply({
|
||||
filter: null,
|
||||
sort: null,
|
||||
})
|
||||
}, [onApply])
|
||||
|
||||
const hasChanges = conditions.length > 0 || sortConfig !== null
|
||||
const hasChanges = conditions.length > 0 || sortCondition !== null
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
@@ -259,12 +143,12 @@ export function TableQueryBuilder({ columns, onApply, onAddRow }: TableQueryBuil
|
||||
))}
|
||||
|
||||
{/* Sort Row */}
|
||||
{sortConfig && (
|
||||
<SortConfigRow
|
||||
sortConfig={sortConfig}
|
||||
{sortCondition && (
|
||||
<SortConditionRow
|
||||
sortCondition={sortCondition}
|
||||
columnOptions={columnOptions}
|
||||
sortDirectionOptions={sortDirectionOptions}
|
||||
onChange={setSortConfig}
|
||||
onChange={setSortCondition}
|
||||
onRemove={handleRemoveSort}
|
||||
/>
|
||||
)}
|
||||
@@ -281,7 +165,7 @@ export function TableQueryBuilder({ columns, onApply, onAddRow }: TableQueryBuil
|
||||
Add filter
|
||||
</Button>
|
||||
|
||||
{!sortConfig && (
|
||||
{!sortCondition && (
|
||||
<Button variant='default' size='sm' onClick={handleAddSort}>
|
||||
<ArrowUpAZ className='mr-[4px] h-[12px] w-[12px]' />
|
||||
Add sort
|
||||
@@ -406,31 +290,31 @@ function FilterConditionRow({
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the SortConfigRow component.
|
||||
* Props for the SortConditionRow component.
|
||||
*/
|
||||
interface SortConfigRowProps {
|
||||
/** The sort configuration */
|
||||
sortConfig: SortConfig
|
||||
interface SortConditionRowProps {
|
||||
/** The sort condition */
|
||||
sortCondition: SortCondition
|
||||
/** Available column options */
|
||||
columnOptions: Array<{ value: string; label: string }>
|
||||
/** Available sort direction options */
|
||||
sortDirectionOptions: Array<{ value: string; label: string }>
|
||||
/** Callback to update the sort configuration */
|
||||
onChange: (config: SortConfig | null) => void
|
||||
/** Callback to update the sort condition */
|
||||
onChange: (condition: SortCondition | null) => void
|
||||
/** Callback to remove the sort */
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort configuration row component.
|
||||
* Sort condition row component.
|
||||
*/
|
||||
function SortConfigRow({
|
||||
sortConfig,
|
||||
function SortConditionRow({
|
||||
sortCondition,
|
||||
columnOptions,
|
||||
sortDirectionOptions,
|
||||
onChange,
|
||||
onRemove,
|
||||
}: SortConfigRowProps) {
|
||||
}: SortConditionRowProps) {
|
||||
return (
|
||||
<div className='flex items-center gap-[8px]'>
|
||||
<Button
|
||||
@@ -450,8 +334,8 @@ function SortConfigRow({
|
||||
<Combobox
|
||||
size='sm'
|
||||
options={columnOptions}
|
||||
value={sortConfig.column}
|
||||
onChange={(value) => onChange({ ...sortConfig, column: value })}
|
||||
value={sortCondition.column}
|
||||
onChange={(value) => onChange({ ...sortCondition, column: value })}
|
||||
placeholder='Column'
|
||||
/>
|
||||
</div>
|
||||
@@ -460,13 +344,13 @@ function SortConfigRow({
|
||||
<Combobox
|
||||
size='sm'
|
||||
options={sortDirectionOptions}
|
||||
value={sortConfig.direction}
|
||||
onChange={(value) => onChange({ ...sortConfig, direction: value as 'asc' | 'desc' })}
|
||||
value={sortCondition.direction}
|
||||
onChange={(value) => onChange({ ...sortCondition, direction: value as 'asc' | 'desc' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center text-[12px] text-[var(--text-tertiary)]'>
|
||||
{sortConfig.direction === 'asc' ? (
|
||||
{sortCondition.direction === 'asc' ? (
|
||||
<ArrowUpAZ className='h-[14px] w-[14px]' />
|
||||
) : (
|
||||
<ArrowDownAZ className='h-[14px] w-[14px]' />
|
||||
|
||||
@@ -16,24 +16,10 @@ import {
|
||||
Textarea,
|
||||
} from '@/components/emcn'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import type { ColumnDefinition, TableSchema } from '@/lib/table'
|
||||
import type { ColumnDefinition, TableRow, TableSchema } from '@/lib/table'
|
||||
|
||||
const logger = createLogger('TableRowModal')
|
||||
|
||||
/**
|
||||
* Represents row data from the table.
|
||||
*/
|
||||
export interface TableRowData {
|
||||
/** Unique identifier for the row */
|
||||
id: string
|
||||
/** Row field values keyed by column name */
|
||||
data: Record<string, unknown>
|
||||
/** ISO timestamp when the row was created */
|
||||
createdAt: string
|
||||
/** ISO timestamp when the row was last updated */
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Table metadata needed for row operations.
|
||||
*/
|
||||
@@ -64,7 +50,7 @@ export interface TableRowModalProps {
|
||||
/** Table to operate on */
|
||||
table: TableInfo
|
||||
/** Row being edited/deleted (required for edit/delete modes) */
|
||||
row?: TableRowData
|
||||
row?: TableRow
|
||||
/** Row IDs to delete (for delete mode batch operations) */
|
||||
rowIds?: string[]
|
||||
/** Callback when operation is successful */
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { ContextMenuState, TableRowData } from '../types'
|
||||
import type { TableRow } from '@/lib/table'
|
||||
import type { ContextMenuState } from '../types'
|
||||
|
||||
interface UseContextMenuReturn {
|
||||
contextMenu: ContextMenuState
|
||||
handleRowContextMenu: (e: React.MouseEvent, row: TableRowData) => void
|
||||
handleRowContextMenu: (e: React.MouseEvent, row: TableRow) => void
|
||||
closeContextMenu: () => void
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ export function useContextMenu(): UseContextMenuReturn {
|
||||
/**
|
||||
* Opens the context menu for a row.
|
||||
*/
|
||||
const handleRowContextMenu = useCallback((e: React.MouseEvent, row: TableRowData) => {
|
||||
const handleRowContextMenu = useCallback((e: React.MouseEvent, row: TableRow) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setContextMenu({
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { TableRowData } from '../types'
|
||||
import type { TableRow } from '@/lib/table'
|
||||
|
||||
interface UseRowSelectionReturn {
|
||||
selectedRows: Set<string>
|
||||
@@ -20,7 +20,7 @@ interface UseRowSelectionReturn {
|
||||
* @param rows - The current rows to select from
|
||||
* @returns Selection state and handlers
|
||||
*/
|
||||
export function useRowSelection(rows: TableRowData[]): UseRowSelectionReturn {
|
||||
export function useRowSelection(rows: TableRow[]): UseRowSelectionReturn {
|
||||
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set())
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { TableDefinition, TableRow } from '@/lib/table'
|
||||
import type { QueryOptions } from '../../components/table-query-builder'
|
||||
import { ROWS_PER_PAGE } from '../constants'
|
||||
import type { TableData, TableRowData } from '../types'
|
||||
|
||||
interface UseTableDataParams {
|
||||
workspaceId: string
|
||||
@@ -17,9 +17,9 @@ interface UseTableDataParams {
|
||||
}
|
||||
|
||||
interface UseTableDataReturn {
|
||||
tableData: TableData | undefined
|
||||
tableData: TableDefinition | undefined
|
||||
isLoadingTable: boolean
|
||||
rows: TableRowData[]
|
||||
rows: TableRow[]
|
||||
totalCount: number
|
||||
totalPages: number
|
||||
isLoadingRows: boolean
|
||||
@@ -43,9 +43,9 @@ export function useTableData({
|
||||
queryFn: async () => {
|
||||
const res = await fetch(`/api/table/${tableId}?workspaceId=${workspaceId}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch table')
|
||||
const json: { data?: { table: TableData }; table?: TableData } = await res.json()
|
||||
const json: { data?: { table: TableDefinition }; table?: TableDefinition } = await res.json()
|
||||
const data = json.data || json
|
||||
return (data as { table: TableData }).table
|
||||
return (data as { table: TableDefinition }).table
|
||||
},
|
||||
})
|
||||
|
||||
@@ -74,8 +74,8 @@ export function useTableData({
|
||||
const res = await fetch(`/api/table/${tableId}/rows?${searchParams}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch rows')
|
||||
const json: {
|
||||
data?: { rows: TableRowData[]; totalCount: number }
|
||||
rows?: TableRowData[]
|
||||
data?: { rows: TableRow[]; totalCount: number }
|
||||
rows?: TableRow[]
|
||||
totalCount?: number
|
||||
} = await res.json()
|
||||
return json.data || json
|
||||
@@ -83,7 +83,7 @@ export function useTableData({
|
||||
enabled: !!tableData,
|
||||
})
|
||||
|
||||
const rows = (rowsData?.rows || []) as TableRowData[]
|
||||
const rows = (rowsData?.rows || []) as TableRow[]
|
||||
const totalCount = rowsData?.totalCount || 0
|
||||
const totalPages = Math.ceil(totalCount / ROWS_PER_PAGE)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import type { TableRow as TableRowType } from '@/lib/table'
|
||||
import { type QueryOptions, TableActionBar, TableQueryBuilder, TableRowModal } from '../components'
|
||||
import {
|
||||
CellViewerModal,
|
||||
@@ -31,7 +32,7 @@ import {
|
||||
TablePagination,
|
||||
} from './components'
|
||||
import { useContextMenu, useRowSelection, useTableData } from './hooks'
|
||||
import type { CellViewerData, TableRowData } from './types'
|
||||
import type { CellViewerData } from './types'
|
||||
|
||||
/**
|
||||
* Main component for viewing and managing table data.
|
||||
@@ -62,7 +63,7 @@ export function TableDataViewer() {
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
|
||||
const [showAddModal, setShowAddModal] = useState(false)
|
||||
const [editingRow, setEditingRow] = useState<TableRowData | null>(null)
|
||||
const [editingRow, setEditingRow] = useState<TableRowType | null>(null)
|
||||
const [deletingRows, setDeletingRows] = useState<string[]>([])
|
||||
const [showSchemaModal, setShowSchemaModal] = useState(false)
|
||||
|
||||
|
||||
@@ -4,43 +4,7 @@
|
||||
* @module tables/[tableId]/table-data-viewer/types
|
||||
*/
|
||||
|
||||
import type { TableSchema } from '@/lib/table'
|
||||
|
||||
/**
|
||||
* Represents row data stored in a table.
|
||||
*/
|
||||
export interface TableRowData {
|
||||
/** Unique identifier for the row */
|
||||
id: string
|
||||
/** Row field values keyed by column name */
|
||||
data: Record<string, unknown>
|
||||
/** ISO timestamp when the row was created */
|
||||
createdAt: string
|
||||
/** ISO timestamp when the row was last updated */
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents table metadata.
|
||||
*/
|
||||
export interface TableData {
|
||||
/** Unique identifier for the table */
|
||||
id: string
|
||||
/** Table name */
|
||||
name: string
|
||||
/** Optional description */
|
||||
description?: string
|
||||
/** Schema defining columns */
|
||||
schema: TableSchema
|
||||
/** Current number of rows */
|
||||
rowCount: number
|
||||
/** Maximum allowed rows */
|
||||
maxRows: number
|
||||
/** ISO timestamp when created */
|
||||
createdAt: string
|
||||
/** ISO timestamp when last updated */
|
||||
updatedAt: string
|
||||
}
|
||||
import type { TableRow } from '@/lib/table'
|
||||
|
||||
/**
|
||||
* Data for the cell viewer modal.
|
||||
@@ -63,5 +27,5 @@ export interface ContextMenuState {
|
||||
/** Screen position of the menu */
|
||||
position: { x: number; y: number }
|
||||
/** Row the menu was opened on */
|
||||
row: TableRowData | null
|
||||
row: TableRow | null
|
||||
}
|
||||
|
||||
@@ -17,29 +17,11 @@ import {
|
||||
ModalHeader,
|
||||
Textarea,
|
||||
} from '@/components/emcn'
|
||||
import type { ColumnDefinition, ColumnType } from '@/lib/table'
|
||||
import { useCreateTable } from '@/hooks/queries/use-tables'
|
||||
|
||||
const logger = createLogger('CreateTableModal')
|
||||
|
||||
/**
|
||||
* Supported column data types for table schemas.
|
||||
*/
|
||||
type ColumnType = 'string' | 'number' | 'boolean' | 'date' | 'json'
|
||||
|
||||
/**
|
||||
* Definition for a single table column.
|
||||
*/
|
||||
interface ColumnDefinition {
|
||||
/** Name of the column */
|
||||
name: string
|
||||
/** Data type of the column */
|
||||
type: ColumnType
|
||||
/** Whether this column is required */
|
||||
required: boolean
|
||||
/** Whether this column must have unique values */
|
||||
unique: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the CreateTableModal component.
|
||||
*/
|
||||
@@ -51,9 +33,9 @@ interface CreateTableModalProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Available column type options for the combobox.
|
||||
* Available column type options for the combobox UI.
|
||||
*/
|
||||
const COLUMN_TYPES: Array<{ value: ColumnType; label: string }> = [
|
||||
const COLUMN_TYPE_OPTIONS: Array<{ value: ColumnType; label: string }> = [
|
||||
{ value: 'string', label: 'String' },
|
||||
{ value: 'number', label: 'Number' },
|
||||
{ value: 'boolean', label: 'Boolean' },
|
||||
@@ -356,7 +338,7 @@ function ColumnRow({ column, index, isRemovable, onChange, onRemove }: ColumnRow
|
||||
{/* Column Type */}
|
||||
<div className='w-[110px]'>
|
||||
<Combobox
|
||||
options={COLUMN_TYPES}
|
||||
options={COLUMN_TYPE_OPTIONS}
|
||||
value={column.type}
|
||||
selectedValue={column.type}
|
||||
onChange={(value) => onChange(index, 'type', value as ColumnType)}
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from '@/components/emcn'
|
||||
import type { TableDefinition } from '@/lib/table'
|
||||
import { useDeleteTable } from '@/hooks/queries/use-tables'
|
||||
import type { TableDefinition } from '@/tools/table/types'
|
||||
|
||||
const logger = createLogger('TableCard')
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { conditionsToJsonString, jsonStringToConditions } from '@/lib/table/filters/builder-utils'
|
||||
import type { FilterCondition } from '@/lib/table/filters/constants'
|
||||
import { useFilterBuilder } from '@/lib/table/filters/use-builder'
|
||||
import { useBuilderJsonSync, useTableColumns } from '@/lib/table/hooks'
|
||||
import { useTableColumns } from '@/lib/table/hooks'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { EmptyState } from './components/empty-state'
|
||||
import { FilterConditionRow } from './components/filter-condition-row'
|
||||
@@ -19,15 +18,10 @@ interface FilterFormatProps {
|
||||
disabled?: boolean
|
||||
columns?: Array<{ value: string; label: string }>
|
||||
tableIdSubBlockId?: string
|
||||
modeSubBlockId?: string
|
||||
jsonSubBlockId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual builder for filter conditions with optional JSON sync.
|
||||
*
|
||||
* When `modeSubBlockId` and `jsonSubBlockId` are provided, handles bidirectional
|
||||
* conversion between builder conditions and JSON format.
|
||||
* Visual builder for filter conditions.
|
||||
*/
|
||||
export function FilterFormat({
|
||||
blockId,
|
||||
@@ -37,16 +31,9 @@ export function FilterFormat({
|
||||
disabled = false,
|
||||
columns: propColumns,
|
||||
tableIdSubBlockId = 'tableId',
|
||||
modeSubBlockId,
|
||||
jsonSubBlockId,
|
||||
}: FilterFormatProps) {
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<FilterCondition[]>(blockId, subBlockId)
|
||||
const [tableIdValue] = useSubBlockValue<string>(blockId, tableIdSubBlockId)
|
||||
const [modeValue] = useSubBlockValue<string>(blockId, modeSubBlockId || '_unused_mode')
|
||||
const [jsonValue, setJsonValue] = useSubBlockValue<string>(
|
||||
blockId,
|
||||
jsonSubBlockId || '_unused_json'
|
||||
)
|
||||
|
||||
const dynamicColumns = useTableColumns({ tableId: tableIdValue })
|
||||
const columns = useMemo(() => {
|
||||
@@ -58,18 +45,6 @@ export function FilterFormat({
|
||||
const conditions: FilterCondition[] = Array.isArray(value) && value.length > 0 ? value : []
|
||||
const isReadOnly = isPreview || disabled
|
||||
|
||||
useBuilderJsonSync({
|
||||
modeValue,
|
||||
jsonValue,
|
||||
setJsonValue,
|
||||
isPreview,
|
||||
conditions,
|
||||
setConditions: setStoreValue,
|
||||
jsonToConditions: jsonStringToConditions,
|
||||
conditionsToJson: conditionsToJsonString,
|
||||
enabled: Boolean(modeSubBlockId && jsonSubBlockId),
|
||||
})
|
||||
|
||||
const { comparisonOptions, logicalOptions, addCondition, removeCondition, updateCondition } =
|
||||
useFilterBuilder({
|
||||
columns,
|
||||
|
||||
@@ -4,12 +4,8 @@ import { useCallback, useMemo } from 'react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { Button, type ComboboxOption } from '@/components/emcn'
|
||||
import {
|
||||
jsonStringToSortConditions,
|
||||
sortConditionsToJsonString,
|
||||
} from '@/lib/table/filters/builder-utils'
|
||||
import { SORT_DIRECTIONS, type SortCondition } from '@/lib/table/filters/constants'
|
||||
import { useBuilderJsonSync, useTableColumns } from '@/lib/table/hooks'
|
||||
import { useTableColumns } from '@/lib/table/hooks'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import { EmptyState } from './components/empty-state'
|
||||
import { SortConditionRow } from './components/sort-condition-row'
|
||||
@@ -22,8 +18,6 @@ interface SortFormatProps {
|
||||
disabled?: boolean
|
||||
columns?: Array<{ value: string; label: string }>
|
||||
tableIdSubBlockId?: string
|
||||
modeSubBlockId?: string
|
||||
jsonSubBlockId?: string
|
||||
}
|
||||
|
||||
const createDefaultCondition = (columns: ComboboxOption[]): SortCondition => ({
|
||||
@@ -33,10 +27,7 @@ const createDefaultCondition = (columns: ComboboxOption[]): SortCondition => ({
|
||||
})
|
||||
|
||||
/**
|
||||
* Visual builder for sort conditions with optional JSON sync.
|
||||
*
|
||||
* When `modeSubBlockId` and `jsonSubBlockId` are provided, handles bidirectional
|
||||
* conversion between builder conditions and JSON format.
|
||||
* Visual builder for sort conditions.
|
||||
*/
|
||||
export function SortFormat({
|
||||
blockId,
|
||||
@@ -46,16 +37,9 @@ export function SortFormat({
|
||||
disabled = false,
|
||||
columns: propColumns,
|
||||
tableIdSubBlockId = 'tableId',
|
||||
modeSubBlockId,
|
||||
jsonSubBlockId,
|
||||
}: SortFormatProps) {
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<SortCondition[]>(blockId, subBlockId)
|
||||
const [tableIdValue] = useSubBlockValue<string>(blockId, tableIdSubBlockId)
|
||||
const [modeValue] = useSubBlockValue<string>(blockId, modeSubBlockId || '_unused_mode')
|
||||
const [jsonValue, setJsonValue] = useSubBlockValue<string>(
|
||||
blockId,
|
||||
jsonSubBlockId || '_unused_json'
|
||||
)
|
||||
|
||||
const dynamicColumns = useTableColumns({ tableId: tableIdValue, includeBuiltIn: true })
|
||||
const columns = useMemo(() => {
|
||||
@@ -72,18 +56,6 @@ export function SortFormat({
|
||||
const conditions: SortCondition[] = Array.isArray(value) && value.length > 0 ? value : []
|
||||
const isReadOnly = isPreview || disabled
|
||||
|
||||
useBuilderJsonSync({
|
||||
modeValue,
|
||||
jsonValue,
|
||||
setJsonValue,
|
||||
isPreview,
|
||||
conditions,
|
||||
setConditions: setStoreValue,
|
||||
jsonToConditions: jsonStringToSortConditions,
|
||||
conditionsToJson: sortConditionsToJsonString,
|
||||
enabled: Boolean(modeSubBlockId && jsonSubBlockId),
|
||||
})
|
||||
|
||||
const addCondition = useCallback(() => {
|
||||
if (isReadOnly) return
|
||||
setStoreValue([...conditions, createDefaultCondition(columns)])
|
||||
|
||||
@@ -19,11 +19,11 @@ interface TableProps {
|
||||
subBlockId: string
|
||||
columns: string[]
|
||||
isPreview?: boolean
|
||||
previewValue?: TableRow[] | null
|
||||
previewValue?: WorkflowTableRow[] | null
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface TableRow {
|
||||
interface WorkflowTableRow {
|
||||
id: string
|
||||
cells: Record<string, string>
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export function Table({
|
||||
}: TableProps) {
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<TableRow[]>(blockId, subBlockId)
|
||||
const [storeValue, setStoreValue] = useSubBlockValue<WorkflowTableRow[]>(blockId, subBlockId)
|
||||
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
|
||||
|
||||
// Use the extended hook for field-level management
|
||||
@@ -73,7 +73,7 @@ export function Table({
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isPreview && !disabled && (!Array.isArray(storeValue) || storeValue.length === 0)) {
|
||||
const initialRow: TableRow = {
|
||||
const initialRow: WorkflowTableRow = {
|
||||
id: crypto.randomUUID(),
|
||||
cells: { ...emptyCellsTemplate },
|
||||
}
|
||||
@@ -110,7 +110,7 @@ export function Table({
|
||||
}
|
||||
})
|
||||
|
||||
return validatedRows as TableRow[]
|
||||
return validatedRows as WorkflowTableRow[]
|
||||
}, [value, emptyCellsTemplate])
|
||||
|
||||
// Helper to update a cell value
|
||||
@@ -164,7 +164,12 @@ export function Table({
|
||||
</thead>
|
||||
)
|
||||
|
||||
const renderCell = (row: TableRow, rowIndex: number, column: string, cellIndex: number) => {
|
||||
const renderCell = (
|
||||
row: WorkflowTableRow,
|
||||
rowIndex: number,
|
||||
column: string,
|
||||
cellIndex: number
|
||||
) => {
|
||||
// Defensive programming: ensure row.cells exists and has the expected structure
|
||||
const hasValidCells = row.cells && typeof row.cells === 'object'
|
||||
if (!hasValidCells) logger.warn('Table row has malformed cells data:', row)
|
||||
|
||||
@@ -814,17 +814,7 @@ function SubBlockComponent({
|
||||
/>
|
||||
)
|
||||
|
||||
case 'filter-format': {
|
||||
// Determine sync props based on subBlockId
|
||||
let modeSubBlockId: string | undefined
|
||||
let jsonSubBlockId: string | undefined
|
||||
if (config.id === 'filterBuilder') {
|
||||
modeSubBlockId = 'builderMode'
|
||||
jsonSubBlockId = 'filter'
|
||||
} else if (config.id === 'bulkFilterBuilder') {
|
||||
modeSubBlockId = 'bulkFilterMode'
|
||||
jsonSubBlockId = 'filterCriteria'
|
||||
}
|
||||
case 'filter-format':
|
||||
return (
|
||||
<FilterFormat
|
||||
blockId={blockId}
|
||||
@@ -832,20 +822,10 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as FilterCondition[] | null | undefined}
|
||||
disabled={isDisabled}
|
||||
modeSubBlockId={modeSubBlockId}
|
||||
jsonSubBlockId={jsonSubBlockId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
case 'sort-format': {
|
||||
// Determine sync props based on subBlockId
|
||||
let modeSubBlockId: string | undefined
|
||||
let jsonSubBlockId: string | undefined
|
||||
if (config.id === 'sortBuilder') {
|
||||
modeSubBlockId = 'builderMode'
|
||||
jsonSubBlockId = 'sort'
|
||||
}
|
||||
case 'sort-format':
|
||||
return (
|
||||
<SortFormat
|
||||
blockId={blockId}
|
||||
@@ -853,11 +833,8 @@ function SubBlockComponent({
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue as SortCondition[] | null | undefined}
|
||||
disabled={isDisabled}
|
||||
modeSubBlockId={modeSubBlockId}
|
||||
jsonSubBlockId={jsonSubBlockId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
case 'channel-selector':
|
||||
case 'user-selector':
|
||||
|
||||
@@ -46,9 +46,9 @@ import { wouldCreateCycle } from '@/stores/workflows/workflow/utils'
|
||||
const logger = createLogger('WorkflowBlock')
|
||||
|
||||
/**
|
||||
* Type guard for table row structure
|
||||
* Type guard for workflow table row structure (sub-block table inputs)
|
||||
*/
|
||||
interface TableRow {
|
||||
interface WorkflowTableRow {
|
||||
id: string
|
||||
cells: Record<string, string>
|
||||
}
|
||||
@@ -67,7 +67,7 @@ interface FieldFormat {
|
||||
/**
|
||||
* Checks if a value is a table row array
|
||||
*/
|
||||
const isTableRowArray = (value: unknown): value is TableRow[] => {
|
||||
const isTableRowArray = (value: unknown): value is WorkflowTableRow[] => {
|
||||
if (!Array.isArray(value) || value.length === 0) return false
|
||||
const firstItem = value[0]
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import type { TableDefinition } from '@/tools/table/types'
|
||||
import type { TableDefinition } from '@/lib/table'
|
||||
|
||||
export const tableKeys = {
|
||||
all: ['tables'] as const,
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
*/
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
import type { JsonValue } from '../types'
|
||||
import type { FilterCondition, SortCondition } from './constants'
|
||||
|
||||
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
/**
|
||||
* Parses a string value into its appropriate type based on the operator.
|
||||
*
|
||||
@@ -163,35 +162,6 @@ function formatValueForBuilder(value: JsonValue): string {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts builder conditions to JSON string.
|
||||
*
|
||||
* @param conditions - Array of filter conditions
|
||||
* @returns JSON string representation
|
||||
*/
|
||||
export function conditionsToJsonString(conditions: FilterCondition[]): string {
|
||||
const filter = conditionsToFilter(conditions)
|
||||
if (!filter) return ''
|
||||
return JSON.stringify(filter, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts JSON string to builder conditions.
|
||||
*
|
||||
* @param jsonString - JSON string to parse
|
||||
* @returns Array of filter conditions or empty array if invalid
|
||||
*/
|
||||
export function jsonStringToConditions(jsonString: string): FilterCondition[] {
|
||||
if (!jsonString || !jsonString.trim()) return []
|
||||
|
||||
try {
|
||||
const filter = JSON.parse(jsonString)
|
||||
return filterToConditions(filter)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts builder sort conditions to sort object.
|
||||
*
|
||||
@@ -226,32 +196,3 @@ export function sortToConditions(sort: Record<string, string> | null): SortCondi
|
||||
direction: direction === 'desc' ? 'desc' : 'asc',
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts builder sort conditions to JSON string.
|
||||
*
|
||||
* @param conditions - Array of sort conditions
|
||||
* @returns JSON string representation
|
||||
*/
|
||||
export function sortConditionsToJsonString(conditions: SortCondition[]): string {
|
||||
const sort = sortConditionsToSort(conditions)
|
||||
if (!sort) return ''
|
||||
return JSON.stringify(sort, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts JSON string to sort builder conditions.
|
||||
*
|
||||
* @param jsonString - JSON string to parse
|
||||
* @returns Array of sort conditions or empty array if invalid
|
||||
*/
|
||||
export function jsonStringToSortConditions(jsonString: string): SortCondition[] {
|
||||
if (!jsonString || !jsonString.trim()) return []
|
||||
|
||||
try {
|
||||
const sort = JSON.parse(jsonString)
|
||||
return sortToConditions(sort)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { useBuilderJsonSync } from './use-builder-json-sync'
|
||||
export { useTableColumns } from './use-table-columns'
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
interface UseBuilderJsonSyncOptions<T> {
|
||||
modeValue: string | null
|
||||
jsonValue: string | null
|
||||
setJsonValue: (value: string) => void
|
||||
isPreview: boolean
|
||||
conditions: T[]
|
||||
setConditions: (conditions: T[]) => void
|
||||
jsonToConditions: (json: string) => T[]
|
||||
conditionsToJson: (conditions: T[]) => string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles bidirectional sync between builder conditions and JSON format.
|
||||
*
|
||||
* - JSON → Builder: When mode switches to 'builder', parses JSON into conditions
|
||||
* - Builder → JSON: When conditions change in builder mode, converts to JSON
|
||||
*/
|
||||
export function useBuilderJsonSync<T>({
|
||||
modeValue,
|
||||
jsonValue,
|
||||
setJsonValue,
|
||||
isPreview,
|
||||
conditions,
|
||||
setConditions,
|
||||
jsonToConditions,
|
||||
conditionsToJson,
|
||||
enabled = true,
|
||||
}: UseBuilderJsonSyncOptions<T>) {
|
||||
const prevModeRef = useRef<string | null>(null)
|
||||
const isSyncingRef = useRef(false)
|
||||
|
||||
// Sync JSON → Builder when switching to builder mode
|
||||
useEffect(() => {
|
||||
if (!enabled || isPreview) return
|
||||
|
||||
const switchingToBuilder =
|
||||
prevModeRef.current !== null && prevModeRef.current !== 'builder' && modeValue === 'builder'
|
||||
|
||||
if (switchingToBuilder && jsonValue?.trim()) {
|
||||
isSyncingRef.current = true
|
||||
const parsedConditions = jsonToConditions(jsonValue)
|
||||
if (parsedConditions.length > 0) {
|
||||
setConditions(parsedConditions)
|
||||
}
|
||||
isSyncingRef.current = false
|
||||
}
|
||||
|
||||
prevModeRef.current = modeValue
|
||||
}, [modeValue, jsonValue, setConditions, isPreview, jsonToConditions, enabled])
|
||||
|
||||
// Sync Builder → JSON when conditions change in builder mode
|
||||
useEffect(() => {
|
||||
if (!enabled || isPreview || isSyncingRef.current) return
|
||||
if (modeValue !== 'builder') return
|
||||
|
||||
if (conditions.length > 0) {
|
||||
const newJson = conditionsToJson(conditions)
|
||||
if (newJson !== jsonValue) {
|
||||
setJsonValue(newJson)
|
||||
}
|
||||
}
|
||||
}, [conditions, modeValue, jsonValue, setJsonValue, isPreview, conditionsToJson, enabled])
|
||||
}
|
||||
@@ -4,12 +4,14 @@
|
||||
* Provides validation, query building, service layer, and filter utilities
|
||||
* for user-defined tables.
|
||||
*
|
||||
* Hooks are not re-exported here to avoid pulling React into server code.
|
||||
* Import hooks directly from '@/lib/table/hooks' in client components.
|
||||
*
|
||||
* @module lib/table
|
||||
*/
|
||||
|
||||
export * from './constants'
|
||||
export * from './filters'
|
||||
export * from './hooks'
|
||||
export * from './query-builder'
|
||||
export * from './service'
|
||||
export * from './types'
|
||||
|
||||
@@ -10,12 +10,6 @@ import { sql } from 'drizzle-orm'
|
||||
import { NAME_PATTERN } from './constants'
|
||||
import type { FilterOperators, JsonValue, QueryFilter } from './types'
|
||||
|
||||
/**
|
||||
* Field condition is an alias for FilterOperators.
|
||||
* @deprecated Use FilterOperators from types.ts instead.
|
||||
*/
|
||||
export type FieldCondition = FilterOperators
|
||||
|
||||
/**
|
||||
* Whitelist of allowed operators for query filtering.
|
||||
* Only these operators can be used in filter conditions.
|
||||
@@ -107,7 +101,7 @@ function buildContainsClause(tableName: string, field: string, value: string): S
|
||||
*
|
||||
* @param tableName - The name of the table to query (used for SQL table reference)
|
||||
* @param field - The field name to filter on (must match NAME_PATTERN)
|
||||
* @param condition - Either a simple value (for equality) or a FieldCondition
|
||||
* @param condition - Either a simple value (for equality) or a FilterOperators
|
||||
* object with operators like $eq, $gt, $in, etc.
|
||||
* @returns Array of SQL condition fragments. Multiple conditions are returned
|
||||
* when the condition object contains multiple operators.
|
||||
@@ -116,7 +110,7 @@ function buildContainsClause(tableName: string, field: string, value: string): S
|
||||
function buildFieldCondition(
|
||||
tableName: string,
|
||||
field: string,
|
||||
condition: JsonValue | FieldCondition
|
||||
condition: JsonValue | FilterOperators
|
||||
): SQL[] {
|
||||
validateFieldName(field)
|
||||
|
||||
@@ -249,7 +243,7 @@ export function buildFilterClause(filter: QueryFilter, tableName: string): SQL |
|
||||
const fieldConditions = buildFieldCondition(
|
||||
tableName,
|
||||
field,
|
||||
condition as JsonValue | FieldCondition
|
||||
condition as JsonValue | FilterOperators
|
||||
)
|
||||
conditions.push(...fieldConditions)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
/**
|
||||
* Core type definitions for user-defined tables.
|
||||
*
|
||||
* This module provides the single source of truth for all table-related types.
|
||||
* Import types from here rather than defining them locally.
|
||||
*
|
||||
* @module lib/table/types
|
||||
*/
|
||||
|
||||
import type { ColumnType } from './constants'
|
||||
|
||||
// Re-export ColumnType for convenience
|
||||
export type { ColumnType }
|
||||
|
||||
/** Primitive values that can be stored in table columns */
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// Re-export shared types from lib/table for convenience
|
||||
export type {
|
||||
ColumnDefinition,
|
||||
ColumnType,
|
||||
ColumnValue,
|
||||
FilterOperators,
|
||||
JsonValue,
|
||||
QueryFilter,
|
||||
RowData,
|
||||
TableDefinition,
|
||||
TableRow,
|
||||
TableSchema,
|
||||
} from '@/lib/table/types'
|
||||
|
||||
// Import types for use in this file
|
||||
import type {
|
||||
ColumnDefinition,
|
||||
QueryFilter,
|
||||
@@ -23,6 +6,7 @@ import type {
|
||||
TableRow,
|
||||
TableSchema,
|
||||
} from '@/lib/table/types'
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Execution context provided by the workflow executor
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TableRow } from '@/lib/table'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
import type { TableRow, TableRowInsertParams } from './types'
|
||||
import type { TableRowInsertParams } from './types'
|
||||
|
||||
interface TableUpsertResponse extends ToolResponse {
|
||||
output: {
|
||||
|
||||
@@ -112,7 +112,8 @@ export interface ToolConfig<P = any, R = any> {
|
||||
directExecution?: (params: P) => Promise<ToolResponse>
|
||||
}
|
||||
|
||||
export interface TableRow {
|
||||
/** Key-value pair row for HTTP request tables (headers, params) */
|
||||
export interface KeyValueRow {
|
||||
id: string
|
||||
cells: {
|
||||
Key: string
|
||||
@@ -120,6 +121,9 @@ export interface TableRow {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use KeyValueRow instead */
|
||||
export type TableRow = KeyValueRow
|
||||
|
||||
export interface OAuthTokenPayload {
|
||||
credentialId?: string
|
||||
credentialAccountUserId?: string
|
||||
|
||||
Reference in New Issue
Block a user