@fachkraftfreund/n8n-nodes-supabase 1.2.9 → 1.2.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.
|
@@ -150,38 +150,39 @@ async function handleBulkUpdate(supabase, itemCount) {
|
|
|
150
150
|
}
|
|
151
151
|
return [{ json: { data, operation: 'update', table } }];
|
|
152
152
|
}
|
|
153
|
-
function
|
|
154
|
-
const selectFields = returnFields && returnFields !== '*' ? returnFields : '*';
|
|
155
|
-
let query = supabase.from(table).select(selectFields, options);
|
|
153
|
+
function getFilters(context, itemIndex) {
|
|
156
154
|
const uiMode = context.getNodeParameter('uiMode', itemIndex, 'simple');
|
|
157
155
|
if (uiMode === 'simple') {
|
|
158
|
-
|
|
159
|
-
for (const filter of filters) {
|
|
160
|
-
const operator = (0, supabaseClient_1.convertFilterOperator)(filter.operator);
|
|
161
|
-
query = query.filter(filter.column, operator, (0, supabaseClient_1.normalizeFilterValue)(filter.operator, filter.value));
|
|
162
|
-
}
|
|
156
|
+
return context.getNodeParameter('filters.filter', itemIndex, []);
|
|
163
157
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
else {
|
|
175
|
-
query = query.eq(column, condition);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
158
|
+
const advancedFilters = context.getNodeParameter('advancedFilters', itemIndex, '');
|
|
159
|
+
if (!advancedFilters)
|
|
160
|
+
return [];
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(advancedFilters);
|
|
163
|
+
const filters = [];
|
|
164
|
+
for (const [column, condition] of Object.entries(parsed)) {
|
|
165
|
+
if (typeof condition === 'object' && condition !== null) {
|
|
166
|
+
const [operator, value] = Object.entries(condition)[0];
|
|
167
|
+
filters.push({ column, operator: operator, value });
|
|
178
168
|
}
|
|
179
|
-
|
|
180
|
-
|
|
169
|
+
else {
|
|
170
|
+
filters.push({ column, operator: 'eq', value: condition });
|
|
181
171
|
}
|
|
182
172
|
}
|
|
173
|
+
return filters;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
throw new Error('Invalid advanced filters JSON');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function buildReadQuery(supabase, table, returnFields, filters, sort, options) {
|
|
180
|
+
const selectFields = returnFields && returnFields !== '*' ? returnFields : '*';
|
|
181
|
+
let query = supabase.from(table).select(selectFields, options);
|
|
182
|
+
for (const filter of filters) {
|
|
183
|
+
const operator = (0, supabaseClient_1.convertFilterOperator)(filter.operator);
|
|
184
|
+
query = query.filter(filter.column, operator, (0, supabaseClient_1.normalizeFilterValue)(filter.operator, filter.value));
|
|
183
185
|
}
|
|
184
|
-
const sort = context.getNodeParameter('sort.sortField', itemIndex, []);
|
|
185
186
|
for (const sortField of sort) {
|
|
186
187
|
query = query.order(sortField.column, { ascending: sortField.ascending });
|
|
187
188
|
}
|
|
@@ -192,46 +193,51 @@ async function handleRead(supabase, itemIndex) {
|
|
|
192
193
|
(0, supabaseClient_1.validateTableName)(table);
|
|
193
194
|
const returnFields = this.getNodeParameter('returnFields', itemIndex, '*');
|
|
194
195
|
const returnAll = this.getNodeParameter('returnAll', itemIndex, false);
|
|
196
|
+
const filters = getFilters(this, itemIndex);
|
|
197
|
+
const sort = this.getNodeParameter('sort.sortField', itemIndex, []);
|
|
198
|
+
const filterChunks = (0, supabaseClient_1.expandChunkedFilters)(filters);
|
|
195
199
|
const returnData = [];
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (Array.isArray(batchData)) {
|
|
207
|
-
for (const row of batchData) {
|
|
208
|
-
returnData.push({ json: row });
|
|
200
|
+
for (const chunkFilters of filterChunks) {
|
|
201
|
+
if (returnAll) {
|
|
202
|
+
const batchSize = 1000;
|
|
203
|
+
let offset = 0;
|
|
204
|
+
let hasMore = true;
|
|
205
|
+
while (hasMore) {
|
|
206
|
+
const batchQuery = buildReadQuery(supabase, table, returnFields, chunkFilters, sort, { count: 'exact' });
|
|
207
|
+
const { data: batchData, error: batchError } = await batchQuery.range(offset, offset + batchSize - 1);
|
|
208
|
+
if (batchError) {
|
|
209
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(batchError));
|
|
209
210
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
if (Array.isArray(batchData)) {
|
|
212
|
+
for (const row of batchData) {
|
|
213
|
+
returnData.push({ json: row });
|
|
214
|
+
}
|
|
215
|
+
hasMore = batchData.length === batchSize;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
hasMore = false;
|
|
219
|
+
}
|
|
220
|
+
offset += batchSize;
|
|
214
221
|
}
|
|
215
|
-
offset += batchSize;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
const limit = this.getNodeParameter('limit', itemIndex, undefined);
|
|
220
|
-
const offset = this.getNodeParameter('offset', itemIndex, undefined);
|
|
221
|
-
let query = buildReadQuery(this, supabase, table, returnFields, itemIndex);
|
|
222
|
-
if (limit !== undefined) {
|
|
223
|
-
query = query.limit(limit);
|
|
224
|
-
}
|
|
225
|
-
if (offset !== undefined) {
|
|
226
|
-
query = query.range(offset, offset + (limit || 1000) - 1);
|
|
227
|
-
}
|
|
228
|
-
const { data, error } = await query;
|
|
229
|
-
if (error) {
|
|
230
|
-
throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
|
|
231
222
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
223
|
+
else {
|
|
224
|
+
const limit = this.getNodeParameter('limit', itemIndex, undefined);
|
|
225
|
+
const offset = this.getNodeParameter('offset', itemIndex, undefined);
|
|
226
|
+
let query = buildReadQuery(supabase, table, returnFields, chunkFilters, sort);
|
|
227
|
+
if (limit !== undefined) {
|
|
228
|
+
query = query.limit(limit);
|
|
229
|
+
}
|
|
230
|
+
if (offset !== undefined) {
|
|
231
|
+
query = query.range(offset, offset + (limit || 1000) - 1);
|
|
232
|
+
}
|
|
233
|
+
const { data, error } = await query;
|
|
234
|
+
if (error) {
|
|
235
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(data)) {
|
|
238
|
+
for (const row of data) {
|
|
239
|
+
returnData.push({ json: row });
|
|
240
|
+
}
|
|
235
241
|
}
|
|
236
242
|
}
|
|
237
243
|
}
|
|
@@ -251,20 +257,27 @@ async function handleRead(supabase, itemIndex) {
|
|
|
251
257
|
async function handleDelete(supabase, itemIndex) {
|
|
252
258
|
const table = this.getNodeParameter('table', itemIndex);
|
|
253
259
|
(0, supabaseClient_1.validateTableName)(table);
|
|
254
|
-
let query = supabase.from(table).delete();
|
|
255
260
|
const filters = this.getNodeParameter('filters.filter', itemIndex, []);
|
|
256
261
|
if (filters.length === 0) {
|
|
257
262
|
throw new Error('At least one filter is required for delete operations to prevent accidental data loss');
|
|
258
263
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
264
|
+
const filterChunks = (0, supabaseClient_1.expandChunkedFilters)(filters);
|
|
265
|
+
const allDeleted = [];
|
|
266
|
+
for (const chunkFilters of filterChunks) {
|
|
267
|
+
let query = supabase.from(table).delete();
|
|
268
|
+
for (const filter of chunkFilters) {
|
|
269
|
+
const operator = (0, supabaseClient_1.convertFilterOperator)(filter.operator);
|
|
270
|
+
query = query.filter(filter.column, operator, (0, supabaseClient_1.normalizeFilterValue)(filter.operator, filter.value));
|
|
271
|
+
}
|
|
272
|
+
const { data, error } = await query.select();
|
|
273
|
+
if (error) {
|
|
274
|
+
throw new Error((0, supabaseClient_1.formatSupabaseError)(error));
|
|
275
|
+
}
|
|
276
|
+
if (Array.isArray(data)) {
|
|
277
|
+
allDeleted.push(...data);
|
|
278
|
+
}
|
|
266
279
|
}
|
|
267
|
-
return [{ json: { data, operation: 'delete', table, deleted:
|
|
280
|
+
return [{ json: { data: allDeleted, operation: 'delete', table, deleted: allDeleted.length } }];
|
|
268
281
|
}
|
|
269
282
|
async function handleCreateTable(supabase, itemIndex) {
|
|
270
283
|
const tableName = this.getNodeParameter('tableName', itemIndex);
|
|
@@ -8,7 +8,7 @@ export interface ISupabaseCredentials {
|
|
|
8
8
|
export interface IRowFilter {
|
|
9
9
|
column: string;
|
|
10
10
|
operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'is' | 'in' | 'cs' | 'cd';
|
|
11
|
-
value: string | number | boolean | null;
|
|
11
|
+
value: string | number | boolean | null | unknown[];
|
|
12
12
|
}
|
|
13
13
|
export interface IRowSort {
|
|
14
14
|
column: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import { ISupabaseCredentials } from '../types';
|
|
2
|
+
import { ISupabaseCredentials, IRowFilter } from '../types';
|
|
3
3
|
export declare function createSupabaseClient(credentials: ISupabaseCredentials): SupabaseClient;
|
|
4
4
|
export declare function validateCredentials(credentials: ISupabaseCredentials): void;
|
|
5
5
|
export declare function getStorageUrl(projectUrl: string): string;
|
|
@@ -11,4 +11,7 @@ export declare function sanitizeColumnName(columnName: string): string;
|
|
|
11
11
|
export declare function validateTableName(tableName: string): void;
|
|
12
12
|
export declare function validateColumnName(columnName: string): void;
|
|
13
13
|
export declare function convertFilterOperator(operator: string): string;
|
|
14
|
-
export declare function normalizeFilterValue(operator: string, value: string | number | boolean | null): string | number | boolean | null;
|
|
14
|
+
export declare function normalizeFilterValue(operator: string, value: string | number | boolean | null | unknown[]): string | number | boolean | null;
|
|
15
|
+
export declare const IN_FILTER_MAX_CHAR_LENGTH = 4000;
|
|
16
|
+
export declare function chunkInFilterValues(values: unknown[]): unknown[][];
|
|
17
|
+
export declare function expandChunkedFilters(filters: IRowFilter[]): IRowFilter[][];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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.IN_FILTER_MAX_CHAR_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, {
|
|
@@ -137,13 +137,77 @@ function convertFilterOperator(operator) {
|
|
|
137
137
|
}
|
|
138
138
|
exports.convertFilterOperator = convertFilterOperator;
|
|
139
139
|
function normalizeFilterValue(operator, value) {
|
|
140
|
-
if (operator === 'in'
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
if (operator === 'in') {
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return `(${value.join(',')})`;
|
|
143
|
+
}
|
|
144
|
+
if (typeof value === 'string') {
|
|
145
|
+
const trimmed = value.trim();
|
|
146
|
+
if (!trimmed.startsWith('(')) {
|
|
147
|
+
return `(${trimmed})`;
|
|
148
|
+
}
|
|
149
|
+
return trimmed;
|
|
144
150
|
}
|
|
145
|
-
return trimmed;
|
|
146
151
|
}
|
|
147
152
|
return value;
|
|
148
153
|
}
|
|
149
154
|
exports.normalizeFilterValue = normalizeFilterValue;
|
|
155
|
+
exports.IN_FILTER_MAX_CHAR_LENGTH = 4000;
|
|
156
|
+
function chunkInFilterValues(values) {
|
|
157
|
+
const chunks = [];
|
|
158
|
+
let currentChunk = [];
|
|
159
|
+
let currentLength = 0;
|
|
160
|
+
for (const value of values) {
|
|
161
|
+
const valueStr = String(value);
|
|
162
|
+
const addedLength = currentChunk.length === 0 ? valueStr.length : valueStr.length + 1;
|
|
163
|
+
if (currentLength + addedLength > exports.IN_FILTER_MAX_CHAR_LENGTH && currentChunk.length > 0) {
|
|
164
|
+
chunks.push(currentChunk);
|
|
165
|
+
currentChunk = [value];
|
|
166
|
+
currentLength = valueStr.length;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
currentChunk.push(value);
|
|
170
|
+
currentLength += addedLength;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (currentChunk.length > 0) {
|
|
174
|
+
chunks.push(currentChunk);
|
|
175
|
+
}
|
|
176
|
+
return chunks;
|
|
177
|
+
}
|
|
178
|
+
exports.chunkInFilterValues = chunkInFilterValues;
|
|
179
|
+
function expandChunkedFilters(filters) {
|
|
180
|
+
const staticFilters = [];
|
|
181
|
+
const chunkedEntries = [];
|
|
182
|
+
for (const filter of filters) {
|
|
183
|
+
if (filter.operator === 'in' && Array.isArray(filter.value)) {
|
|
184
|
+
const serialized = filter.value.map(String).join(',');
|
|
185
|
+
if (serialized.length > exports.IN_FILTER_MAX_CHAR_LENGTH) {
|
|
186
|
+
chunkedEntries.push({
|
|
187
|
+
filter,
|
|
188
|
+
chunks: chunkInFilterValues(filter.value),
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
staticFilters.push(filter);
|
|
194
|
+
}
|
|
195
|
+
if (chunkedEntries.length === 0) {
|
|
196
|
+
return [filters];
|
|
197
|
+
}
|
|
198
|
+
let combinations = [staticFilters];
|
|
199
|
+
for (const { filter, chunks } of chunkedEntries) {
|
|
200
|
+
const newCombinations = [];
|
|
201
|
+
for (const existing of combinations) {
|
|
202
|
+
for (const chunk of chunks) {
|
|
203
|
+
newCombinations.push([
|
|
204
|
+
...existing,
|
|
205
|
+
{ ...filter, value: chunk },
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
combinations = newCombinations;
|
|
210
|
+
}
|
|
211
|
+
return combinations;
|
|
212
|
+
}
|
|
213
|
+
exports.expandChunkedFilters = expandChunkedFilters;
|
package/package.json
CHANGED