@prisma/orm-family-sql 8.0.0-rc.4-dev.13 → 8.0.0-rc.4-dev.15

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.
@@ -910,6 +910,104 @@ function buildAggregateInput(contract, namespaceId, tableName, state, modelName,
910
910
  if (state.offset !== void 0) inner = inner.withOffset(state.offset);
911
911
  return { source: DerivedTableSource.as(tableName, inner) };
912
912
  }
913
+ function emptyState() {
914
+ return {
915
+ filters: [],
916
+ includes: [],
917
+ orderBy: void 0,
918
+ cursor: void 0,
919
+ distinct: void 0,
920
+ distinctOn: void 0,
921
+ selectedFields: void 0,
922
+ limit: void 0,
923
+ offset: void 0,
924
+ variantName: void 0,
925
+ annotations: /* @__PURE__ */ new Map()
926
+ };
927
+ }
928
+ function emptyGroupPagingState() {
929
+ return {
930
+ orderBy: [],
931
+ limit: void 0,
932
+ offset: void 0
933
+ };
934
+ }
935
+ function param(codec, value) {
936
+ if (codec === void 0) return ParamRef.of(value);
937
+ return ParamRef.of(value, { codec });
938
+ }
939
+ function paramList(codec, values) {
940
+ return ListExpression.of(values.map((value) => param(codec, value)));
941
+ }
942
+ function scalarComparisonMethod(op) {
943
+ return ((left, codec) => (value) => {
944
+ if (value === null && (op === "eq" || op === "neq")) return op === "eq" ? NullCheckExpr.isNull(left) : NullCheckExpr.isNotNull(left);
945
+ return new BinaryExpr(op, left, param(codec, value));
946
+ });
947
+ }
948
+ function listComparisonMethod(op) {
949
+ return ((left, codec) => (values) => new BinaryExpr(op, left, paramList(codec, values)));
950
+ }
951
+ /**
952
+ * Declares trait requirements and runtime factory for each comparison method.
953
+ *
954
+ * - `traits: []` means "no trait required" — always available
955
+ * - Multi-trait: `traits: ['equality', 'order']` means BOTH traits are required
956
+ */
957
+ const COMPARISON_METHODS_META = {
958
+ eq: {
959
+ traits: ["equality"],
960
+ create: scalarComparisonMethod("eq")
961
+ },
962
+ neq: {
963
+ traits: ["equality"],
964
+ create: scalarComparisonMethod("neq")
965
+ },
966
+ in: {
967
+ traits: ["equality"],
968
+ create: listComparisonMethod("in")
969
+ },
970
+ notIn: {
971
+ traits: ["equality"],
972
+ create: listComparisonMethod("notIn")
973
+ },
974
+ gt: {
975
+ traits: ["order"],
976
+ create: scalarComparisonMethod("gt")
977
+ },
978
+ lt: {
979
+ traits: ["order"],
980
+ create: scalarComparisonMethod("lt")
981
+ },
982
+ gte: {
983
+ traits: ["order"],
984
+ create: scalarComparisonMethod("gte")
985
+ },
986
+ lte: {
987
+ traits: ["order"],
988
+ create: scalarComparisonMethod("lte")
989
+ },
990
+ like: {
991
+ traits: ["textual"],
992
+ create: scalarComparisonMethod("like")
993
+ },
994
+ asc: {
995
+ traits: ["order"],
996
+ create: (left) => () => OrderByItem.asc(left)
997
+ },
998
+ desc: {
999
+ traits: ["order"],
1000
+ create: (left) => () => OrderByItem.desc(left)
1001
+ },
1002
+ isNull: {
1003
+ traits: [],
1004
+ create: (left) => () => NullCheckExpr.isNull(left)
1005
+ },
1006
+ isNotNull: {
1007
+ traits: [],
1008
+ create: (left) => () => NullCheckExpr.isNotNull(left)
1009
+ }
1010
+ };
913
1011
  function toAggregateProjection(contract, aggregates, namespaceId, tableName, selector) {
914
1012
  const { codec, input: inputCodec, lower } = resolveAggregate({
915
1013
  aggregates,
@@ -992,8 +1090,8 @@ function validateGroupedHavingExpr(expr) {
992
1090
  rawExpr: rejectHavingExpr
993
1091
  });
994
1092
  }
995
- function aggregateInputColumns(tableName, entries, orderBy) {
996
- const columns = /* @__PURE__ */ new Set();
1093
+ function aggregateInputColumns(tableName, entries, orderBy, groupByColumns = []) {
1094
+ const columns = new Set(groupByColumns);
997
1095
  for (const [, selector] of entries) if (selector.column !== void 0) columns.add(selector.column);
998
1096
  for (const item of orderBy ?? []) if (item.expr.kind === "column-ref") columns.add(item.expr.column);
999
1097
  if (columns.size === 0) return [ProjectionItem.of("__row", LiteralExpr.of(1))];
@@ -1032,7 +1130,7 @@ function compileAggregate(contract, aggregates, namespaceId, tableName, state, a
1032
1130
  const { params } = deriveParamsFromAst(ast);
1033
1131
  return buildOrmQueryPlan(contract, ast, params);
1034
1132
  }
1035
- function compileGroupedAggregate(contract, aggregates, namespaceId, tableName, filters, groupByColumns, aggregateSpec, havingExpr) {
1133
+ function compileGroupedAggregate(contract, aggregates, namespaceId, tableName, preGroupState, groupByColumns, aggregateSpec, havingExpr, modelName, postGroup = emptyGroupPagingState()) {
1036
1134
  if (groupByColumns.length === 0) throw ormError("ORM.GROUP_BY_FIELD_MISSING", "groupBy() requires at least one field", { meta: {
1037
1135
  namespaceId,
1038
1136
  tableName
@@ -1043,14 +1141,30 @@ function compileGroupedAggregate(contract, aggregates, namespaceId, tableName, f
1043
1141
  namespaceId,
1044
1142
  tableName
1045
1143
  } });
1144
+ if (preGroupState.distinctOn !== void 0 && preGroupState.distinctOn.length > 0) assertDistinctOnCapability(contract, "distinctOn");
1046
1145
  const projection = [...groupByColumns.map((column) => ProjectionItem.of(column, ColumnRef.of(tableName, column), codecRefForStorageColumn(contract.storage, namespaceId, tableName, column))), ...entries.map(([alias, selector]) => {
1047
1146
  const { expr, codec } = toAggregateProjection(contract, aggregates, namespaceId, tableName, selector);
1048
1147
  return ProjectionItem.of(alias, expr, codec);
1049
1148
  })];
1050
- let ast = SelectAst.from(tableSourceForContract(contract, namespaceId, tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
1051
- const where = combineWhereExprs(filters);
1052
- if (where) ast = ast.withWhere(where);
1149
+ const hasPagination = preGroupState.limit !== void 0 || preGroupState.offset !== void 0;
1150
+ const hasDistinct = preGroupState.distinct !== void 0 && preGroupState.distinct.length > 0 || preGroupState.distinctOn !== void 0 && preGroupState.distinctOn.length > 0;
1151
+ const needsInputSelect = hasPagination || hasDistinct;
1152
+ let ast;
1153
+ if (needsInputSelect) {
1154
+ const { source } = buildAggregateInput(contract, namespaceId, tableName, preGroupState, modelName, aggregateInputColumns(tableName, entries, preGroupState.orderBy, groupByColumns));
1155
+ ast = SelectAst.from(source).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
1156
+ } else {
1157
+ const polyInfo = modelName ? resolvePolymorphismInfo(contract, namespaceId, modelName) : void 0;
1158
+ const variantJoins = polyInfo && polyInfo.mtiVariants.length > 0 ? buildMtiJoins(contract, namespaceId, polyInfo, preGroupState.variantName, void 0).joins : [];
1159
+ ast = SelectAst.from(tableSourceForContract(contract, namespaceId, tableName)).withProjection(projection).withGroupBy(groupByColumns.map((column) => ColumnRef.of(tableName, column)));
1160
+ if (variantJoins.length > 0) ast = ast.withJoins(variantJoins);
1161
+ const where = buildStateWhere(contract, tableName, preGroupState, { namespaceId });
1162
+ if (where) ast = ast.withWhere(where);
1163
+ }
1053
1164
  if (havingExpr) ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
1165
+ if (postGroup.orderBy.length > 0) ast = ast.withOrderBy(postGroup.orderBy);
1166
+ if (postGroup.limit !== void 0) ast = ast.withLimit(postGroup.limit);
1167
+ if (postGroup.offset !== void 0) ast = ast.withOffset(postGroup.offset);
1054
1168
  const { params } = deriveParamsFromAst(ast);
1055
1169
  return buildOrmQueryPlan(contract, ast, params);
1056
1170
  }
@@ -1851,97 +1965,6 @@ function compileSelectWithIncludes(contract, aggregates, namespaceId, tableName,
1851
1965
  function queryPlanRows(scope, plan) {
1852
1966
  return scope.query(plan);
1853
1967
  }
1854
- function emptyState() {
1855
- return {
1856
- filters: [],
1857
- includes: [],
1858
- orderBy: void 0,
1859
- cursor: void 0,
1860
- distinct: void 0,
1861
- distinctOn: void 0,
1862
- selectedFields: void 0,
1863
- limit: void 0,
1864
- offset: void 0,
1865
- variantName: void 0,
1866
- annotations: /* @__PURE__ */ new Map()
1867
- };
1868
- }
1869
- function param(codec, value) {
1870
- if (codec === void 0) return ParamRef.of(value);
1871
- return ParamRef.of(value, { codec });
1872
- }
1873
- function paramList(codec, values) {
1874
- return ListExpression.of(values.map((value) => param(codec, value)));
1875
- }
1876
- function scalarComparisonMethod(op) {
1877
- return ((left, codec) => (value) => {
1878
- if (value === null && (op === "eq" || op === "neq")) return op === "eq" ? NullCheckExpr.isNull(left) : NullCheckExpr.isNotNull(left);
1879
- return new BinaryExpr(op, left, param(codec, value));
1880
- });
1881
- }
1882
- function listComparisonMethod(op) {
1883
- return ((left, codec) => (values) => new BinaryExpr(op, left, paramList(codec, values)));
1884
- }
1885
- /**
1886
- * Declares trait requirements and runtime factory for each comparison method.
1887
- *
1888
- * - `traits: []` means "no trait required" — always available
1889
- * - Multi-trait: `traits: ['equality', 'order']` means BOTH traits are required
1890
- */
1891
- const COMPARISON_METHODS_META = {
1892
- eq: {
1893
- traits: ["equality"],
1894
- create: scalarComparisonMethod("eq")
1895
- },
1896
- neq: {
1897
- traits: ["equality"],
1898
- create: scalarComparisonMethod("neq")
1899
- },
1900
- in: {
1901
- traits: ["equality"],
1902
- create: listComparisonMethod("in")
1903
- },
1904
- notIn: {
1905
- traits: ["equality"],
1906
- create: listComparisonMethod("notIn")
1907
- },
1908
- gt: {
1909
- traits: ["order"],
1910
- create: scalarComparisonMethod("gt")
1911
- },
1912
- lt: {
1913
- traits: ["order"],
1914
- create: scalarComparisonMethod("lt")
1915
- },
1916
- gte: {
1917
- traits: ["order"],
1918
- create: scalarComparisonMethod("gte")
1919
- },
1920
- lte: {
1921
- traits: ["order"],
1922
- create: scalarComparisonMethod("lte")
1923
- },
1924
- like: {
1925
- traits: ["textual"],
1926
- create: scalarComparisonMethod("like")
1927
- },
1928
- asc: {
1929
- traits: ["order"],
1930
- create: (left) => () => OrderByItem.asc(left)
1931
- },
1932
- desc: {
1933
- traits: ["order"],
1934
- create: (left) => () => OrderByItem.desc(left)
1935
- },
1936
- isNull: {
1937
- traits: [],
1938
- create: (left) => () => NullCheckExpr.isNull(left)
1939
- },
1940
- isNotNull: {
1941
- traits: [],
1942
- create: (left) => () => NullCheckExpr.isNotNull(left)
1943
- }
1944
- };
1945
1968
  function dispatchCollectionRows(options) {
1946
1969
  const { context, runtime, state, tableName, modelName, namespaceId } = options;
1947
1970
  const { contract } = context;
@@ -2389,152 +2412,6 @@ function assertFieldHasEqualityTrait(context, namespaceId, modelName, fieldName)
2389
2412
  trait: "equality"
2390
2413
  } });
2391
2414
  }
2392
- var GroupedCollection = class GroupedCollection {
2393
- ctx;
2394
- contract;
2395
- modelName;
2396
- tableName;
2397
- namespaceId;
2398
- baseFilters;
2399
- groupByFields;
2400
- groupByColumns;
2401
- havingFilters;
2402
- constructor(ctx, modelName, options) {
2403
- this.ctx = ctx;
2404
- this.contract = ctx.context.contract;
2405
- this.modelName = modelName;
2406
- this.tableName = options.tableName;
2407
- this.namespaceId = options.namespaceId;
2408
- this.baseFilters = options.baseFilters;
2409
- this.groupByFields = options.groupByFields;
2410
- this.groupByColumns = options.groupByColumns;
2411
- this.havingFilters = options.havingFilters;
2412
- }
2413
- having(predicate) {
2414
- const havingExpr = predicate(createHavingBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName, this.tableName));
2415
- return new GroupedCollection(this.ctx, this.modelName, {
2416
- tableName: this.tableName,
2417
- namespaceId: this.namespaceId,
2418
- baseFilters: this.baseFilters,
2419
- groupByFields: this.groupByFields,
2420
- groupByColumns: this.groupByColumns,
2421
- havingFilters: [...this.havingFilters, havingExpr]
2422
- });
2423
- }
2424
- /**
2425
- * Read terminal: run a grouped aggregate query.
2426
- *
2427
- * Accepts an optional `configure` callback that receives a
2428
- * `MetaBuilder<'read'>` for attaching typed annotations.
2429
- * Annotations are merged into the compiled plan's `meta.annotations`.
2430
- */
2431
- async aggregate(fn, configure) {
2432
- const aggregateSpec = fn(createAggregateBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName));
2433
- const aggregateEntries = Object.entries(aggregateSpec);
2434
- if (aggregateEntries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
2435
- method: "groupBy.aggregate",
2436
- model: this.modelName
2437
- } });
2438
- for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `groupBy().aggregate() selector "${alias}" is invalid`, { meta: {
2439
- method: "groupBy.aggregate",
2440
- model: this.modelName,
2441
- alias
2442
- } });
2443
- let annotationsMap;
2444
- if (configure !== void 0) {
2445
- const meta = createMetaBuilder("read", "groupBy.aggregate");
2446
- configure(meta);
2447
- if (meta.annotations.size > 0) annotationsMap = meta.annotations;
2448
- }
2449
- const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.tableName, this.baseFilters, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters)), annotationsMap);
2450
- return (await queryPlanRows(this.ctx.runtime, compiled).toArray()).map((row) => {
2451
- const mapped = mapStorageRowToModelFields(this.contract, this.namespaceId, this.modelName, row);
2452
- for (const [alias] of aggregateEntries) mapped[alias] = row[alias];
2453
- return mapped;
2454
- });
2455
- }
2456
- };
2457
- /**
2458
- * The having metric methods, one per operation the registry contributes —
2459
- * the runtime mirror of the contract's emitted aggregate map, which is what
2460
- * types the surface as {@link HavingBuilder}. HAVING compares the value
2461
- * inside the database, so only an operation's plain `AggregateExpr` form is
2462
- * sound here: an operation outside the SQL aggregate alphabet exists only in
2463
- * its descriptor-lowered form — a rendering for the driver boundary — and is
2464
- * refused. The typed surface already excludes it; the runtime refusal covers
2465
- * dynamic invocation.
2466
- */
2467
- function createHavingBuilder(contract, aggregates, namespaceId, modelName, tableName) {
2468
- const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
2469
- const builder = {};
2470
- for (const operation of aggregateOperationNames(aggregates)) builder[operation] = (field) => {
2471
- if (!isAggregateFn(operation)) throw ormError("ORM.AGGREGATE_PROJECTION_ONLY", `Aggregate operation '${operation}' is projection-only: it has no plain SQL form for HAVING, ORDER BY, or comparison positions.`, {
2472
- why: "An operation outside the SQL aggregate alphabet reaches SQL only through its descriptor's lowering hook — a rendering for the driver boundary. HAVING and ORDER BY compare the value inside the database, where that rendering would change SQL semantics.",
2473
- fix: `Project '${operation}' in a select and filter or order on the projected value, or use an operation from the SQL aggregate alphabet.`,
2474
- meta: { operation }
2475
- });
2476
- return createHavingComparisonMethods(new AggregateExpr(operation, field === void 0 ? void 0 : ColumnRef.of(tableName, fieldToColumn[field] ?? field)));
2477
- };
2478
- return blindCast(builder);
2479
- }
2480
- function createHavingComparisonMethods(metric) {
2481
- const buildBinaryExpr = (op, value) => new BinaryExpr(op, metric, LiteralExpr.of(value));
2482
- return {
2483
- eq(value) {
2484
- return buildBinaryExpr("eq", value);
2485
- },
2486
- neq(value) {
2487
- return buildBinaryExpr("neq", value);
2488
- },
2489
- gt(value) {
2490
- return buildBinaryExpr("gt", value);
2491
- },
2492
- lt(value) {
2493
- return buildBinaryExpr("lt", value);
2494
- },
2495
- gte(value) {
2496
- return buildBinaryExpr("gte", value);
2497
- },
2498
- lte(value) {
2499
- return buildBinaryExpr("lte", value);
2500
- }
2501
- };
2502
- }
2503
- function createIncludeScalar(fn, state, column) {
2504
- return {
2505
- kind: "includeScalar",
2506
- fn,
2507
- state,
2508
- ...column !== void 0 ? { column } : {}
2509
- };
2510
- }
2511
- function createIncludeCombine(branches) {
2512
- return {
2513
- kind: "includeCombine",
2514
- branches
2515
- };
2516
- }
2517
- function isIncludeScalar(value) {
2518
- if (typeof value !== "object" || value === null) return false;
2519
- const candidate = value;
2520
- return candidate.kind === "includeScalar" && typeof candidate.fn === "string" && isCollectionState(candidate.state);
2521
- }
2522
- function isIncludeCombine(value) {
2523
- if (typeof value !== "object" || value === null) return false;
2524
- const candidate = value;
2525
- if (candidate.kind !== "includeCombine") return false;
2526
- if (typeof candidate.branches !== "object" || candidate.branches === null) return false;
2527
- return true;
2528
- }
2529
- function isCollectionStateCarrier(value) {
2530
- if (typeof value !== "object" || value === null) return false;
2531
- return isCollectionState(value.state);
2532
- }
2533
- function isCollectionState(value) {
2534
- if (typeof value !== "object" || value === null) return false;
2535
- const candidate = value;
2536
- return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
2537
- }
2538
2415
  function hasThrough$1(relation) {
2539
2416
  return relation.through !== void 0;
2540
2417
  }
