querier-ts 2.5.4 → 2.6.0
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 +14 -0
- package/README.md +86 -10
- package/dist/cjs/index.cjs +56 -0
- package/dist/esm/index.d.ts +50 -0
- package/dist/esm/index.js +56 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.6.0
|
|
4
|
+
|
|
5
|
+
### Summary
|
|
6
|
+
|
|
7
|
+
- [ ] Bug fixes
|
|
8
|
+
- [ ] Code refactoring
|
|
9
|
+
- [x] New features
|
|
10
|
+
- [ ] Build and packaging updates
|
|
11
|
+
- [ ] Breaking changes
|
|
12
|
+
|
|
13
|
+
### New features
|
|
14
|
+
|
|
15
|
+
- Added `limitPerGroup()` and `top()` methods to `Query`.
|
|
16
|
+
|
|
3
17
|
## v2.5.4
|
|
4
18
|
|
|
5
19
|
### Summary
|
package/README.md
CHANGED
|
@@ -165,7 +165,28 @@ const lastId = Query.from(users)
|
|
|
165
165
|
|
|
166
166
|
---
|
|
167
167
|
|
|
168
|
-
###
|
|
168
|
+
### Paginating results
|
|
169
|
+
|
|
170
|
+
#### `skip(numberOfRows)`
|
|
171
|
+
|
|
172
|
+
Skips the first results.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
.skip(5)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
const secondId = Query.from(users)
|
|
182
|
+
.select('id')
|
|
183
|
+
.skip(1)
|
|
184
|
+
.scalar();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
> Passing a non-integer or a negative number will throw an `InvalidArgumentError`.
|
|
188
|
+
|
|
189
|
+
---
|
|
169
190
|
|
|
170
191
|
#### `limit(limit)`
|
|
171
192
|
|
|
@@ -179,24 +200,79 @@ Limits the number of results returned.
|
|
|
179
200
|
|
|
180
201
|
---
|
|
181
202
|
|
|
182
|
-
#### `
|
|
203
|
+
#### `limitPerGroup(key | fn, limit)`
|
|
183
204
|
|
|
184
|
-
|
|
205
|
+
Limits the number of rows per group.
|
|
206
|
+
|
|
207
|
+
- `key`: The property name to group by.
|
|
208
|
+
- `fn`: A function that maps each row to a grouping value.
|
|
209
|
+
- `limit`: The maximum number of rows to keep per group.
|
|
210
|
+
|
|
211
|
+
> ⚠️ The rows kept depend on the current ordering of the query.
|
|
212
|
+
> Use `orderBy()` beforehand to control which rows are selected.
|
|
213
|
+
|
|
214
|
+
Examples:
|
|
185
215
|
|
|
186
216
|
```ts
|
|
187
|
-
|
|
217
|
+
// with key
|
|
218
|
+
const countries = Query.from(addresses)
|
|
219
|
+
.orderBy('-createdAt')
|
|
220
|
+
.limitPerGroup('country', 2)
|
|
221
|
+
.column('country'); // ['Argentina', 'Argentina', 'Brazil', 'Brazil', 'Chile', 'Chile']
|
|
222
|
+
|
|
223
|
+
// with callback
|
|
224
|
+
const countries = Query.from(addresses)
|
|
225
|
+
.orderBy('-createdAt')
|
|
226
|
+
.limitPerGroup((row) => row.country, 2)
|
|
227
|
+
.column('country');
|
|
188
228
|
```
|
|
189
229
|
|
|
190
|
-
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
#### `top(limit, options?)`
|
|
233
|
+
|
|
234
|
+
Keeps the top N rows, optionally partitioned by a key or callback.
|
|
235
|
+
|
|
236
|
+
- `limit`: The maximum number of rows to keep.
|
|
237
|
+
- `options.partitionBy`: A property name or function to define groups.
|
|
238
|
+
- `options.orderBy`: A column or list of columns to define ordering.
|
|
239
|
+
|
|
240
|
+
If `partitionBy` is provided, the limit is applied per group.
|
|
241
|
+
|
|
242
|
+
The rows kept depend on the ordering. Use `orderBy` (either here or before)
|
|
243
|
+
to control which rows are selected.
|
|
244
|
+
|
|
245
|
+
Examples:
|
|
191
246
|
|
|
192
247
|
```ts
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
.
|
|
196
|
-
.
|
|
248
|
+
// top N globally
|
|
249
|
+
const ids = Query.from(addresses)
|
|
250
|
+
.orderBy('-createdAt')
|
|
251
|
+
.top(3)
|
|
252
|
+
.column('id'); // [6, 5, 4]
|
|
253
|
+
|
|
254
|
+
// top N per group (key)
|
|
255
|
+
const countries = Query.from(addresses)
|
|
256
|
+
.top(2, {
|
|
257
|
+
partitionBy: 'country',
|
|
258
|
+
orderBy: '-createdAt',
|
|
259
|
+
})
|
|
260
|
+
.column('country'); // ['Argentina', 'Argentina', 'Brazil', 'Brazil', 'Chile', 'Chile']
|
|
261
|
+
|
|
262
|
+
// top N per group (callback)
|
|
263
|
+
const countries = Query.from(addresses)
|
|
264
|
+
.top(1, {
|
|
265
|
+
partitionBy: (row) => row.country,
|
|
266
|
+
orderBy: '-createdAt',
|
|
267
|
+
})
|
|
268
|
+
.column('country'); // ['Argentina', 'Brazil', 'Chile']
|
|
269
|
+
|
|
270
|
+
// without orderBy (keeps original order)
|
|
271
|
+
const countries = Query.from(addresses)
|
|
272
|
+
.top(1, { partitionBy: 'country' })
|
|
273
|
+
.column('country'); // ['Brazil', 'Chile', 'Argentina']
|
|
197
274
|
```
|
|
198
275
|
|
|
199
|
-
> Passing a non-integer or a negative number will throw an `InvalidArgumentError`.
|
|
200
276
|
|
|
201
277
|
---
|
|
202
278
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -375,6 +375,52 @@ var _Query = class _Query {
|
|
|
375
375
|
this.#limit = limit;
|
|
376
376
|
return this;
|
|
377
377
|
}
|
|
378
|
+
limitPerGroup(arg, limit) {
|
|
379
|
+
const counts = /* @__PURE__ */ new Map();
|
|
380
|
+
const result = [];
|
|
381
|
+
const rows = this.#rows;
|
|
382
|
+
const isFn = isFunction(arg);
|
|
383
|
+
for (let i = 0; i < rows.length; i++) {
|
|
384
|
+
const row = rows[i];
|
|
385
|
+
const group = isFn ? arg(row) : row[arg];
|
|
386
|
+
const count = counts.get(group) ?? 0;
|
|
387
|
+
if (count < limit) {
|
|
388
|
+
result.push(row);
|
|
389
|
+
counts.set(group, count + 1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
this.#rows = result;
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
top(limit, options) {
|
|
396
|
+
const rows = this.getLimitedRows();
|
|
397
|
+
if (rows.length === 0) {
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
const { partitionBy, orderBy } = options ?? {};
|
|
401
|
+
if (orderBy) {
|
|
402
|
+
const columns = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
403
|
+
this.#rows = rows.sort(sortByProperties(...columns));
|
|
404
|
+
}
|
|
405
|
+
if (!partitionBy) {
|
|
406
|
+
this.#rows = rows.slice(0, limit);
|
|
407
|
+
return this;
|
|
408
|
+
}
|
|
409
|
+
const isFn = isFunction(partitionBy);
|
|
410
|
+
const counts = /* @__PURE__ */ new Map();
|
|
411
|
+
const result = [];
|
|
412
|
+
for (let i = 0; i < rows.length; i++) {
|
|
413
|
+
const row = rows[i];
|
|
414
|
+
const group = isFn ? partitionBy(row) : row[partitionBy];
|
|
415
|
+
const count = counts.get(group) ?? 0;
|
|
416
|
+
if (count < limit) {
|
|
417
|
+
result.push(row);
|
|
418
|
+
counts.set(group, count + 1);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
this.#rows = result;
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
378
424
|
/**
|
|
379
425
|
* Returns all results.
|
|
380
426
|
*
|
|
@@ -616,6 +662,16 @@ __decorateClass([
|
|
|
616
662
|
__decorateParam(0, integer),
|
|
617
663
|
__decorateParam(0, min(0))
|
|
618
664
|
], _Query.prototype, "limit");
|
|
665
|
+
__decorateClass([
|
|
666
|
+
validateNumbers,
|
|
667
|
+
__decorateParam(1, integer),
|
|
668
|
+
__decorateParam(1, min(0))
|
|
669
|
+
], _Query.prototype, "limitPerGroup");
|
|
670
|
+
__decorateClass([
|
|
671
|
+
validateNumbers,
|
|
672
|
+
__decorateParam(0, integer),
|
|
673
|
+
__decorateParam(0, min(0))
|
|
674
|
+
], _Query.prototype, "top");
|
|
619
675
|
var Query = _Query;
|
|
620
676
|
|
|
621
677
|
exports.Query = Query;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -197,6 +197,56 @@ declare class Query<T extends object> {
|
|
|
197
197
|
* @throws {InvalidArgumentError} If the given limit is less than 0.
|
|
198
198
|
*/
|
|
199
199
|
limit(limit: number): this;
|
|
200
|
+
/**
|
|
201
|
+
* Limits the number of rows per group.
|
|
202
|
+
*
|
|
203
|
+
* @param key Key to group by.
|
|
204
|
+
* @param limit Maximum number of rows per group.
|
|
205
|
+
*
|
|
206
|
+
* @returns Current query.
|
|
207
|
+
*/
|
|
208
|
+
limitPerGroup<K extends keyof T>(key: K, limit: number): this;
|
|
209
|
+
/**
|
|
210
|
+
* Limits the number of rows per group using a callback.
|
|
211
|
+
*
|
|
212
|
+
* @param fn Function to define the group key.
|
|
213
|
+
* @param limit Maximum number of rows per group.
|
|
214
|
+
*
|
|
215
|
+
* @returns Current query.
|
|
216
|
+
*/
|
|
217
|
+
limitPerGroup<TValue>(fn: (row: T) => TValue, limit: number): this;
|
|
218
|
+
/**
|
|
219
|
+
* Keep the top N rows.
|
|
220
|
+
*
|
|
221
|
+
* @param limit Maximum number of rows per group.
|
|
222
|
+
*
|
|
223
|
+
* @returns Current query.
|
|
224
|
+
*/
|
|
225
|
+
top(limit: number): this;
|
|
226
|
+
/**
|
|
227
|
+
* Keep the top N rows, optionally partitioned by a key or callback.
|
|
228
|
+
*
|
|
229
|
+
* @param limit Maximum number of rows per group (or globally if no partition is provided).
|
|
230
|
+
* @param options Options to define partitioning and ordering.
|
|
231
|
+
*
|
|
232
|
+
* @returns Current query.
|
|
233
|
+
*/
|
|
234
|
+
top<K extends keyof T>(limit: number, options: {
|
|
235
|
+
partitionBy: K;
|
|
236
|
+
orderBy?: OrderingColumn<T> | OrderingColumn<T>[];
|
|
237
|
+
}): this;
|
|
238
|
+
/**
|
|
239
|
+
* Keep the top N rows, optionally partitioned by a key or callback.
|
|
240
|
+
*
|
|
241
|
+
* @param limit Maximum number of rows per group (or globally if no partition is provided).
|
|
242
|
+
* @param options Options to define partitioning and ordering.
|
|
243
|
+
*
|
|
244
|
+
* @returns Current query.
|
|
245
|
+
*/
|
|
246
|
+
top<TValue>(limit: number, options: {
|
|
247
|
+
partitionBy: (row: T) => TValue;
|
|
248
|
+
orderBy?: OrderingColumn<T> | OrderingColumn<T>[];
|
|
249
|
+
}): this;
|
|
200
250
|
/**
|
|
201
251
|
* Returns all results.
|
|
202
252
|
*
|
package/dist/esm/index.js
CHANGED
|
@@ -373,6 +373,52 @@ var _Query = class _Query {
|
|
|
373
373
|
this.#limit = limit;
|
|
374
374
|
return this;
|
|
375
375
|
}
|
|
376
|
+
limitPerGroup(arg, limit) {
|
|
377
|
+
const counts = /* @__PURE__ */ new Map();
|
|
378
|
+
const result = [];
|
|
379
|
+
const rows = this.#rows;
|
|
380
|
+
const isFn = isFunction(arg);
|
|
381
|
+
for (let i = 0; i < rows.length; i++) {
|
|
382
|
+
const row = rows[i];
|
|
383
|
+
const group = isFn ? arg(row) : row[arg];
|
|
384
|
+
const count = counts.get(group) ?? 0;
|
|
385
|
+
if (count < limit) {
|
|
386
|
+
result.push(row);
|
|
387
|
+
counts.set(group, count + 1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
this.#rows = result;
|
|
391
|
+
return this;
|
|
392
|
+
}
|
|
393
|
+
top(limit, options) {
|
|
394
|
+
const rows = this.getLimitedRows();
|
|
395
|
+
if (rows.length === 0) {
|
|
396
|
+
return this;
|
|
397
|
+
}
|
|
398
|
+
const { partitionBy, orderBy } = options ?? {};
|
|
399
|
+
if (orderBy) {
|
|
400
|
+
const columns = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
401
|
+
this.#rows = rows.sort(sortByProperties(...columns));
|
|
402
|
+
}
|
|
403
|
+
if (!partitionBy) {
|
|
404
|
+
this.#rows = rows.slice(0, limit);
|
|
405
|
+
return this;
|
|
406
|
+
}
|
|
407
|
+
const isFn = isFunction(partitionBy);
|
|
408
|
+
const counts = /* @__PURE__ */ new Map();
|
|
409
|
+
const result = [];
|
|
410
|
+
for (let i = 0; i < rows.length; i++) {
|
|
411
|
+
const row = rows[i];
|
|
412
|
+
const group = isFn ? partitionBy(row) : row[partitionBy];
|
|
413
|
+
const count = counts.get(group) ?? 0;
|
|
414
|
+
if (count < limit) {
|
|
415
|
+
result.push(row);
|
|
416
|
+
counts.set(group, count + 1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
this.#rows = result;
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
376
422
|
/**
|
|
377
423
|
* Returns all results.
|
|
378
424
|
*
|
|
@@ -614,6 +660,16 @@ __decorateClass([
|
|
|
614
660
|
__decorateParam(0, integer),
|
|
615
661
|
__decorateParam(0, min(0))
|
|
616
662
|
], _Query.prototype, "limit");
|
|
663
|
+
__decorateClass([
|
|
664
|
+
validateNumbers,
|
|
665
|
+
__decorateParam(1, integer),
|
|
666
|
+
__decorateParam(1, min(0))
|
|
667
|
+
], _Query.prototype, "limitPerGroup");
|
|
668
|
+
__decorateClass([
|
|
669
|
+
validateNumbers,
|
|
670
|
+
__decorateParam(0, integer),
|
|
671
|
+
__decorateParam(0, min(0))
|
|
672
|
+
], _Query.prototype, "top");
|
|
617
673
|
var Query = _Query;
|
|
618
674
|
|
|
619
675
|
export { Query };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querier-ts",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "A lightweight, type-safe in-memory query engine for JavaScript and TypeScript",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@eslint/js": "^10.0.1",
|
|
63
|
-
"@vitest/coverage-v8": "^4.1.
|
|
63
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
64
64
|
"eslint": "^10.2.0",
|
|
65
65
|
"eslint-config-prettier": "^10.1.8",
|
|
66
66
|
"eslint-plugin-prettier": "^5.5.5",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
73
73
|
"tsup": "^8.5.1",
|
|
74
74
|
"typescript": "~5.9.3",
|
|
75
|
-
"typescript-eslint": "^8.58.
|
|
76
|
-
"vitest": "^4.1.
|
|
75
|
+
"typescript-eslint": "^8.58.1",
|
|
76
|
+
"vitest": "^4.1.4"
|
|
77
77
|
},
|
|
78
78
|
"packageManager": "pnpm@10.33.0"
|
|
79
79
|
}
|