@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
|
-
|
|
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
|
-
|
|
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