@uniqu/url 0.1.1 → 0.1.2

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
@@ -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/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.1",
3
+ "version": "0.1.2",
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.1"
48
+ "@uniqu/core": "^0.1.2"
49
49
  },
50
50
  "scripts": {
51
51
  "pub": "pnpm publish --access public",