@fachkraftfreund/n8n-nodes-supabase 1.3.9 → 1.3.11

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.
@@ -594,6 +594,36 @@ class Supabase {
594
594
  default: 'left',
595
595
  description: 'Left returns all rows even without a match; Inner only returns rows with a match',
596
596
  },
597
+ {
598
+ displayName: 'Order By',
599
+ name: 'orderBy',
600
+ type: 'string',
601
+ default: '',
602
+ placeholder: 'created_at',
603
+ description: 'Column in the joined table to order by (leave empty for no ordering)',
604
+ },
605
+ {
606
+ displayName: 'Order Ascending',
607
+ name: 'orderAscending',
608
+ type: 'boolean',
609
+ default: false,
610
+ description: 'Whether to sort in ascending order (default descending, useful for "latest first")',
611
+ displayOptions: {
612
+ hide: {
613
+ orderBy: [''],
614
+ },
615
+ },
616
+ },
617
+ {
618
+ displayName: 'Limit',
619
+ name: 'limit',
620
+ type: 'number',
621
+ typeOptions: {
622
+ minValue: 0,
623
+ },
624
+ default: 0,
625
+ description: 'Max rows to return from the joined table per parent row (0 = no limit). Set to 1 to get only the latest match.',
626
+ },
597
627
  ],
598
628
  },
599
629
  ],
@@ -54,7 +54,8 @@ function streamWrite(stream, data) {
54
54
  }
55
55
  });
56
56
  }
57
- function buildSelectQuery(supabase, table, selectFields, filters, sort) {
57
+ function buildSelectQuery(supabase, table, selectFields, filters, sort, joins) {
58
+ var _a;
58
59
  let query = supabase.from(table).select(selectFields);
59
60
  for (const filter of filters) {
60
61
  const operator = (0, supabaseClient_2.convertFilterOperator)(filter.operator);
@@ -63,6 +64,21 @@ function buildSelectQuery(supabase, table, selectFields, filters, sort) {
63
64
  for (const s of sort) {
64
65
  query = query.order(s.column, { ascending: s.ascending });
65
66
  }
67
+ if (joins) {
68
+ for (const j of joins) {
69
+ if (!j.table)
70
+ continue;
71
+ if (j.orderBy) {
72
+ query = query.order(j.orderBy, {
73
+ ascending: (_a = j.orderAscending) !== null && _a !== void 0 ? _a : false,
74
+ referencedTable: j.table,
75
+ });
76
+ }
77
+ if (j.limit && j.limit > 0) {
78
+ query = query.limit(j.limit, { referencedTable: j.table });
79
+ }
80
+ }
81
+ }
66
82
  return query;
67
83
  }
68
84
  function parseFilters(context, itemIndex) {
@@ -92,7 +108,7 @@ function parseFilters(context, itemIndex) {
92
108
  }
93
109
  }
94
110
  const BATCH_SIZE = 1000;
95
- async function* fetchBatches(supabase, table, selectFields, filters, sort, hostUrl, returnAll, limit) {
111
+ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostUrl, returnAll, limit, joins) {
96
112
  const overhead = (0, supabaseClient_2.estimateUrlOverhead)(hostUrl, table, selectFields, filters, sort);
97
113
  const maxInChars = Math.max(500, supabaseClient_2.MAX_SAFE_URL_LENGTH - overhead);
98
114
  const maxItems = (0, supabaseClient_2.computeMaxIdsPerChunk)(selectFields);
@@ -112,7 +128,7 @@ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostU
112
128
  if (hasIdColumn) {
113
129
  let lastId = null;
114
130
  while (hasMore) {
115
- let query = buildSelectQuery(supabase, table, selectFields, chunkFilters, []);
131
+ let query = buildSelectQuery(supabase, table, selectFields, chunkFilters, [], joins);
116
132
  if (lastId !== null)
117
133
  query = query.gt('id', lastId);
118
134
  query = query.order('id', { ascending: true }).limit(BATCH_SIZE);
@@ -138,7 +154,7 @@ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostU
138
154
  else {
139
155
  let offset = 0;
140
156
  while (hasMore) {
141
- const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort);
157
+ const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort, joins);
142
158
  const { data, error } = await query.range(offset, offset + BATCH_SIZE - 1);
143
159
  if (error)
144
160
  throw new Error((0, supabaseClient_2.formatSupabaseError)(error));
@@ -163,7 +179,7 @@ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostU
163
179
  const remaining = maxRows - totalYielded;
164
180
  if (remaining <= 0)
165
181
  break;
166
- const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort);
182
+ const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort, joins);
167
183
  const { data, error } = await query.limit(remaining);
168
184
  if (error)
169
185
  throw new Error((0, supabaseClient_2.formatSupabaseError)(error));
@@ -256,6 +272,36 @@ class SupabaseCsvExport {
256
272
  ],
257
273
  default: 'left',
258
274
  },
275
+ {
276
+ displayName: 'Order By',
277
+ name: 'orderBy',
278
+ type: 'string',
279
+ default: '',
280
+ placeholder: 'created_at',
281
+ description: 'Column in the joined table to order by (leave empty for no ordering)',
282
+ },
283
+ {
284
+ displayName: 'Order Ascending',
285
+ name: 'orderAscending',
286
+ type: 'boolean',
287
+ default: false,
288
+ description: 'Whether to sort in ascending order (default descending, useful for "latest first")',
289
+ displayOptions: {
290
+ hide: {
291
+ orderBy: [''],
292
+ },
293
+ },
294
+ },
295
+ {
296
+ displayName: 'Limit',
297
+ name: 'limit',
298
+ type: 'number',
299
+ typeOptions: {
300
+ minValue: 0,
301
+ },
302
+ default: 0,
303
+ description: 'Max rows from the joined table per parent row (0 = no limit, 1 = latest only)',
304
+ },
259
305
  ],
260
306
  },
261
307
  ],
