This commit is contained in:
Lakee Sivaraya
2026-01-14 21:03:53 -08:00
parent b08ce03409
commit ffad20efc5
8 changed files with 36 additions and 70 deletions

View File

@@ -26,11 +26,6 @@ import {
const logger = createLogger('TableRowsAPI')
/**
* Type for sort direction specification.
*/
type SortDirection = Record<string, 'asc' | 'desc'>
/**
* Zod schema for inserting a single row into a table.
*
@@ -56,9 +51,6 @@ const BatchInsertRowsSchema = z.object({
.max(1000, 'Cannot insert more than 1000 rows per batch'),
})
/** Inferred type for batch insert request body */
type BatchInsertBody = z.infer<typeof BatchInsertRowsSchema>
/**
* Zod schema for querying rows with filtering, sorting, and pagination.
*/
@@ -137,7 +129,7 @@ interface TableRowsRouteParams {
async function handleBatchInsert(
requestId: string,
tableId: string,
body: BatchInsertBody,
body: z.infer<typeof BatchInsertRowsSchema>,
userId: string
): Promise<NextResponse> {
const validated = BatchInsertRowsSchema.parse(body)
@@ -271,7 +263,12 @@ export async function POST(request: NextRequest, { params }: TableRowsRouteParam
'rows' in body &&
Array.isArray((body as Record<string, unknown>).rows)
) {
return handleBatchInsert(requestId, tableId, body as BatchInsertBody, authResult.userId)
return handleBatchInsert(
requestId,
tableId,
body as z.infer<typeof BatchInsertRowsSchema>,
authResult.userId
)
}
// Single row insert
@@ -406,14 +403,14 @@ export async function GET(request: NextRequest, { params }: TableRowsRouteParams
const offset = searchParams.get('offset')
let filter: Record<string, unknown> | undefined
let sort: SortDirection | undefined
let sort: Record<string, 'asc' | 'desc'> | undefined
try {
if (filterParam) {
filter = JSON.parse(filterParam) as Record<string, unknown>
}
if (sortParam) {
sort = JSON.parse(sortParam) as SortDirection
sort = JSON.parse(sortParam) as Record<string, 'asc' | 'desc'>
}
} catch {
return NextResponse.json({ error: 'Invalid filter or sort JSON' }, { status: 400 })

View File

@@ -70,19 +70,6 @@ export interface TableAccessDenied {
reason?: string
}
/**
* Union type for table access check results.
*/
export type TableAccessCheck = TableAccessResult | TableAccessDenied
/**
* Permission level required for table access.
* - 'read': Any workspace permission (read, write, or admin)
* - 'write': Write or admin permission required
* - 'admin': Admin permission required
*/
export type TablePermissionLevel = 'read' | 'write' | 'admin'
/**
* Internal function to check if a user has the required permission level for a table.
*
@@ -100,8 +87,8 @@ export type TablePermissionLevel = 'read' | 'write' | 'admin'
async function checkTableAccessInternal(
tableId: string,
userId: string,
requiredLevel: TablePermissionLevel
): Promise<TableAccessCheck> {
requiredLevel: 'read' | 'write' | 'admin'
): Promise<TableAccessResult | TableAccessDenied> {
// Fetch table data
const table = await db
.select({
@@ -178,7 +165,10 @@ async function checkTableAccessInternal(
* // User has access, proceed with operation
* ```
*/
export async function checkTableAccess(tableId: string, userId: string): Promise<TableAccessCheck> {
export async function checkTableAccess(
tableId: string,
userId: string
): Promise<TableAccessResult | TableAccessDenied> {
return checkTableAccessInternal(tableId, userId, 'read')
}
@@ -205,7 +195,7 @@ export async function checkTableAccess(tableId: string, userId: string): Promise
export async function checkTableWriteAccess(
tableId: string,
userId: string
): Promise<TableAccessCheck> {
): Promise<TableAccessResult | TableAccessDenied> {
return checkTableAccessInternal(tableId, userId, 'write')
}
@@ -234,7 +224,7 @@ export async function checkAccessOrRespond(
tableId: string,
userId: string,
requestId: string,
level: TablePermissionLevel = 'write'
level: 'read' | 'write' | 'admin' = 'write'
): Promise<TableAccessResult | NextResponse> {
const checkFn = level === 'read' ? checkTableAccess : checkTableWriteAccess
const accessCheck = await checkFn(tableId, userId)
@@ -277,7 +267,7 @@ export async function checkAccessWithFullTable(
tableId: string,
userId: string,
requestId: string,
level: TablePermissionLevel = 'write'
level: 'read' | 'write' | 'admin' = 'write'
): Promise<TableAccessResultFull | NextResponse> {
// Fetch full table data in one query
const [tableData] = await db

View File

@@ -32,17 +32,12 @@ export interface TableInfo {
schema: TableSchema
}
/**
* Modal mode determines the operation and UI.
*/
export type TableRowModalMode = 'add' | 'edit' | 'delete'
/**
* Props for the TableRowModal component.
*/
export interface TableRowModalProps {
/** The operation mode */
mode: TableRowModalMode
mode: 'add' | 'edit' | 'delete'
/** Whether the modal is open */
isOpen: boolean
/** Callback when the modal should close */
@@ -57,14 +52,11 @@ export interface TableRowModalProps {
onSuccess: () => void
}
/** Row data being edited in the form */
type RowFormData = Record<string, unknown>
/**
* Creates initial form data for columns.
*/
function createInitialRowData(columns: ColumnDefinition[]): RowFormData {
const initial: RowFormData = {}
function createInitialRowData(columns: ColumnDefinition[]): Record<string, unknown> {
const initial: Record<string, unknown> = {}
columns.forEach((col) => {
if (col.type === 'boolean') {
initial[col.name] = false
@@ -78,7 +70,10 @@ function createInitialRowData(columns: ColumnDefinition[]): RowFormData {
/**
* Cleans and transforms form data for API submission.
*/
function cleanRowData(columns: ColumnDefinition[], rowData: RowFormData): Record<string, unknown> {
function cleanRowData(
columns: ColumnDefinition[],
rowData: Record<string, unknown>
): Record<string, unknown> {
const cleanData: Record<string, unknown> = {}
columns.forEach((col) => {
@@ -181,7 +176,7 @@ export function TableRowModal({
const schema = table?.schema
const columns = schema?.columns || []
const [rowData, setRowData] = useState<RowFormData>({})
const [rowData, setRowData] = useState<Record<string, unknown>>({})
const [error, setError] = useState<string | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)

View File

@@ -17,7 +17,7 @@ import {
ModalHeader,
Textarea,
} from '@/components/emcn'
import type { ColumnDefinition, ColumnType } from '@/lib/table'
import type { ColumnDefinition } from '@/lib/table'
import { useCreateTable } from '@/hooks/queries/use-tables'
const logger = createLogger('CreateTableModal')
@@ -35,7 +35,7 @@ interface CreateTableModalProps {
/**
* Available column type options for the combobox UI.
*/
const COLUMN_TYPE_OPTIONS: Array<{ value: ColumnType; label: string }> = [
const COLUMN_TYPE_OPTIONS: Array<{ value: ColumnDefinition['type']; label: string }> = [
{ value: 'string', label: 'String' },
{ value: 'number', label: 'Number' },
{ value: 'boolean', label: 'Boolean' },
@@ -341,7 +341,7 @@ function ColumnRow({ column, index, isRemovable, onChange, onRemove }: ColumnRow
options={COLUMN_TYPE_OPTIONS}
value={column.type}
selectedValue={column.type}
onChange={(value) => onChange(index, 'type', value as ColumnType)}
onChange={(value) => onChange(index, 'type', value as ColumnDefinition['type'])}
placeholder='Type'
editable={false}
filterOptions={false}

View File

@@ -27,6 +27,4 @@ export const TABLE_LIMITS = {
export const COLUMN_TYPES = ['string', 'number', 'boolean', 'date', 'json'] as const
export type ColumnType = (typeof COLUMN_TYPES)[number]
export const NAME_PATTERN = /^[a-z_][a-z0-9_]*$/i

View File

@@ -4,9 +4,7 @@
* @module lib/table/types
*/
import type { ColumnType } from './constants'
export type { ColumnType }
import type { COLUMN_TYPES } from './constants'
/** Primitive values that can be stored in table columns */
export type ColumnValue = string | number | boolean | null | Date
@@ -30,7 +28,7 @@ export interface ColumnDefinition {
/** Column name (must match NAME_PATTERN) */
name: string
/** Data type for the column */
type: ColumnType
type: (typeof COLUMN_TYPES)[number]
/** Whether the column is required (non-null) */
required?: boolean
/** Whether the column must have unique values */

View File

@@ -34,11 +34,6 @@ interface ValidationFailure {
response: NextResponse
}
/**
* Union type for validation results.
*/
export type RowValidationResult = ValidationSuccess | ValidationFailure
/**
* Options for single row validation.
*/
@@ -77,7 +72,9 @@ export interface ValidateRowOptions {
* // Proceed with insert/update
* ```
*/
export async function validateRowData(options: ValidateRowOptions): Promise<RowValidationResult> {
export async function validateRowData(
options: ValidateRowOptions
): Promise<ValidationSuccess | ValidationFailure> {
const { rowData, schema, tableId, excludeRowId, checkUnique = true } = options
// 1. Validate row size
@@ -161,11 +158,6 @@ interface BatchValidationFailure {
response: NextResponse
}
/**
* Union type for batch validation results.
*/
export type BatchValidationResult = BatchValidationSuccess | BatchValidationFailure
/**
* Options for batch row validation.
*/
@@ -203,7 +195,7 @@ export interface ValidateBatchRowsOptions {
*/
export async function validateBatchRows(
options: ValidateBatchRowsOptions
): Promise<BatchValidationResult> {
): Promise<BatchValidationSuccess | BatchValidationFailure> {
const { rows, schema, tableId, checkUnique = true } = options
const errors: BatchRowError[] = []

View File

@@ -112,8 +112,7 @@ export interface ToolConfig<P = any, R = any> {
directExecution?: (params: P) => Promise<ToolResponse>
}
/** Key-value pair row for HTTP request tables (headers, params) */
export interface KeyValueRow {
export interface TableRow {
id: string
cells: {
Key: string
@@ -121,9 +120,6 @@ export interface KeyValueRow {
}
}
/** @deprecated Use KeyValueRow instead */
export type TableRow = KeyValueRow
export interface OAuthTokenPayload {
credentialId?: string
credentialAccountUserId?: string