@dwtechs/antity-pgsql 0.10.0 → 0.11.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.
package/README.md CHANGED
@@ -160,10 +160,14 @@ type MatchMode =
160
160
  "st_dwithin";
161
161
 
162
162
 
163
+ type Filters = {
164
+ [key: string]: Filter | Filter[]; // Supports both simple (object) and complex (array) formats
165
+ }
166
+
163
167
  type Filter = {
164
168
  value: string | number | boolean | Date | number[];
165
- subProps?: string[];
166
169
  matchMode?: MatchMode;
170
+ operator?: string; // 'and' | 'or' - Used when multiple filters apply to the same property
167
171
  }
168
172
 
169
173
  type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void;
@@ -278,6 +282,56 @@ Using substacks simplifies your route definitions and ensures consistent data pr
278
282
  - **deleteArchive()**: Deletes archived rows that were archived before a specific date using a PostgreSQL SECURITY DEFINER function. Expects `req.body.date` to be a Date object.
279
283
  - **getHistory()**: Retrieves modification history for rows from the `log.history` table. Expects `req.body.rows` to be an array of objects with `id` property. Returns all historical records for the specified entity IDs.
280
284
 
285
+ ### Filters
286
+
287
+ Filters support two formats for maximum flexibility:
288
+
289
+ #### Simple Format (Single Filter per Property)
290
+
291
+ Backward-compatible format using a single filter object:
292
+
293
+ ```javascript
294
+ const filters = {
295
+ name: { value: 'John', matchMode: 'contains' },
296
+ age: { value: 30, matchMode: 'equals' },
297
+ archived: { value: false, matchMode: 'equals' }
298
+ };
299
+ ```
300
+
301
+ #### Complex Format (Multiple Filters per Property)
302
+
303
+ Array-based format supporting multiple filters with logical operators:
304
+
305
+ ```javascript
306
+ const filters = {
307
+ // Multiple filters on the same property with OR operator
308
+ name: [
309
+ { value: 'John', matchMode: 'contains', operator: 'or' },
310
+ { value: 'Jane', matchMode: 'contains', operator: 'or' }
311
+ ],
312
+ // Age range with AND operator
313
+ age: [
314
+ { value: 18, matchMode: 'gte', operator: 'and' },
315
+ { value: 65, matchMode: 'lte', operator: 'and' }
316
+ ],
317
+ // Single filter in array format
318
+ archived: [{ value: false, matchMode: 'equals' }]
319
+ };
320
+ ```
321
+
322
+ This generates SQL like:
323
+ ```sql
324
+ WHERE (name LIKE '%John%' OR name LIKE '%Jane%')
325
+ AND (age >= 18 AND age <= 65)
326
+ AND archived = false
327
+ ```
328
+
329
+ **Notes:**
330
+ - Both formats can be mixed in the same filters object
331
+ - When using arrays with a single filter, the operator is optional
332
+ - Default operator is 'AND' if not specified
333
+ - The operator field is case-insensitive
334
+
281
335
 
282
336
  ## Match modes
283
337
 
@@ -36,13 +36,13 @@ export type Filters = {
36
36
  };
37
37
  export type Filter = {
38
38
  value: string | number | boolean | Date | number[];
39
- subProps?: string[];
40
39
  matchMode?: MatchMode;
40
+ operator?: string;
41
41
  };
42
42
  export type { Type };
43
43
 
