From c4f46c0c342ee5405623666241fe65dafe4dd29d Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 26 May 2026 12:47:27 -0400 Subject: [PATCH 1/2] docs(reference): document v3.1 getDocuments v0/v1 surface and aggregate queries Rewrite the getDocuments entry to cover the v0 legacy CBOR surface and the v1 typed SQL-shaped surface with Fetch / Count / Sum / Average modes. Add doctype-level aggregate query flags (documentsCountable, rangeCountable, documentsSummable, rangeSummable, documentsAverageable, rangeAverageable) to the data-contract-document reference, an aggregate-queries section to query-syntax, and a v3.1 annotation on the dapi-endpoints overview row. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/protocol-ref/data-contract-document.md | 27 + docs/protocol-ref/data-contract.md | 2 +- .../dapi-endpoints-platform-endpoints.md | 462 ++++++++++++------ docs/reference/dapi-endpoints.md | 2 +- docs/reference/query-syntax.md | 24 + 5 files changed, 379 insertions(+), 138 deletions(-) diff --git a/docs/protocol-ref/data-contract-document.md b/docs/protocol-ref/data-contract-document.md index ce360232e..22d85fbcc 100644 --- a/docs/protocol-ref/data-contract-document.md +++ b/docs/protocol-ref/data-contract-document.md @@ -328,6 +328,33 @@ The following example (from the [DPNS contract's `domain` document](https://gith } ``` +## Aggregate Query Flags + +:::{versionadded} 3.1.0 +::: + +Document types can opt into aggregate query support (count / sum / average) by setting flags at the document-type level. These flags control the underlying storage layout — once set on a published contract they cannot be changed by a contract update. + +There are two axes: + +* **Doctype-wide** (`documents*`) — applies the aggregate over the entire document type. Set at the document type root, alongside other doctype options like `documentsKeepHistory`. +* **Per-index range** (`range*`) — extends the corresponding aggregate to range queries on indexed properties. Set on the index's property entry. Requires the matching base flag. + +| Flag | Type | Purpose | Required for | +| - | - | - | - | +| `documentsCountable` | Boolean | Doctype-wide counts (empty `where` or `==`/`IN` clauses on indexed fields). | `SELECT COUNT(*)` without a range clause. | +| `rangeCountable` | Boolean | Per-index counts over a range. Requires `documentsCountable`. | `SELECT COUNT(*)` with a range clause or `GROUP BY `. | +| `documentsSummable` | String | Doctype-wide sums of the named integer property. | `SELECT SUM()`. | +| `rangeSummable` | Boolean | Per-index sums over a range. Requires `documentsSummable`. | `SELECT SUM()` with a range clause. | +| `documentsAverageable` | String | Syntactic sugar for `documentsCountable: true` + `documentsSummable: ""`. | `SELECT AVG()`. | +| `rangeAverageable` | Boolean | Syntactic sugar for `rangeCountable: true` + `rangeSummable: true`. Requires `documentsAverageable`. | `SELECT AVG()` with a range clause. | + +The averageable flags desugar to the underlying count + sum flags during contract parsing — same on-disk layout — so authors who think in terms of averages get a single flag and downstream code paths (insert, query, estimation) stay unchanged. If both `documentsAverageable` and `documentsSummable` are set, they must name the same property. + +These flags are validated against the v1 document meta-schema and are rejected when applied to pre-v12 contracts. The full v1 meta-schema, including these flags, is defined [in rs-dpp](https://github.com/dashpay/platform/blob/v3.1-dev/packages/rs-dpp/schema/meta_schemas/document/v1/document-meta.json). + +See the [`getDocuments` reference](../reference/dapi-endpoints-platform-endpoints.md#getdocuments) for the request/response shapes that consume these flags. + ## Keyword Constraints There are a variety of keyword constraints currently defined for performance and security reasons. The diff --git a/docs/protocol-ref/data-contract.md b/docs/protocol-ref/data-contract.md index 252be8ec7..c2c351576 100644 --- a/docs/protocol-ref/data-contract.md +++ b/docs/protocol-ref/data-contract.md @@ -740,7 +740,7 @@ property must be incremented if the contract is updated. ### Data Contract documents -See the [data contract documents](./data-contract-document.md) page for details. +See the [data contract documents](./data-contract-document.md) page for details, including the [aggregate query flags](./data-contract-document.md#aggregate-query-flags) that opt document types into count/sum/average queries. ### Data Contract config diff --git a/docs/reference/dapi-endpoints-platform-endpoints.md b/docs/reference/dapi-endpoints-platform-endpoints.md index 47ca03209..9ec4003da 100644 --- a/docs/reference/dapi-endpoints-platform-endpoints.md +++ b/docs/reference/dapi-endpoints-platform-endpoints.md @@ -984,32 +984,89 @@ grpcurl -proto protos/platform/v0/platform.proto \ ### getDocuments -**Returns**: [Document](../explanations/platform-protocol-document.md) information for the requested document(s) -**Parameters**: +:::{versionchanged} 3.1.0 +Adds a typed v1 request surface (`WhereClause` / `OrderClause` / `Select`) and four aggregate modes — `DOCUMENTS`, `COUNT`, `SUM`, `AVG`. The legacy v0 CBOR surface is still supported. +::: -:::{note} -The `where`, `order_by`, `limit`, `start_at`, and `start_after` parameters must comply with the limits defined on the [Query Syntax](../reference/query-syntax.md) page. -::: - -| Name | Type | Required | Description | -| ----------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------ | -| `data_contract_id` | Bytes | Yes | A data contract `id` | -| `document_type` | String | Yes | A document type defined by the data contract (e.g. `preorder` or `domain` for the DPNS contract) | -| `where` \* | Bytes | No | Where clause to filter the results | -| `order_by` \* | Bytes | No | Sort records by the field(s) provided | -| `limit` | Integer | No | Maximum number of results to return | -| ---------- | | | | -| _One_ of the following: | | | | -| `start_at` | Integer | No | Return records beginning with the index provided | -| `start_after` | Integer | No | Return records beginning after the index provided | -| ---------- | | | | -| `prove` | Boolean | No | Set to `true` to receive a proof that contains the requested document(s). The data requested will be encoded as part of the proof in the response. | +**Returns**: [Document](../explanations/platform-protocol-document.md) information for the requested document(s), or an aggregate count/sum/average over the matched document set. -**Example Request and Response** +The request envelope is `oneof version { v0; v1; }`. Pick a version per call: + +- **v1** (default for new code, v3.1+) — typed request fields and aggregate `select` modes. +- **v0** (legacy) — CBOR-encoded `where` / `order_by` byte strings. Fetch only. + +**Common request fields** (apply to every variant below) + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `data_contract_id` | Bytes | Yes | A data contract `id`. | +| `document_type` | String | Yes | A document type defined by the data contract. | +| `where_clauses` (v1) / `where` (v0) | Typed (v1) or CBOR bytes (v0) | No | Filter clauses. See [Query Syntax](../reference/query-syntax.md). | +| `order_by` | Typed (v1) or CBOR bytes (v0) | No | Sort order. See [Query Syntax](../reference/query-syntax.md). | +| `prove` | Boolean | No | Return a proof instead of data. See [Platform proofs](../reference/platform-proofs.md). | + +For v1, see also the [doctype-level aggregate flags](../protocol-ref/data-contract-document.md#aggregate-query-flags), which control whether a document type supports the `COUNT` / `SUM` / `AVG` modes below. + +#### Fetch documents + +Returns matched documents. + +**Mode-specific request fields** + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `limit` | Integer | No | Maximum number of documents to return. | +| `start_at` _or_ `start_after` | Bytes | No | Cursor — start at / after this document ID. | + +For v1, `selects` may be omitted (defaults to `[Select{ function: DOCUMENTS }]`) or set explicitly. + +**Example Request** ::::{tab-set} +:::{tab-item} v1 (gRPCurl) +:sync: v1-grpcurl + +```shell +# `data_contract_id` must be represented in base64 +grpcurl -proto protos/platform/v0/platform.proto \ + -d '{ + "v1": { + "data_contract_id": "5mjGWa9mruHnLBht3ntbfgodcSoJxA1XIfYiv1PFMVU=", + "document_type": "domain", + "where_clauses": [ + { "field": "normalizedParentDomainName", "operator": "EQUAL", "value": { "text": "dash" } } + ], + "limit": 1 + } + }' \ + seed-1.testnet.networks.dash.org:1443 \ + org.dash.platform.dapi.v0.Platform/getDocuments +``` + +::: + +:::{tab-item} v0 (gRPCurl) +:sync: v0-grpcurl + +```shell +# `data_contract_id` must be represented in base64 +grpcurl -proto protos/platform/v0/platform.proto \ + -d '{ + "v0": { + "data_contract_id": "5mjGWa9mruHnLBht3ntbfgodcSoJxA1XIfYiv1PFMVU=", + "document_type": "domain", + "limit": 1 + } + }' \ + seed-1.testnet.networks.dash.org:1443 \ + org.dash.platform.dapi.v0.Platform/getDocuments +``` + +::: + :::{tab-item} JavaScript (dapi-client) :sync: js-dapi-client + ```javascript const DAPIClient = require('@dashevo/dapi-client'); const { @@ -1029,11 +1086,8 @@ client.platform.getDataContract(contractId).then((contractResponse) => { dpp.dataContract .createFromBuffer(contractResponse.getDataContract()) .then((contract) => { - // Get document(s) client.platform - .getDocuments(contractId, type, { - limit, - }) + .getDocuments(contractId, type, { limit }) .then((response) => { for (const document of response.documents) { const doc = dpp.document.createExtendedDocumentFromDocumentBuffer( @@ -1047,155 +1101,291 @@ client.platform.getDataContract(contractId).then((contractResponse) => { }); }); ``` + ::: +:::: -:::{tab-item} JavaScript (dapi-grpc) -:sync: js-dapi-grpc -```javascript -const { - v0: { PlatformPromiseClient, GetDataContractRequest, GetDocumentsRequest }, -} = require('@dashevo/dapi-grpc'); -const { default: loadDpp, DashPlatformProtocol, Identifier } = require('@dashevo/wasm-dpp'); +**Example Response** -loadDpp(); -const dpp = new DashPlatformProtocol(null); -const platformPromiseClient = new PlatformPromiseClient( - 'https://seed-1.testnet.networks.dash.org:1443', -); +::::{tab-set} +:::{tab-item} v1 (gRPCurl) +:sync: v1-grpcurl -const contractId = Identifier.from('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'); -const contractIdBuffer = Buffer.from(contractId); -const getDataContractRequest = new GetDataContractRequest(); -getDataContractRequest.setId(contractIdBuffer); +```json +{ + "v1": { + "data": { + "documents": { + "documents": [ + "AAZ1S7dbhY4VJrSCvjs2Z1DIwa9Qt9MAyjbJdh7gPu6oDsGC/h1Ayf+ZzXp2zLWDF4XB2qMLWZ0brsAKo0r/0sYBAAcAAAGRivixugAAAZGK+LG6AAABkYr4sboAF2F1ZzI1LTEyMzQ1Njc4OTAxMjM0NTY3F2F1ZzI1LTEyMzQ1Njc4OTAxMjM0NTY3AQRkYXNoBGRhc2gAIQEOwYL+HUDJ/5nNenbMtYMXhcHaowtZnRuuwAqjSv/SxgEA" + ] + } + }, + "metadata": { + "height": "5991", + "coreChainLockedHeight": 1097384, + "epoch": 1170, + "timeMs": "1725567845055", + "protocolVersion": 1, + "chainId": "dash-testnet-51" + } + } +} +``` -platformPromiseClient - .getDataContract(getDataContractRequest) - .then((contractResponse) => { - dpp.dataContract.createFromBuffer(contractResponse.getDataContract()).then((contract) => { - // Get documents - const getDocumentsRequest = new GetDocumentsRequest(); - const type = 'domain'; - const limit = 10; - - getDocumentsRequest.setDataContractId(contractIdBuffer); - getDocumentsRequest.setDocumentType(type); - // getDocumentsRequest.setWhere(whereSerialized); - // getDocumentsRequest.setOrderBy(orderBySerialized); - getDocumentsRequest.setLimit(limit); - // getDocumentsRequest.setStartAfter(startAfter); - // getDocumentsRequest.setStartAt(startAt); - - platformPromiseClient.getDocuments(getDocumentsRequest).then((response) => { - for (const document of response.getDocuments().getDocumentsList()) { - const documentBuffer = Buffer.from(document); - const doc = dpp.document.createExtendedDocumentFromDocumentBuffer( - documentBuffer, - type, - contract, - ); - console.log(doc.toJSON()); - } - }); - }); - }) - .catch((e) => console.error(e)); +::: + +:::{tab-item} v0 (gRPCurl) +:sync: v0-grpcurl + +```json +{ + "v0": { + "documents": { + "documents": [ + "AAZ1S7dbhY4VJrSCvjs2Z1DIwa9Qt9MAyjbJdh7gPu6oDsGC/h1Ayf+ZzXp2zLWDF4XB2qMLWZ0brsAKo0r/0sYBAAcAAAGRivixugAAAZGK+LG6AAABkYr4sboAF2F1ZzI1LTEyMzQ1Njc4OTAxMjM0NTY3F2F1ZzI1LTEyMzQ1Njc4OTAxMjM0NTY3AQRkYXNoBGRhc2gAIQEOwYL+HUDJ/5nNenbMtYMXhcHaowtZnRuuwAqjSv/SxgEA" + ] + }, + "metadata": { + "height": "5991", + "coreChainLockedHeight": 1097384, + "epoch": 1170, + "timeMs": "1725567845055", + "protocolVersion": 1, + "chainId": "dash-testnet-51" + } + } +} ``` + ::: -:::{tab-item} Request (gRPCurl) +:::{tab-item} JavaScript (decoded document) +:sync: js-dapi-client + +```json +{ + "$id": "Do3YtBPJG72zG4tCbN5VE8djJ6rLpvx7yvtMWEy89HC", + "$ownerId": "4pk6ZhgDtxn9yN2bbB6kfsYLRmUBH7PKUq275cjyzepT", + "label": "Chronic", + "normalizedLabel": "chr0n1c", + "normalizedParentDomainName": "dash", + "parentDomainName": "dash", + "records": { + "dashUniqueIdentityId": "OM4WaCQNLedQ0rpbl1UMTZhEbnVeMfL4941ZD08iyFw=" + }, + "subdomainRules": { "allowSubdomains": false }, + "$revision": 1, + "$type": "domain" +} +``` + +::: +:::: + +#### Count documents + +:::{versionadded} 3.1.0 +::: + +Returns one aggregate count, or per-group counts when `group_by` is set. Requires the doctype to set `documentsCountable: true` (and `rangeCountable: true` for range-grouped queries). See [aggregate query flags](../protocol-ref/data-contract-document.md#aggregate-query-flags). + +**Mode-specific request fields** + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `selects` | `[Select{ function: COUNT }]` | Yes | Projection. | +| `group_by` | Repeated string | No | `[]`, `[in_field]`, `[range_field]`, or `[in_field, range_field]`. | + +`limit` is rejected for `group_by=[]` and `group_by=[in_field]` (the result is bounded by construction). `start_at` / `start_after` are not valid in this mode — paginate by narrowing the where clause. + +**Response shape** + +- `group_by = []` → `counts.aggregate_count` (single integer). +- `group_by = [...]` → `counts.entries[]` of `{ in_key?, key, count }`. + +`IN` values that match no documents are omitted from `counts.entries` rather than returned with `count: 0`. Diff your request's `IN` array against the returned `key` values to detect "queried but absent." + +**Example Request** + +::::{tab-set} +:::{tab-item} gRPCurl :sync: grpcurl + ```shell -# Request documents -# `id` must be represented in base64 +# TODO: Replace with a real example once a contract using +# `documentsCountable` is published on testnet. The contract id, +# document type, and field name below are illustrative only. grpcurl -proto protos/platform/v0/platform.proto \ -d '{ - "v0": { - "data_contract_id":"5mjGWa9mruHnLBht3ntbfgodcSoJxA1XIfYiv1PFMVU=", - "document_type":"domain", - "limit":1 + "v1": { + "data_contract_id": "", + "document_type": "shipments", + "selects": [{ "function": "COUNT" }], + "where_clauses": [ + { "field": "status", "operator": "EQUAL", "value": { "text": "delivered" } } + ] } }' \ seed-1.testnet.networks.dash.org:1443 \ org.dash.platform.dapi.v0.Platform/getDocuments ``` + ::: :::: -::::{tab-set} -:::{tab-item} Response (JavaScript) -:sync: js-dapi-client +**Example Response** + ```json { - "$id":"Do3YtBPJG72zG4tCbN5VE8djJ6rLpvx7yvtMWEy89HC", - "$ownerId":"4pk6ZhgDtxn9yN2bbB6kfsYLRmUBH7PKUq275cjyzepT", - "label":"Chronic", - "normalizedLabel":"chr0n1c", - "normalizedParentDomainName":"dash", - "parentDomainName":"dash", - "preorderSalt":"1P9N5qv1Ww2xkv6/XXpsvymyGYychRsLXMhCqvW79Jo=", - "records":{ - "dashUniqueIdentityId":"OM4WaCQNLedQ0rpbl1UMTZhEbnVeMfL4941ZD08iyFw=" - }, - "subdomainRules":{ - "allowSubdomains":false - }, - "$revision":1, - "$createdAt":null, - "$updatedAt":null, - "$dataContract":{ - "$format_version":"0", - "id":"GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec", - "config":{ - "$format_version":"0", - "canBeDeleted":false, - "readonly":false, - "keepsHistory":false, - "documentsKeepHistoryContractDefault":false, - "documentsMutableContractDefault":true, - "requiresIdentityEncryptionBoundedKey":null, - "requiresIdentityDecryptionBoundedKey":null + "v1": { + "data": { + "counts": { + "aggregateCount": "1234" + } }, - "version":1, - "ownerId":"EuzJmuZdBSJs2eTrxHEp6QqJztbp6FKDNGMeb4W2Ds7h", - "schemaDefs":null, - "documentSchemas":{ - "domain":[ - "Object" - ], - "preorder":[ - "Object" - ] - } - }, - "$type":"domain" + "metadata": { "height": "5991", "coreChainLockedHeight": 1097384 } + } } ``` + +Count values use `[jstype = JS_STRING]` on the proto, so JavaScript clients receive strings to avoid precision loss above `Number.MAX_SAFE_INTEGER`. + +#### Sum documents + +:::{versionadded} 3.1.0 ::: -:::{tab-item} Response (gRPCurl) +Returns the sum of an integer field across matched documents, or per-group sums when `group_by` is set. Requires the doctype to set `documentsSummable: ""` (and `rangeSummable: true` for range-grouped queries). See [aggregate query flags](../protocol-ref/data-contract-document.md#aggregate-query-flags). + +**Mode-specific request fields** + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `selects` | `[Select{ function: SUM, field: "" }]` | Yes | `field` must name the summable property. | +| `group_by` | Repeated string | No | Same shape rules as Count above. | + +`start_at` / `start_after` are not valid. + +**Response shape** + +- `group_by = []` → `sums.aggregate_sum` (signed integer). +- `group_by = [...]` → `sums.entries[]` of `{ in_key?, key, sum }`. + +**Example Request** + +::::{tab-set} +:::{tab-item} gRPCurl :sync: grpcurl + +```shell +# TODO: Replace with a real example once a contract using +# `documentsSummable` is published on testnet. The contract id, +# document type, and field name below are illustrative only. +grpcurl -proto protos/platform/v0/platform.proto \ + -d '{ + "v1": { + "data_contract_id": "", + "document_type": "inventory", + "selects": [{ "function": "SUM", "field": "quantity" }], + "where_clauses": [ + { "field": "warehouse", "operator": "EQUAL", "value": { "text": "north" } } + ] + } + }' \ + seed-1.testnet.networks.dash.org:1443 \ + org.dash.platform.dapi.v0.Platform/getDocuments +``` + +::: +:::: + +**Example Response** + ```json { - "v0": { - "documents": { - "documents": [ - "AAZ1S7dbhY4VJrSCvjs2Z1DIwa9Qt9MAyjbJdh7gPu6oDsGC/h1Ayf+ZzXp2zLWDF4XB2qMLWZ0brsAKo0r/0sYBAAcAAAGRivixugAAAZGK+LG6AAABkYr4sboAF2F1ZzI1LTEyMzQ1Njc4OTAxMjM0NTY3F2F1ZzI1LTEyMzQ1Njc4OTAxMjM0NTY3AQRkYXNoBGRhc2gAIQEOwYL+HUDJ/5nNenbMtYMXhcHaowtZnRuuwAqjSv/SxgEA" - ] + "v1": { + "data": { + "sums": { + "aggregateSum": "42000" + } }, - "metadata": { - "height": "5991", - "coreChainLockedHeight": 1097384, - "epoch": 1170, - "timeMs": "1725567845055", - "protocolVersion": 1, - "chainId": "dash-testnet-51" - } + "metadata": { "height": "5991", "coreChainLockedHeight": 1097384 } } } ``` + +#### Average documents + +:::{versionadded} 3.1.0 +::: + +Returns a `(count, sum)` pair the client divides to compute the average, or per-group `(count, sum)` pairs when `group_by` is set. Requires the doctype to set `documentsAverageable: ""` (and `rangeAverageable: true` for range-grouped queries). See [aggregate query flags](../protocol-ref/data-contract-document.md#aggregate-query-flags). + +Why `(count, sum)` instead of a single `average`? Returning the pair preserves full precision and lets the client pick how to represent the result (integer division, floating-point, decimal). + +**Mode-specific request fields** + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `selects` | `[Select{ function: AVG, field: "" }]` | Yes | `field` must name the averageable property. | +| `group_by` | Repeated string | No | Same shape rules as Count above. | + +`start_at` / `start_after` are not valid. + +**Response shape** + +- `group_by = []` → `averages.aggregate_average` of `{ count, sum }`. +- `group_by = [...]` → `averages.entries[]` of `{ in_key?, key, count, sum }`. + +**Example Request** + +::::{tab-set} +:::{tab-item} gRPCurl +:sync: grpcurl + +```shell +# TODO: Replace with a real example once a contract using +# `documentsAverageable` is published on testnet. The contract id, +# document type, and field name below are illustrative only. +grpcurl -proto protos/platform/v0/platform.proto \ + -d '{ + "v1": { + "data_contract_id": "", + "document_type": "ratings", + "selects": [{ "function": "AVG", "field": "score" }], + "where_clauses": [ + { "field": "productId", "operator": "EQUAL", "value": { "text": "abc123" } } + ] + } + }' \ + seed-1.testnet.networks.dash.org:1443 \ + org.dash.platform.dapi.v0.Platform/getDocuments +``` + ::: :::: +**Example Response** + +```json +{ + "v1": { + "data": { + "averages": { + "aggregateAverage": { + "count": "50", + "sum": "215" + } + } + }, + "metadata": { "height": "5991", "coreChainLockedHeight": 1097384 } + } +} +``` + +Client computes `avg = 215 / 50 = 4.3`. + ## Identity Endpoints ### getIdentity diff --git a/docs/reference/dapi-endpoints.md b/docs/reference/dapi-endpoints.md index 0e996f47d..9ea2f26d2 100644 --- a/docs/reference/dapi-endpoints.md +++ b/docs/reference/dapi-endpoints.md @@ -34,7 +34,7 @@ without introducing issues for endpoint consumers. | [`getDataContract`](../reference/dapi-endpoints-platform-endpoints.md#getdatacontract) | Returns the requested data contract | | [`getDataContracts`](../reference/dapi-endpoints-platform-endpoints.md#getdatacontracts) | Returns the requested data contracts | | [`getDataContractHistory`](../reference/dapi-endpoints-platform-endpoints.md#getdatacontracthistory) | Returns the requested data contract history | -| [`getDocuments`](../reference/dapi-endpoints-platform-endpoints.md#getdocuments) | Returns the requested document(s) | +| [`getDocuments`](../reference/dapi-endpoints-platform-endpoints.md#getdocuments) | **Updated in Dash Platform v3.1.0**
Returns the requested document(s), or an aggregate count/sum/average over the matched document set. | ### Identities diff --git a/docs/reference/query-syntax.md b/docs/reference/query-syntax.md index 266dcc899..4bef41722 100644 --- a/docs/reference/query-syntax.md +++ b/docs/reference/query-syntax.md @@ -148,6 +148,30 @@ The query modifiers described here determine how query results will be sorted an For indices composed of multiple fields ([example from the DPNS data contract](https://github.com/dashpay/platform/blob/master/packages/dpns-contract/schema/v1/dpns-contract-documents.json)), the sort order in an `orderBy` must either match the order defined in the data contract OR be the inverse order. ::: +## Aggregate Queries + +:::{versionadded} 3.1.0 +::: + +The [getDocuments](../reference/dapi-endpoints-platform-endpoints.md#getdocuments) v1 surface adds an aggregate-query mode. The same `where` / `orderBy` clauses described above still apply; an additional `select` projection (and optional `groupBy`) determines whether the request returns documents or aggregate values over the matched set. + +| `select` | Returns | +| ---------------- | ------- | +| `DOCUMENTS` | Matched documents (same as v0). | +| `COUNT(*)` | Number of documents matching the query. | +| `SUM()` | Sum of `` across matching documents. | +| `AVG()` | `(count, sum)` pair the client divides to compute the average. | + +`groupBy` is optional. With an empty `groupBy`, the response carries a single aggregate value; with a `groupBy` of one or two fields, the response carries one entry per group. + +Aggregate queries impose extra schema requirements on the document type — `COUNT` needs `documentsCountable`, `SUM` needs `documentsSummable`, `AVG` needs `documentsAverageable` (or both base flags). Range-grouped aggregates additionally need the `range*` variants. See the [doctype-level aggregate flags](../protocol-ref/data-contract-document.md#aggregate-query-flags) for the schema annotations and the [`getDocuments` reference](../reference/dapi-endpoints-platform-endpoints.md#getdocuments) for the full `select` × `groupBy` shape table. + +`SUM` / `AVG` integer values are returned as JS strings so JavaScript clients don't lose precision on values larger than `Number.MAX_SAFE_INTEGER`. + +:::{note} +`HAVING`, `OFFSET`, `COUNT()`, `MIN`, `MAX`, and multi-projection `SELECT` are present on the wire but currently return `Unsupported`. Callers can encode them in builders ahead of server support landing, but evaluation rejects them today. +::: + ## Example query The following query combines both a where clause and query modifiers. From 27a5b9c82fe41b6561e445fad943f03a1bb34c04 Mon Sep 17 00:00:00 2001 From: thephez Date: Tue, 26 May 2026 12:49:51 -0400 Subject: [PATCH 2/2] docs: add DAPI endpoint reference convention and release checklist Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 12 ++++++++++++ RELEASE.md | 14 ++++++++++++++ conf.py | 1 + 3 files changed, 27 insertions(+) create mode 100644 RELEASE.md diff --git a/CLAUDE.md b/CLAUDE.md index 01c6b2abe..8cc4a1371 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,6 +74,18 @@ When updating documentation values that include GitHub source links: - Update the line anchor (`#L`) to match the correct line **in the branch the link points to** - When available, use the local platform repository checkout to verify line numbers against the correct branch +## DAPI endpoint reference + +The DAPI endpoint reference is split between an overview page (`docs/reference/dapi-endpoints.md`) and per-section detail pages (`docs/reference/dapi-endpoints-*.md`). The authoritative list of endpoints lives in the platform proto at `https://github.com/dashpay/platform/tree//packages/dapi-grpc/protos` — check the proto when adding or modifying entries. + +When you add or materially update an entry on a detail page, also update the matching row on `dapi-endpoints.md`: + +- Keep the description in sync between the two pages. +- Prefix the overview row's description with `**Added in Dash Platform vX.Y.Z**` (new endpoints) or `**Updated in Dash Platform vX.Y.Z**` (modified endpoints), followed by `
` and the description. Use **bold** for the current release's annotations; older releases use *italics*. +- For a whole new endpoint group, wrap the new section in a `:::{versionadded} X.Y.Z` admonition above its table — see Security Groups, Tokens, Address System, and Shielded Transactions for the pattern. + +For the full per-release endpoint review process (proto diff, example refresh, demoting annotations to italics), see [RELEASE.md](RELEASE.md). + ## File Patterns - Documentation files: `docs/**/*.md` diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..c87e3594d --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,14 @@ +# Release Checklist + +Per-release tasks for the docs. For editing conventions, see [CLAUDE.md](CLAUDE.md). + +## DAPI endpoint review + +1. Diff the platform proto between the previous release tag and the current release branch — that's the source of truth for what changed. Proto source: `https://github.com/dashpay/platform/tree//packages/dapi-grpc/protos`. +2. For each affected endpoint, update both the detail page (`docs/reference/dapi-endpoints-*.md`) and the matching row on the overview page (`docs/reference/dapi-endpoints.md`), with the bold version annotation on the overview row. +3. Demote the previous release's bold annotations on the overview page to italics. +4. Re-run example requests against testnet and refresh response examples if necessary. Testnet state may have been wiped, so even unchanged endpoints may have stale data. + +## Update "Previous version" links + +Several pages (including the DAPI endpoints pages) link to the previous version of the docs. These links are not updated automatically. Search the site for "previous version" and update each link to point to the appropriate version. diff --git a/conf.py b/conf.py index f4814c927..8945ee5ff 100644 --- a/conf.py +++ b/conf.py @@ -41,6 +41,7 @@ '.DS_Store', 'README.md', 'CLAUDE.md', + 'RELEASE.md', '.devcontainer', '.codex', '.local',