simplify comments

This commit is contained in:
Lakee Sivaraya
2026-01-15 13:13:40 -08:00
parent 57fbd2aa1c
commit 7f894ec023
21 changed files with 228 additions and 330 deletions

View File

@@ -142,15 +142,7 @@ export async function GET(request: NextRequest, { params }: TableRouteParams) {
/**
* DELETE /api/table/[tableId]?workspaceId=xxx
*
* Deletes a table.
*
* @param request - The incoming HTTP request
* @param context - Route context containing tableId param
* @returns JSON response confirming deletion or error
*
* @remarks
* This performs a hard delete, removing the table and its rows.
* The operation requires write access to the table.
* Deletes a table and all its rows (hard delete, requires write access).
*/
export async function DELETE(request: NextRequest, { params }: TableRouteParams) {
const requestId = generateRequestId()

View File

@@ -162,22 +162,7 @@ export async function GET(request: NextRequest, { params }: RowRouteParams) {
/**
* PATCH /api/table/[tableId]/rows/[rowId]
*
* Updates an existing row with new data.
*
* @param request - The incoming HTTP request with update data
* @param context - Route context containing tableId and rowId params
* @returns JSON response with updated row or error
*
* @remarks
* The entire row data must be provided; this is a full replacement,
* not a partial update.
*
* @example Request body:
* ```json
* {
* "data": { "name": "Jane", "email": "jane@example.com" }
* }
* ```
* Updates an existing row with new data (full replacement, not partial).
*/
export async function PATCH(request: NextRequest, { params }: RowRouteParams) {
const requestId = generateRequestId()

View File

@@ -36,12 +36,7 @@ const InsertRowSchema = z.object({
data: z.record(z.unknown(), { required_error: 'Row data is required' }),
})
/**
* Zod schema for batch inserting multiple rows.
*
* @remarks
* Maximum 1000 rows per batch for performance and safety.
*/
/** Zod schema for batch inserting multiple rows (max 1000 per batch) */
const BatchInsertRowsSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required'),
rows: z
@@ -72,12 +67,7 @@ const QueryRowsSchema = z.object({
.default(0),
})
/**
* Zod schema for updating multiple rows by filter criteria.
*
* @remarks
* Maximum 1000 rows can be updated per operation for safety.
*/
/** Zod schema for updating multiple rows by filter (max 1000 per operation) */
const UpdateRowsByFilterSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required'),
filter: z.record(z.unknown(), { required_error: 'Filter criteria is required' }),
@@ -90,12 +80,7 @@ const UpdateRowsByFilterSchema = z.object({
.optional(),
})
/**
* Zod schema for deleting multiple rows by filter criteria.
*
* @remarks
* Maximum 1000 rows can be deleted per operation for safety.
*/
/** Zod schema for deleting multiple rows by filter (max 1000 per operation) */
const DeleteRowsByFilterSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required'),
filter: z.record(z.unknown(), { required_error: 'Filter criteria is required' }),
@@ -408,7 +393,6 @@ export async function GET(request: NextRequest, { params }: TableRowsRouteParams
}
// Security check: verify workspaceId matches the table's workspace
const actualWorkspaceId = validated.workspaceId
const isValidWorkspace = await verifyTableWorkspace(tableId, validated.workspaceId)
if (!isValidWorkspace) {
logger.warn(
@@ -420,7 +404,7 @@ export async function GET(request: NextRequest, { params }: TableRowsRouteParams
// Build base where conditions
const baseConditions = [
eq(userTableRows.tableId, tableId),
eq(userTableRows.workspaceId, actualWorkspaceId),
eq(userTableRows.workspaceId, validated.workspaceId),
]
// Add filter conditions if provided
@@ -544,7 +528,6 @@ export async function PUT(request: NextRequest, { params }: TableRowsRouteParams
return NextResponse.json({ error: 'Invalid workspace ID' }, { status: 400 })
}
const actualWorkspaceId = validated.workspaceId
const updateData = validated.data as RowData
// Validate new data size
@@ -559,7 +542,7 @@ export async function PUT(request: NextRequest, { params }: TableRowsRouteParams
// Build base where conditions
const baseConditions = [
eq(userTableRows.tableId, tableId),
eq(userTableRows.workspaceId, actualWorkspaceId),
eq(userTableRows.workspaceId, validated.workspaceId),
]
// Add filter conditions
@@ -742,7 +725,6 @@ export async function DELETE(request: NextRequest, { params }: TableRowsRoutePar
if (accessResult instanceof NextResponse) return accessResult
// Security check: verify workspaceId matches the table's workspace
const actualWorkspaceId = validated.workspaceId
const isValidWorkspace = await verifyTableWorkspace(tableId, validated.workspaceId)
if (!isValidWorkspace) {
logger.warn(
@@ -754,7 +736,7 @@ export async function DELETE(request: NextRequest, { params }: TableRowsRoutePar
// Build base where conditions
const baseConditions = [
eq(userTableRows.tableId, tableId),
eq(userTableRows.workspaceId, actualWorkspaceId),
eq(userTableRows.workspaceId, validated.workspaceId),
]
// Add filter conditions
@@ -805,7 +787,7 @@ export async function DELETE(request: NextRequest, { params }: TableRowsRoutePar
await trx.delete(userTableRows).where(
and(
eq(userTableRows.tableId, tableId),
eq(userTableRows.workspaceId, actualWorkspaceId),
eq(userTableRows.workspaceId, validated.workspaceId),
sql`${userTableRows.id} = ANY(ARRAY[${sql.join(
batch.map((id) => sql`${id}`),
sql`, `

View File

@@ -12,16 +12,7 @@ import { checkAccessOrRespond, getTableById, verifyTableWorkspace } from '../../
const logger = createLogger('TableUpsertAPI')
/**
* Zod schema for validating upsert (insert or update) requests.
*
* If a row with matching unique field(s) exists, it will be updated.
* Otherwise, a new row will be inserted.
*
* @remarks
* The workspaceId is optional for backward compatibility but
* is validated via table access checks when provided.
*/
/** Zod schema for upsert requests - inserts new row or updates if unique fields match */
const UpsertRowSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required'),
data: z.record(z.unknown(), { required_error: 'Row data is required' }),
@@ -38,49 +29,7 @@ interface UpsertRouteParams {
* POST /api/table/[tableId]/rows/upsert
*
* Inserts or updates a row based on unique column constraints.
* If a row with matching unique field(s) exists, it will be updated;
* otherwise, a new row will be inserted.
*
* @param request - The incoming HTTP request with row data
* @param context - Route context containing tableId param
* @returns JSON response with upserted row and operation type
*
* @remarks
* Requires at least one unique column in the table schema.
* The operation is determined by checking existing rows against
* the unique field values provided in the request.
*
* @example Request body:
* ```json
* {
* "workspaceId": "ws_123",
* "data": { "email": "john@example.com", "name": "John Doe" }
* }
* ```
*
* @example Response (insert):
* ```json
* {
* "success": true,
* "data": {
* "row": { ... },
* "operation": "insert",
* "message": "Row inserted successfully"
* }
* }
* ```
*
* @example Response (update):
* ```json
* {
* "success": true,
* "data": {
* "row": { ... },
* "operation": "update",
* "message": "Row updated successfully"
* }
* }
* ```
*/
export async function POST(request: NextRequest, { params }: UpsertRouteParams) {
const requestId = generateRequestId()

View File

@@ -8,6 +8,30 @@ import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
const logger = createLogger('TableUtils')
/** Permission hierarchy: read < write < admin */
type PermissionLevel = 'read' | 'write' | 'admin'
/**
* Checks if a user's permission meets or exceeds the required level.
*/
function hasPermissionLevel(
userPermission: 'read' | 'write' | 'admin' | null,
requiredLevel: PermissionLevel
): boolean {
if (userPermission === null) return false
switch (requiredLevel) {
case 'read':
return true
case 'write':
return userPermission === 'write' || userPermission === 'admin'
case 'admin':
return userPermission === 'admin'
default:
return false
}
}
async function getTableRowCount(tableId: string): Promise<number> {
const [result] = await db
.select({ count: count() })
@@ -121,28 +145,7 @@ async function checkTableAccessInternal(
// Case 2: Check workspace permissions
const userPermission = await getUserEntityPermissions(userId, 'workspace', tableData.workspaceId)
if (userPermission === null) {
return { hasAccess: false }
}
// Determine if user has sufficient permission level
const hasAccess = (() => {
switch (requiredLevel) {
case 'read':
// Any permission level grants read access
return true
case 'write':
// Write or admin permission required
return userPermission === 'write' || userPermission === 'admin'
case 'admin':
// Only admin permission grants admin access
return userPermission === 'admin'
default:
return false
}
})()
if (hasAccess) {
if (hasPermissionLevel(userPermission, requiredLevel)) {
return { hasAccess: true, table: tableData }
}
@@ -299,26 +302,7 @@ export async function checkAccessWithFullTable(
// Case 2: Check workspace permissions
const userPermission = await getUserEntityPermissions(userId, 'workspace', table.workspaceId)
if (userPermission === null) {
logger.warn(`[${requestId}] User ${userId} denied ${level} access to table ${tableId}`)
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
// Determine if user has sufficient permission level
const hasAccess = (() => {
switch (level) {
case 'read':
return true
case 'write':
return userPermission === 'write' || userPermission === 'admin'
case 'admin':
return userPermission === 'admin'
default:
return false
}
})()
if (!hasAccess) {
if (!hasPermissionLevel(userPermission, level)) {
logger.warn(`[${requestId}] User ${userId} denied ${level} access to table ${tableId}`)
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}

View File

@@ -3,6 +3,173 @@ import { conditionsToFilter, sortConditionsToSort } from '@/lib/table/filters/bu
import type { BlockConfig } from '@/blocks/types'
import type { TableQueryResponse } from '@/tools/table/types'
/**
* Parses a JSON string with helpful error messages.
*
* Handles common issues like unquoted block references in JSON values.
*
* @param value - The value to parse (string or already-parsed object)
* @param fieldName - Name of the field for error messages
* @returns Parsed JSON value
* @throws Error with helpful hints if JSON is invalid
*/
function parseJSON(value: string | unknown, fieldName: string): unknown {
if (typeof value !== 'string') return value
try {
return JSON.parse(value)
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
// Check if the error might be due to unquoted string values
// This happens when users write {"field": <ref>} instead of {"field": "<ref>"}
const unquotedValueMatch = value.match(
/:\s*([a-zA-Z][a-zA-Z0-9_\s]*[a-zA-Z0-9]|[a-zA-Z])\s*[,}]/
)
let hint =
'Make sure all property names are in double quotes (e.g., {"name": "value"} not {name: "value"}).'
if (unquotedValueMatch) {
hint =
'It looks like a string value is not quoted. When using block references in JSON, wrap them in double quotes: {"field": "<blockName.output>"} not {"field": <blockName.output>}.'
}
throw new Error(`Invalid JSON in ${fieldName}: ${errorMsg}. ${hint}`)
}
}
/** Raw params from block UI before JSON parsing and type conversion */
interface TableBlockParams {
operation: string
tableId?: string
rowId?: string
data?: string | unknown
rows?: string | unknown
filter?: string | unknown
sort?: string | unknown
limit?: string
offset?: string
builderMode?: string
filterBuilder?: unknown
sortBuilder?: unknown
bulkFilterMode?: string
bulkFilterBuilder?: unknown
}
/** Normalized params after parsing, ready for tool request body */
interface ParsedParams {
tableId?: string
rowId?: string
data?: unknown
rows?: unknown
filter?: unknown
sort?: unknown
limit?: number
offset?: number
}
/** Transforms raw block params into tool request params for each operation */
const paramTransformers: Record<string, (params: TableBlockParams) => ParsedParams> = {
insert_row: (params) => ({
tableId: params.tableId,
data: parseJSON(params.data, 'Row Data'),
}),
upsert_row: (params) => ({
tableId: params.tableId,
data: parseJSON(params.data, 'Row Data'),
}),
batch_insert_rows: (params) => ({
tableId: params.tableId,
rows: parseJSON(params.rows, 'Rows Data'),
}),
update_row: (params) => ({
tableId: params.tableId,
rowId: params.rowId,
data: parseJSON(params.data, 'Row Data'),
}),
update_rows_by_filter: (params) => {
let filter: unknown
if (params.bulkFilterMode === 'builder' && params.bulkFilterBuilder) {
filter =
conditionsToFilter(params.bulkFilterBuilder as Parameters<typeof conditionsToFilter>[0]) ||
undefined
} else if (params.filter) {
filter = parseJSON(params.filter, 'Filter')
}
return {
tableId: params.tableId,
filter,
data: parseJSON(params.data, 'Row Data'),
limit: params.limit ? Number.parseInt(params.limit) : undefined,
}
},
delete_row: (params) => ({
tableId: params.tableId,
rowId: params.rowId,
}),
delete_rows_by_filter: (params) => {
let filter: unknown
if (params.bulkFilterMode === 'builder' && params.bulkFilterBuilder) {
filter =
conditionsToFilter(params.bulkFilterBuilder as Parameters<typeof conditionsToFilter>[0]) ||
undefined
} else if (params.filter) {
filter = parseJSON(params.filter, 'Filter')
}
return {
tableId: params.tableId,
filter,
limit: params.limit ? Number.parseInt(params.limit) : undefined,
}
},
get_row: (params) => ({
tableId: params.tableId,
rowId: params.rowId,
}),
get_schema: (params) => ({
tableId: params.tableId,
}),
query_rows: (params) => {
let filter: unknown
if (params.builderMode === 'builder' && params.filterBuilder) {
filter =
conditionsToFilter(params.filterBuilder as Parameters<typeof conditionsToFilter>[0]) ||
undefined
} else if (params.filter) {
filter = parseJSON(params.filter, 'Filter')
}
let sort: unknown
if (params.builderMode === 'builder' && params.sortBuilder) {
sort =
sortConditionsToSort(params.sortBuilder as Parameters<typeof sortConditionsToSort>[0]) ||
undefined
} else if (params.sort) {
sort = parseJSON(params.sort, 'Sort')
}
return {
tableId: params.tableId,
filter,
sort,
limit: params.limit ? Number.parseInt(params.limit) : 100,
offset: params.offset ? Number.parseInt(params.offset) : 0,
}
},
}
export const TableBlock: BlockConfig<TableQueryResponse> = {
type: 'table',
name: 'Table',
@@ -353,154 +520,10 @@ Return ONLY the sort JSON:`,
},
params: (params) => {
const { operation, ...rest } = params
const transformer = paramTransformers[operation]
/**
* Helper to parse JSON with better error messages.
* Also handles common issues with block references in JSON.
*/
const parseJSON = (value: string | any, fieldName: string): any => {
if (typeof value !== 'string') return value
try {
return JSON.parse(value)
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
// Check if the error might be due to unquoted string values (common when block references are resolved)
// This happens when users write {"field": <ref>} instead of {"field": "<ref>"}
const unquotedValueMatch = value.match(
/:\s*([a-zA-Z][a-zA-Z0-9_\s]*[a-zA-Z0-9]|[a-zA-Z])\s*[,}]/
)
let hint =
'Make sure all property names are in double quotes (e.g., {"name": "value"} not {name: "value"}).'
if (unquotedValueMatch) {
hint =
'It looks like a string value is not quoted. When using block references in JSON, wrap them in double quotes: {"field": "<blockName.output>"} not {"field": <blockName.output>}.'
}
throw new Error(`Invalid JSON in ${fieldName}: ${errorMsg}. ${hint}`)
}
}
// Insert Row
if (operation === 'insert_row') {
const data = parseJSON(rest.data, 'Row Data')
return {
tableId: rest.tableId,
data,
}
}
// Upsert Row
if (operation === 'upsert_row') {
const data = parseJSON(rest.data, 'Row Data')
return {
tableId: rest.tableId,
data,
}
}
// Batch Insert Rows
if (operation === 'batch_insert_rows') {
const rows = parseJSON(rest.rows, 'Rows Data')
return {
tableId: rest.tableId,
rows,
}
}
// Update Row by ID
if (operation === 'update_row') {
const data = parseJSON(rest.data, 'Row Data')
return {
tableId: rest.tableId,
rowId: rest.rowId,
data,
}
}
// Update Rows by Filter
if (operation === 'update_rows_by_filter') {
let filter: any
if (rest.bulkFilterMode === 'builder' && rest.bulkFilterBuilder) {
filter = conditionsToFilter(rest.bulkFilterBuilder as any) || undefined
} else if (rest.filter) {
filter = parseJSON(rest.filter, 'Filter')
}
const data = parseJSON(rest.data, 'Row Data')
return {
tableId: rest.tableId,
filter,
data,
limit: rest.limit ? Number.parseInt(rest.limit as string) : undefined,
}
}
// Delete Row by ID
if (operation === 'delete_row') {
return {
tableId: rest.tableId,
rowId: rest.rowId,
}
}
// Delete Rows by Filter
if (operation === 'delete_rows_by_filter') {
let filter: any
if (rest.bulkFilterMode === 'builder' && rest.bulkFilterBuilder) {
filter = conditionsToFilter(rest.bulkFilterBuilder as any) || undefined
} else if (rest.filter) {
filter = parseJSON(rest.filter, 'Filter')
}
return {
tableId: rest.tableId,
filter,
limit: rest.limit ? Number.parseInt(rest.limit as string) : undefined,
}
}
// Get Row by ID
if (operation === 'get_row') {
return {
tableId: rest.tableId,
rowId: rest.rowId,
}
}
// Get Schema
if (operation === 'get_schema') {
return {
tableId: rest.tableId,
}
}
// Query Rows
if (operation === 'query_rows') {
let filter: any
if (rest.builderMode === 'builder' && rest.filterBuilder) {
// Convert builder conditions to filter object
filter = conditionsToFilter(rest.filterBuilder as any) || undefined
} else if (rest.filter) {
filter = parseJSON(rest.filter, 'Filter')
}
let sort: any
if (rest.builderMode === 'builder' && rest.sortBuilder) {
// Convert sort builder conditions to sort object
sort = sortConditionsToSort(rest.sortBuilder as any) || undefined
} else if (rest.sort) {
sort = parseJSON(rest.sort, 'Sort')
}
return {
tableId: rest.tableId,
filter,
sort,
limit: rest.limit ? Number.parseInt(rest.limit as string) : 100,
offset: rest.offset ? Number.parseInt(rest.offset as string) : 0,
}
if (transformer) {
return transformer(rest as TableBlockParams)
}
return rest

View File

@@ -5,12 +5,7 @@
*
*/
/**
* Available comparison operators for filter conditions.
*
* @remarks
* These operators map to the query builder operators in query-builder.ts
*/
/** Comparison operators for filter conditions (maps to query-builder.ts) */
export const COMPARISON_OPERATORS = [
{ value: 'eq', label: 'equals' },
{ value: 'ne', label: 'not equals' },
@@ -38,12 +33,7 @@ export const SORT_DIRECTIONS = [
{ value: 'desc', label: 'descending' },
] as const
/**
* Represents a single filter condition.
*
* @remarks
* Used by filter builder UI components to construct filter queries.
*/
/** Single filter condition used by filter builder UI */
export interface FilterCondition {
/** Unique identifier for the condition (used as React key) */
id: string

View File

@@ -59,19 +59,13 @@ function validateOperator(operator: string): void {
}
}
/**
* Builds a JSONB containment clause using GIN index.
* Generates: `table.data @> '{"field": value}'::jsonb`
*/
/** Builds JSONB containment clause: `data @> '{"field": value}'::jsonb` (uses GIN index) */
function buildContainmentClause(tableName: string, field: string, value: JsonValue): SQL {
const jsonObj = JSON.stringify({ [field]: value })
return sql`${sql.raw(`${tableName}.data`)} @> ${jsonObj}::jsonb`
}
/**
* Builds a numeric comparison clause for JSONB fields.
* Generates: `(table.data->>'field')::numeric <operator> value`
*/
/** Builds numeric comparison: `(data->>'field')::numeric <op> value` (cannot use GIN index) */
function buildComparisonClause(
tableName: string,
field: string,
@@ -82,10 +76,7 @@ function buildComparisonClause(
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%'`
*/
/** Builds case-insensitive pattern match: `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}%`}`

View File

@@ -1,8 +1,10 @@
/**
* Table service layer for business logic operations.
* Table service layer for internal programmatic access.
*
* This module provides the core business logic for user-defined tables,
* extracted from API route handlers for better testability and reuse.
* Use this for: workflow executor, background jobs, testing business logic.
* Use API routes for: HTTP requests, frontend clients.
*
* Note: API routes have their own implementations for HTTP-specific concerns.
*
* @module lib/table/service
*/

View File

@@ -34,7 +34,7 @@ export const tableBatchInsertRowsTool: ToolConfig<
body: (params: TableBatchInsertParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -37,7 +37,7 @@ export const tableCreateTool: ToolConfig<TableCreateParams, TableCreateResponse>
body: (params) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -31,7 +31,7 @@ export const tableDeleteRowTool: ToolConfig<TableRowDeleteParams, TableDeleteRes
body: (params: TableRowDeleteParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -41,7 +41,7 @@ export const tableDeleteRowsByFilterTool: ToolConfig<
body: (params: TableDeleteByFilterParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -26,7 +26,7 @@ export const tableGetRowTool: ToolConfig<TableRowGetParams, TableRowResponse> =
url: (params: TableRowGetParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return `/api/table/${params.tableId}/rows/${params.rowId}?workspaceId=${encodeURIComponent(workspaceId)}`

View File

@@ -20,7 +20,7 @@ export const tableGetSchemaTool: ToolConfig<TableGetSchemaParams, TableGetSchema
url: (params: TableGetSchemaParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return `/api/table/${params.tableId}?workspaceId=${encodeURIComponent(workspaceId)}`

View File

@@ -31,7 +31,7 @@ export const tableInsertRowTool: ToolConfig<TableRowInsertParams, TableRowRespon
body: (params: TableRowInsertParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -13,7 +13,7 @@ export const tableListTool: ToolConfig<TableListParams, TableListResponse> = {
url: (params: TableListParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return `/api/table?workspaceId=${encodeURIComponent(workspaceId)}`
},

View File

@@ -45,7 +45,7 @@ export const tableQueryRowsTool: ToolConfig<TableRowQueryParams, TableQueryRespo
url: (params: TableRowQueryParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
const searchParams = new URLSearchParams({

View File

@@ -37,7 +37,7 @@ export const tableUpdateRowTool: ToolConfig<TableRowUpdateParams, TableRowRespon
body: (params: TableRowUpdateParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -47,7 +47,7 @@ export const tableUpdateRowsByFilterTool: ToolConfig<
body: (params: TableUpdateByFilterParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {

View File

@@ -41,7 +41,7 @@ export const tableUpsertRowTool: ToolConfig<TableRowInsertParams, TableUpsertRes
body: (params: TableRowInsertParams) => {
const workspaceId = params._context?.workspaceId
if (!workspaceId) {
throw new Error('workspaceId is required in execution context')
throw new Error('Workspace ID is required in execution context')
}
return {