@uniqu/url 0.1.1 → 0.1.3
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 +27 -1
- package/dist/builder.cjs +4 -1
- package/dist/builder.mjs +4 -1
- package/dist/index.cjs +9 -0
- package/dist/index.mjs +9 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -152,6 +152,7 @@ Control keywords start with `$` and are separated from filter expressions:
|
|
|
152
152
|
| `$skip` | — | `$skip=40` | `{ $skip: 40 }` |
|
|
153
153
|
| `$count` | — | `$count` | `{ $count: true }` |
|
|
154
154
|
| `$groupBy` | — | `$groupBy=currency,region` | `{ $groupBy: ['currency', 'region'] }` |
|
|
155
|
+
| `$having` | — | `$having=total>1000` | `{ $having: { total: { $gt: 1000 } } }` |
|
|
155
156
|
| `$with` | — | `$with=posts,author` | `{ $with: [{ name: 'posts', filter: {}, controls: {} }, ...] }` |
|
|
156
157
|
| `$<custom>` | — | `$search=term` | `{ $search: 'term' }` |
|
|
157
158
|
|
|
@@ -220,6 +221,31 @@ Aggregates and `$groupBy` work inside `$with` sub-queries too:
|
|
|
220
221
|
$with=orders($select=sum(total):revenue&$groupBy=status)
|
|
221
222
|
```
|
|
222
223
|
|
|
224
|
+
### Post-Aggregation Filter (`$having`)
|
|
225
|
+
|
|
226
|
+
`$having` filters groups after aggregation (SQL `HAVING`). It accepts the same filter expression syntax as the main query:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
$having=total>1000 → { $having: { total: { $gt: 1000 } } }
|
|
230
|
+
$having=total>1000&$having=count_star>=5 → { $having: { $and: [...] } } (AND-merged)
|
|
231
|
+
$having=total>1000^avg_price<50 → { $having: { $or: [...] } } (OR via ^)
|
|
232
|
+
$having=!(total<100) → { $having: { $not: {...} } } (NOT via !())
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
For multi-condition `$having` with AND, either use multiple `$having` params (AND-merged automatically) or wrap in parentheses:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
$having=(total>1000&count_star>=5)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`$having` works inside `$with` sub-queries:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
$with=orders($select=sum(total):revenue&$groupBy=status&$having=revenue>500)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Insights track `$having` fields with the `'$having'` op.
|
|
248
|
+
|
|
223
249
|
### Relation Loading (`$with`)
|
|
224
250
|
|
|
225
251
|
`$with` declares which relations to populate alongside the primary query. Relations are comma-separated:
|
|
@@ -432,7 +458,7 @@ Accepts a `Uniquery` object and returns a URL query string (without leading `?`)
|
|
|
432
458
|
|
|
433
459
|
All features are supported:
|
|
434
460
|
- Filter expressions (comparisons, `$and`/`$or`/`$not`, `$in`/`$nin`, `$exists`, `$regex`)
|
|
435
|
-
- Controls (`$select`, `$sort`, `$limit`, `$skip`, `$count`, `$groupBy`, `$with`)
|
|
461
|
+
- Controls (`$select`, `$sort`, `$limit`, `$skip`, `$count`, `$groupBy`, `$having`, `$with`)
|
|
436
462
|
- Aggregates in `$select` (`sum(amount):total`)
|
|
437
463
|
- Nested `$with` sub-queries
|
|
438
464
|
- Pass-through custom `$`-prefixed controls
|
package/dist/builder.cjs
CHANGED
|
@@ -36,7 +36,10 @@ function serializeFilter(expr) {
|
|
|
36
36
|
return inner ? `!(${inner})` : "";
|
|
37
37
|
}
|
|
38
38
|
let result = "";
|
|
39
|
-
for (const [field, value] of Object.entries(expr)) if (
|
|
39
|
+
for (const [field, value] of Object.entries(expr)) if (value instanceof RegExp) {
|
|
40
|
+
const part = `${field}~=/${value.source}/${value.flags}`;
|
|
41
|
+
result = result ? result + "&" + part : part;
|
|
42
|
+
} else if ((0, _uniqu_core.isPrimitive)(value)) {
|
|
40
43
|
const part = `${field}=${serializeValue(value)}`;
|
|
41
44
|
result = result ? result + "&" + part : part;
|
|
42
45
|
} else for (const [op, opValue] of Object.entries(value)) {
|
package/dist/builder.mjs
CHANGED
|
@@ -35,7 +35,10 @@ function serializeFilter(expr) {
|
|
|
35
35
|
return inner ? `!(${inner})` : "";
|
|
36
36
|
}
|
|
37
37
|
let result = "";
|
|
38
|
-
for (const [field, value] of Object.entries(expr)) if (
|
|
38
|
+
for (const [field, value] of Object.entries(expr)) if (value instanceof RegExp) {
|
|
39
|
+
const part = `${field}~=/${value.source}/${value.flags}`;
|
|
40
|
+
result = result ? result + "&" + part : part;
|
|
41
|
+
} else if (isPrimitive(value)) {
|
|
39
42
|
const part = `${field}=${serializeValue(value)}`;
|
|
40
43
|
result = result ? result + "&" + part : part;
|
|
41
44
|
} else for (const [op, opValue] of Object.entries(value)) {
|
package/dist/index.cjs
CHANGED
|
@@ -412,6 +412,7 @@ function buildExists(fields, positive) {
|
|
|
412
412
|
function handleControls(parts) {
|
|
413
413
|
const controls = {};
|
|
414
414
|
const controlInsights = [];
|
|
415
|
+
const aliasToField = /* @__PURE__ */ new Map();
|
|
415
416
|
for (const raw of parts) {
|
|
416
417
|
const eqIdx = raw.indexOf("=");
|
|
417
418
|
const key = eqIdx === -1 ? raw : raw.slice(0, eqIdx);
|
|
@@ -464,6 +465,7 @@ function handleControls(parts) {
|
|
|
464
465
|
$field: field,
|
|
465
466
|
$as: alias
|
|
466
467
|
});
|
|
468
|
+
aliasToField.set(alias, field);
|
|
467
469
|
controlInsights.push([field, fn]);
|
|
468
470
|
}
|
|
469
471
|
controls.$select = arr;
|
|
@@ -531,6 +533,13 @@ function handleControls(parts) {
|
|
|
531
533
|
default: controls[key] = value;
|
|
532
534
|
}
|
|
533
535
|
}
|
|
536
|
+
if (aliasToField.size) for (let i = 0; i < controlInsights.length; i++) {
|
|
537
|
+
const [field, op] = controlInsights[i];
|
|
538
|
+
if (op === "$order") {
|
|
539
|
+
const real = aliasToField.get(field);
|
|
540
|
+
if (real) controlInsights[i] = [real, op];
|
|
541
|
+
}
|
|
542
|
+
}
|
|
534
543
|
return {
|
|
535
544
|
controls,
|
|
536
545
|
controlInsights
|
package/dist/index.mjs
CHANGED
|
@@ -411,6 +411,7 @@ function buildExists(fields, positive) {
|
|
|
411
411
|
function handleControls(parts) {
|
|
412
412
|
const controls = {};
|
|
413
413
|
const controlInsights = [];
|
|
414
|
+
const aliasToField = /* @__PURE__ */ new Map();
|
|
414
415
|
for (const raw of parts) {
|
|
415
416
|
const eqIdx = raw.indexOf("=");
|
|
416
417
|
const key = eqIdx === -1 ? raw : raw.slice(0, eqIdx);
|
|
@@ -463,6 +464,7 @@ function handleControls(parts) {
|
|
|
463
464
|
$field: field,
|
|
464
465
|
$as: alias
|
|
465
466
|
});
|
|
467
|
+
aliasToField.set(alias, field);
|
|
466
468
|
controlInsights.push([field, fn]);
|
|
467
469
|
}
|
|
468
470
|
controls.$select = arr;
|
|
@@ -530,6 +532,13 @@ function handleControls(parts) {
|
|
|
530
532
|
default: controls[key] = value;
|
|
531
533
|
}
|
|
532
534
|
}
|
|
535
|
+
if (aliasToField.size) for (let i = 0; i < controlInsights.length; i++) {
|
|
536
|
+
const [field, op] = controlInsights[i];
|
|
537
|
+
if (op === "$order") {
|
|
538
|
+
const real = aliasToField.get(field);
|
|
539
|
+
if (real) controlInsights[i] = [real, op];
|
|
540
|
+
}
|
|
541
|
+
}
|
|
533
542
|
return {
|
|
534
543
|
controls,
|
|
535
544
|
controlInsights
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniqu/url",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "URL query string parser producing the Uniqu canonical query format",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Artem Maltsev",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dist"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@uniqu/core": "^0.1.
|
|
48
|
+
"@uniqu/core": "^0.1.3"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"pub": "pnpm publish --access public",
|