@fachkraftfreund/n8n-nodes-supabase 1.3.3 → 1.3.4

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.
@@ -55,6 +55,19 @@ 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
+ function deduplicateByConflictKeys(rows, conflictColumns) {
60
+ const keys = conflictColumns.split(',').map((k) => k.trim()).filter(Boolean);
61
+ if (keys.length === 0)
62
+ return rows;
63
+ const seen = new Map();
64
+ for (let i = 0; i < rows.length; i++) {
65
+ const row = rows[i];
66
+ const compositeKey = keys.map((k) => { var _a; return String((_a = row[k]) !== null && _a !== void 0 ? _a : ''); }).join('\0');
67
+ seen.set(compositeKey, i);
68
+ }
69
+ return Array.from(seen.values()).sort((a, b) => a - b).map((i) => rows[i]);
70
+ }
58
71
  function isRetryableError(msg) {
59
72
  const lower = msg.toLowerCase();
60
73
  return (lower.includes('lock timeout') ||
@@ -198,16 +211,28 @@ async function handleBulkUpsert(supabase, itemCount) {
198
211
  options.onConflict = onConflict;
199
212
  const returnData = [];
200
213
  for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
201
- const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
214
+ let batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
215
+ const batchLabel = `UPSERT ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`;
202
216
  const data = await withRetry(async () => {
203
217
  const { data, error } = await supabase
204
218
  .from(table)
205
219
  .upsert(batch, options)
206
220
  .select();
207
- if (error)
208
- throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
221
+ if (error) {
222
+ const msg = (0, supabaseClient_1.formatSupabaseError)(error);
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
+ }
209
234
  return data;
210
- }, `UPSERT ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`);
235
+ }, batchLabel);
211
236
  if (Array.isArray(data)) {
212
237
  for (const row of data)
213
238
  returnData.push({ json: row });
@@ -233,16 +258,28 @@ async function handleBulkUpdate(supabase, itemCount) {
233
258
  }
234
259
  const returnData = [];
235
260
  for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
236
- const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
261
+ let batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
262
+ const batchLabel = `UPDATE ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`;
237
263
  const data = await withRetry(async () => {
238
264
  const { data, error } = await supabase
239
265
  .from(table)
240
266
  .upsert(batch, { onConflict: matchColumn })
241
267
  .select();
242
- if (error)
243
- throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
268
+ if (error) {
269
+ const msg = (0, supabaseClient_1.formatSupabaseError)(error);
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
+ }
244
281
  return data;
245
- }, `UPDATE ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`);
282
+ }, batchLabel);
246
283
  if (Array.isArray(data)) {
247
284
  for (const row of data)
248
285
  returnData.push({ json: row });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fachkraftfreund/n8n-nodes-supabase",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Comprehensive n8n community node for Supabase with database and storage operations",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",