@fachkraftfreund/n8n-nodes-supabase 1.4.0 → 1.4.1

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.
@@ -729,6 +729,37 @@ class Supabase {
729
729
  },
730
730
  },
731
731
  },
732
+ {
733
+ displayName: 'Batch Counts',
734
+ name: 'batchCounts',
735
+ type: 'boolean',
736
+ default: false,
737
+ description: 'Whether to combine all input items into a single GROUP BY query instead of one count per item. Requires the exec_sql_select RPC function.',
738
+ displayOptions: {
739
+ show: {
740
+ resource: ['database'],
741
+ operation: ['count'],
742
+ },
743
+ },
744
+ },
745
+ {
746
+ displayName: 'Group By Column',
747
+ name: 'groupByColumn',
748
+ type: 'options',
749
+ typeOptions: {
750
+ loadOptionsMethod: 'getColumns',
751
+ },
752
+ required: true,
753
+ default: '',
754
+ description: 'Column to group counts by (e.g. sector). Each unique value produces one output item with its count.',
755
+ displayOptions: {
756
+ show: {
757
+ resource: ['database'],
758
+ operation: ['count'],
759
+ batchCounts: [true],
760
+ },
761
+ },
762
+ },
732
763
  {
733
764
  displayName: 'Bucket',
734
765
  name: 'bucket',
@@ -1023,7 +1054,23 @@ class Supabase {
1023
1054
  const supabase = (0, supabaseClient_1.createSupabaseClient)(credentials);
1024
1055
  const resource = this.getNodeParameter('resource', 0);
1025
1056
  const operation = this.getNodeParameter('operation', 0);
1026
- if (resource === 'database' && ['create', 'upsert', 'update'].includes(operation)) {
1057
+ if (resource === 'database' && operation === 'count' && this.getNodeParameter('batchCounts', 0, false)) {
1058
+ try {
1059
+ const results = await database_1.executeBulkDatabaseOperation.call(this, supabase, operation, items.length);
1060
+ for (const r of results)
1061
+ returnData.push(r);
1062
+ }
1063
+ catch (error) {
1064
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1065
+ if (this.continueOnFail()) {
1066
+ returnData.push({ json: { error: errorMessage } });
1067
+ }
1068
+ else {
1069
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage);
1070
+ }
1071
+ }
1072
+ }
1073
+ else if (resource === 'database' && ['create', 'upsert', 'update'].includes(operation)) {
1027
1074
  const firstItem = items[0];
1028
1075
  if (items.length === 1 &&
1029
1076
  (firstItem === null || firstItem === void 0 ? void 0 : firstItem.json) &&
@@ -1045,7 +1092,7 @@ class Supabase {
1045
1092
  }
1046
1093
  }
1047
1094
  }
1048
- else if (resource === 'database' && (operation === 'read' || operation === 'count')) {
1095
+ else if (resource === 'database' && operation === 'read') {
1049
1096
  try {
1050
1097
  const operationResults = await database_1.executeDatabaseOperation.call(this, supabase, operation, 0, credentials.host);
1051
1098
  for (const r of operationResults)
@@ -178,6 +178,8 @@ async function executeBulkDatabaseOperation(supabase, operation, itemCount) {
178
178
  return await handleBulkUpsert.call(this, supabase, itemCount);
179
179
  case 'update':
180
180
  return await handleBulkUpdate.call(this, supabase, itemCount);
181
+ case 'count':
182
+ return await handleBatchCount.call(this, supabase, itemCount);
181
183
  default:
182
184
  throw new Error(`Operation ${operation} does not support bulk mode`);
183
185
  }
@@ -813,3 +815,90 @@ async function handleCount(supabase, itemIndex, hostUrl) {
813
815
  }
814
816
  return [{ json: { count: totalCount, table } }];
815
817
  }
818
+ async function handleBatchCount(supabase, itemCount) {
819
+ const table = this.getNodeParameter('table', 0);
820
+ const groupByColumn = this.getNodeParameter('groupByColumn', 0);
821
+ (0, supabaseClient_1.validateTableName)(table);
822
+ (0, supabaseClient_1.validateColumnName)(groupByColumn);
823
+ const baseFilters = getFilters(this, 0);
824
+ const groupValues = new Set();
825
+ for (let i = 0; i < itemCount; i++) {
826
+ const itemFilters = getFilters(this, i);
827
+ for (const f of itemFilters) {
828
+ if (f.column === groupByColumn) {
829
+ groupValues.add(String(f.value));
830
+ }
831
+ }
832
+ }
833
+ const whereClauses = [];
834
+ const staticFilters = baseFilters.filter(f => f.column !== groupByColumn);
835
+ for (const f of staticFilters) {
836
+ const col = `"${f.column}"`;
837
+ switch (f.operator) {
838
+ case 'eq':
839
+ whereClauses.push(`${col} = '${String(f.value).replace(/'/g, "''")}'`);
840
+ break;
841
+ case 'neq':
842
+ whereClauses.push(`${col} != '${String(f.value).replace(/'/g, "''")}'`);
843
+ break;
844
+ case 'gt':
845
+ whereClauses.push(`${col} > '${String(f.value).replace(/'/g, "''")}'`);
846
+ break;
847
+ case 'gte':
848
+ whereClauses.push(`${col} >= '${String(f.value).replace(/'/g, "''")}'`);
849
+ break;
850
+ case 'lt':
851
+ whereClauses.push(`${col} < '${String(f.value).replace(/'/g, "''")}'`);
852
+ break;
853
+ case 'lte':
854
+ whereClauses.push(`${col} <= '${String(f.value).replace(/'/g, "''")}'`);
855
+ break;
856
+ case 'is':
857
+ whereClauses.push(`${col} IS ${f.value}`);
858
+ break;
859
+ case 'like':
860
+ whereClauses.push(`${col} LIKE '${String(f.value).replace(/'/g, "''")}'`);
861
+ break;
862
+ case 'ilike':
863
+ whereClauses.push(`${col} ILIKE '${String(f.value).replace(/'/g, "''")}'`);
864
+ break;
865
+ case 'in': {
866
+ const vals = Array.isArray(f.value) ? f.value : String(f.value).split(',');
867
+ const escaped = vals.map(v => `'${String(v).trim().replace(/'/g, "''")}'`).join(',');
868
+ whereClauses.push(`${col} IN (${escaped})`);
869
+ break;
870
+ }
871
+ default: whereClauses.push(`${col} = '${String(f.value).replace(/'/g, "''")}'`);
872
+ }
873
+ }
874
+ if (groupValues.size > 0) {
875
+ const escaped = Array.from(groupValues).map(v => `'${v.replace(/'/g, "''")}'`).join(',');
876
+ whereClauses.push(`"${groupByColumn}" IN (${escaped})`);
877
+ }
878
+ const joins = this.getNodeParameter('joins.join', 0, []);
879
+ const joinClauses = [];
880
+ for (const j of joins) {
881
+ if (!j.table)
882
+ continue;
883
+ (0, supabaseClient_1.validateTableName)(j.table);
884
+ const joinType = j.joinType === 'inner' ? 'INNER JOIN' : 'LEFT JOIN';
885
+ joinClauses.push(`${joinType} "${j.table}" ON "${j.table}"."${table.replace(/s$/, '')}_id" = "${table}"."id"`);
886
+ }
887
+ const whereStr = whereClauses.length > 0 ? ` WHERE ${whereClauses.join(' AND ')}` : '';
888
+ const joinStr = joinClauses.length > 0 ? ` ${joinClauses.join(' ')}` : '';
889
+ const sql = `SELECT "${groupByColumn}", COUNT(*) as count FROM "${table}"${joinStr}${whereStr} GROUP BY "${groupByColumn}" ORDER BY count DESC`;
890
+ console.log(`[Supabase BATCH COUNT] sql: ${sql}`);
891
+ const { data, error } = await supabase.rpc('exec_sql_select', { sql });
892
+ if (error)
893
+ throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
894
+ if (!Array.isArray(data) || data.length === 0) {
895
+ return [{ json: { table, groupByColumn, counts: [], message: 'No rows matched' } }];
896
+ }
897
+ return data.map((row) => ({
898
+ json: {
899
+ [groupByColumn]: row[groupByColumn],
900
+ count: Number(row.count),
901
+ table,
902
+ },
903
+ }));
904
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fachkraftfreund/n8n-nodes-supabase",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Comprehensive n8n community node for Supabase with database and storage operations",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",