@fachkraftfreund/n8n-nodes-supabase 1.2.24 → 1.2.25

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.
@@ -49,6 +49,44 @@ async function executeDatabaseOperation(supabase, operation, itemIndex, hostUrl)
49
49
  return returnData;
50
50
  }
51
51
  exports.executeDatabaseOperation = executeDatabaseOperation;
52
+ const BULK_BATCH_SIZE = 500;
53
+ const MAX_RETRIES = 3;
54
+ const RETRY_BASE_DELAY_MS = 1000;
55
+ function isRetryableError(msg) {
56
+ const lower = msg.toLowerCase();
57
+ return (lower.includes('lock timeout') ||
58
+ lower.includes('canceling statement due to lock') ||
59
+ lower.includes('deadlock') ||
60
+ lower.includes('too many connections') ||
61
+ lower.includes('rate limit') ||
62
+ lower.includes('could not serialize access') ||
63
+ lower.includes('connection terminated') ||
64
+ lower.includes('connection reset') ||
65
+ lower.includes('econnreset') ||
66
+ lower.includes('timeout'));
67
+ }
68
+ async function withRetry(fn, label) {
69
+ var _a;
70
+ let lastError;
71
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
72
+ try {
73
+ return await fn();
74
+ }
75
+ catch (err) {
76
+ const msg = (_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : String(err);
77
+ if (attempt < MAX_RETRIES && isRetryableError(msg)) {
78
+ const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
79
+ console.log(`[Supabase ${label}] transient error (attempt ${attempt + 1}/${MAX_RETRIES + 1}), retrying in ${delay}ms: ${msg}`);
80
+ await new Promise((r) => setTimeout(r, delay));
81
+ lastError = err instanceof Error ? err : new Error(msg);
82
+ }
83
+ else {
84
+ throw err;
85
+ }
86
+ }
87
+ }
88
+ throw lastError;
89
+ }
52
90
  function sanitizeString(value) {
53
91
  return value
54
92
  .replace(/\x00/g, '')
@@ -126,16 +164,26 @@ async function handleBulkCreate(supabase, itemCount) {
126
164
  const table = this.getNodeParameter('table', 0);
127
165
  (0, supabaseClient_1.validateTableName)(table);
128
166
  const rows = collectRowData(this, itemCount);
129
- const { data, error } = await supabase
130
- .from(table)
131
- .insert(rows)
132
- .select();
133
- if (error)
134
- throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
135
- if (Array.isArray(data)) {
136
- return data.map((row) => ({ json: row }));
167
+ const returnData = [];
168
+ for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
169
+ const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
170
+ const data = await withRetry(async () => {
171
+ const { data, error } = await supabase
172
+ .from(table)
173
+ .insert(batch)
174
+ .select();
175
+ if (error)
176
+ throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
177
+ return data;
178
+ }, `CREATE ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`);
179
+ if (Array.isArray(data)) {
180
+ for (const row of data)
181
+ returnData.push({ json: row });
182
+ }
137
183
  }
138
- return [{ json: { data, operation: 'create', table } }];
184
+ return returnData.length > 0
185
+ ? returnData
186
+ : [{ json: { data: [], operation: 'create', table } }];
139
187
  }
140
188
  async function handleBulkUpsert(supabase, itemCount) {
141
189
  const table = this.getNodeParameter('table', 0);
@@ -145,16 +193,26 @@ async function handleBulkUpsert(supabase, itemCount) {
145
193
  const options = {};
146
194
  if (onConflict)
147
195
  options.onConflict = onConflict;
148
- const { data, error } = await supabase
149
- .from(table)
150
- .upsert(rows, options)
151
- .select();
152
- if (error)
153
- throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
154
- if (Array.isArray(data)) {
155
- return data.map((row) => ({ json: row }));
196
+ const returnData = [];
197
+ for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
198
+ const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
199
+ const data = await withRetry(async () => {
200
+ const { data, error } = await supabase
201
+ .from(table)
202
+ .upsert(batch, options)
203
+ .select();
204
+ if (error)
205
+ throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
206
+ return data;
207
+ }, `UPSERT ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`);
208
+ if (Array.isArray(data)) {
209
+ for (const row of data)
210
+ returnData.push({ json: row });
211
+ }
156
212
  }
157
- return [{ json: { data, operation: 'upsert', table } }];
213
+ return returnData.length > 0
214
+ ? returnData
215
+ : [{ json: { data: [], operation: 'upsert', table } }];
158
216
  }
159
217
  async function handleBulkUpdate(supabase, itemCount) {
160
218
  const table = this.getNodeParameter('table', 0);
@@ -170,16 +228,26 @@ async function handleBulkUpdate(supabase, itemCount) {
170
228
  throw new Error(`Item ${i} is missing the match column "${matchColumn}"`);
171
229
  }
172
230
  }
173
- const { data, error } = await supabase
174
- .from(table)
175
- .upsert(rows, { onConflict: matchColumn })
176
- .select();
177
- if (error)
178
- throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
179
- if (Array.isArray(data)) {
180
- return data.map((row) => ({ json: row }));
231
+ const returnData = [];
232
+ for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
233
+ const batch = rows.slice(offset, offset + BULK_BATCH_SIZE);
234
+ const data = await withRetry(async () => {
235
+ const { data, error } = await supabase
236
+ .from(table)
237
+ .upsert(batch, { onConflict: matchColumn })
238
+ .select();
239
+ if (error)
240
+ throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
241
+ return data;
242
+ }, `UPDATE ${table} batch ${Math.floor(offset / BULK_BATCH_SIZE) + 1}`);
243
+ if (Array.isArray(data)) {
244
+ for (const row of data)
245
+ returnData.push({ json: row });
246
+ }
181
247
  }
182
- return [{ json: { data, operation: 'update', table } }];
248
+ return returnData.length > 0
249
+ ? returnData
250
+ : [{ json: { data: [], operation: 'update', table } }];
183
251
  }
184
252
  function getFilters(context, itemIndex) {
185
253
  const uiMode = context.getNodeParameter('uiMode', itemIndex, 'simple');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fachkraftfreund/n8n-nodes-supabase",
3
- "version": "1.2.24",
3
+ "version": "1.2.25",
4
4
  "description": "Comprehensive n8n community node for Supabase with database and storage operations",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",