@@ -2847,6 +2724,200 @@ function firstTargetColumn(contract, relation) {
2847
2724
  if (!firstField) return;
2848
2725
  return resolveFieldToColumn(contract, relation.toNamespace, relation.to, firstField);
2849
2726
  }
2727
+ var GroupedCollection = class GroupedCollection {
2728
+ ctx;
2729
+ contract;
2730
+ modelName;
2731
+ tableName;
2732
+ namespaceId;
2733
+ preGroupState;
2734
+ groupByFields;
2735
+ groupByColumns;
2736
+ havingFilters;
2737
+ postGroup;
2738
+ constructor(ctx, modelName, options) {
2739
+ this.ctx = ctx;
2740
+ this.contract = ctx.context.contract;
2741
+ this.modelName = modelName;
2742
+ this.tableName = options.tableName;
2743
+ this.namespaceId = options.namespaceId;
2744
+ this.preGroupState = options.preGroupState;
2745
+ this.groupByFields = options.groupByFields;
2746
+ this.groupByColumns = options.groupByColumns;
2747
+ this.havingFilters = options.havingFilters;
2748
+ this.postGroup = options.postGroup;
2749
+ }
2750
+ #clone(overrides) {
2751
+ return blindCast(new GroupedCollection(this.ctx, this.modelName, {
2752
+ tableName: this.tableName,
2753
+ namespaceId: this.namespaceId,
2754
+ preGroupState: this.preGroupState,
2755
+ groupByFields: this.groupByFields,
2756
+ groupByColumns: this.groupByColumns,
2757
+ havingFilters: this.havingFilters,
2758
+ postGroup: this.postGroup,
2759
+ ...overrides
2760
+ }));
2761
+ }
2762
+ having(predicate) {
2763
+ const havingExpr = predicate(createHavingBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName, this.tableName));
2764
+ return this.#clone({ havingFilters: [...this.havingFilters, havingExpr] });
2765
+ }
2766
+ /**
2767
+ * Append an `ORDER BY` clause on the grouped rows themselves — orders by
2768
+ * group key. Ordering by an aggregate alias needs a builder surface over
2769
+ * the aliases and isn't supported here. Unlocks post-group `take(...)` /
2770
+ * `skip(...)`, which page a group order that would otherwise be undefined.
2771
+ */
2772
+ orderBy(selection) {
2773
+ const selectors = Array.isArray(selection) ? selection : [selection];
2774
+ if (selectors.length === 0) throw ormError("ORM.ARGUMENT_INVALID", `orderBy() for model "${this.modelName}" requires at least one selector`, { meta: {
2775
+ method: "orderBy",
2776
+ model: this.modelName
2777
+ } });
2778
+ const accessor = createModelAccessor(this.ctx.context, this.namespaceId, this.modelName);
2779
+ const nextOrders = selectors.map((selector) => selector(accessor));
2780
+ return this.#clone({ postGroup: {
2781
+ ...this.postGroup,
2782
+ orderBy: [...this.postGroup.orderBy, ...nextOrders]
2783
+ } });
2784
+ }
2785
+ /**
2786
+ * Apply `LIMIT n` to the grouped rows. Replaces any previous post-group
2787
+ * limit. Requires a prior `orderBy(...)` — a database may return groups in
2788
+ * any order, so "the first n groups" is undefined without one.
2789
+ */
2790
+ take(n) {
2791
+ return this.#clone({ postGroup: {
2792
+ ...this.postGroup,
2793
+ limit: n
2794
+ } });
2795
+ }
2796
+ /**
2797
+ * Apply `OFFSET n` to the grouped rows. Replaces any previous post-group
2798
+ * offset. Requires a prior `orderBy(...)`, same as `take(...)` — Prisma
2799
+ * pairs `skip`/`take` with `orderBy` on `groupBy` for the same reason.
2800
+ */
2801
+ skip(n) {
2802
+ return this.#clone({ postGroup: {
2803
+ ...this.postGroup,
2804
+ offset: n
2805
+ } });
2806
+ }
2807
+ /**
2808
+ * Read terminal: run a grouped aggregate query.
2809
+ *
2810
+ * Accepts an optional `configure` callback that receives a
2811
+ * `MetaBuilder<'read'>` for attaching typed annotations.
2812
+ * Annotations are merged into the compiled plan's `meta.annotations`.
2813
+ */
2814
+ async aggregate(fn, configure) {
2815
+ const aggregateSpec = fn(createAggregateBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName));
2816
+ const aggregateEntries = Object.entries(aggregateSpec);
2817
+ if (aggregateEntries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
2818
+ method: "groupBy.aggregate",
2819
+ model: this.modelName
2820
+ } });
2821
+ for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `groupBy().aggregate() selector "${alias}" is invalid`, { meta: {
2822
+ method: "groupBy.aggregate",
2823
+ model: this.modelName,
2824
+ alias
2825
+ } });
2826
+ let annotationsMap;
2827
+ if (configure !== void 0) {
2828
+ const meta = createMetaBuilder("read", "groupBy.aggregate");
2829
+ configure(meta);
2830
+ if (meta.annotations.size > 0) annotationsMap = meta.annotations;
2831
+ }
2832
+ const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.tableName, this.preGroupState, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters), this.modelName, this.postGroup), annotationsMap);
2833
+ return (await queryPlanRows(this.ctx.runtime, compiled).toArray()).map((row) => {
2834
+ const mapped = mapStorageRowToModelFields(this.contract, this.namespaceId, this.modelName, row);
2835
+ for (const [alias] of aggregateEntries) mapped[alias] = row[alias];
2836
+ return mapped;
2837
+ });
2838
+ }
2839
+ };
2840
+ /**
2841
+ * The having metric methods, one per operation the registry contributes —
2842
+ * the runtime mirror of the contract's emitted aggregate map, which is what
2843
+ * types the surface as {@link HavingBuilder}. HAVING compares the value
2844
+ * inside the database, so only an operation's plain `AggregateExpr` form is
2845
+ * sound here: an operation outside the SQL aggregate alphabet exists only in
2846
+ * its descriptor-lowered form — a rendering for the driver boundary — and is
2847
+ * refused. The typed surface already excludes it; the runtime refusal covers
2848
+ * dynamic invocation.
2849
+ */
2850
+ function createHavingBuilder(contract, aggregates, namespaceId, modelName, tableName) {
2851
+ const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
2852
+ const builder = {};
2853
+ for (const operation of aggregateOperationNames(aggregates)) builder[operation] = (field) => {
2854
+ if (!isAggregateFn(operation)) throw ormError("ORM.AGGREGATE_PROJECTION_ONLY", `Aggregate operation '${operation}' is projection-only: it has no plain SQL form for HAVING, ORDER BY, or comparison positions.`, {
2855
+ why: "An operation outside the SQL aggregate alphabet reaches SQL only through its descriptor's lowering hook — a rendering for the driver boundary. HAVING and ORDER BY compare the value inside the database, where that rendering would change SQL semantics.",
2856
+ fix: `Project '${operation}' in a select and filter or order on the projected value, or use an operation from the SQL aggregate alphabet.`,
2857
+ meta: { operation }
2858
+ });
2859
+ return createHavingComparisonMethods(new AggregateExpr(operation, field === void 0 ? void 0 : ColumnRef.of(tableName, fieldToColumn[field] ?? field)));
2860
+ };
2861
+ return blindCast(builder);
2862
+ }
2863
+ function createHavingComparisonMethods(metric) {
2864
+ const buildBinaryExpr = (op, value) => new BinaryExpr(op, metric, LiteralExpr.of(value));
2865
+ return {
2866
+ eq(value) {
2867
+ return buildBinaryExpr("eq", value);
2868
+ },
2869
+ neq(value) {
2870
+ return buildBinaryExpr("neq", value);
2871
+ },
2872
+ gt(value) {
2873
+ return buildBinaryExpr("gt", value);
2874
+ },
2875
+ lt(value) {
2876
+ return buildBinaryExpr("lt", value);
2877
+ },
2878
+ gte(value) {
2879
+ return buildBinaryExpr("gte", value);
2880
+ },
2881
+ lte(value) {
2882
+ return buildBinaryExpr("lte", value);
2883
+ }
2884
+ };
2885
+ }
2886
+ function createIncludeScalar(fn, state, column) {
2887
+ return {
2888
+ kind: "includeScalar",
2889
+ fn,
2890
+ state,
2891
+ ...column !== void 0 ? { column } : {}
2892
+ };
2893
+ }
2894
+ function createIncludeCombine(branches) {
2895
+ return {
2896
+ kind: "includeCombine",
2897
+ branches
2898
+ };
2899
+ }
2900
+ function isIncludeScalar(value) {
2901
+ if (typeof value !== "object" || value === null) return false;
2902
+ const candidate = value;
2903
+ return candidate.kind === "includeScalar" && typeof candidate.fn === "string" && isCollectionState(candidate.state);
2904
+ }
2905
+ function isIncludeCombine(value) {
2906
+ if (typeof value !== "object" || value === null) return false;
2907
+ const candidate = value;
2908
+ if (candidate.kind !== "includeCombine") return false;
2909
+ if (typeof candidate.branches !== "object" || candidate.branches === null) return false;
2910
+ return true;
2911
+ }
2912
+ function isCollectionStateCarrier(value) {
2913
+ if (typeof value !== "object" || value === null) return false;
2914
+ return isCollectionState(value.state);
2915
+ }
2916
+ function isCollectionState(value) {
2917
+ if (typeof value !== "object" || value === null) return false;
2918
+ const candidate = value;
2919
+ return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
2920
+ }
2850
2921
  function createRelationMutator() {
2851
2922
  return {
2852
2923
  create(data) {
@@ -3655,10 +3726,11 @@ var CollectionImpl = class CollectionImpl {
3655
3726
  return new GroupedCollection(this.ctx, this.modelName, {
3656
3727
  tableName: this.tableName,
3657
3728
  namespaceId: this.namespaceId,
3658
- baseFilters: this.state.filters,
3729
+ preGroupState: this.state,
3659
3730
  groupByFields: [...fields],
3660
3731
  groupByColumns,
3661
- havingFilters: []
3732
+ havingFilters: [],
3733
+ postGroup: emptyGroupPagingState()
3662
3734
  });
3663
3735
  }
3664
3736
  /**