@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.
@@ -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 BATCH_SIZE = 1000;
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
- console.log(`[Supabase READ] item=${itemIndex} table=${table} returnAll=${returnAll} chunks=${filterChunks.length} maxItems=${maxItems} maxInChars=${maxInChars}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fachkraftfreund/n8n-nodes-supabase",
3
- "version": "1.3.10",
3
+ "version": "1.3.12",
4
4
  "description": "Comprehensive n8n community node for Supabase with database and storage operations",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",