diff --git a/packages/nodes-base/nodes/Supabase/GenericFunctions.ts b/packages/nodes-base/nodes/Supabase/GenericFunctions.ts index 50df54863e..fb24d28157 100644 --- a/packages/nodes-base/nodes/Supabase/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Supabase/GenericFunctions.ts @@ -22,6 +22,7 @@ export async function supabaseApiRequest( qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, + returnData: boolean = true, ) { const credentials = await this.getCredentials<{ host: string; @@ -29,9 +30,7 @@ export async function supabaseApiRequest( }>('supabaseApi'); const options: IRequestOptions = { - headers: { - Prefer: 'return=representation', - }, + headers: returnData ? { Prefer: 'return=representation' } : { Prefer: 'return=minimal' }, method, qs, body, diff --git a/packages/nodes-base/nodes/Supabase/RowDescription.ts b/packages/nodes-base/nodes/Supabase/RowDescription.ts index eafe601752..905ccf9d4c 100644 --- a/packages/nodes-base/nodes/Supabase/RowDescription.ts +++ b/packages/nodes-base/nodes/Supabase/RowDescription.ts @@ -170,6 +170,19 @@ export const rowFields: INodeProperties[] = [ }, ], }, + { + displayName: 'Return Inserted Data', + name: 'returnData', + type: 'boolean', + displayOptions: { + show: { + resource: ['row'], + operation: ['create'], + }, + }, + default: true, + description: 'Whether to return the inserted data. Disable if RLS policies restrict SELECT.', + }, /* -------------------------------------------------------------------------- */ /* row:delete */ /* -------------------------------------------------------------------------- */ diff --git a/packages/nodes-base/nodes/Supabase/Supabase.node.ts b/packages/nodes-base/nodes/Supabase/Supabase.node.ts index eac85e6232..1c39b80068 100644 --- a/packages/nodes-base/nodes/Supabase/Supabase.node.ts +++ b/packages/nodes-base/nodes/Supabase/Supabase.node.ts @@ -132,6 +132,7 @@ export class Supabase implements INodeType { if (operation === 'create') { const records: IDataObject[] = []; + const returnDataParam = this.getNodeParameter('returnData', 0, true) as boolean; for (let i = 0; i < length; i++) { const record: IDataObject = {}; @@ -159,12 +160,17 @@ export class Supabase implements INodeType { const endpoint = `/${tableId}`; try { - const createdRows: IDataObject[] = await supabaseApiRequest.call( + const response = await supabaseApiRequest.call( this, 'POST', endpoint, records, + {}, + undefined, + {}, + returnDataParam, ); + const createdRows = returnDataParam ? (response as IDataObject[]) : []; createdRows.forEach((row, i) => { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(row), @@ -172,6 +178,13 @@ export class Supabase implements INodeType { ); returnData.push(...executionData); }); + if (!returnDataParam) { + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ success: true, message: 'Row inserted' }), + { itemData: mapPairedItemsFrom(records) }, + ); + returnData.push(...executionData); + } } catch (error) { if (this.continueOnFail()) { const executionData = this.helpers.constructExecutionMetaData( diff --git a/packages/nodes-base/nodes/Supabase/tests/Supabase.node.test.ts b/packages/nodes-base/nodes/Supabase/tests/Supabase.node.test.ts index f786426b3d..321a702721 100644 --- a/packages/nodes-base/nodes/Supabase/tests/Supabase.node.test.ts +++ b/packages/nodes-base/nodes/Supabase/tests/Supabase.node.test.ts @@ -15,7 +15,8 @@ import { Supabase } from '../Supabase.node'; describe('Test Supabase Node', () => { const node = new Supabase(); - const input = [{ json: {} }]; + // Input for create tests + const input = [{ json: { email: 'test@example.com', phone: '+1234567890' } }]; const createMockExecuteFunction = ( nodeParameters: IDataObject, @@ -29,7 +30,6 @@ describe('Test Supabase Node', () => { options?: IGetNodeParameterOptions | undefined, ) { const parameter = options?.extractValue ? `${parameterName}.value` : parameterName; - const parameterValue = get(nodeParameters, parameter, fallbackValue); if ((parameterValue as IDataObject)?.nodeOperationError) { @@ -45,10 +45,11 @@ describe('Test Supabase Node', () => { getInputData: () => input, helpers: { constructExecutionMetaData: ( - _inputData: INodeExecutionData[], + inputData: INodeExecutionData[], _options: { itemData: IPairedItemData | IPairedItemData[] }, - ) => [], - returnJsonArray: (_jsonData: IDataObject | IDataObject[]) => [], + ) => inputData, + returnJsonArray: (jsonData: IDataObject | IDataObject[]) => + Array.isArray(jsonData) ? jsonData.map((data) => ({ json: data })) : [{ json: jsonData }], }, } as unknown as IExecuteFunctions; return fakeExecuteFunction; @@ -96,4 +97,98 @@ describe('Test Supabase Node', () => { }, ); }); + + describe('Create Operation', () => { + it('should create a row and return data when returnData is true', async () => { + const supabaseApiRequest = jest + .spyOn(utils, 'supabaseApiRequest') + .mockResolvedValue([{ email: 'test@example.com', phone: '+1234567890' }]); + + const fakeExecuteFunction = createMockExecuteFunction({ + resource: 'row', + operation: 'create', + tableId: 'signups', + dataToSend: 'autoMapInputData', + inputsToIgnore: '', + returnData: true, + }); + + const result = await node.execute.call(fakeExecuteFunction); + + expect(supabaseApiRequest).toHaveBeenCalledWith( + 'POST', + '/signups', + [{ email: 'test@example.com', phone: '+1234567890' }], + {}, + undefined, + {}, + true, + ); + expect(result[0][0].json).toEqual({ + email: 'test@example.com', + phone: '+1234567890', + }); + }); + + it('should create a row without returning data when returnData is false', async () => { + const supabaseApiRequest = jest.spyOn(utils, 'supabaseApiRequest').mockResolvedValue({}); + + const fakeExecuteFunction = createMockExecuteFunction({ + resource: 'row', + operation: 'create', + tableId: 'signups', + dataToSend: 'autoMapInputData', + inputsToIgnore: '', + returnData: false, + }); + + const result = await node.execute.call(fakeExecuteFunction); + + expect(supabaseApiRequest).toHaveBeenCalledWith( + 'POST', + '/signups', + [{ email: 'test@example.com', phone: '+1234567890' }], + {}, + undefined, + {}, + false, + ); + expect(result[0][0].json).toEqual({ + success: true, + message: 'Row inserted', + }); + }); + + it('should coerce non-boolean returnData to boolean and proceed', async () => { + const supabaseApiRequest = jest + .spyOn(utils, 'supabaseApiRequest') + .mockResolvedValue([{ email: 'test@example.com', phone: '+1234567890' }]); + + const fakeExecuteFunction = createMockExecuteFunction({ + resource: 'row', + operation: 'create', + tableId: 'signups', + dataToSend: 'autoMapInputData', + inputsToIgnore: '', + returnData: 'hello', // Non-boolean expression result + }); + + const result = await node.execute.call(fakeExecuteFunction); + + // 'hello' is truthy, so expect returnData to be treated as true + expect(supabaseApiRequest).toHaveBeenCalledWith( + 'POST', + '/signups', + [{ email: 'test@example.com', phone: '+1234567890' }], + {}, + undefined, + {}, + 'hello', // Passed as-is, but coerced to true in JS + ); + expect(result[0][0].json).toEqual({ + email: 'test@example.com', + phone: '+1234567890', + }); + }); + }); });