@dashevo/dapi-grpc 4.1.1 → 4.2.0-dev.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashevo/dapi-grpc",
3
- "version": "4.1.1",
3
+ "version": "4.2.0-dev.2",
4
4
  "description": "DAPI GRPC definition file and generated clients",
5
5
  "browser": "browser.js",
6
6
  "main": "node.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "homepage": "https://github.com/dashevo/dapi-grpc#readme",
47
47
  "dependencies": {
48
- "@dashevo/grpc-common": "4.1.1",
48
+ "@dashevo/grpc-common": "4.2.0-dev.2",
49
49
  "@dashevo/protobufjs": "6.10.5",
50
50
  "@grpc/grpc-js": "^1.14.3",
51
51
  "@improbable-eng/grpc-web": "^0.15.0",
@@ -594,6 +594,18 @@ message GetDocumentsRequest {
594
594
  BETWEEN_EXCLUDE_RIGHT = 8;
595
595
  IN = 9;
596
596
  STARTS_WITH = 10;
597
+ // Time-range bucket selection (v1 only; the v0 CBOR surface is
598
+ // unaffected). `field` names a timestamp covered by a `timeRange`
599
+ // index. Operand: `text` selector `"newest"`/`"oldest"` when one grid
600
+ // buckets the field, or `list` `[selector, range, step(, phase)]` in
601
+ // the contract's declared seconds to name one of several grids (zero
602
+ // phase is spelled by omission — one wire spelling per grid). The
603
+ // server resolves it to a bucket-start equality from current block
604
+ // time; the verifier re-derives the same bucket from the quorum-signed
605
+ // metadata time — an ordinary index/count proof. See `timeRange` in
606
+ // the document meta-schema and
607
+ // `drive::query::resolve_time_range_bucket_clause`.
608
+ IN_TIME_RANGE = 11;
597
609
  }
598
610
 
599
611
  // Tagged scalar (or list) operand for a `WhereClause`. The
@@ -661,9 +673,13 @@ message GetDocumentsRequest {
661
673
  }
662
674
 
663
675
  // Per-group aggregate operand for the left side of a
664
- // `HavingClause`. Only the per-group aggregates live here:
665
- // `MIN` / `MAX` / `TOP` / `BOTTOM` are **cross-group** ranking
666
- // primitives and appear on the right side via `HavingRanking`.
676
+ // `HavingClause`, and the aggregate-function target of an
677
+ // `OrderClause`. Only the per-group aggregates live here
678
+ // `HAVING` is a boolean predicate over one group's own aggregate,
679
+ // and nothing on this message reaches across groups. Cross-group
680
+ // ranking ("which groups score highest?") is expressed with SQL's
681
+ // ordering surface instead: `ORDER BY <the selected aggregate>
682
+ // DESC LIMIT n [OFFSET m]`. See `GetDocumentsRequestV1.order_by`.
667
683
  //
668
684
  // **Field semantics by function**:
669
685
  // - `COUNT`: empty `field` means `COUNT(*)` (group cardinality);
@@ -682,58 +698,51 @@ message GetDocumentsRequest {
682
698
  string field = 2;
683
699
  }
684
700
 
685
- // Cross-group ranking primitive on the right side of a
686
- // `HavingClause`. The ranking is computed over the set of
687
- // group-aggregate results (one per `GROUP BY` row), so
688
- // `HAVING COUNT(*) EQ MAX` selects groups whose count equals
689
- // the maximum count across all groups, and
690
- // `HAVING COUNT(*) IN TOP(5)` selects groups whose count is
691
- // among the five largest. Concise way to express top-N /
692
- // bottom-N selection without window functions or
693
- // `ORDER BY` + `LIMIT`.
694
- //
695
- // **Operator compatibility**:
696
- // - Scalar operators (`=`, `!=`, `<`, `<=`, `>`, `>=`) work
697
- // with `MIN` / `MAX`. `TOP` / `BOTTOM` with scalar operators
698
- // only make sense when `n=1` (the single largest / smallest);
699
- // evaluation rejects other combinations as ambiguous.
700
- // - `IN` works with `TOP(n)` / `BOTTOM(n)` for set membership.
701
- // - `BETWEEN*` doesn't compose meaningfully with rankings and
702
- // is rejected at evaluation time.
703
- message HavingRanking {
704
- enum Kind {
705
- MIN = 0;
706
- MAX = 1;
707
- TOP = 2;
708
- BOTTOM = 3;
709
- }
710
- Kind kind = 1;
711
- // N-th rank for `TOP` / `BOTTOM` (1-indexed: `n=1` is the
712
- // single largest / smallest). Required for those two kinds;
713
- // must be unset for `MIN` / `MAX`. The wire allows setting
714
- // it on `MIN` / `MAX` for forward compatibility, but
715
- // evaluation rejects it as a malformed ranking.
716
- optional uint64 n = 2 [jstype = JS_STRING];
717
- }
718
-
719
- // Single `HAVING <aggregate> <op> <right>` clause. Multiple
701
+ // Single `HAVING <aggregate> <op> <value>` clause. Multiple
720
702
  // entries in `GetDocumentsRequestV1.having` combine with
721
703
  // implicit AND — same semantics as multiple `where_clauses`
722
704
  // entries. `HAVING COUNT(*) > 5 AND SUM(amount) > 100` is two
723
705
  // `HavingClause` rows, not a tree.
724
706
  //
707
+ // **`HAVING` is a boolean per-group predicate and nothing else.**
708
+ // Its right operand is always a literal `DocumentFieldValue`; it
709
+ // never names another group or the set of groups. Cross-group
710
+ // ranking — "the 5 highest-scoring groups" — is `ORDER BY <the
711
+ // selected aggregate> DESC LIMIT 5`, exactly as in SQL, and is
712
+ // served by the ranked executor (protocol v14+). An earlier draft
713
+ // of this surface carried the ranking on the right of a `HAVING`
714
+ // (`HAVING AVG(grade) IN TOP(5)`); that spelling was removed
715
+ // before release rather than deprecated, because it invented
716
+ // non-SQL grammar for something SQL already expresses.
717
+ //
718
+ // **From protocol v14 a single `HAVING` clause is served as a
719
+ // bounded range read** (having-range mode): `SELECT <agg> GROUP BY
720
+ // p HAVING <agg> <op> <value> [ORDER BY <order-key> ASC|DESC]
721
+ // LIMIT n` answers from the same per-axis secondary as ranked
722
+ // mode, on an index declaring the matching ranked axis. The
723
+ // clause's aggregate must be the selected aggregate, the operator
724
+ // must describe one contiguous range (`NOT_EQUAL` / `IN` are
725
+ // rejected), and the optional `ORDER BY` picks the walk direction
726
+ // using the same order-key spelling as ranked mode: `f` for
727
+ // `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)` —
728
+ // never an explicit `OrderClause.aggregate` target, which is
729
+ // rejected. See the supported-shape table on
730
+ // `GetDocumentsRequestV1`. On protocol v13 and earlier every
731
+ // non-empty `having` stays rejected with `Unsupported`, exactly as
732
+ // before.
733
+ //
725
734
  // The operator set mirrors `WhereOperator` minus `STARTS_WITH`
726
735
  // (prefix matching has no natural meaning against a scalar
727
736
  // aggregate result, even a string-typed one). `BETWEEN*` and
728
737
  // `IN` operand semantics match `WhereOperator`: `BETWEEN*`
729
738
  // expects a 2-element `DocumentFieldValue.list` carrying
730
739
  // `[lower, upper]`, and `IN` expects a `list` of candidate
731
- // values (or a ranking set via `right.ranking`).
740
+ // values.
732
741
  //
733
- // The `right` oneof carries either a concrete
734
- // `DocumentFieldValue` (literal comparison target) or a
735
- // `HavingRanking` (cross-group reference). Exactly one is set;
736
- // the wire rejects an unset `right`.
742
+ // The `right` oneof exists (rather than a bare
743
+ // `DocumentFieldValue` field) so the "unset right operand" case
744
+ // stays distinguishable from "the literal null value"; the wire
745
+ // rejects an unset `right`.
737
746
  message HavingClause {
738
747
  enum Operator {
739
748
  EQUAL = 0;
@@ -752,33 +761,65 @@ message GetDocumentsRequest {
752
761
  Operator operator = 2;
753
762
  oneof right {
754
763
  DocumentFieldValue value = 3;
755
- HavingRanking ranking = 4;
756
764
  }
757
765
  }
758
766
 
759
- // Single `ORDER BY field <direction>` clause. Multi-field
767
+ // Single `ORDER BY <target> <direction>` clause. Multi-field
760
768
  // ordering is expressed by repeating this message at the
761
769
  // request level (`repeated OrderClause order_by = 4`), matching
762
770
  // SQL's `ORDER BY a ASC, b DESC` shape.
763
- // Single ORDER BY entry. Multi-entry ordering is expressed by
764
- // repeating this message at the request level.
765
771
  //
766
772
  // The `target` oneof carries either a plain field name
767
773
  // (`ORDER BY field`) or an aggregate function applied to a
768
- // field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`) — the
769
- // latter sorts per-group result rows produced by `GROUP BY`,
770
- // useful with `LIMIT` for top-N / bottom-N selection at the
771
- // routing layer (overlapping `HavingRanking::Top` / `Bottom`
772
- // but more general because the ranking field can be any
773
- // aggregate, not just count).
774
+ // field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`).
775
+ //
776
+ // **Two distinct roles ride the `field` target.**
777
+ //
778
+ // 1. *Row ordering* `select = DOCUMENTS`. `field` names a
779
+ // document property and the matched rows come back in that
780
+ // order. This is v0's behaviour, unchanged.
781
+ //
782
+ // 2. *Aggregate ordering* — the **ranked** surface (protocol
783
+ // v14+). With a `GROUP BY` and a single aggregate `select`,
784
+ // exactly one `order_by` clause naming that select's
785
+ // aggregate orders the *groups* by their aggregate value and
786
+ // routes the request to the ranked executor:
774
787
  //
775
- // **Aggregate target currently rejected** with
776
- // `Unsupported("ORDER BY on aggregate is not yet implemented")`.
777
- // The wire surface is shipped now so callers can encode the
778
- // shape ahead of server support landing.
788
+ // ```text
789
+ // SELECT AVG(grade) GROUP BY restaurantId
790
+ // ORDER BY grade DESC LIMIT 3 -- 3 best restaurants
791
+ // SELECT COUNT(*) GROUP BY restaurantId
792
+ // ORDER BY $count DESC LIMIT 10 OFFSET 10 -- busiest, page 2
793
+ // ```
794
+ //
795
+ // `SUM(f)` / `AVG(f)` are named by `f` — the same property the
796
+ // projection aggregates, which is how `ORDER BY avg(grade)`
797
+ // reads once `SELECT` has already fixed the function.
798
+ // `COUNT(*)` aggregates no property, so it is named by the
799
+ // reserved sentinel **`$count`**. The `$` prefix is what keeps
800
+ // the sentinel from colliding with a real document property:
801
+ // document properties cannot start with `$` (that namespace is
802
+ // the system fields' `$id` / `$ownerId` / …), so `$count` can
803
+ // never be mistaken for a column. `DESC` is the "top n"
804
+ // reading, `ASC` the "bottom n" reading.
805
+ //
806
+ // An `order_by` naming anything other than the selected
807
+ // aggregate — a second clause, the `GROUP BY` property, an
808
+ // unrelated field — is rejected rather than normalized: it
809
+ // asks for an ordering the ranked secondary cannot produce.
810
+ //
811
+ // **Aggregate target still rejected** with
812
+ // `Unsupported("ORDER BY on aggregate keys is not yet
813
+ // implemented")`. It is the *explicit* spelling of role 2
814
+ // (`ORDER BY AVG(grade)` rather than `ORDER BY grade` under a
815
+ // `SELECT AVG(grade)`) and is wire-stable so it can start being
816
+ // evaluated without another version bump; today the field-target
817
+ // spelling above is the one the ranked executor reads.
779
818
  message OrderClause {
780
819
  oneof target {
781
- // Plain field name. Today's evaluated form.
820
+ // Plain field name. Today's evaluated form — a document
821
+ // property for row ordering, or the selected aggregate's
822
+ // property (`$count` for `COUNT(*)`) for aggregate ordering.
782
823
  string field = 1;
783
824
  // Aggregate function applied to a field, sorted by the
784
825
  // per-group result. `function = DOCUMENTS` is invalid
@@ -823,12 +864,22 @@ message GetDocumentsRequest {
823
864
  // other shapes return `Unsupported` (see supported-shape table
824
865
  // below).
825
866
  //
826
- // `having` is wire-reserved for a future server capability. Any
827
- // non-empty `having` list currently returns
828
- // `Unsupported("HAVING clause is not yet implemented")`
829
- // regardless of `select` / `group_by`. The wire shape is
830
- // `repeated WhereClause` so when execution lands the surface is
831
- // already typed end-to-end and callers don't need to re-encode.
867
+ // **Ranked mode** is served from protocol v14 and is selected by
868
+ // `group_by` + a single `order_by` naming the selected aggregate —
869
+ // SQL's own top-n spelling, `ORDER BY <agg> DESC LIMIT n OFFSET m`.
870
+ // It returns `ResultData.ranked`. See `order_by` and the
871
+ // supported-shape table below.
872
+ //
873
+ // **Having-range mode** is served from protocol v14: a single
874
+ // `having` clause whose aggregate is the selected aggregate turns
875
+ // the request into a bounded range read over the same per-axis
876
+ // secondary ranked mode walks, answered in `ResultData.ranked`.
877
+ // On protocol v13 and earlier every non-empty `having` is rejected
878
+ // (`"HAVING clause is not yet implemented"`). `having` carries no
879
+ // ranking spelling: an earlier draft put cross-group ranking on the
880
+ // right of a `HAVING` (`HAVING AVG(grade) IN TOP(5)`) and that
881
+ // grammar was removed before release in favour of `ORDER BY` +
882
+ // `LIMIT`. See the supported-shape table below.
832
883
  //
833
884
  // **Supported shapes** (everything else rejects with a typed
834
885
  // `QuerySyntaxError::Unsupported` so callers can detect un-wired
@@ -853,8 +904,17 @@ message GetDocumentsRequest {
853
904
  // `select=COUNT, group_by=[a, b]`:
854
905
  // - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value).
855
906
  //
907
+ // `select=<COUNT(*)|SUM(f)|AVG(f)>, group_by=[p], order_by=[<the selected aggregate>]` (protocol v14+) — **ranked mode**:
908
+ // - exactly one `group_by` property, exactly one `order_by` clause naming the select's aggregate (`f` for `SUM(f)` / `AVG(f)`, the `$count` sentinel for `COUNT(*)`), a `limit` in `1 ..= 100`, an optional `offset`, and no `having` / `start_at`, on an index declaring the matching `rankedCountable` / `rankedSummable` / `rankedAverageable` axis → ranked executor, answered in `ResultData.ranked`. On a single-property ranked index no `where` is accepted; on a compound ranked index every leading index property must be pinned (one clause per property, `group_by` names the trailing property) — `EQUAL` pins one prefix, and **at most one** clause may be `IN` (2..=10 distinct elements, `null` legal; a single-element `IN` normalizes to the equality pin; an element whose prefix was never written — at any depth of its pinned chain — contributes an empty branch, union semantics), fanning the walk out across one prefix branch per element and merging by `(aggregate, encoded prefix, group key)`; merged entries carry `in_key`. A non-zero `offset` is rejected together with `IN` (rank-skip is per-secondary; `OFFSET 0` is the offset-free request).
909
+ // - `DESC` is the "top n" reading (walk the axis from the largest aggregate down), `ASC` the "bottom n" reading. Worked example: `SELECT AVG(grade) GROUP BY restaurantId ORDER BY grade DESC LIMIT 1 OFFSET 4` is the 5th-best restaurant.
910
+ //
911
+ // `select=<COUNT(*)|SUM(f)|AVG(f)>, group_by=[p], having=[<the selected aggregate> <op> <value>]` (protocol v14+) — **having-range mode**:
912
+ // - exactly one `group_by` property, exactly one `having` clause whose aggregate is the select's aggregate, an operator describing one contiguous range (`EQUAL`, `GREATER_THAN[_OR_EQUALS]`, `LESS_THAN[_OR_EQUALS]`, `BETWEEN*`; `NOT_EQUAL` / `IN` rejected), a `limit` in `1 ..= 100`, an optional `order_by` naming the same aggregate (walk direction; ascending by default), and no `offset` / `start_at` / `start_after`, on an index declaring the matching ranked axis → having-range executor, answered in `ResultData.ranked`. `where` follows the same rule as ranked mode: none on a single-property ranked index; exactly one pin per leading index property on a compound ranked index, at most one of them an `IN` (2..=10 distinct elements; a never-written element contributes an empty branch) that fans the bound out across prefix branches and merges, entries carrying `in_key`.
913
+ // - no offset or cursor pagination: a page cut at `limit` continues only by tightening the bound past the last *distinct* aggregate value seen; a cut inside a tie (several groups sharing the boundary aggregate) cannot be continued, so size `limit` above the widest expected tie.
914
+ //
856
915
  // **Rejected shapes** (return `Unsupported`):
857
- // - any non-empty `having` (alwayspending future server capability).
916
+ // - any non-empty `having` on protocol v13 and earlier; at v14+, any `having` shape outside having-range mode above (multiple clauses, an aggregate other than the select's, `NOT_EQUAL` / `IN` as the having operator, a `where` shape other than the compound-index prefix pins above equality pins plus at most one bounded `IN` — or a carried `offset` / cursor).
917
+ // - at v14+: a ranked-shaped request carrying a `where` shape other than the compound-index prefix pins above (an operator other than `EQUAL` or the one permitted `IN`, more than one `IN`, a `null` pin combined with an `IN`, a repeated or non-leading property, or a missing pin), a `start_at` / `start_after` cursor, more than one `order_by`, or an `order_by` naming anything but the selected aggregate.
858
918
  // - `select=DOCUMENTS` with non-empty `group_by`.
859
919
  // - `select=COUNT` with `group_by` on a field that is not constrained by an `In` or range where clause.
860
920
  // - `select=COUNT` with `group_by.len() > 2`.
@@ -927,11 +987,11 @@ message GetDocumentsRequest {
927
987
  AVG = 3;
928
988
  // Per-group MIN / MAX — `SELECT MIN(field) GROUP BY
929
989
  // category` returns the smallest `field` value in each
930
- // category. Semantically distinct from
931
- // `HavingRanking::Min` / `Max` (which are cross-group
932
- // meta-aggregates over group results). MIN/MAX here
933
- // operate over the row values within each group, the
934
- // same way `SUM` and `AVG` do.
990
+ // category. These operate over the row values *within*
991
+ // each group, the same way `SUM` and `AVG` do; they are
992
+ // not a cross-group ranking. To pick out the extreme
993
+ // *group*, order by the aggregate instead:
994
+ // `ORDER BY <agg> ASC|DESC LIMIT 1` (see `order_by`).
935
995
  MIN = 4;
936
996
  MAX = 5;
937
997
  }
@@ -1044,33 +1104,59 @@ message GetDocumentsRequest {
1044
1104
  // message-level docstring for the supported-shape table.
1045
1105
  repeated string group_by = 10;
1046
1106
 
1047
- // SQL `HAVING` clauses — aggregate filters that apply to the
1048
- // grouped rows produced by `select=COUNT, group_by=[…]`. The
1049
- // wire shape is `HavingClause`, not `WhereClause`, because
1050
- // HAVING evaluates against per-group aggregates
1051
- // (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`/`TOP`/`BOTTOM`) rather than
1052
- // row field values. Multiple entries combine with implicit
1053
- // AND. See `HavingClause` / `HavingAggregate` for the
1054
- // operator and aggregate-function catalogs.
1107
+ // SQL `HAVING` clauses — **boolean** aggregate filters that
1108
+ // apply to the grouped rows produced by `select=<COUNT|SUM|AVG>,
1109
+ // group_by=[…]`. The wire shape is `HavingClause`, not
1110
+ // `WhereClause`, because HAVING evaluates against per-group
1111
+ // aggregates (`COUNT` / `SUM` / `AVG`) rather than row field
1112
+ // values. Multiple entries combine with implicit AND. See
1113
+ // `HavingClause` / `HavingAggregate` for the operator and
1114
+ // aggregate-function catalogs.
1115
+ //
1116
+ // **From protocol v14 a single clause is served** as a bounded
1117
+ // range read — having-range mode; see the message-level
1118
+ // supported-shape table. On v13 and earlier every non-empty
1119
+ // `having` is rejected with `Unsupported("HAVING clause is not
1120
+ // yet implemented")`. Multi-clause `HAVING COUNT(*) > 5 AND
1121
+ // SUM(amount) > 100` requests can still be constructed on the
1122
+ // wire, but stay rejected until a multi-clause evaluator lands.
1055
1123
  //
1056
- // **Always rejected when non-empty** today with
1057
- // `Unsupported("HAVING clause is not yet implemented")`. The
1058
- // wire shape is shipped now so the future server capability
1059
- // can land without another version bump — and so callers can
1060
- // construct full `HAVING COUNT(*) > 5 AND SUM(amount) > 100`
1061
- // requests in their builders even before the server evaluates
1062
- // them.
1124
+ // **`having` does not express ranking.** "The n highest-scoring
1125
+ // groups" is `ORDER BY <the selected aggregate> DESC LIMIT n`
1126
+ // (see `order_by`), which *is* served, from protocol v14. An
1127
+ // earlier draft of this surface spelled it
1128
+ // `HAVING AVG(grade) IN TOP(5)`; that grammar was removed
1129
+ // before release rather than deprecated.
1063
1130
  repeated HavingClause having = 11;
1064
1131
 
1065
1132
  // Row-based pagination offset, on top of the cursor-based
1066
1133
  // `start_after` / `start_at` pagination. `OFFSET N` skips the
1067
- // first `N` matching rows before applying `limit`. Currently
1068
- // **always rejected when non-`None`** with
1069
- // `Unsupported("OFFSET pagination is not yet implemented")`
1070
- // the wire surface is shipped now so callers can encode it
1071
- // ahead of server support landing without another version
1072
- // bump. Cursor pagination via `start_after` / `start_at`
1073
- // remains the supported way to page through results.
1134
+ // first `N` result rows before applying `limit`.
1135
+ //
1136
+ // **Consumed in ranked mode** (protocol v14+): on a request that
1137
+ // routes to the ranked executor (`group_by` + a single `order_by`
1138
+ // naming the selected aggregate), `offset` skips that many ranks
1139
+ // before the returned page, so `ORDER BY avg(grade) DESC LIMIT 1
1140
+ // OFFSET 4` is the 5th-best group. The skip is **counted, not
1141
+ // walked**: grovedb descends on each subtree's aggregate count and
1142
+ // collapses whole subtrees that fit inside the remaining offset, so
1143
+ // the work stays `O(log n + k)` at any offset and the response
1144
+ // reports the skip it performed in `RankedEntries.skipped`. On a
1145
+ // proved request that count is additionally *attested* — committed
1146
+ // to by the proof and re-derived by the verifier; on an unproved
1147
+ // one it is the node's own report. See `RankedEntries.skipped`. There is deliberately no ceiling — an
1148
+ // offset of 4 and an offset of four billion cost the same *order*
1149
+ // of work — neither walks the region it skips — so there is no
1150
+ // denial-of-service lever a cap would close. An offset
1151
+ // past the end of the ranking is a provable answer rather than an
1152
+ // error: `entries` comes back empty and `skipped` is the ranking's
1153
+ // whole population.
1154
+ //
1155
+ // **Rejected everywhere else** with
1156
+ // `Unsupported("OFFSET pagination is not yet implemented")` —
1157
+ // including on every path at protocol v13 and earlier, which has
1158
+ // no ranked executor. Cursor pagination via `start_after` /
1159
+ // `start_at` remains the supported way to page through documents.
1074
1160
  optional uint32 offset = 12;
1075
1161
  }
1076
1162
 
@@ -1258,9 +1344,134 @@ message GetDocumentsResponse {
1258
1344
  }
1259
1345
  }
1260
1346
 
1347
+ // One group in a ranked (`GROUP BY … ORDER BY <agg> LIMIT n`)
1348
+ // result: the group's index key plus the aggregate it was ranked
1349
+ // by.
1350
+ //
1351
+ // `key` is the raw index-key bytes of the GROUP BY property's
1352
+ // value — the same bytes that name the group's value tree under
1353
+ // the index (for a `string` property, its UTF-8 bytes). Clients
1354
+ // that want the typed value decode it with the document type's
1355
+ // key deserialization; the wire carries bytes so prover and
1356
+ // verifier agree without a schema round-trip.
1357
+ //
1358
+ // Exactly one `value` variant is set, determined by the
1359
+ // request's SELECT function:
1360
+ // * `count` — `SELECT COUNT(*)`, ranked on the index's
1361
+ // `rankedCountable` axis.
1362
+ // * `sum` — `SELECT SUM(field)`, `rankedSummable` axis.
1363
+ // Signed for the same reason `SumEntry.sum` is.
1364
+ // * `avg` — `SELECT AVG(field)`,
1365
+ // `rankedAverageable` axis.
1366
+ message RankedEntry {
1367
+ bytes key = 1;
1368
+ oneof value {
1369
+ // `jstype = JS_STRING` so JS/Web clients receive a string
1370
+ // and don't round counts > 2^53−1 to the nearest
1371
+ // representable Number — same choice as `CountEntry.count`.
1372
+ uint64 count = 2 [jstype = JS_STRING];
1373
+ // `jstype = JS_STRING` for the same precision reason as
1374
+ // `SumEntry.sum`.
1375
+ sint64 sum = 3 [jstype = JS_STRING];
1376
+ // The group's average, as a **`double` approximation** of the
1377
+ // exact value the Avg axis is ordered by.
1378
+ //
1379
+ // What grovedb actually commits to and sorts by is an `i128`
1380
+ // fixed-point integer: `floor(sum * SCALE / count)` with
1381
+ // euclidean (toward −∞) division, where SCALE is grovedb's
1382
+ // `AVG_FIXED_POINT_SCALE` (currently 10^19). This field is
1383
+ // that integer divided by SCALE in `f64`, i.e.
1384
+ // `fixed_point as f64 / SCALE as f64`.
1385
+ //
1386
+ // A `double` is honest here because `RankedEntry` is only ever
1387
+ // populated on the **no-proof ("quick answer") path**, where
1388
+ // the client has already chosen to trust the server's reply.
1389
+ // A proof-verifying client never reads this field: it
1390
+ // reconstructs each entry from the grovedb proof itself, where
1391
+ // the exact fixed-point `i128` lives, so nothing about proof
1392
+ // verification depends on this number's precision.
1393
+ //
1394
+ // Precision bound: `f64` carries ~15–16 significant decimal
1395
+ // digits, so two groups whose exact fixed-point averages differ
1396
+ // only beyond that can compare equal here. Do not use this
1397
+ // value for equality checks, tie-breaking, or any
1398
+ // reconstruction of the committed integer — request the proof
1399
+ // and read the fixed point from it instead. Entry *order* is
1400
+ // still exact: the server ranks on the i128 before converting.
1401
+ //
1402
+ // SCALE is a grovedb constant, not a wire constant: it moved
1403
+ // from 10^15 to 10^19 before release. Clients that need it
1404
+ // (e.g. to go back to fixed point on the proof path) should
1405
+ // read it from the SDK's re-export (`RANKED_AVG_SCALE`) rather
1406
+ // than hardcoding the literal.
1407
+ double avg = 4;
1408
+ }
1409
+ // The prefix branch this entry came from, set **only** on an
1410
+ // `IN`-pinned request (see the supported-shape table): the
1411
+ // encoded index-key bytes of the `IN` property's pinned value —
1412
+ // empty bytes for the `null` (absent-value) branch. Absent on
1413
+ // single-prefix responses. The same group key can legally appear
1414
+ // under two prefixes, so `(in_key, key)` is the entry's identity
1415
+ // on a merged page, exactly as on `CountEntry`.
1416
+ optional bytes in_key = 5;
1417
+ }
1418
+
1419
+ // Ranked result entries. **Entry order IS the ranking order** —
1420
+ // best-first for `ORDER BY <agg> DESC`, worst-first for `ASC`.
1421
+ // Clients must not re-sort; ties (equal aggregates) come back in
1422
+ // group-key order in the direction of the walk, which is
1423
+ // descending group-key order for `DESC`.
1424
+ //
1425
+ // Fewer than `limit` entries is normal — the index simply has
1426
+ // fewer groups than requested — and is not an error.
1427
+ message RankedEntries {
1428
+ repeated RankedEntry entries = 1;
1429
+
1430
+ // How many groups were skipped before the first entry — the
1431
+ // page's **starting rank base**. Entry `i` of `entries` is the
1432
+ // group at rank `skipped + i` (0-based).
1433
+ //
1434
+ // `0` for an `OFFSET 0` (or offset-less) query, which is what
1435
+ // makes this field additive: a caller that never paginates sees
1436
+ // the value it would have assumed. For `OFFSET m` it is
1437
+ // normally `m`, and it is what turns a page back into a
1438
+ // *ranking* — without it, a caller who asked for
1439
+ // `ORDER BY avg(grade) DESC LIMIT 1 OFFSET 4` receives one
1440
+ // entry with no way to tell that it really is the 5th-best
1441
+ // group rather than the best.
1442
+ //
1443
+ // **When a requested offset exceeds the population**, `entries`
1444
+ // is empty and `skipped` is the ranking's *total* reported
1445
+ // population — a positive, useful answer ("there are only 12
1446
+ // groups") rather than a bare empty list.
1447
+ //
1448
+ // Both paths report the same quantity: the offset you asked for
1449
+ // when the skip succeeded, and the ranking's total population
1450
+ // when the walk ran out of groups first. They no longer disagree
1451
+ // anywhere, including past the end.
1452
+ //
1453
+ // What differs is the *warrant*, not the value. On the proved
1454
+ // path the number is cryptographically attested — re-derived by
1455
+ // the verifier from the counted subtree commitments in the proof
1456
+ // bytes rather than trusted from this field — so a proving client
1457
+ // should use the verified value and ignore this one. On the
1458
+ // unproven path it is an **unverified claim**, exactly like the
1459
+ // entries beside it: it equals the attested value on an honest
1460
+ // node, and nothing forces a node to be honest. Read "the true
1461
+ // population" as "what this node says the population is".
1462
+ // Callers who need to trust it, rather than merely receive it,
1463
+ // must still prove.
1464
+ //
1465
+ // Do not assume this field equals the offset you requested. It
1466
+ // equals the offset only when the skip succeeded; when the walk
1467
+ // ran out of groups first it is smaller, and that is the answer
1468
+ // rather than an inconsistency.
1469
+ optional uint64 skipped = 2 [jstype = JS_STRING];
1470
+ }
1471
+
1261
1472
  // Non-proof result wrapper. The outer `oneof result` switches
1262
1473
  // between this and `proof`; this inner oneof switches between
1263
- // the four non-proof shapes the v1 surface can return.
1474
+ // the non-proof shapes the v1 surface can return.
1264
1475
  message ResultData {
1265
1476
  oneof variant {
1266
1477
  Documents documents = 1;
@@ -1280,6 +1491,18 @@ message GetDocumentsResponse {
1280
1491
  // `book/src/drive/average-index-examples.md` for the design
1281
1492
  // and the grades-contract worked example.
1282
1493
  AverageResults averages = 4;
1494
+ // Ranked-aggregate result. Routed when the request pairs a
1495
+ // single-property `group_by` with a single `order_by` clause
1496
+ // naming the single aggregate `select` (the `$count` sentinel
1497
+ // for `COUNT(*)`) and a `limit` — SQL's `ORDER BY <agg>
1498
+ // DESC LIMIT n [OFFSET m]`. Answered from the per-axis
1499
+ // secondary of an indexed tree, so the index must declare the
1500
+ // matching `rankedCountable` / `rankedSummable` /
1501
+ // `rankedAverageable` keyword (meta-schema v3, protocol
1502
+ // version 14+). Entry order is the ranking order, and
1503
+ // `skipped` carries the page's starting rank — see
1504
+ // `RankedEntries`.
1505
+ RankedEntries ranked = 5;
1283
1506
  }
1284
1507
  }
1285
1508