querier-ts 2.4.1 → 2.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.5.1
4
+
5
+ ### Summary
6
+
7
+ - [ ] Bug fixes
8
+ - [ ] Code refactoring
9
+ - [ ] New features
10
+ - [x] Build and packaging updates
11
+ - [ ] Breaking changes
12
+
13
+ ### Build and packaging updates
14
+
15
+ - Updated target from `es2020` to `es2022`, improving performance and reducing bundle size.
16
+ - Added `pnpm-workspace.yaml` file with `allowBuilds` configuration.
17
+ - Updated development dependencies.
18
+
19
+ ## v2.5.0
20
+
21
+ ### Summary
22
+
23
+ - [ ] Bug fixes
24
+ - [x] Code refactoring
25
+ - [x] New features
26
+ - [x] Build and packaging updates
27
+ - [ ] Breaking changes
28
+
29
+ ### New features
30
+
31
+ - Added `distinct()` method to `Query`.
32
+
33
+ ### Code refactoring
34
+
35
+ - Improve conditions order in `column()` method.
36
+ - Added a new unit test for the `column()` method, covering no rows case.
37
+
38
+ ### Build and packaging updates
39
+
40
+ - Updated development dependencies.
41
+
3
42
  ## v2.4.1
4
43
 
5
44
  ### Summary
@@ -7,7 +46,7 @@
7
46
  - [ ] Bug fixes
8
47
  - [x] Code refactoring
9
48
  - [ ] New features
10
- - [x] Compatibility fixes
49
+ - [x] Compatibility improvements
11
50
  - [ ] Breaking changes
12
51
 
13
52
  ### Code refactoring
@@ -15,10 +54,10 @@
15
54
  - Removed redundant conditions and unecessary code.
16
55
  - Optimized array iteration performance:
17
56
  - Pre-allocate arrays to avoid dynamic resizing.
18
- - Replace Array.map() and `for...of` with indexed `for` loops.
57
+ - Replace `Array.map()` and `for...of` with indexed `for` loops.
19
58
  - Updated JSDoc for `Query`.
20
59
 
21
- ### Compatibility fixes
60
+ ### Compatibility improvements
22
61
 
23
62
  - Updated development dependencies.
24
63
 
package/README.md CHANGED
@@ -10,7 +10,7 @@ A lightweight, type-safe in-memory query engine for JavaScript and TypeScript.
10
10
 
11
11
  ## `Query`
12
12
 
13
- You can create a `Query` instance like this:
13
+ Usage:
14
14
 