@@ -619,7 +665,7 @@ class SupabaseCsvExport {
619
665
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Transform code syntax error: ${msg}`);
620
666
  }
621
667
  }
622
- for await (const batch of fetchBatches(supabase, table, selectWithJoins, filters, sort, credentials.host, returnAll, limit)) {
668
+ for await (const batch of fetchBatches(supabase, table, selectWithJoins, filters, sort, credentials.host, returnAll, limit, joins)) {
623
669
  let rows = batch;
624
670
  if (transformFn) {
625
671
  try {
@@ -65,8 +65,12 @@ function deduplicateByConflictKeys(rows, conflictColumns) {
65
65
  const compositeKey = keys.map((k) => {
66
66
  const val = row[k];
67
67
  if (val === null || val === undefined)
68
- return '';
69
- return String(val).trim().toLowerCase();
68
+ return '\x00null';
69
+ const str = String(val).trim();
70
+ const num = Number(str);
71
+ if (str !== '' && !isNaN(num))
72
+ return String(num);
73
+ return str.toLowerCase();
70
74
  }).join('\0');
71
75
  seen.set(compositeKey, i);
72
76
  }
@@ -217,7 +221,9 @@ async function handleBulkUpsert(supabase, itemCount) {
217
221
  if (deduplicate && onConflict) {
218
222
  const before = rows.length;
219
223
  rows = deduplicateByConflictKeys(rows, onConflict);
220
- console.log(`[Supabase UPSERT ${table}] dedup by "${onConflict}": ${before} → ${rows.length} rows`);
224
+ if (rows.length < before) {
225
+ console.log(`[Supabase UPSERT ${table}] dedup by "${onConflict}": ${before} → ${rows.length} rows`);
226
+ }
221
227
  }
222
228
  const returnData = [];
223
229
  for (let offset = 0; offset < rows.length; offset += BULK_BATCH_SIZE) {
@@ -309,7 +315,8 @@ function getFilters(context, itemIndex) {
309
315
  throw new Error('Invalid advanced filters JSON');
310
316
  }
311
317
  }
312
- function buildReadQuery(supabase, table, returnFields, filters, sort, options) {
318
+ function buildReadQuery(supabase, table, returnFields, filters, sort, options, joins) {
319
+ var _a;
313
320
  const selectFields = returnFields && returnFields !== '*' ? returnFields : '*';
314
321
  let query = supabase.from(table).select(selectFields, options);
315
322
  for (const filter of filters) {
@@ -319,6 +326,21 @@ function buildReadQuery(supabase, table, returnFields, filters, sort, options) {
319
326
  for (const sortField of sort) {
320
327
  query = query.order(sortField.column, { ascending: sortField.ascending });
321
328
  }
329
+ if (joins) {
330
+ for (const j of joins) {
331
+ if (!j.table)
332
+ continue;
333
+ if (j.orderBy) {
334
+ query = query.order(j.orderBy, {
335
+ ascending: (_a = j.orderAscending) !== null && _a !== void 0 ? _a : false,
336
+ referencedTable: j.table,
337
+ });
338
+ }
339
+ if (j.limit && j.limit > 0) {
340
+ query = query.limit(j.limit, { referencedTable: j.table });
341
+ }
342
+ }
343
+ }
322
344
  return query;
323
345
  }
324
346
  async function handleRead(supabase, itemIndex, hostUrl) {
@@ -364,7 +386,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
364
386
  if (hasIdColumn) {
365
387
  let lastId = null;
366
388
  while (hasMore) {
367
- let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, []);
389
+ let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, [], undefined, joins);
368
390
  if (lastId !== null) {
369
391
  query = query.gt('id', lastId);
370
392
  }
@@ -392,7 +414,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
392
414
  else {
393
415
  let batchOffset = 0;
394
416
  while (hasMore) {
395
- const query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort);
417
+ const query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort, undefined, joins);
396
418
  const { data, error } = await query.range(batchOffset, batchOffset + batchSize - 1);
397
419
  if (error) {
398
420
  console.log(`[Supabase READ] chunk ${ci + 1} batch ${batchCount + 1} FAILED after ${Date.now() - chunkStart}ms: ${(0, supabaseClient_1.formatSupabaseError)(error)}`);
@@ -441,7 +463,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
441
463
  const userOffset = this.getNodeParameter('offset', itemIndex, 0);
442
464
  const isMultiChunk = filterChunks.length > 1;
443
465
  for (const chunkFilters of filterChunks) {
444
- let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort);
466
+ let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort, undefined, joins);
445
467
  if (isMultiChunk) {
446
468
  query = query.limit(userOffset + limit);
447
469
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fachkraftfreund/n8n-nodes-supabase",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "Comprehensive n8n community node for Supabase with database and storage operations",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",