@fachkraftfreund/n8n-nodes-supabase 1.3.10 → 1.3.12
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.
- package/dist/nodes/Supabase/Supabase.node.js +30 -0
- package/dist/nodes/Supabase/SupabaseCsvExport.node.js +55 -8
- package/dist/nodes/Supabase/operations/database/index.js +22 -6
- package/dist/nodes/Supabase/utils/supabaseClient.d.ts +1 -0
- package/dist/nodes/Supabase/utils/supabaseClient.js +11 -1
- package/package.json +1 -1
|
@@ -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) {
|
|
@@ -91,17 +107,18 @@ function parseFilters(context, itemIndex) {
|
|
|
91
107
|
throw new Error('Invalid advanced filters JSON');
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
|
-
const
|
|
95
|
-
async function* fetchBatches(supabase, table, selectFields, filters, sort, hostUrl, returnAll, limit) {
|
|
110
|
+
const DEFAULT_BATCH_SIZE = 1000;
|
|
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);
|
|
99
115
|
const filterChunks = (0, supabaseClient_2.expandChunkedFilters)(filters, maxInChars, maxItems);
|
|
116
|
+
const BATCH_SIZE = (0, supabaseClient_2.computeBatchSize)(selectFields);
|
|
100
117
|
const hasIdColumn = selectFields === '*' || selectFields.split(',').some((f) => f.trim() === 'id');
|
|
101
118
|
let totalYielded = 0;
|
|
102
119
|
const maxRows = returnAll ? Infinity : limit;
|
|
103
120
|
const startTime = Date.now();
|
|
104
|
-
console.log(`[Supabase CSV] starting export table=${table} returnAll=${returnAll} chunks=${filterChunks.length} keyset=${hasIdColumn}`);
|
|
121
|
+
console.log(`[Supabase CSV] starting export table=${table} returnAll=${returnAll} chunks=${filterChunks.length} keyset=${hasIdColumn} batchSize=${BATCH_SIZE}`);
|
|
105
122
|
for (let ci = 0; ci < filterChunks.length; ci++) {
|
|
106
123
|
const chunkFilters = filterChunks[ci];
|
|
107
124
|
if (totalYielded >= maxRows)
|
|
@@ -112,7 +129,7 @@ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostU
|
|
|
112
129
|
if (hasIdColumn) {
|
|
113
130
|
let lastId = null;
|
|
114
131
|
while (hasMore) {
|
|
115
|
-
let query = buildSelectQuery(supabase, table, selectFields, chunkFilters, []);
|
|
132
|
+
let query = buildSelectQuery(supabase, table, selectFields, chunkFilters, [], joins);
|
|
116
133
|
if (lastId !== null)
|
|
117
134
|
query = query.gt('id', lastId);
|
|
118
135
|
query = query.order('id', { ascending: true }).limit(BATCH_SIZE);
|
|
@@ -138,7 +155,7 @@ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostU
|
|
|
138
155
|
else {
|
|
139
156
|
let offset = 0;
|
|
140
157
|
while (hasMore) {
|
|
141
|
-
const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort);
|
|
158
|
+
const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort, joins);
|
|
142
159
|
const { data, error } = await query.range(offset, offset + BATCH_SIZE - 1);
|
|
143
160
|
if (error)
|
|
144
161
|
throw new Error((0, supabaseClient_2.formatSupabaseError)(error));
|
|
@@ -163,7 +180,7 @@ async function* fetchBatches(supabase, table, selectFields, filters, sort, hostU
|
|
|
163
180
|
const remaining = maxRows - totalYielded;
|
|
164
181
|
if (remaining <= 0)
|
|
165
182
|
break;
|
|
166
|
-
const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort);
|
|
183
|
+
const query = buildSelectQuery(supabase, table, selectFields, chunkFilters, sort, joins);
|
|
167
184
|
const { data, error } = await query.limit(remaining);
|
|
168
185
|
if (error)
|
|
169
186
|
throw new Error((0, supabaseClient_2.formatSupabaseError)(error));
|
|
@@ -256,6 +273,36 @@ class SupabaseCsvExport {
|
|
|
256
273
|
],
|
|
257
274
|
default: 'left',
|
|
258
275
|
},
|
|
276
|
+
{
|
|
277
|
+
displayName: 'Order By',
|
|
278
|
+
name: 'orderBy',
|
|
279
|
+
type: 'string',
|
|
280
|
+
default: '',
|
|
281
|
+
placeholder: 'created_at',
|
|
282
|
+
description: 'Column in the joined table to order by (leave empty for no ordering)',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
displayName: 'Order Ascending',
|
|
286
|
+
name: 'orderAscending',
|
|
287
|
+
type: 'boolean',
|
|
288
|
+
default: false,
|
|
289
|
+
description: 'Whether to sort in ascending order (default descending, useful for "latest first")',
|
|
290
|
+
displayOptions: {
|
|
291
|
+
hide: {
|
|
292
|
+
orderBy: [''],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
displayName: 'Limit',
|
|
298
|
+
name: 'limit',
|
|
299
|
+
type: 'number',
|
|
300
|
+
typeOptions: {
|
|
301
|
+
minValue: 0,
|
|
302
|
+
},
|
|
303
|
+
default: 0,
|
|
304
|
+
description: 'Max rows from the joined table per parent row (0 = no limit, 1 = latest only)',
|
|
305
|
+
},
|
|
259
306
|
],
|
|
260
307
|
},
|
|
261
308
|
],
|
|
@@ -619,7 +666,7 @@ class SupabaseCsvExport {
|
|
|
619
666
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Transform code syntax error: ${msg}`);
|
|
620
667
|
}
|
|
621
668
|
}
|
|
622
|
-
for await (const batch of fetchBatches(supabase, table, selectWithJoins, filters, sort, credentials.host, returnAll, limit)) {
|
|
669
|
+
for await (const batch of fetchBatches(supabase, table, selectWithJoins, filters, sort, credentials.host, returnAll, limit, joins)) {
|
|
623
670
|
let rows = batch;
|
|
624
671
|
if (transformFn) {
|
|
625
672
|
try {
|
|
@@ -315,7 +315,8 @@ function getFilters(context, itemIndex) {
|
|
|
315
315
|
throw new Error('Invalid advanced filters JSON');
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
|
-
function buildReadQuery(supabase, table, returnFields, filters, sort, options) {
|
|
318
|
+
function buildReadQuery(supabase, table, returnFields, filters, sort, options, joins) {
|
|
319
|
+
var _a;
|
|
319
320
|
const selectFields = returnFields && returnFields !== '*' ? returnFields : '*';
|
|
320
321
|
let query = supabase.from(table).select(selectFields, options);
|
|
321
322
|
for (const filter of filters) {
|
|
@@ -325,6 +326,21 @@ function buildReadQuery(supabase, table, returnFields, filters, sort, options) {
|
|
|
325
326
|
for (const sortField of sort) {
|
|
326
327
|
query = query.order(sortField.column, { ascending: sortField.ascending });
|
|
327
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
|
+
}
|
|
328
344
|
return query;
|
|
329
345
|
}
|
|
330
346
|
async function handleRead(supabase, itemIndex, hostUrl) {
|
|
@@ -353,10 +369,10 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
353
369
|
const maxInChars = Math.max(500, supabaseClient_1.MAX_SAFE_URL_LENGTH - overhead);
|
|
354
370
|
const maxItems = (0, supabaseClient_1.computeMaxIdsPerChunk)(selectWithJoins);
|
|
355
371
|
const filterChunks = (0, supabaseClient_1.expandChunkedFilters)(filters, maxInChars, maxItems);
|
|
356
|
-
|
|
372
|
+
const batchSize = (0, supabaseClient_1.computeBatchSize)(selectWithJoins);
|
|
373
|
+
console.log(`[Supabase READ] item=${itemIndex} table=${table} returnAll=${returnAll} chunks=${filterChunks.length} maxItems=${maxItems} maxInChars=${maxInChars} batchSize=${batchSize}`);
|
|
357
374
|
const returnData = [];
|
|
358
375
|
if (returnAll) {
|
|
359
|
-
const batchSize = 1000;
|
|
360
376
|
const selectFields = returnFields && returnFields !== '*' ? returnFields : '*';
|
|
361
377
|
const hasIdColumn = selectFields === '*' || selectFields.split(',').some(f => f.trim() === 'id');
|
|
362
378
|
for (let ci = 0; ci < filterChunks.length; ci++) {
|
|
@@ -370,7 +386,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
370
386
|
if (hasIdColumn) {
|
|
371
387
|
let lastId = null;
|
|
372
388
|
while (hasMore) {
|
|
373
|
-
let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, []);
|
|
389
|
+
let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, [], undefined, joins);
|
|
374
390
|
if (lastId !== null) {
|
|
375
391
|
query = query.gt('id', lastId);
|
|
376
392
|
}
|
|
@@ -398,7 +414,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
398
414
|
else {
|
|
399
415
|
let batchOffset = 0;
|
|
400
416
|
while (hasMore) {
|
|
401
|
-
const query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort);
|
|
417
|
+
const query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort, undefined, joins);
|
|
402
418
|
const { data, error } = await query.range(batchOffset, batchOffset + batchSize - 1);
|
|
403
419
|
if (error) {
|
|
404
420
|
console.log(`[Supabase READ] chunk ${ci + 1} batch ${batchCount + 1} FAILED after ${Date.now() - chunkStart}ms: ${(0, supabaseClient_1.formatSupabaseError)(error)}`);
|
|
@@ -447,7 +463,7 @@ async function handleRead(supabase, itemIndex, hostUrl) {
|
|
|
447
463
|
const userOffset = this.getNodeParameter('offset', itemIndex, 0);
|
|
448
464
|
const isMultiChunk = filterChunks.length > 1;
|
|
449
465
|
for (const chunkFilters of filterChunks) {
|
|
450
|
-
let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort);
|
|
466
|
+
let query = buildReadQuery(supabase, table, selectWithJoins, chunkFilters, sort, undefined, joins);
|
|
451
467
|
if (isMultiChunk) {
|
|
452
468
|
query = query.limit(userOffset + limit);
|
|
453
469
|
}
|
|
@@ -14,6 +14,7 @@ export declare function convertFilterOperator(operator: string): string;
|
|
|
14
14
|
export declare function normalizeFilterValue(operator: string, value: string | number | boolean | null | unknown[]): string | number | boolean | null;
|
|
15
15
|
export declare const MAX_SAFE_URL_LENGTH = 7500;
|
|
16
16
|
export declare function computeMaxIdsPerChunk(selectFields?: string): number;
|
|
17
|
+
export declare function computeBatchSize(selectFields?: string): number;
|
|
17
18
|
export declare function estimateUrlOverhead(hostUrl: string, table: string, selectFields?: string, filters?: IRowFilter[], sort?: IRowSort[]): number;
|
|
18
19
|
export declare function chunkInFilterValues(values: unknown[], maxChars: number, maxItems?: number): unknown[][];
|
|
19
20
|
export declare function expandChunkedFilters(filters: IRowFilter[], maxInChars?: number, maxItems?: number): IRowFilter[][];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.expandChunkedFilters = exports.chunkInFilterValues = exports.estimateUrlOverhead = exports.computeMaxIdsPerChunk = exports.MAX_SAFE_URL_LENGTH = exports.normalizeFilterValue = exports.convertFilterOperator = exports.validateColumnName = exports.validateTableName = exports.sanitizeColumnName = exports.isNetworkError = exports.isAuthError = exports.formatSupabaseError = exports.getDatabaseUrl = exports.getStorageUrl = exports.validateCredentials = exports.createSupabaseClient = void 0;
|
|
3
|
+
exports.expandChunkedFilters = exports.chunkInFilterValues = exports.estimateUrlOverhead = exports.computeBatchSize = exports.computeMaxIdsPerChunk = exports.MAX_SAFE_URL_LENGTH = exports.normalizeFilterValue = exports.convertFilterOperator = exports.validateColumnName = exports.validateTableName = exports.sanitizeColumnName = exports.isNetworkError = exports.isAuthError = exports.formatSupabaseError = exports.getDatabaseUrl = exports.getStorageUrl = exports.validateCredentials = exports.createSupabaseClient = void 0;
|
|
4
4
|
const supabase_js_1 = require("@supabase/supabase-js");
|
|
5
5
|
function createSupabaseClient(credentials) {
|
|
6
6
|
const client = (0, supabase_js_1.createClient)(credentials.host, credentials.serviceKey, {
|
|
@@ -171,6 +171,16 @@ function computeMaxIdsPerChunk(selectFields) {
|
|
|
171
171
|
return Math.max(100, Math.floor(BASE_LIMIT / (1 + joinCount * 1.5)));
|
|
172
172
|
}
|
|
173
173
|
exports.computeMaxIdsPerChunk = computeMaxIdsPerChunk;
|
|
174
|
+
function computeBatchSize(selectFields) {
|
|
175
|
+
const BASE = 2000;
|
|
176
|
+
if (!selectFields || selectFields === '*')
|
|
177
|
+
return BASE;
|
|
178
|
+
const joinCount = (selectFields.match(/\(/g) || []).length;
|
|
179
|
+
if (joinCount === 0)
|
|
180
|
+
return BASE;
|
|
181
|
+
return Math.max(50, Math.floor(BASE / (1 + joinCount * 0.5)));
|
|
182
|
+
}
|
|
183
|
+
exports.computeBatchSize = computeBatchSize;
|
|
174
184
|
function estimateUrlOverhead(hostUrl, table, selectFields, filters, sort) {
|
|
175
185
|
let overhead = hostUrl.length + '/rest/v1/'.length + table.length + 1;
|
|
176
186
|
if (selectFields) {
|
package/package.json
CHANGED