15
15
  ```ts
16
16
  const usersQuery = Query.from(users);
@@ -79,6 +79,23 @@ const filteredUsers = Query.from(users)
79
79
 
80
80
  ---
81
81
 
82
+ #### `distinct(key | callback)`
83
+
84
+ Removes duplicate rows based on a key or a callback function.
85
+
86
+ - `key`: The key to be used for comparison.
87
+ - `callback`: A function that maps each row to a value.
88
+
89
+ ```ts
90
+ const uniqueCountries = Query.from(addresses)
91
+ .distinct('country')
92
+ .column('country');
93
+
94
+ const uniqueEmailProviders = Query.from(users)
95
+ .distinct((user) => user.email.split('@')[1])
96
+ .column('email');
97
+ ```
98
+
82
99
  ### Selecting data
83
100
 
84
101
  #### `select(columns)`
@@ -1 +1,618 @@
1
- 'use strict';var $=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var F=s=>{throw TypeError(s)};var O=(s,e,t,r)=>{for(var o=E(e,t),n=s.length-1,i;n>=0;n--)(i=s[n])&&(o=(i(e,t,o))||o);return o&&$(e,t,o),o},g=(s,e)=>(t,r)=>e(t,r,s);var N=(s,e,t)=>e.has(s)||F("Cannot "+t);var l=(s,e,t)=>(N(s,e,"read from private field"),e.get(s)),b=(s,e,t)=>e.has(s)?F("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(s):e.set(s,t),p=(s,e,t,r)=>(N(s,e,"write to private field"),e.set(s,t),t);var v=s=>typeof s=="number";var d=class extends Error{constructor(e){super(e),this.name="InvalidArgumentError";}};var k=new WeakMap;function V(s,e){let t=k.get(s);t||(t=new Map,k.set(s,t));let r=t.get(e);return r||(r=[],t.set(e,r)),r}function P(s){return function(e,t,r){let o=V(e,t),n=o.find(i=>i.index===r);n?n.min=s:o.push({index:r,min:s});}}function C(s,e,t){let r=V(s,e),o=r.find(n=>n.index===t);o?o.integer=true:r.push({index:t,integer:true});}function j(s,e,t){let r=t.value;t.value=function(...o){let n=k.get(s)?.get(e)??[];for(let i of n){let u=o[i.index];if(i.min!==void 0&&(!v(u)||u<i.min))throw new d(`${String(u)} is not a valid argument to param ${i.index} on ${e}(). Expected value to be equal or greater than ${i.min}.`);if(i.max!==void 0&&(!v(u)||u>i.max))throw new d(`${String(u)} is not a valid argument to param ${i.index} on ${e}(). Expected value to be equal or less than ${i.max}.`);if(i.integer&&!Number.isSafeInteger(u))throw new d(`${String(u)} is not a valid argument to param ${i.index} on ${e}(). Expected value to be an integer.`)}return r.apply(this,o)};}var h=s=>typeof s=="object"&&s!==null;var R=(s,e)=>{if(s===e)return true;if(!h(s)||!h(e)||Array.isArray(s)!==Array.isArray(e))return false;let t=Object.keys(s),r=Object.keys(e);return t.length!==r.length?false:t.every(o=>Object.prototype.hasOwnProperty.call(e,o)&&R(s[o],e[o]))};var G=(s,e)=>s.length===e.length&&s.every((t,r)=>R(t,e[r]));function B(s){return Object.entries(s)}var m=s=>typeof s=="function";var x,w=class{static validate(e,t,r){for(let[o,n]of B(t))if(!this.validateColumnCondition(e,o,n,r??l(this,x)))return false;return true}static validateColumnCondition(e,t,r,o){if(o.ignoreNullValues&&r==null)return true;let n=e[t];return m(r)?r(n):Array.isArray(r)?Array.isArray(n)?G(n,r):false:h(r)?h(n)?this.validate(n,r):false:n===r}};x=new WeakMap,b(w,x,{ignoreNullValues:false});function L(s){return Object.keys(s).filter(e=>typeof s[e]!="function")}function M(s){let e=1,t=s;t.startsWith("-")&&(e=-1,t=t.slice(1));let r=t;return (o,n)=>{let i=o[r],u=n[r];return i==null&&u==null?0:i==null?1*e:u==null||i<u?-1*e:i>u?1*e:0}}function S(...s){return (e,t)=>{if(s.length===0)return 0;for(let r of s){let o=M(r)(e,t);if(o!==0)return o}return 0}}var a,f,y,c=class c{constructor(e){b(this,a,[]);b(this,f,0);b(this,y,null);p(this,a,[...e]);}static from(e){return new c(e)}select(...e){let t=l(this,a),r=t.length,o=new Array(r);for(let i=0;i<r;i++){let u={};for(let T of e)u[T]=t[i][T];o[i]=u;}let n=new c(o);return this.cloneStateInto(n),n}map(e){let t=l(this,a),r=t.length,o=new Array(r);for(let i=0;i<r;i++)o[i]=e(t[i]);let n=new c(o);return this.cloneStateInto(n),n}where(e){return this.filterRows(e),this}filterWhere(e){return this.filterRows(e,{ignoreNullValues:true}),this}orderBy(...e){return p(this,a,l(this,a).sort(S(...e))),this}skip(e){return p(this,f,e),this}limit(e){return p(this,y,e),this}all(){return this.getLimitedRows()}first(){let e=this.getLimitedRows();return e.length>0?e[0]:null}last(){let e=this.getLimitedRows();return e[e.length-1]??null}count(){return this.getLimitedRows().length}exists(){return this.count()>0}scalar(){let e=this.first(),t=this.getFirstColumn();return e&&t?e[t]:false}column(e){if(e===void 0){let n=this.getFirstColumn();if(!n)return [];e=n;}let t=this.getLimitedRows(),r=t.length;if(r===0)return [];let o=new Array(r);for(let n=0;n<r;n++)o[n]=t[n][e];return o}values(){let e=this.getLimitedRows(),t=e.length,r=new Array(t);for(let o=0;o<t;o++)r[o]=Object.values(e[o]);return r}groupBy(e,t){let r=this.getLimitedRows(),o=new Map,n=m(e);for(let i=0;i<r.length;i++){let u=r[i],T=n?e(u):u[e],K=t?t(u):u;o.has(T)?o.get(T).push(K):o.set(T,[K]);}return o}min(e){let t=this.getLimitedRows(),r=t.length;if(r===0)return null;let o=new Array(r);if(m(e))for(let n=0;n<r;n++)o[n]=e(t[n]);else for(let n=0;n<r;n++)o[n]=t[n][e];return Math.min(...o)}max(e){let t=this.getLimitedRows(),r=t.length;if(r===0)return null;let o=new Array(r);if(m(e))for(let n=0;n<r;n++)o[n]=e(t[n]);else for(let n=0;n<r;n++)o[n]=t[n][e];return Math.max(...o)}sum(e){let t=this.getLimitedRows(),r=t.length;if(r===0)return 0;let o=new Array(r);if(m(e))for(let n=0;n<r;n++)o[n]=e(t[n]);else for(let n=0;n<r;n++)o[n]=t[n][e];return o.reduce((n,i)=>n+i,0)}average(e){let t=this.count();return t===0?null:(m(e)?this.sum(e):this.sum(e))/t}filterRows(e,t){let r=l(this,a),o=[];if(m(e))for(let n=0;n<r.length;n++){let i=r[n];e(i)&&o.push(i);}else for(let n=0;n<r.length;n++){let i=r[n];w.validate(i,e,t)&&o.push(i);}p(this,a,o);}getFirstColumn(){let e=this.first();if(!e)return null;let t=L(e);return t.length>0?t[0]:null}getLimitedRows(){return l(this,a).slice(l(this,f),l(this,f)+(l(this,y)??l(this,a).length))}cloneStateInto(e){p(e,f,l(this,f)),p(e,y,l(this,y));}};a=new WeakMap,f=new WeakMap,y=new WeakMap,O([j,g(0,C),g(0,P(0))],c.prototype,"skip"),O([j,g(0,C),g(0,P(0))],c.prototype,"limit");var A=c;exports.Query=A;
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __decorateClass = (decorators, target, key, kind) => {
6
+ var result = __getOwnPropDesc(target, key) ;
7
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
8
+ if (decorator = decorators[i])
9
+ result = (decorator(target, key, result) ) || result;
10
+ if (result) __defProp(target, key, result);
11
+ return result;
12
+ };
13
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
14
+
15
+ // src/utils/functions/type-guards/is-number.ts
16
+ var isNumber = (value) => typeof value === "number";
17
+
18
+ // src/core/errors/invalid-argument-error.ts
19
+ var InvalidArgumentError = class extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "InvalidArgumentError";
23
+ }
24
+ };
25
+
26
+ // src/core/validation/decorators/number-validaton.ts
27
+ var metadata = /* @__PURE__ */ new WeakMap();
28
+ function getParams(target, propertyKey) {
29
+ let methods = metadata.get(target);
30
+ if (!methods) {
31
+ methods = /* @__PURE__ */ new Map();
32
+ metadata.set(target, methods);
33
+ }
34
+ let params = methods.get(propertyKey);
35
+ if (!params) {
36
+ params = [];
37
+ methods.set(propertyKey, params);
38
+ }
39
+ return params;
40
+ }
41
+ function min(value) {
42
+ return function(target, propertyKey, index) {
43
+ const params = getParams(target, propertyKey);
44
+ const existing = params.find((p) => p.index === index);
45
+ if (existing) {
46
+ existing.min = value;
47
+ } else {
48
+ params.push({ index, min: value });
49
+ }
50
+ };
51
+ }
52
+ function integer(target, propertyKey, index) {
53
+ const params = getParams(target, propertyKey);
54
+ const existing = params.find((p) => p.index === index);
55
+ if (existing) {
56
+ existing.integer = true;
57
+ } else {
58
+ params.push({ index, integer: true });
59
+ }
60
+ }
61
+ function validateNumbers(target, propertyKey, descriptor) {
62
+ const original = descriptor.value;
63
+ descriptor.value = function(...args) {
64
+ const params = metadata.get(target)?.get(propertyKey) ?? [];
65
+ for (const rule of params) {
66
+ const value = args[rule.index];
67
+ if (rule.min !== void 0) {
68
+ if (!isNumber(value) || value < rule.min) {
69
+ throw new InvalidArgumentError(
70
+ `${String(value)} is not a valid argument to param ${rule.index} on ${propertyKey}(). Expected value to be equal or greater than ${rule.min}.`
71
+ );
72
+ }
73
+ }
74
+ if (rule.max !== void 0) {
75
+ if (!isNumber(value) || value > rule.max) {
76
+ throw new InvalidArgumentError(
77
+ `${String(value)} is not a valid argument to param ${rule.index} on ${propertyKey}(). Expected value to be equal or less than ${rule.max}.`
78
+ );
79
+ }
80
+ }
81
+ if (rule.integer) {
82
+ if (!Number.isSafeInteger(value)) {
83
+ throw new InvalidArgumentError(
84
+ `${String(value)} is not a valid argument to param ${rule.index} on ${propertyKey}(). Expected value to be an integer.`
85
+ );
86
+ }
87
+ }
88
+ }
89
+ return original.apply(this, args);
90
+ };
91
+ }
92
+
93
+ // src/utils/functions/type-guards/is-object.ts
94
+ var isObject = (value) => typeof value === "object" && value !== null;
95
+
96
+ // src/utils/functions/generic/deep-equal.ts
97
+ var deepEqual = (a, b) => {
98
+ if (a === b) {
99
+ return true;
100
+ }
101
+ if (!isObject(a) || !isObject(b)) {
102
+ return false;
103
+ }
104
+ if (Array.isArray(a) !== Array.isArray(b)) {
105
+ return false;
106
+ }
107
+ const keysA = Object.keys(a);
108
+ const keysB = Object.keys(b);
109
+ if (keysA.length !== keysB.length) {
110
+ return false;
111
+ }
112
+ return keysA.every(
113
+ (key) => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(
114
+ a[key],
115
+ b[key]
116
+ )
117
+ );
118
+ };
119
+
120
+ // src/utils/functions/generic/compare-arrays.ts
121
+ var compareArrays = (a, b) => a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
122
+
123
+ // src/utils/functions/generic/get-entries.ts
124
+ function getEntries(obj) {
125
+ return Object.entries(obj);
126
+ }
127
+
128
+ // src/utils/functions/type-guards/is-function.ts
129
+ var isFunction = (value) => typeof value === "function";
130
+
131
+ // src/core/validation/query-row-validator.ts
132
+ var QueryRowValidator = class {
133
+ static #defaultOptions = {
134
+ ignoreNullValues: false
135
+ };
136
+ /**
137
+ * Validates all conditions of the row.
138
+ *
139
+ * @returns Validation result.
140
+ */
141
+ static validate(row, condition, options) {
142
+ for (const [column, columnCondition] of getEntries(condition)) {
143
+ const validated = this.validateColumnCondition(
144
+ row,
145
+ column,
146
+ columnCondition,
147
+ options ?? this.#defaultOptions
148
+ );
149
+ if (!validated) {
150
+ return false;
151
+ }
152
+ }
153
+ return true;
154
+ }
155
+ /**
156
+ * Validate a condition to a specific column.
157
+ *
158
+ * @param columnName Column name.
159
+ * @param condition Condition to be validated.
160
+ *
161
+ * @returns Validation result.
162
+ */
163
+ static validateColumnCondition(row, column, condition, options) {
164
+ if (options.ignoreNullValues && (condition === null || condition === void 0)) {
165
+ return true;
166
+ }
167
+ const cellValue = row[column];
168
+ if (isFunction(condition)) {
169
+ return condition(cellValue);
170
+ }
171
+ if (Array.isArray(condition)) {
172
+ return Array.isArray(cellValue) ? compareArrays(cellValue, condition) : false;
173
+ }
174
+ if (isObject(condition)) {
175
+ return isObject(cellValue) ? this.validate(cellValue, condition) : false;
176
+ }
177
+ return cellValue === condition;
178
+ }
179
+ };
180
+
181
+ // src/utils/functions/generic/get-object-property-names.ts
182
+ function getObjectPropertyNames(obj) {
183
+ return Object.keys(obj).filter(
184
+ (key) => typeof obj[key] !== "function"
185
+ );
186
+ }
187
+
188
+ // src/utils/functions/sort/sort-by-property.ts
189
+ function sortByProperty(property) {
190
+ let sortOrder = 1;
191
+ let prop = property;
192
+ if (prop.startsWith("-")) {
193
+ sortOrder = -1;
194
+ prop = prop.slice(1);
195
+ }
196
+ const key = prop;
197
+ return (a, b) => {
198
+ const valueA = a[key];
199
+ const valueB = b[key];
200
+ if (valueA == null && valueB == null) return 0;
201
+ if (valueA == null) return 1 * sortOrder;
202
+ if (valueB == null) return -1 * sortOrder;
203
+ if (valueA < valueB) return -1 * sortOrder;
204
+ if (valueA > valueB) return 1 * sortOrder;
205
+ return 0;
206
+ };
207
+ }
208
+
209
+ // src/utils/functions/sort/sort-by-properties.ts
210
+ function sortByProperties(...props) {
211
+ return (a, b) => {
212
+ if (props.length === 0) {
213
+ return 0;
214
+ }
215
+ for (const prop of props) {
216
+ const result = sortByProperty(prop)(a, b);
217
+ if (result !== 0) {
218
+ return result;
219
+ }
220
+ }
221
+ return 0;
222
+ };
223
+ }
224
+
225
+ // src/query.ts
226
+ var _Query = class _Query {
227
+ /**
228
+ * Rows to be queried.
229
+ */
230
+ #rows = [];
231
+ /**
232
+ * Number of results to skip.
233
+ */
234
+ #startAt = 0;
235
+ /**
236
+ * Limit of results.
237
+ */
238
+ #limit = null;
239
+ /**
240
+ * Initializes the query.
241
+ *
242
+ * @param rows Rows to be queried.
243
+ */
244
+ constructor(rows) {
245
+ this.#rows = [...rows];
246
+ }
247
+ /**
248
+ * Creates a new query based on the given data.
249
+ *
250
+ * @param rows Rows to be queried.
251
+ *
252
+ * @returns Query to the given rows.
253
+ */
254
+ static from(rows) {
255
+ return new _Query(rows);
256
+ }
257
+ /**
258
+ * Defines specific columns to be returned on the final results.
259
+ *
260
+ * @param columns Selected columns.
261
+ *
262
+ * @returns Current query.
263
+ */
264
+ select(...columns) {
265
+ const source = this.#rows;
266
+ const length = source.length;
267
+ const rows = new Array(length);
268
+ for (let i = 0; i < length; i++) {
269
+ const result = {};
270
+ for (const column of columns) {
271
+ result[column] = source[i][column];
272
+ }
273
+ rows[i] = result;
274
+ }
275
+ const query = new _Query(rows);
276
+ this.cloneStateInto(query);
277
+ return query;
278
+ }
279
+ /**
280
+ * Maps each row to a new object and returns a new query.
281
+ *
282
+ * @param callback Function to be called for each row.
283
+ * @returns New query with the mapped rows.
284
+ */
285
+ map(callback) {
286
+ const source = this.#rows;
287
+ const length = source.length;
288
+ const rows = new Array(length);
289
+ for (let i = 0; i < length; i++) {
290
+ rows[i] = callback(source[i]);
291
+ }
292
+ const query = new _Query(rows);
293
+ this.cloneStateInto(query);
294
+ return query;
295
+ }
296
+ distinct(arg) {
297
+ const seen = /* @__PURE__ */ new Set();
298
+ const result = [];
299
+ const rows = this.#rows;
300
+ const isFn = isFunction(arg);
301
+ for (let i = 0; i < rows.length; i++) {
302
+ const row = rows[i];
303
+ const value = isFn ? arg(row) : row[arg];
304
+ if (!seen.has(value)) {
305
+ seen.add(value);
306
+ result.push(row);
307
+ }
308
+ }
309
+ this.#rows = result;
310
+ return this;
311
+ }
312
+ /**
313
+ * Applies conditions to the query.
314
+ *
315
+ * @param condition Filter to be applied to the query.
316
+ *
317
+ * If a callback function is provided, it must return a boolean value.
318
+ *
319
+ * If an object is provided, its properties must be attributes of `T` and their
320
+ * corresponding values must be the expected values for the attributes or a
321
+ * callback functions that return boolean values.
322
+ *
323
+ * @returns Current query.
324
+ */
325
+ where(condition) {
326
+ this.filterRows(condition);
327
+ return this;
328
+ }
329
+ /**
330
+ * Applies a set of conditions to the query ignoring `null` and `undefined`
331
+ * values as conditions.
332
+ *
333
+ * @param condition An object where each property represents an attribute
334
+ * to be validated. The values can be literal or callback functions that
335
+ * return a boolean. If `null` or `undefined` is passed, that condition
336
+ * will be skipped.
337
+ *
338
+ * @returns Current query.
339
+ */
340
+ filterWhere(condition) {
341
+ this.filterRows(condition, { ignoreNullValues: true });
342
+ return this;
343
+ }
344
+ /**
345
+ * Adds ordering to the results.
346
+ *
347
+ * This method should be called after `select()`; otherwise, the ordering will
348
+ * be applied only to the selected columns.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * // ❌ "age" will not be ordered, because it is not part of the selected columns
353
+ * const query = Query.from(users)
354
+ * .orderBy('-age')
355
+ * .select('name');
356
+ *
357
+ * // ✅ "age" will be ordered
358
+ * const query = Query.from(users)
359
+ * .select('name', 'age')
360
+ * .orderBy('-age');
361
+ * ```
362
+ *
363
+ * @param columns Ascending or descending columns. To mark a field as
364
+ * descending, prefix it with `-`.
365
+ *
366
+ * @returns Current query.
367
+ */
368
+ orderBy(...columns) {
369
+ this.#rows = this.#rows.sort(sortByProperties(...columns));
370
+ return this;
371
+ }
372
+ skip(numberOfRows) {
373
+ this.#startAt = numberOfRows;
374
+ return this;
375
+ }
376
+ limit(limit) {
377
+ this.#limit = limit;
378
+ return this;
379
+ }
380
+ /**
381
+ * Returns all results.
382
+ *
383
+ * @returns All filtered rows.
384
+ */
385
+ all() {
386
+ return this.getLimitedRows();
387
+ }
388
+ /**
389
+ * Returns the first result.
390
+ *
391
+ * @returns The first result.
392
+ */
393
+ first() {
394
+ const rows = this.getLimitedRows();
395
+ return rows.length > 0 ? rows[0] : null;
396
+ }
397
+ /**
398
+ * Returns the last result.
399
+ *
400
+ * @returns The last result.
401
+ */
402
+ last() {
403
+ const rows = this.getLimitedRows();
404
+ return rows[rows.length - 1] ?? null;
405
+ }
406
+ /**
407
+ * Returns the current number of rows.
408
+ *
409
+ * @return Filtered rows count.
410
+ */
411
+ count() {
412
+ return this.getLimitedRows().length;
413
+ }
414
+ /**
415
+ * Checks if there is at least one row compatible with the query.
416
+ *
417
+ * @returns Whether any row exists after filtering.
418
+ */
419
+ exists() {
420
+ return this.count() > 0;
421
+ }
422
+ /**
423
+ * Returns the value of the first (selected) column of the first row.
424
+ *
425
+ * @returns First value or `false`, if none row exists.
426
+ */
427
+ scalar() {
428
+ const firstObject = this.first();
429
+ const firstColumn = this.getFirstColumn();
430
+ return firstObject && firstColumn ? firstObject[firstColumn] : false;
431
+ }
432
+ column(column) {
433
+ const source = this.getLimitedRows();
434
+ const length = source.length;
435
+ if (length === 0) {
436
+ return [];
437
+ }
438
+ if (column === void 0) {
439
+ const firstColumn = this.getFirstColumn();
440
+ if (!firstColumn) {
441
+ return [];
442
+ }
443
+ column = firstColumn;
444
+ }
445
+ const values = new Array(length);
446
+ for (let i = 0; i < length; i++) {
447
+ values[i] = source[i][column];
448
+ }
449
+ return values;
450
+ }
451
+ /**
452
+ * Returns the values of the rows. If there are selected columns, only their
453
+ * values will be returned.
454
+ *
455
+ * @returns Array with the values of all rows.
456
+ */
457
+ values() {
458
+ const source = this.getLimitedRows();
459
+ const length = source.length;
460
+ const rows = new Array(length);
461
+ for (let i = 0; i < length; i++) {
462
+ rows[i] = Object.values(source[i]);
463
+ }
464
+ return rows;
465
+ }
466
+ groupBy(groupArg, mapArg) {
467
+ const rows = this.getLimitedRows();
468
+ const map = /* @__PURE__ */ new Map();
469
+ const hasCallback = isFunction(groupArg);
470
+ for (let i = 0; i < rows.length; i++) {
471
+ const row = rows[i];
472
+ const key = hasCallback ? groupArg(row) : row[groupArg];
473
+ const value = mapArg ? mapArg(row) : row;
474
+ if (map.has(key)) {
475
+ map.get(key).push(value);
476
+ } else {
477
+ map.set(key, [value]);
478
+ }
479
+ }
480
+ return map;
481
+ }
482
+ min(arg) {
483
+ const source = this.getLimitedRows();
484
+ const length = source.length;
485
+ if (length === 0) {
486
+ return null;
487
+ }
488
+ const values = new Array(length);
489
+ if (isFunction(arg)) {
490
+ for (let i = 0; i < length; i++) {
491
+ values[i] = arg(source[i]);
492
+ }
493
+ } else {
494
+ for (let i = 0; i < length; i++) {
495
+ values[i] = source[i][arg];
496
+ }
497
+ }
498
+ return Math.min(...values);
499
+ }
500
+ max(arg) {
501
+ const source = this.getLimitedRows();
502
+ const length = source.length;
503
+ if (length === 0) {
504
+ return null;
505
+ }
506
+ const values = new Array(length);
507
+ if (isFunction(arg)) {
508
+ for (let i = 0; i < length; i++) {
509
+ values[i] = arg(source[i]);
510
+ }
511
+ } else {
512
+ for (let i = 0; i < length; i++) {
513
+ values[i] = source[i][arg];
514
+ }
515
+ }
516
+ return Math.max(...values);
517
+ }
518
+ sum(arg) {
519
+ const source = this.getLimitedRows();
520
+ const length = source.length;
521
+ if (length === 0) {
522
+ return 0;
523
+ }
524
+ const values = new Array(length);
525
+ if (isFunction(arg)) {
526
+ for (let i = 0; i < length; i++) {
527
+ values[i] = arg(source[i]);
528
+ }
529
+ } else {
530
+ for (let i = 0; i < length; i++) {
531
+ values[i] = source[i][arg];
532
+ }
533
+ }
534
+ return values.reduce((total, value) => total + value, 0);
535
+ }
536
+ average(arg) {
537
+ const count = this.count();
538
+ if (count === 0) {
539
+ return null;
540
+ }
541
+ const sum = isFunction(arg) ? this.sum(arg) : this.sum(arg);
542
+ return sum / count;
543
+ }
544
+ /**
545
+ * Filters the rows according to the given conditions.
546
+ *
547
+ * @param condition Object or callback function.
548
+ */
549
+ filterRows(condition, options) {
550
+ const rows = this.#rows;
551
+ const result = [];
552
+ if (isFunction(condition)) {
553
+ for (let i = 0; i < rows.length; i++) {
554
+ const row = rows[i];
555
+ if (condition(row)) {
556
+ result.push(row);
557
+ }
558
+ }
559
+ } else {
560
+ for (let i = 0; i < rows.length; i++) {
561
+ const row = rows[i];
562
+ if (QueryRowValidator.validate(row, condition, options)) {
563
+ result.push(row);
564
+ }
565
+ }
566
+ }
567
+ this.#rows = result;
568
+ }
569
+ /**
570
+ * Returns the first selected column or the first key of some row.
571
+ *
572
+ * @returns The first column or `null`, if none is selected or there is no row.
573
+ */
574
+ getFirstColumn() {
575
+ const firstRow = this.first();
576
+ if (!firstRow) {
577
+ return null;
578
+ }
579
+ const columns = getObjectPropertyNames(firstRow);
580
+ if (columns.length > 0) {
581
+ return columns[0];
582
+ }
583
+ return null;
584
+ }
585
+ /**
586
+ * Returns the rows that should be used in the final results.
587
+ *
588
+ * @returns Rows within the specified limit.
589
+ */
590
+ getLimitedRows() {
591
+ return this.#rows.slice(
592
+ this.#startAt,
593
+ this.#startAt + (this.#limit ?? this.#rows.length)
594
+ );
595
+ }
596
+ /**
597
+ * Copies the state of the current query into the given query.
598
+ *
599
+ * @param query Query to copy the state into.
600
+ */
601
+ cloneStateInto(query) {
602
+ query.#startAt = this.#startAt;
603
+ query.#limit = this.#limit;
604
+ }
605
+ };
606
+ __decorateClass([
607
+ validateNumbers,
608
+ __decorateParam(0, integer),
609
+ __decorateParam(0, min(0))
610
+ ], _Query.prototype, "skip");
611
+ __decorateClass([
612
+ validateNumbers,
613
+ __decorateParam(0, integer),
614
+ __decorateParam(0, min(0))
615
+ ], _Query.prototype, "limit");
616
+ var Query = _Query;
617
+
618
+ exports.Query = Query;
@@ -111,6 +111,20 @@ declare class Query<T extends object> {
111
111
  * @returns New query with the mapped rows.
112
112
  */
113
113
  map<TReturn extends object>(callback: (obj: T) => TReturn): Query<TReturn>;
114
+ /**
115
+ * Removes duplicate rows based on a key.
116
+ *
117
+ * @param key Key to be used for comparison.
118
+ * @returns Current query.
119
+ */
120
+ distinct<K extends PropOf<T>>(key: K): this;
121
+ /**
122
+ * Removes duplicate rows based on a function.
123
+ *
124
+ * @param fn Function to be used for comparison.
125
+ * @returns Current query.
126
+ */
127
+ distinct<TValue>(fn: (row: T) => TValue): this;
114
128
  /**
115
129
  * Applies conditions to the query.
116
130
  *
package/dist/esm/index.js CHANGED
@@ -1 +1,616 @@
1
- var $=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var F=s=>{throw TypeError(s)};var O=(s,e,t,r)=>{for(var o=E(e,t),n=s.length-1,i;n>=0;n--)(i=s[n])&&(o=(i(e,t,o))||o);return o&&$(e,t,o),o},g=(s,e)=>(t,r)=>e(t,r,s);var N=(s,e,t)=>e.has(s)||F("Cannot "+t);var l=(s,e,t)=>(N(s,e,"read from private field"),e.get(s)),b=(s,e,t)=>e.has(s)?F("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(s):e.set(s,t),p=(s,e,t,r)=>(N(s,e,"write to private field"),e.set(s,t),t);var v=s=>typeof s=="number";var d=class extends Error{constructor(e){super(e),this.name="InvalidArgumentError";}};var k=new WeakMap;function V(s,e){let t=k.get(s);t||(t=new Map,k.set(s,t));let r=t.get(e);return r||(r=[],t.set(e,r)),r}function P(s){return function(e,t,r){let o=V(e,t),n=o.find(i=>i.index===r);n?n.min=s:o.push({index:r,min:s});}}function C(s,e,t){let r=V(s,e),o=r.find(n=>n.index===t);o?o.integer=true:r.push({index:t,integer:true});}function j(s,e,t){let r=t.value;t.value=function(...o){let n=k.get(s)?.get(e)??[];for(let i of n){let u=o[i.index];if(i.min!==void 0&&(!v(u)||u<i.min))throw new d(`${String(u)} is not a valid argument to param ${i.index} on ${e}(). Expected value to be equal or greater than ${i.min}.`);if(i.max!==void 0&&(!v(u)||u>i.max))throw new d(`${String(u)} is not a valid argument to param ${i.index} on ${e}(). Expected value to be equal or less than ${i.max}.`);if(i.integer&&!Number.isSafeInteger(u))throw new d(`${String(u)} is not a valid argument to param ${i.index} on ${e}(). Expected value to be an integer.`)}return r.apply(this,o)};}var h=s=>typeof s=="object"&&s!==null;var R=(s,e)=>{if(s===e)return true;if(!h(s)||!h(e)||Array.isArray(s)!==Array.isArray(e))return false;let t=Object.keys(s),r=Object.keys(e);return t.length!==r.length?false:t.every(o=>Object.prototype.hasOwnProperty.call(e,o)&&R(s[o],e[o]))};var G=(s,e)=>s.length===e.length&&s.every((t,r)=>R(t,e[r]));function B(s){return Object.entries(s)}var m=s=>typeof s=="function";var x,w=class{static validate(e,t,r){for(let[o,n]of B(t))if(!this.validateColumnCondition(e,o,n,r??l(this,x)))return false;return true}static validateColumnCondition(e,t,r,o){if(o.ignoreNullValues&&r==null)return true;let n=e[t];return m(r)?r(n):Array.isArray(r)?Array.isArray(n)?G(n,r):false:h(r)?h(n)?this.validate(n,r):false:n===r}};x=new WeakMap,b(w,x,{ignoreNullValues:false});function L(s){return Object.keys(s).filter(e=>typeof s[e]!="function")}function M(s){let e=1,t=s;t.startsWith("-")&&(e=-1,t=t.slice(1));let r=t;return (o,n)=>{let i=o[r],u=n[r];return i==null&&u==null?0:i==null?1*e:u==null||i<u?-1*e:i>u?1*e:0}}function S(...s){return (e,t)=>{if(s.length===0)return 0;for(let r of s){let o=M(r)(e,t);if(o!==0)return o}return 0}}var a,f,y,c=class c{constructor(e){b(this,a,[]);b(this,f,0);b(this,y,null);p(this,a,[...e]);}static from(e){return new c(e)}select(...e){let t=l(this,a),r=t.length,o=new Array(r);for(let i=0;i<r;i++){let u={};for(let T of e)u[T]=t[i][T];o[i]=u;}let n=new c(o);return this.cloneStateInto(n),n}map(e){let t=l(this,a),r=t.length,o=new Array(r);for(let i=0;i<r;i++)o[i]=e(t[i]);let n=new c(o);return this.cloneStateInto(n),n}where(e){return this.filterRows(e),this}filterWhere(e){return this.filterRows(e,{ignoreNullValues:true}),this}orderBy(...e){return p(this,a,l(this,a).sort(S(...e))),this}skip(e){return p(this,f,e),this}limit(e){return p(this,y,e),this}all(){return this.getLimitedRows()}first(){let e=this.getLimitedRows();return e.length>0?e[0]:null}last(){let e=this.getLimitedRows();return e[e.length-1]??null}count(){return this.getLimitedRows().length}exists(){return this.count()>0}scalar(){let e=this.first(),t=this.getFirstColumn();return e&&t?e[t]:false}column(e){if(e===void 0){let n=this.getFirstColumn();if(!n)return [];e=n;}let t=this.getLimitedRows(),r=t.length;if(r===0)return [];let o=new Array(r);for(let n=0;n<r;n++)o[n]=t[n][e];return o}values(){let e=this.getLimitedRows(),t=e.length,r=new Array(t);for(let o=0;o<t;o++)r[o]=Object.values(e[o]);return r}groupBy(e,t){let r=this.getLimitedRows(),o=new Map,n=m(e);for(let i=0;i<r.length;i++){let u=r[i],T=n?e(u):u[e],K=t?t(u):u;o.has(T)?o.get(T).push(K):o.set(T,[K]);}return o}min(e){let t=this.getLimitedRows(),r=t.length;if(r===0)return null;let o=new Array(r);if(m(e))for(let n=0;n<r;n++)o[n]=e(t[n]);else for(let n=0;n<r;n++)o[n]=t[n][e];return Math.min(...o)}max(e){let t=this.getLimitedRows(),r=t.length;if(r===0)return null;let o=new Array(r);if(m(e))for(let n=0;n<r;n++)o[n]=e(t[n]);else for(let n=0;n<r;n++)o[n]=t[n][e];return Math.max(...o)}sum(e){let t=this.getLimitedRows(),r=t.length;if(r===0)return 0;let o=new Array(r);if(m(e))for(let n=0;n<r;n++)o[n]=e(t[n]);else for(let n=0;n<r;n++)o[n]=t[n][e];return o.reduce((n,i)=>n+i,0)}average(e){let t=this.count();return t===0?null:(m(e)?this.sum(e):this.sum(e))/t}filterRows(e,t){let r=l(this,a),o=[];if(m(e))for(let n=0;n<r.length;n++){let i=r[n];e(i)&&o.push(i);}else for(let n=0;n<r.length;n++){let i=r[n];w.validate(i,e,t)&&o.push(i);}p(this,a,o);}getFirstColumn(){let e=this.first();if(!e)return null;let t=L(e);return t.length>0?t[0]:null}getLimitedRows(){return l(this,a).slice(l(this,f),l(this,f)+(l(this,y)??l(this,a).length))}cloneStateInto(e){p(e,f,l(this,f)),p(e,y,l(this,y));}};a=new WeakMap,f=new WeakMap,y=new WeakMap,O([j,g(0,C),g(0,P(0))],c.prototype,"skip"),O([j,g(0,C),g(0,P(0))],c.prototype,"limit");var A=c;export{A as Query};
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = __getOwnPropDesc(target, key) ;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (decorator(target, key, result) ) || result;
8
+ if (result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
12
+
13
+ // src/utils/functions/type-guards/is-number.ts
14
+ var isNumber = (value) => typeof value === "number";
15
+
16
+ // src/core/errors/invalid-argument-error.ts
17
+ var InvalidArgumentError = class extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = "InvalidArgumentError";
21
+ }
22
+ };
23
+
24
+ // src/core/validation/decorators/number-validaton.ts
25
+ var metadata = /* @__PURE__ */ new WeakMap();
26
+ function getParams(target, propertyKey) {
27
+ let methods = metadata.get(target);
28
+ if (!methods) {
29
+ methods = /* @__PURE__ */ new Map();
30
+ metadata.set(target, methods);
31
+ }
32
+ let params = methods.get(propertyKey);
33
+ if (!params) {
34
+ params = [];
35
+ methods.set(propertyKey, params);
36
+ }
37
+ return params;
38
+ }
39
+ function min(value) {
40
+ return function(target, propertyKey, index) {
41
+ const params = getParams(target, propertyKey);
42
+ const existing = params.find((p) => p.index === index);
43
+ if (existing) {
44
+ existing.min = value;
45
+ } else {
46
+ params.push({ index, min: value });
47
+ }
48
+ };
49
+ }
50
+ function integer(target, propertyKey, index) {
51
+ const params = getParams(target, propertyKey);
52
+ const existing = params.find((p) => p.index === index);
53
+ if (existing) {
54
+ existing.integer = true;
55
+ } else {
56
+ params.push({ index, integer: true });
57
+ }
58
+ }
59
+ function validateNumbers(target, propertyKey, descriptor) {
60
+ const original = descriptor.value;
61
+ descriptor.value = function(...args) {
62
+ const params = metadata.get(target)?.get(propertyKey) ?? [];
63
+ for (const rule of params) {
64
+ const value = args[rule.index];
65
+ if (rule.min !== void 0) {
66
+ if (!isNumber(value) || value < rule.min) {
67
+ throw new InvalidArgumentError(
68
+ `${String(value)} is not a valid argument to param ${rule.index} on ${propertyKey}(). Expected value to be equal or greater than ${rule.min}.`
69
+ );
70
+ }
71
+ }
72
+ if (rule.max !== void 0) {
73
+ if (!isNumber(value) || value > rule.max) {
74
+ throw new InvalidArgumentError(
75
+ `${String(value)} is not a valid argument to param ${rule.index} on ${propertyKey}(). Expected value to be equal or less than ${rule.max}.`
76
+ );
77
+ }
78
+ }
79
+ if (rule.integer) {
80
+ if (!Number.isSafeInteger(value)) {
81
+ throw new InvalidArgumentError(
82
+ `${String(value)} is not a valid argument to param ${rule.index} on ${propertyKey}(). Expected value to be an integer.`
83
+ );
84
+ }
85
+ }
86
+ }
87
+ return original.apply(this, args);
88
+ };
89
+ }
90
+
91
+ // src/utils/functions/type-guards/is-object.ts
92
+ var isObject = (value) => typeof value === "object" && value !== null;
93
+
94
+ // src/utils/functions/generic/deep-equal.ts
95
+ var deepEqual = (a, b) => {
96
+ if (a === b) {
97
+ return true;
98
+ }
99
+ if (!isObject(a) || !isObject(b)) {
100
+ return false;
101
+ }
102
+ if (Array.isArray(a) !== Array.isArray(b)) {
103
+ return false;
104
+ }
105
+ const keysA = Object.keys(a);
106
+ const keysB = Object.keys(b);
107
+ if (keysA.length !== keysB.length) {
108
+ return false;
109
+ }
110
+ return keysA.every(
111
+ (key) => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(
112
+ a[key],
113
+ b[key]
114
+ )
115
+ );
116
+ };
117
+
118
+ // src/utils/functions/generic/compare-arrays.ts
119
+ var compareArrays = (a, b) => a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
120
+
121
+ // src/utils/functions/generic/get-entries.ts
122
+ function getEntries(obj) {
123
+ return Object.entries(obj);
124
+ }
125
+
126
+ // src/utils/functions/type-guards/is-function.ts
127
+ var isFunction = (value) => typeof value === "function";
128
+
129
+ // src/core/validation/query-row-validator.ts
130
+ var QueryRowValidator = class {
131
+ static #defaultOptions = {
132
+ ignoreNullValues: false
133
+ };
134
+ /**
135
+ * Validates all conditions of the row.
136
+ *
137
+ * @returns Validation result.
138
+ */
139
+ static validate(row, condition, options) {
140
+ for (const [column, columnCondition] of getEntries(condition)) {
141
+ const validated = this.validateColumnCondition(
142
+ row,
143
+ column,
144
+ columnCondition,
145
+ options ?? this.#defaultOptions
146
+ );
147
+ if (!validated) {
148
+ return false;
149
+ }
150
+ }
151
+ return true;
152
+ }
153
+ /**
154
+ * Validate a condition to a specific column.
155
+ *
156
+ * @param columnName Column name.
157
+ * @param condition Condition to be validated.
158
+ *
159
+ * @returns Validation result.
160
+ */
161
+ static validateColumnCondition(row, column, condition, options) {
162
+ if (options.ignoreNullValues && (condition === null || condition === void 0)) {
163
+ return true;
164
+ }
165
+ const cellValue = row[column];
166
+ if (isFunction(condition)) {
167
+ return condition(cellValue);
168
+ }
169
+ if (Array.isArray(condition)) {
170
+ return Array.isArray(cellValue) ? compareArrays(cellValue, condition) : false;
171
+ }
172
+ if (isObject(condition)) {
173
+ return isObject(cellValue) ? this.validate(cellValue, condition) : false;
174
+ }
175
+ return cellValue === condition;
176
+ }
177
+ };
178
+
179
+ // src/utils/functions/generic/get-object-property-names.ts
180
+ function getObjectPropertyNames(obj) {
181
+ return Object.keys(obj).filter(
182
+ (key) => typeof obj[key] !== "function"
183
+ );
184
+ }
185
+
186
+ // src/utils/functions/sort/sort-by-property.ts
187
+ function sortByProperty(property) {
188
+ let sortOrder = 1;
189
+ let prop = property;
190
+ if (prop.startsWith("-")) {
191
+ sortOrder = -1;
192
+ prop = prop.slice(1);
193
+ }
194
+ const key = prop;
195
+ return (a, b) => {
196
+ const valueA = a[key];
197
+ const valueB = b[key];
198
+ if (valueA == null && valueB == null) return 0;
199
+ if (valueA == null) return 1 * sortOrder;
200
+ if (valueB == null) return -1 * sortOrder;
201
+ if (valueA < valueB) return -1 * sortOrder;
202
+ if (valueA > valueB) return 1 * sortOrder;
203
+ return 0;
204
+ };
205
+ }
206
+
207
+ // src/utils/functions/sort/sort-by-properties.ts
208
+ function sortByProperties(...props) {
209
+ return (a, b) => {
210
+ if (props.length === 0) {
211
+ return 0;
212
+ }
213
+ for (const prop of props) {
214
+ const result = sortByProperty(prop)(a, b);
215
+ if (result !== 0) {
216
+ return result;
217
+ }
218
+ }
219
+ return 0;
220
+ };
221
+ }
222
+
223
+ // src/query.ts
224
+ var _Query = class _Query {
225
+ /**
226
+ * Rows to be queried.
227
+ */
228
+ #rows = [];
229
+ /**
230
+ * Number of results to skip.
231
+ */
232
+ #startAt = 0;
233
+ /**
234
+ * Limit of results.
235
+ */
236
+ #limit = null;
237
+ /**
238
+ * Initializes the query.
239
+ *
240
+ * @param rows Rows to be queried.
241
+ */
242
+ constructor(rows) {
243
+ this.#rows = [...rows];
244
+ }
245
+ /**
246
+ * Creates a new query based on the given data.
247
+ *
248
+ * @param rows Rows to be queried.
249
+ *
250
+ * @returns Query to the given rows.
251
+ */
252
+ static from(rows) {
253
+ return new _Query(rows);
254
+ }
255
+ /**
256
+ * Defines specific columns to be returned on the final results.
257
+ *
258
+ * @param columns Selected columns.
259
+ *
260
+ * @returns Current query.
261
+ */
262
+ select(...columns) {
263
+ const source = this.#rows;
264
+ const length = source.length;
265
+ const rows = new Array(length);
266
+ for (let i = 0; i < length; i++) {
267
+ const result = {};
268
+ for (const column of columns) {
269
+ result[column] = source[i][column];
270
+ }
271
+ rows[i] = result;
272
+ }
273
+ const query = new _Query(rows);
274
+ this.cloneStateInto(query);
275
+ return query;
276
+ }
277
+ /**
278
+ * Maps each row to a new object and returns a new query.
279
+ *
280
+ * @param callback Function to be called for each row.
281
+ * @returns New query with the mapped rows.
282
+ */
283
+ map(callback) {
284
+ const source = this.#rows;
285
+ const length = source.length;
286
+ const rows = new Array(length);
287
+ for (let i = 0; i < length; i++) {
288
+ rows[i] = callback(source[i]);
289
+ }
290
+ const query = new _Query(rows);
291
+ this.cloneStateInto(query);
292
+ return query;
293
+ }
294
+ distinct(arg) {
295
+ const seen = /* @__PURE__ */ new Set();
296
+ const result = [];
297
+ const rows = this.#rows;
298
+ const isFn = isFunction(arg);
299
+ for (let i = 0; i < rows.length; i++) {
300
+ const row = rows[i];
301
+ const value = isFn ? arg(row) : row[arg];
302
+ if (!seen.has(value)) {
303
+ seen.add(value);
304
+ result.push(row);
305
+ }
306
+ }
307
+ this.#rows = result;
308
+ return this;
309
+ }
310
+ /**
311
+ * Applies conditions to the query.
312
+ *
313
+ * @param condition Filter to be applied to the query.
314
+ *
315
+ * If a callback function is provided, it must return a boolean value.
316
+ *
317
+ * If an object is provided, its properties must be attributes of `T` and their
318
+ * corresponding values must be the expected values for the attributes or a
319
+ * callback functions that return boolean values.
320
+ *
321
+ * @returns Current query.
322
+ */
323
+ where(condition) {
324
+ this.filterRows(condition);
325
+ return this;
326
+ }
327
+ /**
328
+ * Applies a set of conditions to the query ignoring `null` and `undefined`
329
+ * values as conditions.
330
+ *
331
+ * @param condition An object where each property represents an attribute
332
+ * to be validated. The values can be literal or callback functions that
333
+ * return a boolean. If `null` or `undefined` is passed, that condition
334
+ * will be skipped.
335
+ *
336
+ * @returns Current query.
337
+ */
338
+ filterWhere(condition) {
339
+ this.filterRows(condition, { ignoreNullValues: true });
340
+ return this;
341
+ }
342
+ /**
343
+ * Adds ordering to the results.
344
+ *
345
+ * This method should be called after `select()`; otherwise, the ordering will
346
+ * be applied only to the selected columns.
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * // ❌ "age" will not be ordered, because it is not part of the selected columns
351
+ * const query = Query.from(users)
352
+ * .orderBy('-age')
353
+ * .select('name');
354
+ *
355
+ * // ✅ "age" will be ordered
356
+ * const query = Query.from(users)
357
+ * .select('name', 'age')
358
+ * .orderBy('-age');
359
+ * ```
360
+ *
361
+ * @param columns Ascending or descending columns. To mark a field as
362
+ * descending, prefix it with `-`.
363
+ *
364
+ * @returns Current query.
365
+ */
366
+ orderBy(...columns) {
367
+ this.#rows = this.#rows.sort(sortByProperties(...columns));
368
+ return this;
369
+ }
370
+ skip(numberOfRows) {
371
+ this.#startAt = numberOfRows;
372
+ return this;
373
+ }
374
+ limit(limit) {
375
+ this.#limit = limit;
376
+ return this;
377
+ }
378
+ /**
379
+ * Returns all results.
380
+ *
381
+ * @returns All filtered rows.
382
+ */
383
+ all() {
384
+ return this.getLimitedRows();
385
+ }
386
+ /**
387
+ * Returns the first result.
388
+ *
389
+ * @returns The first result.
390
+ */
391
+ first() {
392
+ const rows = this.getLimitedRows();
393
+ return rows.length > 0 ? rows[0] : null;
394
+ }
395
+ /**
396
+ * Returns the last result.
397
+ *
398
+ * @returns The last result.
399
+ */
400
+ last() {
401
+ const rows = this.getLimitedRows();
402
+ return rows[rows.length - 1] ?? null;
403
+ }
404
+ /**
405
+ * Returns the current number of rows.
406
+ *
407
+ * @return Filtered rows count.
408
+ */
409
+ count() {
410
+ return this.getLimitedRows().length;
411
+ }
412
+ /**
413
+ * Checks if there is at least one row compatible with the query.
414
+ *
415
+ * @returns Whether any row exists after filtering.
416
+ */
417
+ exists() {
418
+ return this.count() > 0;
419
+ }
420
+ /**
421
+ * Returns the value of the first (selected) column of the first row.
422
+ *
423
+ * @returns First value or `false`, if none row exists.
424
+ */
425
+ scalar() {
426
+ const firstObject = this.first();
427
+ const firstColumn = this.getFirstColumn();
428
+ return firstObject && firstColumn ? firstObject[firstColumn] : false;
429
+ }
430
+ column(column) {
431
+ const source = this.getLimitedRows();
432
+ const length = source.length;
433
+ if (length === 0) {
434
+ return [];
435
+ }
436
+ if (column === void 0) {
437
+ const firstColumn = this.getFirstColumn();
438
+ if (!firstColumn) {
439
+ return [];
440
+ }
441
+ column = firstColumn;
442
+ }
443
+ const values = new Array(length);
444
+ for (let i = 0; i < length; i++) {
445
+ values[i] = source[i][column];
446
+ }
447
+ return values;
448
+ }
449
+ /**
450
+ * Returns the values of the rows. If there are selected columns, only their
451
+ * values will be returned.
452
+ *
453
+ * @returns Array with the values of all rows.
454
+ */
455
+ values() {
456
+ const source = this.getLimitedRows();
457
+ const length = source.length;
458
+ const rows = new Array(length);
459
+ for (let i = 0; i < length; i++) {
460
+ rows[i] = Object.values(source[i]);
461
+ }
462
+ return rows;
463
+ }
464
+ groupBy(groupArg, mapArg) {
465
+ const rows = this.getLimitedRows();
466
+ const map = /* @__PURE__ */ new Map();
467
+ const hasCallback = isFunction(groupArg);
468
+ for (let i = 0; i < rows.length; i++) {
469
+ const row = rows[i];
470
+ const key = hasCallback ? groupArg(row) : row[groupArg];
471
+ const value = mapArg ? mapArg(row) : row;
472
+ if (map.has(key)) {
473
+ map.get(key).push(value);
474
+ } else {
475
+ map.set(key, [value]);
476
+ }
477
+ }
478
+ return map;
479
+ }
480
+ min(arg) {
481
+ const source = this.getLimitedRows();
482
+ const length = source.length;
483
+ if (length === 0) {
484
+ return null;
485
+ }
486
+ const values = new Array(length);
487
+ if (isFunction(arg)) {
488
+ for (let i = 0; i < length; i++) {
489
+ values[i] = arg(source[i]);
490
+ }
491
+ } else {
492
+ for (let i = 0; i < length; i++) {
493
+ values[i] = source[i][arg];
494
+ }
495
+ }
496
+ return Math.min(...values);
497
+ }
498
+ max(arg) {
499
+ const source = this.getLimitedRows();
500
+ const length = source.length;
501
+ if (length === 0) {
502
+ return null;
503
+ }
504
+ const values = new Array(length);
505
+ if (isFunction(arg)) {
506
+ for (let i = 0; i < length; i++) {
507
+ values[i] = arg(source[i]);
508
+ }
509
+ } else {
510
+ for (let i = 0; i < length; i++) {
511
+ values[i] = source[i][arg];
512
+ }
513
+ }
514
+ return Math.max(...values);
515
+ }
516
+ sum(arg) {
517
+ const source = this.getLimitedRows();
518
+ const length = source.length;
519
+ if (length === 0) {
520
+ return 0;
521
+ }
522
+ const values = new Array(length);
523
+ if (isFunction(arg)) {
524
+ for (let i = 0; i < length; i++) {
525
+ values[i] = arg(source[i]);
526
+ }
527
+ } else {
528
+ for (let i = 0; i < length; i++) {
529
+ values[i] = source[i][arg];
530
+ }
531
+ }
532
+ return values.reduce((total, value) => total + value, 0);
533
+ }
534
+ average(arg) {
535
+ const count = this.count();
536
+ if (count === 0) {
537
+ return null;
538
+ }
539
+ const sum = isFunction(arg) ? this.sum(arg) : this.sum(arg);
540
+ return sum / count;
541
+ }
542
+ /**
543
+ * Filters the rows according to the given conditions.
544
+ *
545
+ * @param condition Object or callback function.
546
+ */
547
+ filterRows(condition, options) {
548
+ const rows = this.#rows;
549
+ const result = [];
550
+ if (isFunction(condition)) {
551
+ for (let i = 0; i < rows.length; i++) {
552
+ const row = rows[i];
553
+ if (condition(row)) {
554
+ result.push(row);
555
+ }
556
+ }
557
+ } else {
558
+ for (let i = 0; i < rows.length; i++) {
559
+ const row = rows[i];
560
+ if (QueryRowValidator.validate(row, condition, options)) {
561
+ result.push(row);
562
+ }
563
+ }
564
+ }
565
+ this.#rows = result;
566
+ }
567
+ /**
568
+ * Returns the first selected column or the first key of some row.
569
+ *
570
+ * @returns The first column or `null`, if none is selected or there is no row.
571
+ */
572
+ getFirstColumn() {
573
+ const firstRow = this.first();
574
+ if (!firstRow) {
575
+ return null;
576
+ }
577
+ const columns = getObjectPropertyNames(firstRow);
578
+ if (columns.length > 0) {
579
+ return columns[0];
580
+ }
581
+ return null;
582
+ }
583
+ /**
584
+ * Returns the rows that should be used in the final results.
585
+ *
586
+ * @returns Rows within the specified limit.
587
+ */
588
+ getLimitedRows() {
589
+ return this.#rows.slice(
590
+ this.#startAt,
591
+ this.#startAt + (this.#limit ?? this.#rows.length)
592
+ );
593
+ }
594
+ /**
595
+ * Copies the state of the current query into the given query.
596
+ *
597
+ * @param query Query to copy the state into.
598
+ */
599
+ cloneStateInto(query) {
600
+ query.#startAt = this.#startAt;
601
+ query.#limit = this.#limit;
602
+ }
603
+ };
604
+ __decorateClass([
605
+ validateNumbers,
606
+ __decorateParam(0, integer),
607
+ __decorateParam(0, min(0))
608
+ ], _Query.prototype, "skip");
609
+ __decorateClass([
610
+ validateNumbers,
611
+ __decorateParam(0, integer),
612
+ __decorateParam(0, min(0))
613
+ ], _Query.prototype, "limit");
614
+ var Query = _Query;
615
+
616
+ export { Query };
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "querier-ts",
3
3
  "type": "module",
