@fachkraftfreund/n8n-nodes-supabase 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -129,6 +129,12 @@ class Supabase {
|
|
|
129
129
|
description: 'Execute custom SQL query',
|
|
130
130
|
action: 'Execute custom query',
|
|
131
131
|
},
|
|
132
|
+
{
|
|
133
|
+
name: 'Find or Create',
|
|
134
|
+
value: 'findOrCreate',
|
|
135
|
+
description: 'Return existing row matching criteria, or create it if not found',
|
|
136
|
+
action: 'Find or create row',
|
|
137
|
+
},
|
|
132
138
|
],
|
|
133
139
|
default: 'read',
|
|
134
140
|
},
|
|
@@ -255,7 +261,7 @@ class Supabase {
|
|
|
255
261
|
displayOptions: {
|
|
256
262
|
show: {
|
|
257
263
|
resource: ['database'],
|
|
258
|
-
operation: ['create', 'read', 'update', 'delete', 'upsert'],
|
|
264
|
+
operation: ['create', 'read', 'update', 'delete', 'upsert', 'findOrCreate'],
|
|
259
265
|
},
|
|
260
266
|
},
|
|
261
267
|
},
|
|
@@ -316,14 +322,12 @@ class Supabase {
|
|
|
316
322
|
},
|
|
317
323
|
},
|
|
318
324
|
{
|
|
319
|
-
displayName: 'On Conflict
|
|
325
|
+
displayName: 'On Conflict Columns',
|
|
320
326
|
name: 'onConflict',
|
|
321
|
-
type: '
|
|
322
|
-
typeOptions: {
|
|
323
|
-
loadOptionsMethod: 'getColumns',
|
|
324
|
-
},
|
|
327
|
+
type: 'string',
|
|
325
328
|
default: '',
|
|
326
|
-
|
|
329
|
+
placeholder: 'id or company_id,name',
|
|
330
|
+
description: 'Comma-separated column(s) to use for conflict resolution. Required for composite unique constraints (e.g. company_id,name).',
|
|
327
331
|
displayOptions: {
|
|
328
332
|
show: {
|
|
329
333
|
resource: ['database'],
|
|
@@ -331,6 +335,90 @@ class Supabase {
|
|
|
331
335
|
},
|
|
332
336
|
},
|
|
333
337
|
},
|
|
338
|
+
{
|
|
339
|
+
displayName: 'Match Columns',
|
|
340
|
+
name: 'matchColumns',
|
|
341
|
+
type: 'fixedCollection',
|
|
342
|
+
typeOptions: {
|
|
343
|
+
multipleValues: true,
|
|
344
|
+
},
|
|
345
|
+
default: {},
|
|
346
|
+
placeholder: 'Add Match Column',
|
|
347
|
+
description: 'Columns used to look up an existing row. If a row matches all values, it is returned; otherwise a new row is created.',
|
|
348
|
+
displayOptions: {
|
|
349
|
+
show: {
|
|
350
|
+
resource: ['database'],
|
|
351
|
+
operation: ['findOrCreate'],
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
options: [
|
|
355
|
+
{
|
|
356
|
+
displayName: 'Column',
|
|
357
|
+
name: 'column',
|
|
358
|
+
values: [
|
|
359
|
+
{
|
|
360
|
+
displayName: 'Column',
|
|
361
|
+
name: 'name',
|
|
362
|
+
type: 'options',
|
|
363
|
+
typeOptions: {
|
|
364
|
+
loadOptionsMethod: 'getColumns',
|
|
365
|
+
},
|
|
366
|
+
default: '',
|
|
367
|
+
description: 'Column to match on',
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
displayName: 'Value',
|
|
371
|
+
name: 'value',
|
|
372
|
+
type: 'string',
|
|
373
|
+
default: '',
|
|
374
|
+
description: 'Value to match',
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
displayName: 'Additional Columns',
|
|
382
|
+
name: 'additionalColumns',
|
|
383
|
+
type: 'fixedCollection',
|
|
384
|
+
typeOptions: {
|
|
385
|
+
multipleValues: true,
|
|
386
|
+
},
|
|
387
|
+
default: {},
|
|
388
|
+
placeholder: 'Add Column',
|
|
389
|
+
description: 'Extra columns to set when creating a new row (not used when a match is found)',
|
|
390
|
+
displayOptions: {
|
|
391
|
+
show: {
|
|
392
|
+
resource: ['database'],
|
|
393
|
+
operation: ['findOrCreate'],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
options: [
|
|
397
|
+
{
|
|
398
|
+
displayName: 'Column',
|
|
399
|
+
name: 'column',
|
|
400
|
+
values: [
|
|
401
|
+
{
|
|
402
|
+
displayName: 'Column',
|
|
403
|
+
name: 'name',
|
|
404
|
+
type: 'options',
|
|
405
|
+
typeOptions: {
|
|
406
|
+
loadOptionsMethod: 'getColumns',
|
|
407
|
+
},
|
|
408
|
+
default: '',
|
|
409
|
+
description: 'Column name',
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
displayName: 'Value',
|
|
413
|
+
name: 'value',
|
|
414
|
+
type: 'string',
|
|
415
|
+
default: '',
|
|
416
|
+
description: 'Value to set',
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
},
|
|
334
422
|
{
|
|
335
423
|
displayName: 'Filters',
|
|
336
424
|
name: 'filters',
|
|
@@ -42,6 +42,9 @@ async function executeDatabaseOperation(supabase, operation, itemIndex) {
|
|
|
42
42
|
case 'customQuery':
|
|
43
43
|
returnData.push(...await handleCustomQuery.call(this, supabase, itemIndex));
|
|
44
44
|
break;
|
|
45
|
+
case 'findOrCreate':
|
|
46
|
+
returnData.push(...await handleFindOrCreate.call(this, supabase, itemIndex));
|
|
47
|
+
break;
|
|
45
48
|
default:
|
|
46
49
|
throw new Error(`Unknown database operation: ${operation}`);
|
|
47
50
|
}
|
|
@@ -355,7 +358,8 @@ async function handleCustomQuery(supabase, itemIndex) {
|
|
|
355
358
|
if (!(customSql === null || customSql === void 0 ? void 0 : customSql.trim())) {
|
|
356
359
|
throw new Error('SQL query is required');
|
|
357
360
|
}
|
|
358
|
-
|
|
361
|
+
const returnsRows = /^\s*(select|with|explain|table)\b/i.test(customSql);
|
|
362
|
+
if (returnsRows) {
|
|
359
363
|
const { data, error } = await supabase.rpc('exec_sql_select', { sql: customSql });
|
|
360
364
|
if (error) {
|
|
361
365
|
throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
|
|
@@ -386,3 +390,39 @@ async function handleCustomQuery(supabase, itemIndex) {
|
|
|
386
390
|
return [{ json: { operation: 'customQuery', sql: customSql, result: data, success: true } }];
|
|
387
391
|
}
|
|
388
392
|
}
|
|
393
|
+
async function handleFindOrCreate(supabase, itemIndex) {
|
|
394
|
+
const table = this.getNodeParameter('table', itemIndex);
|
|
395
|
+
(0, supabaseClient_1.validateTableName)(table);
|
|
396
|
+
const matchCols = this.getNodeParameter('matchColumns.column', itemIndex, []);
|
|
397
|
+
if (matchCols.length === 0) {
|
|
398
|
+
throw new Error('At least one Match Column is required for Find or Create');
|
|
399
|
+
}
|
|
400
|
+
let query = supabase.from(table).select('*');
|
|
401
|
+
for (const col of matchCols) {
|
|
402
|
+
if (!col.name)
|
|
403
|
+
continue;
|
|
404
|
+
(0, supabaseClient_1.validateColumnName)(col.name);
|
|
405
|
+
query = query.eq(col.name, col.value);
|
|
406
|
+
}
|
|
407
|
+
query = query.limit(1);
|
|
408
|
+
const { data: existing, error: readError } = await query;
|
|
409
|
+
if (readError)
|
|
410
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(readError));
|
|
411
|
+
if (existing && existing.length > 0) {
|
|
412
|
+
return [{ json: { ...existing[0], _found: true, _created: false } }];
|
|
413
|
+
}
|
|
414
|
+
const additionalCols = this.getNodeParameter('additionalColumns.column', itemIndex, []);
|
|
415
|
+
const dataToInsert = {};
|
|
416
|
+
for (const col of [...matchCols, ...additionalCols]) {
|
|
417
|
+
if (col.name)
|
|
418
|
+
dataToInsert[col.name] = col.value;
|
|
419
|
+
}
|
|
420
|
+
const { data: created, error: insertError } = await supabase
|
|
421
|
+
.from(table)
|
|
422
|
+
.insert(dataToInsert)
|
|
423
|
+
.select()
|
|
424
|
+
.single();
|
|
425
|
+
if (insertError)
|
|
426
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(insertError));
|
|
427
|
+
return [{ json: { ...created, _found: false, _created: true } }];
|
|
428
|
+
}
|
|
@@ -75,7 +75,7 @@ export interface ISupabaseStorageResponse<T = any> {
|
|
|
75
75
|
statusCode?: string;
|
|
76
76
|
} | null;
|
|
77
77
|
}
|
|
78
|
-
export type DatabaseOperation = 'create' | 'read' | 'update' | 'delete' | 'upsert' | 'createTable' | 'dropTable' | 'addColumn' | 'dropColumn' | 'createIndex' | 'dropIndex' | 'customQuery';
|
|
78
|
+
export type DatabaseOperation = 'create' | 'read' | 'update' | 'delete' | 'upsert' | 'createTable' | 'dropTable' | 'addColumn' | 'dropColumn' | 'createIndex' | 'dropIndex' | 'customQuery' | 'findOrCreate';
|
|
79
79
|
export type StorageOperation = 'uploadFile' | 'downloadFile' | 'listFiles' | 'deleteFile' | 'moveFile' | 'copyFile' | 'createBucket' | 'deleteBucket' | 'listBuckets' | 'getBucketDetails' | 'getFileInfo' | 'generateSignedUrl';
|
|
80
80
|
export type SupabaseResource = 'database' | 'storage';
|
|
81
81
|
export interface ISupabaseNodeParameters {
|
package/package.json
CHANGED