@fachkraftfreund/n8n-nodes-supabase 1.3.4 → 1.3.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.
|
@@ -341,6 +341,19 @@ class Supabase {
|
|
|
341
341
|
},
|
|
342
342
|
},
|
|
343
343
|
},
|
|
344
|
+
{
|
|
345
|
+
displayName: 'Remove Duplicate Rows',
|
|
346
|
+
name: 'deduplicateByConflict',
|
|
347
|
+
type: 'boolean',
|
|
348
|
+
default: false,
|
|
349
|
+
description: 'When enabled, removes duplicate rows by the conflict/match column(s) before sending to Supabase. Keeps the last occurrence. Prevents the "ON CONFLICT DO UPDATE command cannot affect row a second time" error.',
|
|
350
|
+
displayOptions: {
|
|
351
|
+
show: {
|
|
352
|
+
resource: ['database'],
|
|
353
|
+
operation: ['upsert', 'update'],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
344
357
|
{
|
|
345
358
|
displayName: 'Match Column',
|
|
346
359
|
name: 'matchColumn',
|
|
@@ -55,7 +55,6 @@ exports.executeDatabaseOperation = executeDatabaseOperation;
|
|
|
55
55
|
const BULK_BATCH_SIZE = 500;
|
|
56
56
|
const MAX_RETRIES = 3;
|
|
57
57
|
const RETRY_BASE_DELAY_MS = 1000;
|
|
58
|
-
const DUPLICATE_ROW_ERROR = 'cannot affect row a second time';
|
|
59
58
|
function deduplicateByConflictKeys(rows, conflictColumns) {
|
|
60
59
|
const keys = conflictColumns.split(',').map((k) => k.trim()).filter(Boolean);
|
|
61
60
|
if (keys.length === 0)
|
|
@@ -205,32 +204,30 @@ async function handleBulkUpsert(supabase, itemCount) {
|
|
|
205
204
|
const table = this.getNodeParameter('table', 0);
|
|
206
205
|
const onConflict = this.getNodeParameter('onConflict', 0, '');
|
|
207
206
|
(0, supabaseClient_1.validateTableName)(table);
|
|
208
|
-
const
|
|
207
|
+
const deduplicate = this.getNodeParameter('deduplicateByConflict', 0, false);
|
|
208
|
+
let rows = collectRowData(this, itemCount);
|
|
209
209
|
const options = {};
|
|
210
|
-
if (onConflict)
|
|
210
|
+
if (onConflict) {
|
|
211
211
|
options.onConflict = onConflict;
|
|
212
|
+
if (deduplicate) {
|
|
213
|
+
const before = rows.length;
|
|
214
|
+
rows = deduplicateByConflictKeys(rows, onConflict);
|
|
215
|
+
if (rows.length < before) {
|
|
216
|
+
console.log(`[Supabase UPSERT ${table}] deduplicated input: ${before} → ${rows.length} rows by conflict key "${onConflict}"`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
212
220
|
const returnData = [];
|
|
213
221
|
for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
|
|
214
|
-
|
|
222
|
+
const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
|
|
215
223
|
const batchLabel = `UPSERT ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`;
|
|
216
224
|
const data = await withRetry(async () => {
|
|
217
225
|
const { data, error } = await supabase
|
|
218
226
|
.from(table)
|
|
219
227
|
.upsert(batch, options)
|
|
220
228
|
.select();
|
|
221
|
-
if (error)
|
|
222
|
-
|
|
223
|
-
if (msg.toLowerCase().includes(DUPLICATE_ROW_ERROR) && onConflict) {
|
|
224
|
-
const before = batch.length;
|
|
225
|
-
batch = deduplicateByConflictKeys(batch, onConflict);
|
|
226
|
-
console.log(`[Supabase ${batchLabel}] removed ${before - batch.length} duplicate(s) by conflict key "${onConflict}", retrying ${batch.length} rows`);
|
|
227
|
-
const retry = await supabase.from(table).upsert(batch, options).select();
|
|
228
|
-
if (retry.error)
|
|
229
|
-
throw new Error((0, supabaseClient_1.formatSupabaseError)(retry.error));
|
|
230
|
-
return retry.data;
|
|
231
|
-
}
|
|
232
|
-
throw new Error(msg);
|
|
233
|
-
}
|
|
229
|
+
if (error)
|
|
230
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
|
|
234
231
|
return data;
|
|
235
232
|
}, batchLabel);
|
|
236
233
|
if (Array.isArray(data)) {
|
|
@@ -249,35 +246,32 @@ async function handleBulkUpdate(supabase, itemCount) {
|
|
|
249
246
|
if (!matchColumn) {
|
|
250
247
|
throw new Error('Match Column is required for update operations');
|
|
251
248
|
}
|
|
252
|
-
const
|
|
249
|
+
const deduplicate = this.getNodeParameter('deduplicateByConflict', 0, false);
|
|
250
|
+
let rows = collectRowData(this, itemCount);
|
|
253
251
|
for (let i = 0; i < rows.length; i++) {
|
|
254
252
|
const row = rows[i];
|
|
255
253
|
if (!row || row[matchColumn] === undefined) {
|
|
256
254
|
throw new Error(`Item ${i} is missing the match column "${matchColumn}"`);
|
|
257
255
|
}
|
|
258
256
|
}
|
|
257
|
+
if (deduplicate) {
|
|
258
|
+
const before = rows.length;
|
|
259
|
+
rows = deduplicateByConflictKeys(rows, matchColumn);
|
|
260
|
+
if (rows.length < before) {
|
|
261
|
+
console.log(`[Supabase UPDATE ${table}] deduplicated input: ${before} → ${rows.length} rows by match column "${matchColumn}"`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
259
264
|
const returnData = [];
|
|
260
265
|
for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
|
|
261
|
-
|
|
266
|
+
const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
|
|
262
267
|
const batchLabel = `UPDATE ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`;
|
|
263
268
|
const data = await withRetry(async () => {
|
|
264
269
|
const { data, error } = await supabase
|
|
265
270
|
.from(table)
|
|
266
271
|
.upsert(batch, { onConflict: matchColumn })
|
|
267
272
|
.select();
|
|
268
|
-
if (error)
|
|
269
|
-
|
|
270
|
-
if (msg.toLowerCase().includes(DUPLICATE_ROW_ERROR)) {
|
|
271
|
-
const before = batch.length;
|
|
272
|
-
batch = deduplicateByConflictKeys(batch, matchColumn);
|
|
273
|
-
console.log(`[Supabase ${batchLabel}] removed ${before - batch.length} duplicate(s) by match column "${matchColumn}", retrying ${batch.length} rows`);
|
|
274
|
-
const retry = await supabase.from(table).upsert(batch, { onConflict: matchColumn }).select();
|
|
275
|
-
if (retry.error)
|
|
276
|
-
throw new Error((0, supabaseClient_1.formatSupabaseError)(retry.error));
|
|
277
|
-
return retry.data;
|
|
278
|
-
}
|
|
279
|
-
throw new Error(msg);
|
|
280
|
-
}
|
|
273
|
+
if (error)
|
|
274
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
|
|
281
275
|
return data;
|
|
282
276
|
}, batchLabel);
|
|
283
277
|
if (Array.isArray(data)) {
|
package/package.json
CHANGED