4
- "version": "2.4.1",
4
+ "version": "2.5.1",
5
5
  "description": "A lightweight, type-safe in-memory query engine for JavaScript and TypeScript",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/luizfilipezs/query-ts.git"
8
+ "url": "git+https://github.com/luizfilipezs/querier-ts.git"
9
9
  },
10
10
  "keywords": [
11
11
  "query",
@@ -19,9 +19,9 @@
19
19
  },
20
20
  "license": "MIT",
21
21
  "bugs": {
22
- "url": "https://github.com/luizfilipezs/query-ts/issues"
22
+ "url": "https://github.com/luizfilipezs/querier-ts/issues"
23
23
  },
24
- "homepage": "https://github.com/luizfilipezs/query-ts#readme",
24
+ "homepage": "https://github.com/luizfilipezs/querier-ts#readme",
25
25
  "files": [
26
26
  "dist",
27
27
  "README.md",
@@ -61,7 +61,7 @@
61
61
  "devDependencies": {
62
62
  "@eslint/js": "^10.0.1",
63
63
  "@vitest/coverage-v8": "^4.1.2",
64
- "eslint": "^10.1.0",
64
+ "eslint": "^10.2.0",
65
65
  "eslint-config-prettier": "^10.1.8",
66
66
  "eslint-plugin-prettier": "^5.5.5",
67
67
  "globals": "^17.4.0",