From b3ca0c947c1e48a10552cb21ff2e4d3573469a5f Mon Sep 17 00:00:00 2001 From: Lakee Sivaraya Date: Wed, 14 Jan 2026 18:09:35 -0800 Subject: [PATCH] updates --- apps/sim/app/api/table/route.ts | 2 +- .../[tableId]/components/filter-builder.tsx | 4 +- .../filter-format/filter-format.tsx | 9 +- .../components/sort-format/sort-format.tsx | 2 +- .../editor/components/sub-block/sub-block.tsx | 2 +- apps/sim/blocks/blocks/table.ts | 2 +- apps/sim/hooks/queries/use-tables.ts | 65 +----- .../{ => filters}/filter-builder-utils.ts | 2 +- .../table/{ => filters}/filter-constants.ts | 5 +- apps/sim/lib/table/filters/index.ts | 9 + .../table/{ => filters}/use-filter-builder.ts | 2 +- apps/sim/lib/table/index.ts | 3 +- apps/sim/lib/table/query-builder.ts | 196 ++++++++---------- apps/sim/lib/table/validation/index.ts | 8 + .../{ => validation}/validation-helpers.ts | 2 +- .../lib/table/{ => validation}/validation.ts | 6 +- 16 files changed, 131 insertions(+), 188 deletions(-) rename apps/sim/lib/table/{ => filters}/filter-builder-utils.ts (99%) rename apps/sim/lib/table/{ => filters}/filter-constants.ts (91%) create mode 100644 apps/sim/lib/table/filters/index.ts rename apps/sim/lib/table/{ => filters}/use-filter-builder.ts (99%) create mode 100644 apps/sim/lib/table/validation/index.ts rename apps/sim/lib/table/{ => validation}/validation-helpers.ts (99%) rename apps/sim/lib/table/{ => validation}/validation.ts (97%) diff --git a/apps/sim/app/api/table/route.ts b/apps/sim/app/api/table/route.ts index a9b657cf0..3f40a58ec 100644 --- a/apps/sim/app/api/table/route.ts +++ b/apps/sim/app/api/table/route.ts @@ -7,7 +7,7 @@ import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' import { TABLE_LIMITS, validateTableName, validateTableSchema } from '@/lib/table' -import type { TableSchema } from '@/lib/table/validation' +import type { TableSchema } from '@/lib/table/validation/validation' import type { TableColumnData, TableSchemaData } from './utils' const logger = createLogger('TableAPI') diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/filter-builder.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/filter-builder.tsx index 99caf8ae5..133b830b2 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/filter-builder.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/filter-builder.tsx @@ -3,8 +3,8 @@ import { useCallback, useMemo, useState } from 'react' import { ArrowDownAZ, ArrowUpAZ, Plus, X } from 'lucide-react' import { Button, Combobox, Input } from '@/components/emcn' -import type { FilterCondition } from '@/lib/table/filter-constants' -import { useFilterBuilder } from '@/lib/table/use-filter-builder' +import type { FilterCondition } from '@/lib/table/filters/filter-constants' +import { useFilterBuilder } from '@/lib/table/filters/use-filter-builder' /** * Represents a sort configuration. diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx index 85f5e46a6..0ae54a0c9 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-format/filter-format.tsx @@ -4,9 +4,12 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Plus, X } from 'lucide-react' import { Button, Combobox, type ComboboxOption, Input } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { conditionsToJsonString, jsonStringToConditions } from '@/lib/table/filter-builder-utils' -import type { FilterCondition } from '@/lib/table/filter-constants' -import { useFilterBuilder } from '@/lib/table/use-filter-builder' +import { + conditionsToJsonString, + jsonStringToConditions, +} from '@/lib/table/filters/filter-builder-utils' +import type { FilterCondition } from '@/lib/table/filters/filter-constants' +import { useFilterBuilder } from '@/lib/table/filters/use-filter-builder' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' import { SubBlockInputController } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sub-block-input-controller' import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx index 1e3da0fc6..50bba388b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-format/sort-format.tsx @@ -9,7 +9,7 @@ import { SORT_DIRECTIONS, type SortCondition, sortConditionsToJsonString, -} from '@/lib/table/filter-builder-utils' +} from '@/lib/table/filters/filter-builder-utils' import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value' interface SortFormatProps { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index b8e6d840f..11593e32c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -3,7 +3,7 @@ import { AlertTriangle, Wand2 } from 'lucide-react' import { Label, Tooltip } from '@/components/emcn/components' import { Button } from '@/components/ui/button' import { cn } from '@/lib/core/utils/cn' -import type { FilterCondition, SortCondition } from '@/lib/table/filter-builder-utils' +import type { FilterCondition, SortCondition } from '@/lib/table/filters/filter-builder-utils' import type { FieldDiffStatus } from '@/lib/workflows/diff/types' import { CheckboxList, diff --git a/apps/sim/blocks/blocks/table.ts b/apps/sim/blocks/blocks/table.ts index bc1f39227..92442390f 100644 --- a/apps/sim/blocks/blocks/table.ts +++ b/apps/sim/blocks/blocks/table.ts @@ -1,5 +1,5 @@ import { TableIcon } from '@/components/icons' -import { conditionsToFilter, sortConditionsToSort } from '@/lib/table/filter-builder-utils' +import { conditionsToFilter, sortConditionsToSort } from '@/lib/table/filters/filter-builder-utils' import type { BlockConfig } from '@/blocks/types' import type { TableQueryResponse } from '@/tools/table/types' diff --git a/apps/sim/hooks/queries/use-tables.ts b/apps/sim/hooks/queries/use-tables.ts index d12cb5e5b..cba8f63c7 100644 --- a/apps/sim/hooks/queries/use-tables.ts +++ b/apps/sim/hooks/queries/use-tables.ts @@ -1,65 +1,20 @@ /** * React Query hooks for managing user-defined tables. - * - * Provides hooks for fetching, creating, and deleting tables within a workspace. - * Tables are user-defined data structures that can store rows of data in JSONB format. - * - * @module hooks/queries/use-tables */ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import type { TableDefinition } from '@/tools/table/types' -/** - * Query key factories for table-related queries. - * Ensures consistent cache invalidation across the app. - */ -/** - * Query keys for table-related queries in React Query. - * Use these keys to ensure correct cache scoping and invalidation - * in queries or mutations dealing with user-defined tables. - */ export const tableKeys = { - /** - * Base key for all table queries. - * Example: ['tables'] - */ all: ['tables'] as const, - - /** - * Key for all lists of tables. - * Useful for cache invalidation across all table lists. - * Example: ['tables', 'list'] - */ lists: () => [...tableKeys.all, 'list'] as const, - - /** - * Key for the list of tables in a specific workspace. - * @param workspaceId - The workspace ID to scope the list to. - * Example: ['tables', 'list', 'workspace_abc123'] - */ list: (workspaceId?: string) => [...tableKeys.lists(), workspaceId ?? ''] as const, - - /** - * Key for all individual table detail queries. - * Useful for cache invalidation for all details. - * Example: ['tables', 'detail'] - */ details: () => [...tableKeys.all, 'detail'] as const, - - /** - * Key for a specific table's detail. - * @param tableId - The table ID to scope the detail to. - * Example: ['tables', 'detail', 'table_abc123'] - */ detail: (tableId: string) => [...tableKeys.details(), tableId] as const, } /** - * Hook to fetch all tables for a workspace. - * - * @param workspaceId - The workspace ID to fetch tables for. If undefined, the query is disabled. - * @returns React Query result containing the list of tables. + * Fetch all tables for a workspace. */ export function useTablesList(workspaceId?: string) { return useQuery({ @@ -78,17 +33,12 @@ export function useTablesList(workspaceId?: string) { return (response.data?.tables || []) as TableDefinition[] }, enabled: Boolean(workspaceId), - staleTime: 30 * 1000, // Cache data for 30 seconds before refetching + staleTime: 30 * 1000, }) } /** - * Hook to create a new table in a workspace. - * - * @param workspaceId - The workspace ID where the table will be created. - * @returns React Query mutation object with mutationFn and onSuccess handler. - * The mutationFn accepts table creation parameters (name, description, schema). - * On success, invalidates the tables list query to refresh the UI. + * Create a new table in a workspace. */ export function useCreateTable(workspaceId: string) { const queryClient = useQueryClient() @@ -113,19 +63,13 @@ export function useCreateTable(workspaceId: string) { return res.json() }, onSuccess: () => { - // Invalidate the tables list query to refresh the UI queryClient.invalidateQueries({ queryKey: tableKeys.list(workspaceId) }) }, }) } /** - * Hook to delete a table from a workspace. - * - * @param workspaceId - The workspace ID containing the table to delete. - * @returns React Query mutation object with mutationFn and onSuccess handler. - * The mutationFn accepts a tableId string. - * On success, invalidates the tables list query to refresh the UI. + * Delete a table from a workspace. */ export function useDeleteTable(workspaceId: string) { const queryClient = useQueryClient() @@ -147,7 +91,6 @@ export function useDeleteTable(workspaceId: string) { return res.json() }, onSuccess: () => { - // Invalidate the tables list query to refresh the UI queryClient.invalidateQueries({ queryKey: tableKeys.list(workspaceId) }) }, }) diff --git a/apps/sim/lib/table/filter-builder-utils.ts b/apps/sim/lib/table/filters/filter-builder-utils.ts similarity index 99% rename from apps/sim/lib/table/filter-builder-utils.ts rename to apps/sim/lib/table/filters/filter-builder-utils.ts index d93b21dc2..ca1664ae2 100644 --- a/apps/sim/lib/table/filter-builder-utils.ts +++ b/apps/sim/lib/table/filters/filter-builder-utils.ts @@ -1,7 +1,7 @@ /** * Shared utilities for filter builder UI components. * - * @module lib/table/filter-builder-utils + * @module lib/table/filters/filter-builder-utils */ // Re-export shared constants and types for backward compatibility diff --git a/apps/sim/lib/table/filter-constants.ts b/apps/sim/lib/table/filters/filter-constants.ts similarity index 91% rename from apps/sim/lib/table/filter-constants.ts rename to apps/sim/lib/table/filters/filter-constants.ts index 9800e2b91..fe634dc11 100644 --- a/apps/sim/lib/table/filter-constants.ts +++ b/apps/sim/lib/table/filters/filter-constants.ts @@ -1,11 +1,8 @@ /** * Shared constants and types for table filtering and sorting. * - * @module lib/table/filter-constants + * @module lib/table/filters/filter-constants * - * @remarks - * This is the single source of truth for all filter and sort constants. - * All components should import from here to ensure consistency. */ /** diff --git a/apps/sim/lib/table/filters/index.ts b/apps/sim/lib/table/filters/index.ts new file mode 100644 index 000000000..e29e7a5ea --- /dev/null +++ b/apps/sim/lib/table/filters/index.ts @@ -0,0 +1,9 @@ +/** + * Filter utilities for table queries. + * + * @module lib/table/filters + */ + +export * from './filter-builder-utils' +export * from './filter-constants' +export * from './use-filter-builder' diff --git a/apps/sim/lib/table/use-filter-builder.ts b/apps/sim/lib/table/filters/use-filter-builder.ts similarity index 99% rename from apps/sim/lib/table/use-filter-builder.ts rename to apps/sim/lib/table/filters/use-filter-builder.ts index 3542721d5..37af180f7 100644 --- a/apps/sim/lib/table/use-filter-builder.ts +++ b/apps/sim/lib/table/filters/use-filter-builder.ts @@ -4,7 +4,7 @@ * Provides reusable filter condition management logic shared between * the table data viewer's FilterBuilder and workflow block's FilterFormat. * - * @module lib/table/use-filter-builder + * @module lib/table/filters/use-filter-builder */ import { useCallback, useMemo } from 'react' diff --git a/apps/sim/lib/table/index.ts b/apps/sim/lib/table/index.ts index d38972648..75065abd4 100644 --- a/apps/sim/lib/table/index.ts +++ b/apps/sim/lib/table/index.ts @@ -7,7 +7,6 @@ */ export * from './constants' +export * from './filters' export * from './query-builder' -export * from './use-filter-builder' export * from './validation' -export * from './validation-helpers' diff --git a/apps/sim/lib/table/query-builder.ts b/apps/sim/lib/table/query-builder.ts index c323796ce..50d9c8a55 100644 --- a/apps/sim/lib/table/query-builder.ts +++ b/apps/sim/lib/table/query-builder.ts @@ -87,16 +87,53 @@ function buildContainmentClause(tableName: string, field: string, value: JsonVal return sql`${sql.raw(`${tableName}.data`)} @> ${jsonObj}::jsonb` } +/** + * Builds a numeric comparison clause for JSONB fields. + * Generates: `(table.data->>'field')::numeric value` + */ +function buildComparisonClause( + tableName: string, + field: string, + operator: '>' | '>=' | '<' | '<=', + value: number +): SQL { + const escapedField = field.replace(/'/g, "''") + return sql`(${sql.raw(`${tableName}.data->>'${escapedField}'`)})::numeric ${sql.raw(operator)} ${value}` +} + +/** + * Builds a case-insensitive pattern matching clause for JSONB text fields. + * Generates: `table.data->>'field' ILIKE '%value%'` + */ +function buildContainsClause(tableName: string, field: string, value: string): SQL { + const escapedField = field.replace(/'/g, "''") + return sql`${sql.raw(`${tableName}.data->>'${escapedField}'`)} ILIKE ${`%${value}%`}` +} + +/** + * Builds SQL conditions for a single field based on the provided condition. + * + * Supports both simple equality checks (using JSONB containment) and complex + * operators like comparison, membership, and pattern matching. Field names are + * validated to prevent SQL injection, and operators are validated against an + * allowed whitelist. + * + * @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 + * 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. + * @throws Error if field name is invalid or operator is not allowed + */ function buildFieldCondition( tableName: string, field: string, condition: JsonValue | FieldCondition ): SQL[] { - // Validate field name to prevent SQL injection validateFieldName(field) const conditions: SQL[] = [] - const escapedField = field.replace(/'/g, "''") if (typeof condition === 'object' && condition !== null && !Array.isArray(condition)) { for (const [op, value] of Object.entries(condition)) { @@ -115,34 +152,28 @@ function buildFieldCondition( break case '$gt': - conditions.push( - sql`(${sql.raw(`${tableName}.data->>'${escapedField}'`)})::numeric > ${value}` - ) + conditions.push(buildComparisonClause(tableName, field, '>', value as number)) break case '$gte': - conditions.push( - sql`(${sql.raw(`${tableName}.data->>'${escapedField}'`)})::numeric >= ${value}` - ) + conditions.push(buildComparisonClause(tableName, field, '>=', value as number)) break case '$lt': - conditions.push( - sql`(${sql.raw(`${tableName}.data->>'${escapedField}'`)})::numeric < ${value}` - ) + conditions.push(buildComparisonClause(tableName, field, '<', value as number)) break case '$lte': - conditions.push( - sql`(${sql.raw(`${tableName}.data->>'${escapedField}'`)})::numeric <= ${value}` - ) + conditions.push(buildComparisonClause(tableName, field, '<=', value as number)) break case '$in': if (Array.isArray(value) && value.length > 0) { if (value.length === 1) { + // Single value then use containment clause conditions.push(buildContainmentClause(tableName, field, value[0])) } else { + // Multiple values then use OR clause const inConditions = value.map((v) => buildContainmentClause(tableName, field, v)) conditions.push(sql`(${sql.join(inConditions, sql.raw(' OR '))})`) } @@ -159,9 +190,7 @@ function buildFieldCondition( break case '$contains': - conditions.push( - sql`${sql.raw(`${tableName}.data->>'${escapedField}'`)} ILIKE ${`%${value}%`}` - ) + conditions.push(buildContainsClause(tableName, field, value as string)) break default: @@ -176,6 +205,28 @@ function buildFieldCondition( return conditions } +/** + * Builds SQL clauses from nested filters and joins them with the specified operator. + */ +function buildLogicalClause( + subFilters: QueryFilter[], + tableName: string, + operator: 'OR' | 'AND' +): SQL | undefined { + const clauses: SQL[] = [] + for (const subFilter of subFilters) { + const clause = buildFilterClause(subFilter, tableName) + if (clause) { + clauses.push(clause) + } + } + + if (clauses.length === 0) return undefined + if (clauses.length === 1) return clauses[0] + + return sql`(${sql.join(clauses, sql.raw(` ${operator} `))})` +} + /** * Builds a WHERE clause from a filter object. * Recursively processes logical operators ($or, $and) and field conditions. @@ -183,82 +234,31 @@ function buildFieldCondition( export function buildFilterClause(filter: QueryFilter, tableName: string): SQL | undefined { const conditions: SQL[] = [] - /** - * Iterate over each field and its associated condition in the filter object. - * - * The filter is expected to be an object where keys are either field names or logical operators - * ('$or', '$and'), and values are the conditions to apply or arrays of nested filter objects. - */ for (const [field, condition] of Object.entries(filter)) { - // Skip undefined conditions (e.g., unused or programmatically removed filters) if (condition === undefined) { continue } - /** - * Handle the logical OR operator: { $or: [filter1, filter2, ...] } - * Recursively build SQL clauses for each sub-filter, - * then join them with an OR. If there is only one sub-filter, - * no need for OR grouping. - */ if (field === '$or' && Array.isArray(condition)) { - const orConditions: SQL[] = [] - for (const subFilter of condition) { - const subClause = buildFilterClause(subFilter as QueryFilter, tableName) - if (subClause) { - orConditions.push(subClause) - } - } - if (orConditions.length > 0) { - if (orConditions.length === 1) { - // Only one condition; no need to wrap in OR - conditions.push(orConditions[0]) - } else { - // Multiple conditions; join by OR - conditions.push(sql`(${sql.join(orConditions, sql.raw(' OR '))})`) - } + const orClause = buildLogicalClause(condition as QueryFilter[], tableName, 'OR') + if (orClause) { + conditions.push(orClause) } continue } - /** - * Handle the logical AND operator: { $and: [filter1, filter2, ...] } - * Recursively build SQL clauses for each sub-filter, - * then join them with an AND. If there is only one sub-filter, - * no need for AND grouping. - */ if (field === '$and' && Array.isArray(condition)) { - const andConditions: SQL[] = [] - for (const subFilter of condition) { - const subClause = buildFilterClause(subFilter as QueryFilter, tableName) - if (subClause) { - andConditions.push(subClause) - } - } - if (andConditions.length > 0) { - if (andConditions.length === 1) { - // Only one condition; no need to wrap in AND - conditions.push(andConditions[0]) - } else { - // Multiple conditions; join by AND - conditions.push(sql`(${sql.join(andConditions, sql.raw(' AND '))})`) - } + const andClause = buildLogicalClause(condition as QueryFilter[], tableName, 'AND') + if (andClause) { + conditions.push(andClause) } continue } - /** - * If the condition is an array, but not a logical operator, - * skip it (invalid filter structure). - */ if (Array.isArray(condition)) { continue } - /** - * Build conditions for regular fields. - * This delegates to buildFieldCondition, which handles comparisons like $eq, $gt, etc. - */ const fieldConditions = buildFieldCondition( tableName, field, @@ -267,17 +267,27 @@ export function buildFilterClause(filter: QueryFilter, tableName: string): SQL | conditions.push(...fieldConditions) } - /** - * If no conditions were built, return undefined to indicate no filter. - * If only one condition exists, return it directly. - * Otherwise, join all conditions using AND. - */ if (conditions.length === 0) return undefined if (conditions.length === 1) return conditions[0] return sql.join(conditions, sql.raw(' AND ')) } +/** + * Builds a single ORDER BY clause for a field. + * Timestamp fields use direct column access, others use JSONB text extraction. + */ +function buildSortFieldClause(tableName: string, field: string, direction: 'asc' | 'desc'): SQL { + const escapedField = field.replace(/'/g, "''") + const directionSql = direction.toUpperCase() + + if (field === 'createdAt' || field === 'updatedAt') { + return sql.raw(`${tableName}.${escapedField} ${directionSql}`) + } + + return sql.raw(`${tableName}.data->>'${escapedField}' ${directionSql}`) +} + /** * Builds an ORDER BY clause from a sort object. * Note: JSONB fields use text extraction, so numeric sorting may not work as expected. @@ -293,40 +303,14 @@ export function buildSortClause( ): SQL | undefined { const clauses: SQL[] = [] - /** - * Build ORDER BY SQL clauses based on the sort object keys and directions. - * - For `createdAt` and `updatedAt`, use the top-level table columns for proper type sorting. - * - For all other fields, treat them as keys in the table's data JSONB column. - * Extraction is performed with ->> to return text, which is then sorted. - * - Field names are validated to prevent SQL injection. - */ for (const [field, direction] of Object.entries(sort)) { - // Validate field name to prevent SQL injection validateFieldName(field) - // Validate direction if (direction !== 'asc' && direction !== 'desc') { throw new Error(`Invalid sort direction "${direction}". Must be "asc" or "desc".`) } - // Escape single quotes for SQL safety (defense in depth) - const escapedField = field.replace(/'/g, "''") - - if (field === 'createdAt' || field === 'updatedAt') { - // Use regular column for timestamp sorting - clauses.push( - direction === 'asc' - ? sql.raw(`${tableName}.${escapedField} ASC`) - : sql.raw(`${tableName}.${escapedField} DESC`) - ) - } else { - // Use text extraction for JSONB field sorting - clauses.push( - direction === 'asc' - ? sql.raw(`${tableName}.data->>'${escapedField}' ASC`) - : sql.raw(`${tableName}.data->>'${escapedField}' DESC`) - ) - } + clauses.push(buildSortFieldClause(tableName, field, direction)) } return clauses.length > 0 ? sql.join(clauses, sql.raw(', ')) : undefined diff --git a/apps/sim/lib/table/validation/index.ts b/apps/sim/lib/table/validation/index.ts new file mode 100644 index 000000000..6347176d4 --- /dev/null +++ b/apps/sim/lib/table/validation/index.ts @@ -0,0 +1,8 @@ +/** + * Validation utilities for table schemas and row data. + * + * @module lib/table/validation + */ + +export * from './validation' +export * from './validation-helpers' diff --git a/apps/sim/lib/table/validation-helpers.ts b/apps/sim/lib/table/validation/validation-helpers.ts similarity index 99% rename from apps/sim/lib/table/validation-helpers.ts rename to apps/sim/lib/table/validation/validation-helpers.ts index 3badfb668..dd2e36996 100644 --- a/apps/sim/lib/table/validation-helpers.ts +++ b/apps/sim/lib/table/validation/validation-helpers.ts @@ -4,7 +4,7 @@ * These helpers consolidate common validation patterns (size, schema, uniqueness) * into reusable functions that return formatted error responses. * - * @module lib/table/validation-helpers + * @module lib/table/validation/validation-helpers */ import { db } from '@sim/db' diff --git a/apps/sim/lib/table/validation.ts b/apps/sim/lib/table/validation/validation.ts similarity index 97% rename from apps/sim/lib/table/validation.ts rename to apps/sim/lib/table/validation/validation.ts index 0a99b9479..f8bd6b8fd 100644 --- a/apps/sim/lib/table/validation.ts +++ b/apps/sim/lib/table/validation/validation.ts @@ -1,11 +1,11 @@ /** * Validation utilities for table schemas and row data. * - * @module lib/table/validation + * @module lib/table/validation/validation */ -import type { ColumnType } from './constants' -import { COLUMN_TYPES, NAME_PATTERN, TABLE_LIMITS } from './constants' +import type { ColumnType } from '../constants' +import { COLUMN_TYPES, NAME_PATTERN, TABLE_LIMITS } from '../constants' export interface ColumnDefinition { name: string