fix: resolve double URL-encoding in datatable filter/sortBy query params (#652)

This commit is contained in:
Romuald Członkowski
2026-03-22 15:59:34 +01:00
committed by GitHub
parent c5665632af
commit 6f6668acc4
34 changed files with 599 additions and 45 deletions

View File

@@ -2861,11 +2861,10 @@ export async function handleGetRows(args: unknown, context?: InstanceContext): P
const { tableId, filter, sortBy, ...params } = getRowsSchema.parse(args);
const queryParams: Record<string, unknown> = { ...params };
if (filter) {
const filterStr = typeof filter === 'string' ? filter : JSON.stringify(filter);
queryParams.filter = encodeURIComponent(filterStr);
queryParams.filter = typeof filter === 'string' ? filter : JSON.stringify(filter);
}
if (sortBy) {
queryParams.sortBy = encodeURIComponent(sortBy);
queryParams.sortBy = sortBy;
}
const result = await client.getDataTableRows(tableId, queryParams as any);
return {
@@ -2931,7 +2930,7 @@ export async function handleDeleteRows(args: unknown, context?: InstanceContext)
const client = ensureApiConfigured(context);
const { tableId, filter, ...params } = deleteRowsSchema.parse(args);
const queryParams = {
filter: encodeURIComponent(JSON.stringify(filter)),
filter: JSON.stringify(filter),
...params,
};
const result = await client.deleteDataTableRows(tableId, queryParams as any);

View File

@@ -9,7 +9,7 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
example: 'n8n_manage_datatable({action: "createTable", name: "Contacts", columns: [{name: "email", type: "string"}]})',
performance: 'Fast (100-500ms)',
tips: [
'Table actions: createTable, listTables, getTable, updateTable, deleteTable',
'Table actions: createTable, listTables, getTable, updateTable (rename only), deleteTable',
'Row actions: getRows, insertRows, updateRows, upsertRows, deleteRows',
'Use dryRun: true to preview update/upsert/delete before applying',
'Filter supports: eq, neq, like, ilike, gt, gte, lt, lte conditions',
@@ -22,7 +22,7 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
- **createTable**: Create a new data table with optional typed columns
- **listTables**: List all data tables (paginated)
- **getTable**: Get table details and column definitions by ID
- **updateTable**: Rename an existing table
- **updateTable**: Rename an existing table (name only — column modifications not supported via API)
- **deleteTable**: Permanently delete a table and all its rows
**Row Actions:**

View File

@@ -637,7 +637,10 @@ export class N8nApiClient {
async getDataTableRows(id: string, params: DataTableRowListParams = {}): Promise<{ data: DataTableRow[]; nextCursor?: string | null }> {
try {
const response = await this.client.get(`/data-tables/${id}/rows`, { params });
const response = await this.client.get(`/data-tables/${id}/rows`, {
params,
paramsSerializer: (p) => this.serializeDataTableParams(p),
});
return this.validateListResponse<DataTableRow>(response.data, 'data-table-rows');
} catch (error) {
throw handleN8nApiError(error);
@@ -673,13 +676,29 @@ export class N8nApiClient {
async deleteDataTableRows(id: string, params: DataTableDeleteRowsParams): Promise<any> {
try {
const response = await this.client.delete(`/data-tables/${id}/rows/delete`, { params });
const response = await this.client.delete(`/data-tables/${id}/rows/delete`, {
params,
paramsSerializer: (p) => this.serializeDataTableParams(p),
});
return response.data;
} catch (error) {
throw handleN8nApiError(error);
}
}
/**
* Serializes data table query params with explicit encodeURIComponent.
* Axios's default serializer doesn't encode some reserved chars that n8n rejects.
*/
private serializeDataTableParams(params: Record<string, any>): string {
const parts: string[] = [];
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null) continue;
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
return parts.join('&');
}
/**
* Validates and normalizes n8n API list responses.
* Handles both modern format {data: [], nextCursor?: string} and legacy array format.