@fachkraftfreund/n8n-nodes-supabase 1.2.22 → 1.2.24
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.
|
@@ -527,6 +527,58 @@ class Supabase {
|
|
|
527
527
|
},
|
|
528
528
|
},
|
|
529
529
|
},
|
|
530
|
+
{
|
|
531
|
+
displayName: 'Joins',
|
|
532
|
+
name: 'joins',
|
|
533
|
+
type: 'fixedCollection',
|
|
534
|
+
typeOptions: {
|
|
535
|
+
multipleValues: true,
|
|
536
|
+
},
|
|
537
|
+
default: {},
|
|
538
|
+
placeholder: 'Add Join',
|
|
539
|
+
description: 'Join related tables via foreign keys (PostgREST resource embedding)',
|
|
540
|
+
displayOptions: {
|
|
541
|
+
show: {
|
|
542
|
+
resource: ['database'],
|
|
543
|
+
operation: ['read'],
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
options: [
|
|
547
|
+
{
|
|
548
|
+
displayName: 'Join',
|
|
549
|
+
name: 'join',
|
|
550
|
+
values: [
|
|
551
|
+
{
|
|
552
|
+
displayName: 'Table',
|
|
553
|
+
name: 'table',
|
|
554
|
+
type: 'string',
|
|
555
|
+
default: '',
|
|
556
|
+
placeholder: 'company_email',
|
|
557
|
+
description: 'Related table to join',
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
displayName: 'Columns',
|
|
561
|
+
name: 'columns',
|
|
562
|
+
type: 'string',
|
|
563
|
+
default: '*',
|
|
564
|
+
placeholder: 'email,verified',
|
|
565
|
+
description: 'Comma-separated columns to select from the joined table (* for all)',
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
displayName: 'Join Type',
|
|
569
|
+
name: 'joinType',
|
|
570
|
+
type: 'options',
|
|
571
|
+
options: [
|
|
572
|
+
{ name: 'Left Join', value: 'left' },
|
|
573
|
+
{ name: 'Inner Join', value: 'inner' },
|
|
574
|
+
],
|
|
575
|
+
default: 'left',
|
|
576
|
+
description: 'Left returns all rows even without a match; Inner only returns rows with a match',
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
},
|
|
530
582
|
{
|
|
531
583
|
displayName: 'Sort',
|
|
532
584
|
name: 'sort',
|
|
@@ -49,6 +49,34 @@ async function executeDatabaseOperation(supabase, operation, itemIndex, hostUrl)
|
|
|
49
49
|
return returnData;
|
|
50
50
|
}
|
|
51
51
|
exports.executeDatabaseOperation = executeDatabaseOperation;
|
|
52
|
+
function sanitizeString(value) {
|
|
53
|
+
return value
|
|
54
|
+
.replace(/\x00/g, '')
|
|
55
|
+
.replace(/\\u[0-9a-fA-F]{4}/g, '')
|
|
56
|
+
.replace(/\\/g, '');
|
|
57
|
+
}
|
|
58
|
+
function sanitizeRow(obj) {
|
|
59
|
+
for (const key of Object.keys(obj)) {
|
|
60
|
+
const val = obj[key];
|
|
61
|
+
if (typeof val === 'string') {
|
|
62
|
+
obj[key] = sanitizeString(val);
|
|
63
|
+
}
|
|
64
|
+
else if (Array.isArray(val)) {
|
|
65
|
+
for (let i = 0; i < val.length; i++) {
|
|
66
|
+
if (typeof val[i] === 'string') {
|
|
67
|
+
val[i] = sanitizeString(val[i]);
|
|
68
|
+
}
|
|
69
|
+
else if (val[i] && typeof val[i] === 'object') {
|
|
70
|
+
sanitizeRow(val[i]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (val && typeof val === 'object') {
|
|
75
|
+
sanitizeRow(val);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return obj;
|
|
79
|
+
}
|
|
52
80
|
function collectRowData(context, itemCount) {
|
|
53
81
|
const rows = [];
|
|
54
82
|
for (let i = 0; i < itemCount; i++) {
|
|
@@ -72,7 +100,7 @@ function collectRowData(context, itemCount) {
|
|
|
72
100
|
}
|
|
73
101
|
}
|
|
74
102
|
}
|
|
75
|
-
rows.push(row);
|
|
103
|
+
rows.push(sanitizeRow(row));
|
|
76
104
|
}
|
|
77
105
|
return rows;
|
|
78
106
|
}
|
|
@@ -196,16 +224,26 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
196
224
|
(0, supabaseClient_1.validateTableName)(table);
|
|
197
225
|
const returnFields = this.getNodeParameter('returnFields', itemIndex, '*');
|
|
198
226
|
const returnAll = this.getNodeParameter('returnAll', itemIndex, false);
|
|
227
|
+
const singleItem = this.getNodeParameter('singleItem', itemIndex, false);
|
|
199
228
|
const filters = getFilters(this, itemIndex);
|
|
200
229
|
const sort = this.getNodeParameter('sort.sortField', itemIndex, []);
|
|
230
|
+
const joins = this.getNodeParameter('joins.join', itemIndex, []);
|
|
231
|
+
let selectWithJoins = returnFields;
|
|
232
|
+
for (const j of joins) {
|
|
233
|
+
if (!j.table)
|
|
234
|
+
continue;
|
|
235
|
+
const cols = j.columns || '*';
|
|
236
|
+
const hint = j.joinType === 'inner' ? `${j.table}!inner` : j.table;
|
|
237
|
+
selectWithJoins += `,${hint}(${cols})`;
|
|
238
|
+
}
|
|
201
239
|
for (const f of filters) {
|
|
202
240
|
const valType = Array.isArray(f.value) ? `array[${f.value.length}]` : typeof f.value;
|
|
203
241
|
const valLen = typeof f.value === 'string' ? f.value.length : Array.isArray(f.value) ? f.value.length : 0;
|
|
204
242
|
console.log(`[Supabase READ] item=${itemIndex} filter: ${f.column} ${f.operator} (${valType}, len=${valLen})`);
|
|
205
243
|
}
|
|
206
|
-
const overhead = (0, supabaseClient_1.estimateUrlOverhead)(hostUrl, table,
|
|
244
|
+
const overhead = (0, supabaseClient_1.estimateUrlOverhead)(hostUrl, table, selectWithJoins, filters, sort);
|
|
207
245
|
const maxInChars = Math.max(500, supabaseClient_1.MAX_SAFE_URL_LENGTH - overhead);
|
|
208
|
-
const maxItems = (0, supabaseClient_1.computeMaxIdsPerChunk)(
|
|
246
|
+
const maxItems = (0, supabaseClient_1.computeMaxIdsPerChunk)(selectWithJoins);
|
|
209
247
|
const filterChunks = (0, supabaseClient_1.expandChunkedFilters)(filters, maxInChars, maxItems);
|
|
210
248
|
console.log(`[Supabase READ] item=${itemIndex} table=${table} returnAll=${returnAll} chunks=${filterChunks.length} maxItems=${maxItems} maxInChars=${maxInChars}`);
|
|
211
249
|
const returnData = [];
|
|
@@ -224,7 +262,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
224
262
|
if (hasIdColumn) {
|
|
225
263
|
let lastId = null;
|
|
226
264
|
while (hasMore) {
|
|
227
|
-
let query = buildReadQuery(supabase, table,
|
|
265
|
+
let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, []);
|
|
228
266
|
if (lastId !== null) {
|
|
229
267
|
query = query.gt('id', lastId);
|
|
230
268
|
}
|
|
@@ -252,7 +290,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
252
290
|
else {
|
|
253
291
|
let batchOffset = 0;
|
|
254
292
|
while (hasMore) {
|
|
255
|
-
const query = buildReadQuery(supabase, table,
|
|
293
|
+
const query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort);
|
|
256
294
|
const { data, error } = await query.range(batchOffset, batchOffset + batchSize - 1);
|
|
257
295
|
if (error) {
|
|
258
296
|
console.log(`[Supabase READ] chunk ${ci + 1} batch ${batchCount + 1} FAILED after ${Date.now() - chunkStart}ms: ${(0, supabaseClient_1.formatSupabaseError)(error)}`);
|
|
@@ -301,7 +339,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
301
339
|
const userOffset = this.getNodeParameter('offset', itemIndex, 0);
|
|
302
340
|
const isMultiChunk = filterChunks.length > 1;
|
|
303
341
|
for (const chunkFilters of filterChunks) {
|
|
304
|
-
let query = buildReadQuery(supabase, table,
|
|
342
|
+
let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort);
|
|
305
343
|
if (isMultiChunk) {
|
|
306
344
|
query = query.limit(userOffset + limit);
|
|
307
345
|
}
|
|
@@ -341,10 +379,13 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
341
379
|
},
|
|
342
380
|
});
|
|
343
381
|
}
|
|
344
|
-
const singleItem = this.getNodeParameter('singleItem', itemIndex, false);
|
|
345
382
|
if (singleItem && returnData.length > 0) {
|
|
346
|
-
const
|
|
347
|
-
|
|
383
|
+
const count = returnData.length;
|
|
384
|
+
const allRows = new Array(count);
|
|
385
|
+
for (let i = 0; i < count; i++)
|
|
386
|
+
allRows[i] = returnData[i].json;
|
|
387
|
+
returnData.length = 0;
|
|
388
|
+
return [{ json: { data: allRows, count } }];
|
|
348
389
|
}
|
|
349
390
|
return returnData;
|
|
350
391
|
}
|
package/package.json
CHANGED