44
44
  export declare class Property extends BaseProperty {
45
- filter: boolean;
45
+ isFilterable: boolean;
46
46
  operations: Operation[];
47
47
  constructor(
48
48
  key: string,
@@ -24,7 +24,7 @@ SOFTWARE.
24
24
  https://github.com/DWTechs/Antity-pgsql.js
25
25
  */
26
26
 
27
- import { isArray, isIn, isString } from '@dwtechs/checkard';
27
+ import { isArray, isString, isIn } from '@dwtechs/checkard';
28
28
  import { deleteProps, chunk, flatten } from '@dwtechs/sparray';
29
29
  import { log } from '@dwtechs/winstan';
30
30
  import { Entity } from '@dwtechs/antity';
@@ -107,14 +107,6 @@ function quoteIfUppercase(word) {
107
107
  function index(index, matchMode) {
108
108
  const i = index.map((i) => `$${i}`);
109
109
  switch (matchMode) {
110
- case "startsWith":
111
- return `${i}%`;
112
- case "endsWith":
113
- return `%${i}`;
114
- case "contains":
115
- return `%${i}%`;
116
- case "notContains":
117
- return `%${i}%`;
118
110
  case "in":
119
111
  return `(${i})`;
120
112
  default:
@@ -159,21 +151,49 @@ function comparator(matchMode) {
159
151
  }
160
152
  }
161
153
 
154
+ function formatValue(value, matchMode) {
155
+ if (!isString(value))
156
+ return value;
157
+ switch (matchMode) {
158
+ case "startsWith":
159
+ return `${value}%`;
160
+ case "endsWith":
161
+ return `%${value}`;
162
+ case "contains":
163
+ case "notContains":
164
+ return `%${value}%`;
165
+ default:
166
+ return value;
167
+ }
168
+ }
162
169
  function add(filters) {
170
+ var _a, _b;
163
171
  const conditions = [];
164
172
  const args = [];
165
173
  if (filters) {
166
174
  let i = 1;
167
175
  for (const k in filters) {
168
- const { value, matchMode } = filters[k];
169
- const indexes = isArray(value) ? value.map(() => i++) : [i++];
170
- const cond = addOne(k, indexes, matchMode);
171
- if (cond) {
172
- conditions.push(cond);
173
- if (isArray(value))
174
- args.push(...value);
175
- else
176
- args.push(value);
176
+ const filterValue = filters[k];
177
+ const filterArray = isArray(filterValue) ? filterValue : [filterValue];
178
+ const groupConditions = [];
179
+ for (const filter of filterArray) {
180
+ const { value, matchMode } = filter;
181
+ const indexes = isArray(value) ? value.map(() => i++) : [i++];
182
+ const cond = addOne(k, indexes, matchMode);
183
+ if (cond) {
184
+ groupConditions.push(cond);
185
+ if (isArray(value))
186
+ args.push(...value.map((v) => formatValue(v, matchMode)));
187
+ else
188
+ args.push(formatValue(value, matchMode));
189
+ }
190
+ }
191
+ if (groupConditions.length > 0) {
192
+ const operator = ((_b = (_a = filterArray[0]) === null || _a === void 0 ? void 0 : _a.operator) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || 'AND';
193
+ const combined = groupConditions.length > 1
194
+ ? `(${groupConditions.join(` ${operator} `)})`
195
+ : groupConditions[0];
196
+ conditions.push(combined);
177
197
  }
178
198
  }
179
199
  }
@@ -465,12 +485,20 @@ function cleanFilters(filters, properties) {
465
485
  continue;
466
486
  }
467
487
  const type$1 = type(prop.type);
468
- const { matchMode: matchMode$1 } = filters[k];
469
- if (!matchMode$1 || !matchMode(type$1, matchMode$1)) {
470
- log.warn(`${LOGS_PREFIX}Filters: skipping invalid match mode: "${matchMode$1}" for type: "${type$1}" at property: "${k}"`);
488
+ const filterValue = filters[k];
489
+ const filterArray = isArray(filterValue) ? filterValue : [filterValue];
490
+ const validFilters = filterArray.filter((f) => {
491
+ const { matchMode: matchMode$1 } = f;
492
+ if (!matchMode$1 || !matchMode(type$1, matchMode$1)) {
493
+ log.warn(`${LOGS_PREFIX}Filters: skipping invalid match mode: "${matchMode$1}" for type: "${type$1}" at property: "${k}"`);
494
+ return false;
495
+ }
496
+ return true;
497
+ });
498
+ if (!validFilters.length)
471
499
  delete filters[k];
472
- continue;
473
- }
500
+ else
501
+ filters[k] = validFilters;
474
502
  }
475
503
  }
476
504
  return filters;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwtechs/antity-pgsql",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Open source library to add PostgreSQL support to @dwtechs/Antity entities.",
5
5
  "keywords": [
6
6
  "entities"