@atscript/db-mongo 0.1.128 → 0.1.129

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/dist/agg.cjs CHANGED
@@ -60,17 +60,28 @@ function buildPrefix(query) {
60
60
  };
61
61
  }
62
62
  /**
63
- * Builds a full MongoDB aggregation pipeline for GROUP BY queries.
63
+ * The stages every grouped query shares: `$match(filter)` → `$group`
64
+ * (dimensions + accumulators) → `$project` (flatten `_id`, keep aliases) →
65
+ * `$match($having)`. The row pipeline appends sort/skip/limit, the count
66
+ * pipeline appends `$count`, so both see exactly the same group set.
64
67
  *
65
- * Pipeline: $match → $group → $project → $match(having) → $sort → $skip → $limit
68
+ * With `accumulators: false` only the `$group._id` dimensions are emitted
69
+ * (no accumulators, no `$project`, no `$having`) — the cheapest shape for a
70
+ * plain group count, where no alias has to be resolvable.
66
71
  */
67
- function buildAggregatePipeline(query) {
72
+ function buildGroupedStages(query, { accumulators }) {
68
73
  const { pipeline, groupId, groupKeys, controls } = buildPrefix(query);
69
74
  const groupStage = { _id: groupId };
75
+ if (!accumulators) {
76
+ pipeline.push({ $group: groupStage });
77
+ return {
78
+ pipeline,
79
+ controls
80
+ };
81
+ }
70
82
  const project = { _id: 0 };
71
- const aggregates = controls.$select?.aggregates;
72
83
  for (const [field, idKey] of groupKeys) project[field] = `$_id.${idKey}`;
73
- if (aggregates) for (const expr of aggregates) {
84
+ for (const expr of controls.$select?.aggregates ?? []) {
74
85
  const alias = (0, _atscript_db_agg.resolveAlias)(expr);
75
86
  groupStage[alias] = toAccumulator(expr);
76
87
  project[alias] = 1;
@@ -78,25 +89,34 @@ function buildAggregatePipeline(query) {
78
89
  pipeline.push({ $group: groupStage });
79
90
  pipeline.push({ $project: project });
80
91
  if (controls.$having) pipeline.push({ $match: require_mongo_filter.buildMongoFilter(controls.$having) });
92
+ return {
93
+ pipeline,
94
+ controls
95
+ };
96
+ }
97
+ /**
98
+ * Builds a full MongoDB aggregation pipeline for GROUP BY queries.
99
+ *
100
+ * Pipeline: $match → $group → $project → $match(having) → $sort → $skip → $limit
101
+ */
102
+ function buildAggregatePipeline(query) {
103
+ const { pipeline, controls } = buildGroupedStages(query, { accumulators: true });
81
104
  if (controls.$sort) pipeline.push({ $sort: controls.$sort });
82
105
  if (controls.$skip) pipeline.push({ $skip: controls.$skip });
83
106
  if (controls.$limit) pipeline.push({ $limit: controls.$limit });
84
107
  return pipeline;
85
108
  }
86
109
  /**
87
- * Builds a count-only pipeline: returns the number of distinct groups.
110
+ * Builds a count-only pipeline: the number of groups that survive `$having`
111
+ * (all groups when there is none). With `$having` it runs the same grouped
112
+ * stages as the row pipeline — the accumulators must run so an alias
113
+ * `$having` has a value to match; without it only the `$group._id`
114
+ * dimensions are needed.
88
115
  *
89
- * Pipeline: $match → $group (just _id) → $project → $match(having) → $count
116
+ * Pipeline: $match → $group → [$project → $match(having)] → $count
90
117
  */
91
118
  function buildCountPipeline(query) {
92
- const { pipeline, groupId, groupKeys, controls } = buildPrefix(query);
93
- pipeline.push({ $group: { _id: groupId } });
94
- if (controls.$having) {
95
- const project = { _id: 0 };
96
- for (const [field, idKey] of groupKeys) project[field] = `$_id.${idKey}`;
97
- pipeline.push({ $project: project });
98
- pipeline.push({ $match: require_mongo_filter.buildMongoFilter(controls.$having) });
99
- }
119
+ const { pipeline } = buildGroupedStages(query, { accumulators: Boolean(query.controls?.$having) });
100
120
  pipeline.push({ $count: "count" });
101
121
  return pipeline;
102
122
  }
package/dist/agg.d.cts CHANGED
@@ -9,9 +9,13 @@ import { Document } from "mongodb";
9
9
  */
10
10
  declare function buildAggregatePipeline(query: DbQuery): Document[];
11
11
  /**
12
- * Builds a count-only pipeline: returns the number of distinct groups.
12
+ * Builds a count-only pipeline: the number of groups that survive `$having`
13
+ * (all groups when there is none). With `$having` it runs the same grouped
14
+ * stages as the row pipeline — the accumulators must run so an alias
15
+ * `$having` has a value to match; without it only the `$group._id`
16
+ * dimensions are needed.
13
17
  *
14
- * Pipeline: $match → $group (just _id) → $project → $match(having) → $count
18
+ * Pipeline: $match → $group → [$project → $match(having)] → $count
15
19
  */
16
20
  declare function buildCountPipeline(query: DbQuery): Document[];
17
21
  //#endregion
package/dist/agg.d.mts CHANGED
@@ -9,9 +9,13 @@ import { Document } from "mongodb";
9
9
  */
10
10
  declare function buildAggregatePipeline(query: DbQuery): Document[];
11
11
  /**
12
- * Builds a count-only pipeline: returns the number of distinct groups.
12
+ * Builds a count-only pipeline: the number of groups that survive `$having`
13
+ * (all groups when there is none). With `$having` it runs the same grouped
14
+ * stages as the row pipeline — the accumulators must run so an alias
15
+ * `$having` has a value to match; without it only the `$group._id`
16
+ * dimensions are needed.
13
17
  *
14
- * Pipeline: $match → $group (just _id) → $project → $match(having) → $count
18
+ * Pipeline: $match → $group → [$project → $match(having)] → $count
15
19
  */
16
20
  declare function buildCountPipeline(query: DbQuery): Document[];
17
21
  //#endregion
package/dist/agg.mjs CHANGED
@@ -59,17 +59,28 @@ function buildPrefix(query) {
59
59
  };
60
60
  }
61
61
  /**
62
- * Builds a full MongoDB aggregation pipeline for GROUP BY queries.
62
+ * The stages every grouped query shares: `$match(filter)` → `$group`
63
+ * (dimensions + accumulators) → `$project` (flatten `_id`, keep aliases) →
64
+ * `$match($having)`. The row pipeline appends sort/skip/limit, the count
65
+ * pipeline appends `$count`, so both see exactly the same group set.
63
66
  *
64
- * Pipeline: $match → $group → $project → $match(having) → $sort → $skip → $limit
67
+ * With `accumulators: false` only the `$group._id` dimensions are emitted
68
+ * (no accumulators, no `$project`, no `$having`) — the cheapest shape for a
69
+ * plain group count, where no alias has to be resolvable.
65
70
  */
66
- function buildAggregatePipeline(query) {
71
+ function buildGroupedStages(query, { accumulators }) {
67
72
  const { pipeline, groupId, groupKeys, controls } = buildPrefix(query);
68
73
  const groupStage = { _id: groupId };
74
+ if (!accumulators) {
75
+ pipeline.push({ $group: groupStage });
76
+ return {
77
+ pipeline,
78
+ controls
79
+ };
80
+ }
69
81
  const project = { _id: 0 };
70
- const aggregates = controls.$select?.aggregates;
71
82
  for (const [field, idKey] of groupKeys) project[field] = `$_id.${idKey}`;
72
- if (aggregates) for (const expr of aggregates) {
83
+ for (const expr of controls.$select?.aggregates ?? []) {
73
84
  const alias = resolveAlias(expr);
74
85
  groupStage[alias] = toAccumulator(expr);
75
86
  project[alias] = 1;
@@ -77,25 +88,34 @@ function buildAggregatePipeline(query) {
77
88
  pipeline.push({ $group: groupStage });
78
89
  pipeline.push({ $project: project });
79
90
  if (controls.$having) pipeline.push({ $match: buildMongoFilter(controls.$having) });
91
+ return {
92
+ pipeline,
93
+ controls
94
+ };
95
+ }
96
+ /**
97
+ * Builds a full MongoDB aggregation pipeline for GROUP BY queries.
98
+ *
99
+ * Pipeline: $match → $group → $project → $match(having) → $sort → $skip → $limit
100
+ */
101
+ function buildAggregatePipeline(query) {
102
+ const { pipeline, controls } = buildGroupedStages(query, { accumulators: true });
80
103
  if (controls.$sort) pipeline.push({ $sort: controls.$sort });
81
104
  if (controls.$skip) pipeline.push({ $skip: controls.$skip });
82
105
  if (controls.$limit) pipeline.push({ $limit: controls.$limit });
83
106
  return pipeline;
84
107
  }
85
108
  /**
86
- * Builds a count-only pipeline: returns the number of distinct groups.
109
+ * Builds a count-only pipeline: the number of groups that survive `$having`
110
+ * (all groups when there is none). With `$having` it runs the same grouped
111
+ * stages as the row pipeline — the accumulators must run so an alias
112
+ * `$having` has a value to match; without it only the `$group._id`
113
+ * dimensions are needed.
87
114
  *
88
- * Pipeline: $match → $group (just _id) → $project → $match(having) → $count
115
+ * Pipeline: $match → $group → [$project → $match(having)] → $count
89
116
  */
90
117
  function buildCountPipeline(query) {
91
- const { pipeline, groupId, groupKeys, controls } = buildPrefix(query);
92
- pipeline.push({ $group: { _id: groupId } });
93
- if (controls.$having) {
94
- const project = { _id: 0 };
95
- for (const [field, idKey] of groupKeys) project[field] = `$_id.${idKey}`;
96
- pipeline.push({ $project: project });
97
- pipeline.push({ $match: buildMongoFilter(controls.$having) });
98
- }
118
+ const { pipeline } = buildGroupedStages(query, { accumulators: Boolean(query.controls?.$having) });
99
119
  pipeline.push({ $count: "count" });
100
120
  return pipeline;
101
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-mongo",
3
- "version": "0.1.128",
3
+ "version": "0.1.129",
4
4
  "description": "Mongodb plugin for atscript.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -56,7 +56,7 @@
56
56
  "@atscript/core": "^0.1.92",
57
57
  "@atscript/typescript": "^0.1.92",
58
58
  "mongodb": "^6.17.0",
59
- "@atscript/db": "^0.1.128"
59
+ "@atscript/db": "^0.1.129"
60
60
  },
61
61
  "scripts": {
62
62
  "postinstall": "asc -f dts",