fix(cli): batch record delete sends recordIds as query params

The API controller reads recordIds from @Query(), not @Body.
CLI was sending { records: ids } as JSON body, causing:
'expected array, received undefined at "recordIds"'

Fixed: rename deleteRecord→deleteRecords, send recordIds as
repeated query params (?recordIds=rec1&recordIds=rec2).
This commit is contained in:
yeuimu
2026-08-07 10:32:25 +00:00
parent 0891bda134
commit be21be9465
2 changed files with 90 additions and 24 deletions
+27 -8
View File
@@ -115,7 +115,10 @@ function tableCmd(): Command {
.description('Create a new table') .description('Create a new table')
.requiredOption('--name <name>', 'Table name') .requiredOption('--name <name>', 'Table name')
.option('--base-id <id>', 'Base id (overrides default)') .option('--base-id <id>', 'Base id (overrides default)')
.option('--fields <json>', 'Field definitions JSON array, e.g. \'[{"name":"Title","type":"singleLineText"}]\'') .option(
'--fields <json>',
'Field definitions JSON array, e.g. \'[{"name":"Title","type":"singleLineText"}]\''
)
.action( .action(
run(async (ctx, _args, o) => { run(async (ctx, _args, o) => {
const opts = o as { name: string; baseId?: string; fields?: string }; const opts = o as { name: string; baseId?: string; fields?: string };
@@ -126,7 +129,10 @@ function tableCmd(): Command {
if (ctx.json) printJson(table); if (ctx.json) printJson(table);
else { else {
const t = table as Record<string, unknown>; const t = table as Record<string, unknown>;
printKv([['id', t.id], ['name', t.name]]); printKv([
['id', t.id],
['name', t.name],
]);
} }
}) })
); );
@@ -215,7 +221,10 @@ function fieldCmd(): Command {
.description('Create a new field') .description('Create a new field')
.requiredOption('--table-id <id>', 'Table id') .requiredOption('--table-id <id>', 'Table id')
.requiredOption('--name <name>', 'Field name') .requiredOption('--name <name>', 'Field name')
.requiredOption('--type <type>', 'Field type (singleLineText, number, singleSelect, link, formula, etc.)') .requiredOption(
'--type <type>',
'Field type (singleLineText, number, singleSelect, link, formula, etc.)'
)
.option('--options <json>', 'Field options JSON (type-specific)') .option('--options <json>', 'Field options JSON (type-specific)')
.action( .action(
run(async (ctx, _args, o) => { run(async (ctx, _args, o) => {
@@ -226,7 +235,11 @@ function fieldCmd(): Command {
if (ctx.json) printJson(field); if (ctx.json) printJson(field);
else { else {
const f = field as Record<string, unknown>; const f = field as Record<string, unknown>;
printKv([['id', f.id], ['name', f.name], ['type', f.type]]); printKv([
['id', f.id],
['name', f.name],
['type', f.type],
]);
} }
}) })
); );
@@ -391,7 +404,10 @@ function recordCmd(): Command {
.command('update') .command('update')
.description('Batch update records') .description('Batch update records')
.requiredOption('--table-id <id>', 'Table id') .requiredOption('--table-id <id>', 'Table id')
.requiredOption('--records <json>', 'Records JSON, e.g. \'[{"id":"recX","fields":{"Title":"b"}}]\'') .requiredOption(
'--records <json>',
'Records JSON, e.g. \'[{"id":"recX","fields":{"Title":"b"}}]\''
)
.action( .action(
run(async (ctx, _args, o) => { run(async (ctx, _args, o) => {
const opts = o as { tableId: string; records: string }; const opts = o as { tableId: string; records: string };
@@ -414,8 +430,7 @@ function recordCmd(): Command {
.split(',') .split(',')
.map((s) => s.trim()) .map((s) => s.trim())
.filter(Boolean); .filter(Boolean);
const body = { records: ids }; const result = await ctx.client.deleteRecords(opts.tableId, ids);
const result = await ctx.client.deleteRecord(opts.tableId, body);
if (ctx.json) printJson(result); if (ctx.json) printJson(result);
else process.stdout.write(`✓ ${ids.length} record(s) deleted\n`); else process.stdout.write(`✓ ${ids.length} record(s) deleted\n`);
}) })
@@ -488,7 +503,11 @@ function viewCmd(): Command {
if (ctx.json) printJson(view); if (ctx.json) printJson(view);
else { else {
const v = view as Record<string, unknown>; const v = view as Record<string, unknown>;
printKv([['id', v.id], ['name', v.name], ['type', v.type]]); printKv([
['id', v.id],
['name', v.name],
['type', v.type],
]);
} }
}) })
); );
+63 -16
View File
@@ -124,8 +124,9 @@ export class Client {
updateRecordOne(recordId: string, body: unknown) { updateRecordOne(recordId: string, body: unknown) {
return this.call<unknown>('PATCH', `/api/record/${recordId}`, body); return this.call<unknown>('PATCH', `/api/record/${recordId}`, body);
} }
deleteRecord(tableId: string, body: unknown) { deleteRecords(tableId: string, recordIds: string[]) {
return this.call<unknown>('DELETE', `/api/table/${tableId}/record`, body); const qs = recordIds.map((id) => `recordIds=${encodeURIComponent(id)}`).join('&');
return this.call<unknown>('DELETE', `/api/table/${tableId}/record?${qs}`);
} }
// ---- view ---- // ---- view ----
@@ -163,23 +164,37 @@ export class Client {
createWorkflow(baseId: string, body: { name: string; description?: string }) { createWorkflow(baseId: string, body: { name: string; description?: string }) {
return this.call<IWorkflowVo>('POST', `/api/base/${baseId}/workflow`, body); return this.call<IWorkflowVo>('POST', `/api/base/${baseId}/workflow`, body);
} }
updateWorkflow(baseId: string, workflowId: string, body: { name?: string; description?: string }) { updateWorkflow(
baseId: string,
workflowId: string,
body: { name?: string; description?: string }
) {
return this.call<IWorkflowVo>('PATCH', `/api/base/${baseId}/workflow/${workflowId}`, body); return this.call<IWorkflowVo>('PATCH', `/api/base/${baseId}/workflow/${workflowId}`, body);
} }
deleteWorkflow(baseId: string, workflowId: string) { deleteWorkflow(baseId: string, workflowId: string) {
return this.call<unknown>('DELETE', `/api/base/${baseId}/workflow/${workflowId}`); return this.call<unknown>('DELETE', `/api/base/${baseId}/workflow/${workflowId}`);
} }
activateWorkflow(baseId: string, workflowId: string) { activateWorkflow(baseId: string, workflowId: string) {
return this.call<{ snapshotId: string }>('POST', `/api/base/${baseId}/workflow/${workflowId}/activate`); return this.call<{ snapshotId: string }>(
'POST',
`/api/base/${baseId}/workflow/${workflowId}/activate`
);
} }
deactivateWorkflow(baseId: string, workflowId: string) { deactivateWorkflow(baseId: string, workflowId: string) {
return this.call<unknown>('POST', `/api/base/${baseId}/workflow/${workflowId}/deactivate`); return this.call<unknown>('POST', `/api/base/${baseId}/workflow/${workflowId}/deactivate`);
} }
testWorkflow(baseId: string, workflowId: string) { testWorkflow(baseId: string, workflowId: string) {
return this.call<{ nodeResults: unknown[] }>('POST', `/api/base/${baseId}/workflow/${workflowId}/test`); return this.call<{ nodeResults: unknown[] }>(
'POST',
`/api/base/${baseId}/workflow/${workflowId}/test`
);
} }
/** PUT /node — whole-graph replacement (delete-all + create-all in a txn). */ /** PUT /node — whole-graph replacement (delete-all + create-all in a txn). */
replaceNodes(baseId: string, workflowId: string, body: { nodes: IWorkflowNodeVo[]; edges: IWorkflowEdgeVo[] }) { replaceNodes(
baseId: string,
workflowId: string,
body: { nodes: IWorkflowNodeVo[]; edges: IWorkflowEdgeVo[] }
) {
return this.call<IWorkflowVo>('PUT', `/api/base/${baseId}/workflow/${workflowId}/node`, body); return this.call<IWorkflowVo>('PUT', `/api/base/${baseId}/workflow/${workflowId}/node`, body);
} }
@@ -187,14 +202,30 @@ export class Client {
listSecrets(baseId: string, workflowId: string) { listSecrets(baseId: string, workflowId: string) {
return this.call<unknown[]>('GET', `/api/base/${baseId}/workflow/${workflowId}/secret`); return this.call<unknown[]>('GET', `/api/base/${baseId}/workflow/${workflowId}/secret`);
} }
createSecret(baseId: string, workflowId: string, body: { name: string; description?: string; value: string }) { createSecret(
baseId: string,
workflowId: string,
body: { name: string; description?: string; value: string }
) {
return this.call<unknown>('POST', `/api/base/${baseId}/workflow/${workflowId}/secret`, body); return this.call<unknown>('POST', `/api/base/${baseId}/workflow/${workflowId}/secret`, body);
} }
updateSecret(baseId: string, workflowId: string, secretId: string, body: { name?: string; description?: string; value?: string }) { updateSecret(
return this.call<unknown>('PATCH', `/api/base/${baseId}/workflow/${workflowId}/secret/${secretId}`, body); baseId: string,
workflowId: string,
secretId: string,
body: { name?: string; description?: string; value?: string }
) {
return this.call<unknown>(
'PATCH',
`/api/base/${baseId}/workflow/${workflowId}/secret/${secretId}`,
body
);
} }
deleteSecret(baseId: string, workflowId: string, secretId: string) { deleteSecret(baseId: string, workflowId: string, secretId: string) {
return this.call<unknown>('DELETE', `/api/base/${baseId}/workflow/${workflowId}/secret/${secretId}`); return this.call<unknown>(
'DELETE',
`/api/base/${baseId}/workflow/${workflowId}/secret/${secretId}`
);
} }
// ---- execution history ---- // ---- execution history ----
@@ -202,10 +233,16 @@ export class Client {
return this.call<unknown[]>('GET', `/api/base/${baseId}/workflow/${workflowId}/execution`); return this.call<unknown[]>('GET', `/api/base/${baseId}/workflow/${workflowId}/execution`);
} }
getExecution(baseId: string, workflowId: string, executionId: string) { getExecution(baseId: string, workflowId: string, executionId: string) {
return this.call<unknown>('GET', `/api/base/${baseId}/workflow/${workflowId}/execution/${executionId}`); return this.call<unknown>(
'GET',
`/api/base/${baseId}/workflow/${workflowId}/execution/${executionId}`
);
} }
rerunExecution(baseId: string, workflowId: string, executionId: string) { rerunExecution(baseId: string, workflowId: string, executionId: string) {
return this.call<unknown>('POST', `/api/base/${baseId}/workflow/${workflowId}/execution/${executionId}/rerun`); return this.call<unknown>(
'POST',
`/api/base/${baseId}/workflow/${workflowId}/execution/${executionId}/rerun`
);
} }
// ---- trigger (manual fire for testing) ---- // ---- trigger (manual fire for testing) ----
@@ -243,10 +280,20 @@ export class Client {
// ---- shared types (mirror backend workflow_node / workflow_edge shapes) ---- // ---- shared types (mirror backend workflow_node / workflow_edge shapes) ----
export type WorkflowNodeType = 'trigger' | 'action' | 'control'; export type WorkflowNodeType = 'trigger' | 'action' | 'control';
export type WorkflowNodeCategory = export type WorkflowNodeCategory =
| 'recordCreated' | 'recordUpdated' | 'recordCondition' | 'recordCreatedOrUpdated' | 'recordCreated'
| 'formSubmitted' | 'scheduledTime' | 'buttonClick' | 'webhook' | 'recordUpdated'
| 'script' | 'sendEmail' | 'httpRequest' | 'recordCondition'
| 'createRecord' | 'updateRecord' | 'getRecords' | 'recordCreatedOrUpdated'
| 'formSubmitted'
| 'scheduledTime'
| 'buttonClick'
| 'webhook'
| 'script'
| 'sendEmail'
| 'httpRequest'
| 'createRecord'
| 'updateRecord'
| 'getRecords'
| 'triggerEnd'; | 'triggerEnd';
export interface IWorkflowNodeVo { export interface IWorkflowNodeVo {